--- /dev/null
+build
+config.mk
+make/vs/.vs
+make/vs/packages
+make/vs/**vcxproj.user
+make/vs/vcpkg_installed
+ui/winui/winui.vcxproj.user
+ui/winui/Generated Files
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+all: config.mk
+ $(MAKE) -f make/Makefile.mk
+
+config.mk:
+ ./configure
+
+clean: FORCE
+ rm -fR build/*
+
+FORCE:
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2024 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+BUILD_ROOT = ..
+include ../config.mk
+
+CFLAGS += -I../ui/ -I../ucx -I..
+
+SRC = main.c
+
+OBJ = $(SRC:%.c=../build/application/%.$(OBJ_EXT))
+
+all: ../build/bin/idav$(APP_EXT)
+
+../build/bin/idav$(APP_EXT): $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a
+ $(CC) -o ../build/bin/idav$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx -lidav $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS)
+
+../build/application/%.$(OBJ_EXT): %.c
+ $(CC) $(CFLAGS) $(TK_CFLAGS) $(DAV_CFLAGS) -o $@ -c $<
+
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2024 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+\r
+#ifdef _WIN32\r
+#include <Windows.h>\r
+#endif\r
+\r
+#include <ui/ui.h>\r
+\r
+int app_main(int argc, char **argv) {\r
+ ui_init("notes", argc, argv);\r
+ ui_main();\r
+ return 0;\r
+}\r
+\r
+#ifndef _WIN32\r
+\r
+int main(int argc, char** argv) {\r
+ return app_main(argc, argv);\r
+}\r
+#else\r
+\r
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) {\r
+ return app_main(__argc, __argv);\r
+}\r
+#endif\r
+\r
--- /dev/null
+#!/bin/sh
+
+# create temporary directory
+TEMP_DIR=".tmp-`uname -n`"
+rm -Rf "$TEMP_DIR"
+if mkdir -p "$TEMP_DIR"; then
+ :
+else
+ echo "Cannot create tmp dir $TEMP_DIR"
+ echo "Abort"
+ exit 1
+fi
+touch "$TEMP_DIR/options"
+touch "$TEMP_DIR/features"
+
+# define standard variables
+# also define standard prefix (this is where we will search for config.site)
+prefix=/usr
+exec_prefix=
+bindir=
+sbindir=
+libdir=
+libexecdir=
+datarootdir=
+datadir=
+sysconfdir=
+sharedstatedir=
+localstatedir=
+runstatedir=
+includedir=
+infodir=
+localedir=
+mandir=
+
+# custom variables
+
+# features
+
+# clean abort
+abort_configure()
+{
+ rm -Rf "$TEMP_DIR"
+ exit 1
+}
+
+# help text
+printhelp()
+{
+ echo "Usage: $0 [OPTIONS]..."
+ cat << __EOF__
+Installation directories:
+ --prefix=PREFIX path prefix for architecture-independent files
+ [/usr]
+ --exec-prefix=EPREFIX path prefix for architecture-dependent files
+ [PREFIX]
+
+ --bindir=DIR user executables [EPREFIX/bin]
+ --sbindir=DIR system admin executables [EPREFIX/sbin]
+ --libexecdir=DIR program executables [EPREFIX/libexec]
+ --sysconfdir=DIR system configuration files [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR run-time variable data [LOCALSTATEDIR/run]
+ --libdir=DIR object code libraries [EPREFIX/lib]
+ --includedir=DIR C header files [PREFIX/include]
+ --datarootdir=DIR read-only arch.-independent data root [PREFIX/share]
+ --datadir=DIR read-only architecture-independent data [DATAROOTDIR]
+ --infodir=DIR info documentation [DATAROOTDIR/info]
+ --mandir=DIR man documentation [DATAROOTDIR/man]
+ --localedir=DIR locale-dependent data [DATAROOTDIR/locale]
+
+Options:
+ --debug add extra compile flags for debug builds
+ --release add extra compile flags for release builds
+ --toolkit=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|motif)
+
+__EOF__
+}
+
+#
+# parse arguments
+#
+BUILD_TYPE="default"
+for ARG in "$@"
+do
+ case "$ARG" in
+ "--prefix="*) prefix=${ARG#--prefix=} ;;
+ "--exec-prefix="*) exec_prefix=${ARG#--exec-prefix=} ;;
+ "--bindir="*) bindir=${ARG#----bindir=} ;;
+ "--sbindir="*) sbindir=${ARG#--sbindir=} ;;
+ "--libdir="*) libdir=${ARG#--libdir=} ;;
+ "--libexecdir="*) libexecdir=${ARG#--libexecdir=} ;;
+ "--datarootdir="*) datarootdir=${ARG#--datarootdir=} ;;
+ "--datadir="*) datadir=${ARG#--datadir=} ;;
+ "--sysconfdir="*) sysconfdir=${ARG#--sysconfdir=} ;;
+ "--sharedstatedir="*) sharedstatedir=${ARG#--sharedstatedir=} ;;
+ "--localstatedir="*) localstatedir=${ARG#--localstatedir=} ;;
+ "--includedir="*) includedir=${ARG#--includedir=} ;;
+ "--infodir="*) infodir=${ARG#--infodir=} ;;
+ "--mandir"*) mandir=${ARG#--mandir} ;;
+ "--localedir"*) localedir=${ARG#--localedir} ;;
+ "--help"*) printhelp; abort_configure ;;
+ "--debug") BUILD_TYPE="debug" ;;
+ "--release") BUILD_TYPE="release" ;;
+ "--toolkit="*) OPT_TOOLKIT=${ARG#--toolkit=} ;;
+ "-"*) echo "unknown option: $ARG"; abort_configure ;;
+ esac
+done
+
+
+
+# set defaults for dir variables
+: ${exec_prefix:="$prefix"}
+: ${bindir:='${exec_prefix}/bin'}
+: ${sbindir:='${exec_prefix}/sbin'}
+: ${libdir:='${exec_prefix}/lib'}
+: ${libexecdir:='${exec_prefix}/libexec'}
+: ${datarootdir:='${prefix}/share'}
+: ${datadir:='${datarootdir}'}
+: ${sysconfdir:='${prefix}/etc'}
+: ${sharedstatedir:='${prefix}/com'}
+: ${localstatedir:='${prefix}/var'}
+: ${runstatedir:='${localstatedir}/run'}
+: ${includedir:='${prefix}/include'}
+: ${infodir:='${datarootdir}/info'}
+: ${mandir:='${datarootdir}/man'}
+: ${localedir:='${datarootdir}/locale'}
+
+# check if a config.site exists and load it
+if [ -n "$CONFIG_SITE" ]; then
+ # CONFIG_SITE may contain space separated file names
+ for cs in $CONFIG_SITE; do
+ printf "loading defaults from $cs... "
+ . "$cs"
+ echo ok
+ done
+elif [ -f "$prefix/share/config.site" ]; then
+ printf "loading site defaults... "
+ . "$prefix/share/config.site"
+ echo ok
+elif [ -f "$prefix/etc/config.site" ]; then
+ printf "loading site defaults... "
+ . "$prefix/etc/config.site"
+ echo ok
+fi
+
+# Test for availability of pkg-config
+PKG_CONFIG=`command -v pkg-config`
+: ${PKG_CONFIG:="false"}
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+OS=`uname -s`
+OS_VERSION=`uname -r`
+printf "detect platform... "
+if [ "$OS" = "SunOS" ]; then
+ PLATFORM="solaris sunos unix svr4"
+elif [ "$OS" = "Linux" ]; then
+ PLATFORM="linux unix"
+elif [ "$OS" = "FreeBSD" ]; then
+ PLATFORM="freebsd bsd unix"
+elif [ "$OS" = "OpenBSD" ]; then
+ PLATFORM="openbsd bsd unix"
+elif [ "$OS" = "NetBSD" ]; then
+ PLATFORM="netbsd bsd unix"
+elif [ "$OS" = "Darwin" ]; then
+ PLATFORM="macos osx bsd unix"
+elif echo "$OS" | grep -i "MINGW" > /dev/null; then
+ PLATFORM="windows mingw"
+fi
+: ${PLATFORM:="unix"}
+
+PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -`
+echo "$PLATFORM_NAME"
+
+isplatform()
+{
+ for p in $PLATFORM
+ do
+ if [ "$p" = "$1" ]; then
+ return 0
+ fi
+ done
+ return 1
+}
+notisplatform()
+{
+ for p in $PLATFORM
+ do
+ if [ "$p" = "$1" ]; then
+ return 1
+ fi
+ done
+ return 0
+}
+istoolchain()
+{
+ for t in $TOOLCHAIN
+ do
+ if [ "$t" = "$1" ]; then
+ return 0
+ fi
+ done
+ return 1
+}
+notistoolchain()
+{
+ for t in $TOOLCHAIN
+ do
+ if [ "$t" = "$1" ]; then
+ return 1
+ fi
+ done
+ return 0
+}
+
+
+# generate vars.mk
+cat > "$TEMP_DIR/vars.mk" << __EOF__
+prefix=$prefix
+exec_prefix=$exec_prefix
+bindir=$bindir
+sbindir=$sbindir
+libdir=$libdir
+libexecdir=$libexecdir
+datarootdir=$datarootdir
+datadir=$datadir
+sysconfdir=$sysconfdir
+sharedstatedir=$sharedstatedir
+localstatedir=$localstatedir
+runstatedir=$runstatedir
+includedir=$includedir
+infodir=$infodir
+mandir=$mandir
+localedir=$localedir
+__EOF__
+
+# toolchain detection utilities
+. make/toolchain.sh
+
+#
+# DEPENDENCIES
+#
+
+# check languages
+lang_c=
+lang_cpp=
+if detect_c_compiler ; then
+ lang_c=1
+fi
+
+# create buffer for make variables required by dependencies
+echo > "$TEMP_DIR/make.mk"
+
+test_pkg_config()
+{
+ if "$PKG_CONFIG" --exists "$1" ; then :
+ else return 1 ; fi
+ if [ -z "$2" ] || "$PKG_CONFIG" --atleast-version="$2" "$1" ; then :
+ else return 1 ; fi
+ if [ -z "$3" ] || "$PKG_CONFIG" --exact-version="$3" "$1" ; then :
+ else return 1 ; fi
+ if [ -z "$4" ] || "$PKG_CONFIG" --max-version="$4" "$1" ; then :
+ else return 1 ; fi
+ return 0
+}
+
+print_check_msg()
+{
+ if [ -z "$1" ]; then
+ shift
+ printf "$@"
+ fi
+}
+
+dependency_error_gtk2legacy()
+{
+ print_check_msg "$dep_checked_gtk2legacy" "checking for gtk2legacy... "
+ # dependency gtk2legacy
+ while true
+ do
+ if [ -z "$PKG_CONFIG" ]; then
+ break
+ fi
+ if test_pkg_config "gtk+-2.0" "" "" "" ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk+-2.0`"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk+-2.0`"
+ else
+ break
+ fi
+ TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK2 -DUI_GTK2LEGACY"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+ print_check_msg "$dep_checked_gtk2legacy" "yes\n"
+ dep_checked_gtk2legacy=1
+ return 1
+ done
+
+ print_check_msg "$dep_checked_gtk2legacy" "no\n"
+ dep_checked_gtk2legacy=1
+ return 0
+}
+dependency_error_curl()
+{
+ print_check_msg "$dep_checked_curl" "checking for curl... "
+ # dependency curl platform="macos"
+ while true
+ do
+ if notisplatform "macos"; then
+ break
+ fi
+ if tmp_flags=`curl-config --cflags` ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS $tmp_flags"
+ else
+ break
+ fi
+ if tmp_flags=`curl-config --ldflags` ; then
+ TEMP_LDFLAGS="$TEMP_LDFLAGS $tmp_flags"
+ else
+ break
+ fi
+ print_check_msg "$dep_checked_curl" "yes\n"
+ dep_checked_curl=1
+ return 1
+ done
+
+ # dependency curl
+ while true
+ do
+ if [ -z "$PKG_CONFIG" ]; then
+ break
+ fi
+ if test_pkg_config "libcurl" "" "" "" ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags libcurl`"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs libcurl`"
+ else
+ break
+ fi
+ print_check_msg "$dep_checked_curl" "yes\n"
+ dep_checked_curl=1
+ return 1
+ done
+
+ # dependency curl
+ while true
+ do
+ if tmp_flags=`curl-config --cflags` ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS $tmp_flags"
+ else
+ break
+ fi
+ if tmp_flags=`curl-config --libs` ; then
+ TEMP_LDFLAGS="$TEMP_LDFLAGS $tmp_flags"
+ else
+ break
+ fi
+ print_check_msg "$dep_checked_curl" "yes\n"
+ dep_checked_curl=1
+ return 1
+ done
+
+ print_check_msg "$dep_checked_curl" "no\n"
+ dep_checked_curl=1
+ return 0
+}
+dependency_error_gtk2()
+{
+ print_check_msg "$dep_checked_gtk2" "checking for gtk2... "
+ # dependency gtk2
+ while true
+ do
+ if [ -z "$PKG_CONFIG" ]; then
+ break
+ fi
+ if pkg-config --atleast-version=2.20 gtk+-2.0 > /dev/null ; then
+ :
+ else
+ break
+ fi
+ if test_pkg_config "gtk+-2.0" "" "" "" ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk+-2.0`"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk+-2.0`"
+ else
+ break
+ fi
+ TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK2"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+ print_check_msg "$dep_checked_gtk2" "yes\n"
+ dep_checked_gtk2=1
+ return 1
+ done
+
+ print_check_msg "$dep_checked_gtk2" "no\n"
+ dep_checked_gtk2=1
+ return 0
+}
+dependency_error_gtk3()
+{
+ print_check_msg "$dep_checked_gtk3" "checking for gtk3... "
+ # dependency gtk3
+ while true
+ do
+ if [ -z "$PKG_CONFIG" ]; then
+ break
+ fi
+ if test_pkg_config "gtk+-3.0" "" "" "" ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk+-3.0`"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk+-3.0`"
+ else
+ break
+ fi
+ TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK3"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+ print_check_msg "$dep_checked_gtk3" "yes\n"
+ dep_checked_gtk3=1
+ return 1
+ done
+
+ print_check_msg "$dep_checked_gtk3" "no\n"
+ dep_checked_gtk3=1
+ return 0
+}
+dependency_error_gtk4()
+{
+ print_check_msg "$dep_checked_gtk4" "checking for gtk4... "
+ # dependency gtk4
+ while true
+ do
+ if [ -z "$PKG_CONFIG" ]; then
+ break
+ fi
+ if test_pkg_config "gtk4" "" "" "" ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags gtk4`"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs gtk4`"
+ else
+ break
+ fi
+ TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK3"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+ print_check_msg "$dep_checked_gtk4" "yes\n"
+ dep_checked_gtk4=1
+ return 1
+ done
+
+ print_check_msg "$dep_checked_gtk4" "no\n"
+ dep_checked_gtk4=1
+ return 0
+}
+dependency_error_openssl()
+{
+ print_check_msg "$dep_checked_openssl" "checking for openssl... "
+ # dependency openssl platform="windows"
+ while true
+ do
+ if notisplatform "windows"; then
+ break
+ fi
+ TEMP_LDFLAGS="$TEMP_LDFLAGS -lssl -lcrypto"
+ print_check_msg "$dep_checked_openssl" "yes\n"
+ dep_checked_openssl=1
+ return 1
+ done
+
+ # dependency openssl platform="macos"
+ while true
+ do
+ if notisplatform "macos"; then
+ break
+ fi
+ TEMP_LDFLAGS="$TEMP_LDFLAGS -framework CoreFoundation"
+ print_check_msg "$dep_checked_openssl" "yes\n"
+ dep_checked_openssl=1
+ return 1
+ done
+
+ # dependency openssl platform="bsd"
+ while true
+ do
+ if notisplatform "bsd"; then
+ break
+ fi
+ if isplatform "macos" || istoolchain "macos"; then
+ break
+ fi
+ TEMP_LDFLAGS="$TEMP_LDFLAGS -lssl -lcrypto"
+ print_check_msg "$dep_checked_openssl" "yes\n"
+ dep_checked_openssl=1
+ return 1
+ done
+
+ # dependency openssl
+ while true
+ do
+ if [ -z "$PKG_CONFIG" ]; then
+ break
+ fi
+ if test_pkg_config "openssl" "" "" "" ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags openssl`"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs openssl`"
+ else
+ break
+ fi
+ print_check_msg "$dep_checked_openssl" "yes\n"
+ dep_checked_openssl=1
+ return 1
+ done
+
+ print_check_msg "$dep_checked_openssl" "no\n"
+ dep_checked_openssl=1
+ return 0
+}
+dependency_error_libadwaita()
+{
+ print_check_msg "$dep_checked_libadwaita" "checking for libadwaita... "
+ # dependency libadwaita
+ while true
+ do
+ if [ -z "$PKG_CONFIG" ]; then
+ break
+ fi
+ if test_pkg_config "libadwaita-1" "" "" "" ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags libadwaita-1`"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs libadwaita-1`"
+ else
+ break
+ fi
+ TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK4 -DUI_LIBADWAITA"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+ print_check_msg "$dep_checked_libadwaita" "yes\n"
+ dep_checked_libadwaita=1
+ return 1
+ done
+
+ print_check_msg "$dep_checked_libadwaita" "no\n"
+ dep_checked_libadwaita=1
+ return 0
+}
+dependency_error_motif()
+{
+ print_check_msg "$dep_checked_motif" "checking for motif... "
+ # dependency motif platform="bsd"
+ while true
+ do
+ if notisplatform "bsd"; then
+ break
+ fi
+ TEMP_CFLAGS="$TEMP_CFLAGS -DUI_MOTIF -I/usr/local/include/X11"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS -lXm -lXt -lX11 -lpthread"
+ print_check_msg "$dep_checked_motif" "yes\n"
+ dep_checked_motif=1
+ return 1
+ done
+
+ # dependency motif
+ while true
+ do
+ TEMP_CFLAGS="$TEMP_CFLAGS -DUI_MOTIF"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS -lXm -lXt -lX11 -lpthread"
+ print_check_msg "$dep_checked_motif" "yes\n"
+ dep_checked_motif=1
+ return 1
+ done
+
+ print_check_msg "$dep_checked_motif" "no\n"
+ dep_checked_motif=1
+ return 0
+}
+dependency_error_libxml2()
+{
+ print_check_msg "$dep_checked_libxml2" "checking for libxml2... "
+ # dependency libxml2 platform="windows"
+ while true
+ do
+ if notisplatform "windows"; then
+ break
+ fi
+ if tmp_flags=`xml2-config --cflags` ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS $tmp_flags"
+ else
+ break
+ fi
+ if tmp_flags=`xml2-config --libs` ; then
+ TEMP_LDFLAGS="$TEMP_LDFLAGS $tmp_flags"
+ else
+ break
+ fi
+ print_check_msg "$dep_checked_libxml2" "yes\n"
+ dep_checked_libxml2=1
+ return 1
+ done
+
+ # dependency libxml2 platform="macos"
+ while true
+ do
+ if notisplatform "macos"; then
+ break
+ fi
+ if tmp_flags=`xml2-config --cflags` ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS $tmp_flags"
+ else
+ break
+ fi
+ if tmp_flags=`xml2-config --libs` ; then
+ TEMP_LDFLAGS="$TEMP_LDFLAGS $tmp_flags"
+ else
+ break
+ fi
+ print_check_msg "$dep_checked_libxml2" "yes\n"
+ dep_checked_libxml2=1
+ return 1
+ done
+
+ # dependency libxml2
+ while true
+ do
+ if [ -z "$PKG_CONFIG" ]; then
+ break
+ fi
+ if test_pkg_config "libxml-2.0" "" "" "" ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags libxml-2.0`"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs libxml-2.0`"
+ else
+ break
+ fi
+ print_check_msg "$dep_checked_libxml2" "yes\n"
+ dep_checked_libxml2=1
+ return 1
+ done
+
+ # dependency libxml2
+ while true
+ do
+ if tmp_flags=`xml2-config --cflags` ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS $tmp_flags"
+ else
+ break
+ fi
+ if tmp_flags=`xml2-config --libs` ; then
+ TEMP_LDFLAGS="$TEMP_LDFLAGS $tmp_flags"
+ else
+ break
+ fi
+ print_check_msg "$dep_checked_libxml2" "yes\n"
+ dep_checked_libxml2=1
+ return 1
+ done
+
+ print_check_msg "$dep_checked_libxml2" "no\n"
+ dep_checked_libxml2=1
+ return 0
+}
+dependency_error_cocoa()
+{
+ print_check_msg "$dep_checked_cocoa" "checking for cocoa... "
+ # dependency cocoa platform="macos"
+ while true
+ do
+ if notisplatform "macos"; then
+ break
+ fi
+ TEMP_CFLAGS="$TEMP_CFLAGS -DUI_COCOA"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS -lobjc -framework Cocoa"
+ print_check_msg "$dep_checked_cocoa" "yes\n"
+ dep_checked_cocoa=1
+ return 1
+ done
+
+ print_check_msg "$dep_checked_cocoa" "no\n"
+ dep_checked_cocoa=1
+ return 0
+}
+dependency_error_winui()
+{
+ print_check_msg "$dep_checked_winui" "checking for winui... "
+ # dependency winui platform="windows"
+ while true
+ do
+ if notisplatform "windows"; then
+ break
+ fi
+ TEMP_CFLAGS="$TEMP_CFLAGS -DUI_WINUI"
+ print_check_msg "$dep_checked_winui" "yes\n"
+ dep_checked_winui=1
+ return 1
+ done
+
+ print_check_msg "$dep_checked_winui" "no\n"
+ dep_checked_winui=1
+ return 0
+}
+
+# start collecting dependency information
+echo > "$TEMP_DIR/flags.mk"
+
+DEPENDENCIES_FAILED=
+ERROR=0
+# unnamed dependencies
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
+while true
+do
+ while true
+ do
+ if [ -z "$lang_c" ] ; then
+ ERROR=1
+ break
+ fi
+
+ break
+ done
+ break
+done
+while true
+do
+ if notisplatform "macos"; then
+ break
+ fi
+ while true
+ do
+
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+OBJ_EXT = .o
+LIB_EXT = .a
+PACKAGE_SCRIPT = package_osx.sh
+__EOF__
+ break
+ done
+ break
+done
+while true
+do
+ if notisplatform "unix"; then
+ break
+ fi
+ if isplatform "macos" || istoolchain "macos"; then
+ break
+ fi
+ while true
+ do
+
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+OBJ_EXT = .o
+LIB_EXT = .a
+PACKAGE_SCRIPT = package_unix.sh
+__EOF__
+ break
+ done
+ break
+done
+while true
+do
+ if notisplatform "bsd"; then
+ break
+ fi
+ while true
+ do
+
+ TEMP_CFLAGS="$TEMP_CFLAGS -I/usr/local/include"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS -L/usr/local/lib"
+ break
+ done
+ break
+done
+
+# add general dependency flags to flags.mk
+echo "# general flags" >> "$TEMP_DIR/flags.mk"
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+ echo "CFLAGS += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+ echo "CXXFLAGS += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_LDFLAGS}" ]; then
+ echo "LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+
+#
+# OPTION VALUES
+#
+checkopt_toolkit_libadwaita()
+{
+ VERR=0
+ if dependency_error_libadwaita ; then
+ VERR=1
+ fi
+ if [ $VERR -ne 0 ]; then
+ return 1
+ fi
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_cairo.o
+__EOF__
+ return 0
+}
+checkopt_toolkit_gtk4()
+{
+ VERR=0
+ if dependency_error_gtk4 ; then
+ VERR=1
+ fi
+ if [ $VERR -ne 0 ]; then
+ return 1
+ fi
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_cairo.o
+__EOF__
+ return 0
+}
+checkopt_toolkit_gtk3()
+{
+ VERR=0
+ if dependency_error_gtk3 ; then
+ VERR=1
+ fi
+ if [ $VERR -ne 0 ]; then
+ return 1
+ fi
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_cairo.o
+__EOF__
+ return 0
+}
+checkopt_toolkit_gtk2()
+{
+ VERR=0
+ if dependency_error_gtk2 ; then
+ VERR=1
+ fi
+ if [ $VERR -ne 0 ]; then
+ return 1
+ fi
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_cairo.o
+__EOF__
+ return 0
+}
+checkopt_toolkit_gtk2legacy()
+{
+ VERR=0
+ if dependency_error_gtk2legacy ; then
+ VERR=1
+ fi
+ if [ $VERR -ne 0 ]; then
+ return 1
+ fi
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_gdk.o
+__EOF__
+ return 0
+}
+checkopt_toolkit_qt5()
+{
+ VERR=0
+ if dependency_error_qt5 ; then
+ VERR=1
+ fi
+ if [ $VERR -ne 0 ]; then
+ return 1
+ fi
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = qt
+LD = $(CXX)
+__EOF__
+ return 0
+}
+checkopt_toolkit_qt4()
+{
+ VERR=0
+ if dependency_error_qt4 ; then
+ VERR=1
+ fi
+ if [ $VERR -ne 0 ]; then
+ return 1
+ fi
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = qt
+LD = $(CXX)
+__EOF__
+ return 0
+}
+checkopt_toolkit_motif()
+{
+ VERR=0
+ if dependency_error_motif ; then
+ VERR=1
+ fi
+ if [ $VERR -ne 0 ]; then
+ return 1
+ fi
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = motif
+__EOF__
+ return 0
+}
+
+#
+# TARGETS
+#
+
+echo >> "$TEMP_DIR/flags.mk"
+echo "configuring target: dav"
+echo "# flags for target dav" >> "$TEMP_DIR/flags.mk"
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
+
+if dependency_error_curl; then
+ DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED curl "
+ ERROR=1
+fi
+if dependency_error_libxml2; then
+ DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED libxml2 "
+ ERROR=1
+fi
+if dependency_error_openssl; then
+ DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED openssl "
+ ERROR=1
+fi
+
+# Features
+
+
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+ echo "DAV_CFLAGS += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+ echo "DAV_CXXFLAGS += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ "$BUILD_TYPE" = "debug" ]; then
+ if [ -n "$lang_c" ]; then
+ echo 'DAV_CFLAGS += ${DEBUG_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+ fi
+ if [ -n "$lang_cpp" ]; then
+ echo 'DAV_CXXFLAGS += ${DEBUG_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+ fi
+fi
+if [ "$BUILD_TYPE" = "release" ]; then
+ if [ -n "$lang_c" ]; then
+ echo 'DAV_CFLAGS += ${RELEASE_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+ fi
+ if [ -n "$lang_cpp" ]; then
+ echo 'DAV_CXXFLAGS += ${RELEASE_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+ fi
+fi
+if [ -n "${TEMP_LDFLAGS}" ]; then
+ echo "DAV_LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+
+echo >> "$TEMP_DIR/flags.mk"
+echo "configuring target: tk"
+echo "# flags for target tk" >> "$TEMP_DIR/flags.mk"
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
+
+
+# Features
+
+# Option: --toolkit
+if [ -z "$OPT_TOOLKIT" ]; then
+ echo "auto-detecting option 'toolkit'"
+ SAVED_ERROR="$ERROR"
+ SAVED_DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED"
+ ERROR=1
+ while true
+ do
+ if isplatform "windows"; then
+ if checkopt_toolkit_winui ; then
+ echo " toolkit: winui" >> "$TEMP_DIR/options"
+ ERROR=0
+ break
+ fi
+ fi
+ if isplatform "macos"; then
+ if checkopt_toolkit_cocoa ; then
+ echo " toolkit: cocoa" >> "$TEMP_DIR/options"
+ ERROR=0
+ break
+ fi
+ fi
+ if checkopt_toolkit_gtk4 ; then
+ echo " toolkit: gtk4" >> "$TEMP_DIR/options"
+ ERROR=0
+ break
+ fi
+ if checkopt_toolkit_gtk3 ; then
+ echo " toolkit: gtk3" >> "$TEMP_DIR/options"
+ ERROR=0
+ break
+ fi
+ if checkopt_toolkit_qt5 ; then
+ echo " toolkit: qt5" >> "$TEMP_DIR/options"
+ ERROR=0
+ break
+ fi
+ if checkopt_toolkit_gtk2 ; then
+ echo " toolkit: gtk2" >> "$TEMP_DIR/options"
+ ERROR=0
+ break
+ fi
+ if checkopt_toolkit_qt4 ; then
+ echo " toolkit: qt4" >> "$TEMP_DIR/options"
+ ERROR=0
+ break
+ fi
+ if checkopt_toolkit_motif ; then
+ echo " toolkit: motif" >> "$TEMP_DIR/options"
+ ERROR=0
+ break
+ fi
+ break
+ done
+ if [ $ERROR -ne 0 ]; then
+ SAVED_ERROR=1
+ SAVED_DEPENDENCIES_FAILED="option 'toolkit' $SAVED_DEPENDENCIES_FAILED"
+ fi
+ ERROR="$SAVED_ERROR"
+ DEPENDENCIES_FAILED="$SAVED_DEPENDENCIES_FAILED"
+else
+ echo "checking option toolkit = $OPT_TOOLKIT"
+ if false; then
+ false
+ elif [ "$OPT_TOOLKIT" = "libadwaita" ]; then
+ echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+ if checkopt_toolkit_libadwaita ; then
+ :
+ else
+ ERROR=1
+ DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+ fi
+ elif [ "$OPT_TOOLKIT" = "gtk4" ]; then
+ echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+ if checkopt_toolkit_gtk4 ; then
+ :
+ else
+ ERROR=1
+ DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+ fi
+ elif [ "$OPT_TOOLKIT" = "gtk3" ]; then
+ echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+ if checkopt_toolkit_gtk3 ; then
+ :
+ else
+ ERROR=1
+ DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+ fi
+ elif [ "$OPT_TOOLKIT" = "gtk2" ]; then
+ echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+ if checkopt_toolkit_gtk2 ; then
+ :
+ else
+ ERROR=1
+ DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+ fi
+ elif [ "$OPT_TOOLKIT" = "gtk2legacy" ]; then
+ echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+ if checkopt_toolkit_gtk2legacy ; then
+ :
+ else
+ ERROR=1
+ DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+ fi
+ elif [ "$OPT_TOOLKIT" = "qt5" ]; then
+ echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+ if checkopt_toolkit_qt5 ; then
+ :
+ else
+ ERROR=1
+ DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+ fi
+ elif [ "$OPT_TOOLKIT" = "qt4" ]; then
+ echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+ if checkopt_toolkit_qt4 ; then
+ :
+ else
+ ERROR=1
+ DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+ fi
+ elif [ "$OPT_TOOLKIT" = "motif" ]; then
+ echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options
+ if checkopt_toolkit_motif ; then
+ :
+ else
+ ERROR=1
+ DEPENDENCIES_FAILED="option 'toolkit' $DEPENDENCIES_FAILED"
+ fi
+ fi
+fi
+
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+ echo "TK_CFLAGS += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+ echo "TK_CXXFLAGS += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ "$BUILD_TYPE" = "debug" ]; then
+ if [ -n "$lang_c" ]; then
+ echo 'TK_CFLAGS += ${DEBUG_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+ fi
+ if [ -n "$lang_cpp" ]; then
+ echo 'TK_CXXFLAGS += ${DEBUG_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+ fi
+fi
+if [ "$BUILD_TYPE" = "release" ]; then
+ if [ -n "$lang_c" ]; then
+ echo 'TK_CFLAGS += ${RELEASE_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+ fi
+ if [ -n "$lang_cpp" ]; then
+ echo 'TK_CXXFLAGS += ${RELEASE_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+ fi
+fi
+if [ -n "${TEMP_LDFLAGS}" ]; then
+ echo "TK_LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+
+
+# final result
+if [ $ERROR -ne 0 ]; then
+ echo
+ echo "Error: Unresolved dependencies"
+ echo "$DEPENDENCIES_FAILED"
+ abort_configure
+fi
+
+echo "configure finished"
+echo
+echo "Build Config:"
+echo " PREFIX: $prefix"
+echo " TOOLCHAIN: $TOOLCHAIN_NAME"
+echo "Options:"
+cat "$TEMP_DIR/options"
+echo
+
+# generate the config.mk file
+cat > "$TEMP_DIR/config.mk" << __EOF__
+#
+# config.mk generated by configure
+#
+
+__EOF__
+write_toolchain_defaults "$TEMP_DIR/toolchain.mk"
+cat "$TEMP_DIR/vars.mk" "$TEMP_DIR/toolchain.mk" "$TEMP_DIR/flags.mk" "$TEMP_DIR/make.mk" > config.mk
+rm -Rf "$TEMP_DIR"
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2018 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+include ../config.mk
+
+# list of source files
+SRC = webdav.c
+SRC += session.c
+SRC += resource.c
+SRC += methods.c
+SRC += utils.c
+SRC += davqlparser.c
+SRC += davqlexec.c
+SRC += crypto.c
+SRC += xml.c
+SRC += versioning.c
+SRC += config.c
+SRC += pwdstore.c
+
+OBJ = $(SRC:%.c=../build/libidav/%$(OBJ_EXT))
+
+all: ../build/ucx ../build/lib/libidav$(LIB_EXT)
+
+../build/lib/libidav$(LIB_EXT): $(OBJ)
+ $(AR) $(ARFLAGS) $(AOFLAGS)$@ $(OBJ)
+
+../build/libidav/%$(OBJ_EXT): %.c
+ $(CC) -I../ucx $(CFLAGS) $(DAV_CFLAGS) -c -o $@ $<
+
+../build/ucx:
+ test -d '$@'
+
+cppcheck: $(SRC)
+ $(CPPCHECK) $(CPPCHECK_CONFIG) -I../ucx $(CPPCHECK_FLAGS) $+ 2>> ../$(CPPCHECK_LOG)
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <cx/hash_map.h>
+#include <errno.h>
+#include <libxml/tree.h>
+#include "utils.h"
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+#define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b)
+
+#define print_error(lineno, ...) \
+ do {\
+ fprintf(stderr, "Error (config.xml line %u): ", lineno); \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "Abort.\n"); \
+ } while(0);
+#define print_warning(lineno, ...) \
+ do {\
+ fprintf(stderr, "Warning (config.xml line %u): ", lineno); \
+ fprintf(stderr, __VA_ARGS__); \
+ } while(0);
+
+#ifdef _WIN32
+#define ENV_HOME getenv("USERPROFILE")
+#else
+#define ENV_HOME getenv("HOME")
+#endif /* _WIN32 */
+
+
+static int load_repository(
+ DavConfig *config,
+ DavCfgRepository **list_begin,
+ DavCfgRepository **list_end,
+ xmlNode *reponode);
+static int load_key(
+ DavConfig *config,
+ DavCfgKey **list_begin,
+ DavCfgKey **list_end,
+ xmlNode *keynode);
+static int load_proxy(
+ DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode, int type);
+static int load_namespace(
+ DavConfig *config,
+ DavCfgNamespace **list_begin,
+ DavCfgNamespace **list_end,
+ xmlNode *node);
+static int load_secretstore(DavConfig *config, xmlNode *node);
+
+
+int dav_cfg_string_set_node_value(DavConfig *config, CfgString *str, xmlNode *node) {
+ str->node = node;
+ char *value = util_xml_get_text(node);
+ if(value) {
+ str->value = cx_strdup_a(config->mp->allocator, cx_str(value));
+ return 0;
+ } else {
+ str->value = (cxmutstr){NULL, 0};
+ return 1;
+ }
+}
+
+void dav_cfg_bool_set_node_value(DavConfig *config, CfgBool *cbool, xmlNode *node) {
+ cbool->node = node;
+ char *value = util_xml_get_text(node);
+ cbool->value = util_getboolean(value);
+}
+
+static void set_xml_content(xmlNode *node, const char *content) {
+ xmlNode *child = node->children;
+ while(child) {
+ xmlNode *next = child->next;
+ xmlUnlinkNode(child);
+ xmlFreeNode(child);
+ child = next;
+ }
+
+ if(content) {
+ xmlChar *encoded = xmlEncodeSpecialChars(node->doc, (const xmlChar*)content);
+ if(encoded) {
+ xmlNodeSetContent(node, encoded);
+ xmlFree(encoded);
+ }
+ }
+}
+
+void dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *parent, cxstring new_value, const char *nodename) {
+ if(str->value.ptr) {
+ cxFree(config->mp->allocator, str->value.ptr);
+ }
+ if(new_value.ptr) {
+ str->value = cx_strdup_a(config->mp->allocator, new_value);
+ } else {
+ str->value = cx_mutstrn(NULL, 0);
+ }
+
+ if(!str->node) {
+ str->node = xmlNewNode(NULL, (const xmlChar*) nodename);
+ xmlAddChild(parent, str->node);
+ }
+ set_xml_content(str->node, new_value.ptr);
+}
+
+void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *parent, DavBool new_value, const char *nodename) {
+ const char *content = new_value ? "true" : "false";
+ cbool->value = new_value;
+ if(!cbool->node) {
+ cbool->node = xmlNewNode(NULL, (const xmlChar*) nodename);
+ xmlAddChild(parent, cbool->node);
+ }
+ set_xml_content(cbool->node, content);
+}
+
+void dav_cfg_int_set_value(DavConfig *config, CfgInt *cint, xmlNode *parent, int64_t new_value, const char *nodename) {
+ char content[32];
+ snprintf(content, 32, "%" PRId64, new_value);
+ cint->value = new_value;
+ if(!cint->node) {
+ cint->node = xmlNewNode(NULL, (const xmlChar*) nodename);
+ xmlAddChild(parent, cint->node);
+ }
+ set_xml_content(cint->node, content);
+}
+
+void dav_cfg_uint_set_value(DavConfig *config, CfgUInt *cint, xmlNode *parent, uint64_t new_value, const char *nodename) {
+ char content[32];
+ snprintf(content, 32, "%" PRIu64, new_value);
+ cint->value = new_value;
+ if(!cint->node) {
+ cint->node = xmlNewNode(NULL, (const xmlChar*) nodename);
+ xmlAddChild(parent, cint->node);
+ }
+ set_xml_content(cint->node, content);
+}
+
+void dav_cfg_string_remove(CfgString *str) {
+ if(str->node) {
+ xmlUnlinkNode(str->node);
+ xmlFreeNode(str->node);
+ str->node = NULL;
+ }
+}
+
+void dav_cfg_bool_remove(CfgBool *cbool) {
+ if(cbool->node) {
+ xmlUnlinkNode(cbool->node);
+ xmlFreeNode(cbool->node);
+ cbool->node = NULL;
+ }
+}
+
+void dav_cfg_int_remove(CfgInt *cint) {
+ if(cint->node) {
+ xmlUnlinkNode(cint->node);
+ xmlFreeNode(cint->node);
+ cint->node = NULL;
+ }
+}
+
+void dav_cfg_uint_remove(CfgUInt *cint) {
+ if(cint->node) {
+ xmlUnlinkNode(cint->node);
+ xmlFreeNode(cint->node);
+ cint->node = NULL;
+ }
+}
+
+
+DavConfig* dav_config_new(xmlDoc *doc) {
+ CxMempool *cfg_mp = cxMempoolCreate(128, NULL);
+ DavConfig *config = cxMalloc(cfg_mp->allocator, sizeof(DavConfig));
+ memset(config, 0, sizeof(DavConfig));
+ config->mp = cfg_mp;
+
+ if(!doc) {
+ doc = xmlNewDoc(BAD_CAST "1.0");
+ xmlNode *root = xmlNewNode(NULL, BAD_CAST "configuration");
+ xmlDocSetRootElement(doc, root);
+ }
+ config->doc = doc;
+
+ return config;
+}
+
+DavConfig* dav_config_load(cxmutstr xmlfilecontent, int *error) {
+ xmlDoc *doc = xmlReadMemory(xmlfilecontent.ptr, xmlfilecontent.length, NULL, NULL, 0);
+ if(!doc) {
+ if(error) {
+ *error = DAV_CONFIG_ERROR_XML;
+ }
+ return NULL;
+ }
+
+ DavConfig *config = dav_config_new(doc);
+ CxMempool *cfg_mp = config->mp;
+ cxMempoolRegister(cfg_mp, doc, (cx_destructor_func)xmlFreeDoc);
+
+ DavCfgRepository *repos_begin = NULL;
+ DavCfgRepository *repos_end = NULL;
+ DavCfgKey *keys_begin = NULL;
+ DavCfgKey *keys_end = NULL;
+ DavCfgNamespace *namespaces_begin = NULL;
+ DavCfgNamespace *namespaces_end = NULL;
+
+ xmlNode *xml_root = xmlDocGetRootElement(doc);
+ xmlNode *node = xml_root->children;
+ int ret = 0;
+ while(node && !ret) {
+ if(node->type == XML_ELEMENT_NODE) {
+ if(xstreq(node->name, "repository")) {
+ ret = load_repository(config, &repos_begin, &repos_end, node);
+ } else if(xstreq(node->name, "key")) {
+ ret = load_key(config, &keys_begin, &keys_end, node);
+ } else if (xstreq(node->name, "http-proxy")) {
+ config->http_proxy = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgProxy));
+ ret = load_proxy(config, config->http_proxy, node, DAV_HTTP_PROXY);
+ } else if (xstreq(node->name, "https-proxy")) {
+ config->https_proxy = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgProxy));
+ ret = load_proxy(config, config->https_proxy, node, DAV_HTTPS_PROXY);
+ } else if (xstreq(node->name, "namespace")) {
+ ret = load_namespace(config, &namespaces_begin, &namespaces_end, node);
+ } else if (xstreq(node->name, "secretstore")) {
+ ret = load_secretstore(config, node);
+ } else {
+ fprintf(stderr, "Unknown config element: %s\n", node->name);
+ ret = 1;
+ }
+ }
+ node = node->next;
+ }
+
+ config->repositories = repos_begin;
+ config->keys = keys_begin;
+ config->namespaces = namespaces_begin;
+
+ if(ret != 0 && error) {
+ *error = ret;
+ cxMempoolDestroy(cfg_mp);
+ }
+
+ return config;
+}
+
+void dav_config_free(DavConfig *config) {
+ cxMempoolDestroy(config->mp);
+}
+
+CxBuffer* dav_config2buf(DavConfig *config) {
+ xmlChar* xmlText = NULL;
+ int textLen = 0;
+ xmlDocDumpFormatMemory(config->doc, &xmlText, &textLen, 1);
+
+ if(!xmlText) {
+ return NULL;
+ }
+
+ CxBuffer *buf = cxBufferCreate(NULL, textLen, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
+ cxBufferWrite(xmlText, 1, textLen, buf);
+ xmlFree(xmlText);
+ return buf;
+}
+
+
+static int repo_add_config(
+ DavConfig *config,
+ DavCfgRepository *repo,
+ xmlNode* node)
+{
+ unsigned short lineno = node->line;
+ char *key = (char*)node->name;
+ char *value = util_xml_get_text(node);
+
+ /* every key needs a value */
+ if(!value) {
+ /* TODO: maybe this should only be reported, if the key is valid
+ * But this makes the code very ugly.
+ */
+ print_error(lineno, "missing value for config element: %s\n", key);
+ return 1;
+ }
+
+ if(xstreq(key, "name")) {
+ dav_cfg_string_set_node_value(config, &repo->name, node);
+ } else if(xstreq(key, "url")) {
+ dav_cfg_string_set_node_value(config, &repo->url, node);
+ } else if(xstreq(key, "user")) {
+ dav_cfg_string_set_node_value(config, &repo->user, node);
+ } else if(xstreq(key, "password")) {
+ dav_cfg_string_set_node_value(config, &repo->password, node);
+ } else if(xstreq(key, "stored-user")) {
+ dav_cfg_string_set_node_value(config, &repo->stored_user, node);
+ } else if(xstreq(key, "default-key")) {
+ dav_cfg_string_set_node_value(config, &repo->default_key, node);
+ } else if(xstreq(key, "full-encryption")) {
+ dav_cfg_bool_set_node_value(config, &repo->full_encryption, node);
+ } else if(xstreq(key, "content-encryption")) {
+ dav_cfg_bool_set_node_value(config, &repo->content_encryption, node);
+ } else if(xstreq(key, "decrypt-content")) {
+ dav_cfg_bool_set_node_value(config, &repo->decrypt_content, node);
+ } else if(xstreq(key, "decrypt-name")) {
+ dav_cfg_bool_set_node_value(config, &repo->decrypt_name, node);
+ } else if(xstreq(key, "cert")) {
+ dav_cfg_string_set_node_value(config, &repo->cert, node);
+ } else if(xstreq(key, "verification")) {
+ dav_cfg_bool_set_node_value(config, &repo->verification, node);
+ } else if(xstreq(key, "ssl-version")) {
+ repo->ssl_version.node = node;
+ int ssl_version = dav_str2ssl_version((const char*)value);
+ if(ssl_version == -1) {
+ print_warning(lineno, "unknown ssl version: %s\n", value);
+ repo->ssl_version.value = CURL_SSLVERSION_DEFAULT;
+ } else {
+ repo->ssl_version.value = ssl_version;
+ }
+ } else if(xstreq(key, "authmethods")) {
+ repo->authmethods.node = node;
+ repo->authmethods.value = CURLAUTH_NONE;
+ const char *delims = " \t\r\n";
+ char *meths = strdup(value);
+ char *meth = strtok(meths, delims);
+ while (meth) {
+ if(xstrEQ(meth, "basic")) {
+ repo->authmethods.value |= CURLAUTH_BASIC;
+ } else if(xstrEQ(meth, "digest")) {
+ repo->authmethods.value |= CURLAUTH_DIGEST;
+ } else if(xstrEQ(meth, "negotiate")) {
+ repo->authmethods.value |= CURLAUTH_GSSNEGOTIATE;
+ } else if(xstrEQ(meth, "ntlm")) {
+ repo->authmethods.value |= CURLAUTH_NTLM;
+ } else if(xstrEQ(meth, "any")) {
+ repo->authmethods.value = CURLAUTH_ANY;
+ } else if(xstrEQ(meth, "none")) {
+ /* skip */
+ } else {
+ print_warning(lineno,
+ "unknown authentication method: %s\n", meth);
+ }
+ meth = strtok(NULL, delims);
+ }
+ free(meths);
+ } else {
+ print_error(lineno, "unkown repository config element: %s\n", key);
+ return 1;
+ }
+ return 0;
+}
+
+int dav_str2ssl_version(const char *value) {
+ if(xstrEQ(value, "TLSv1")) {
+ return CURL_SSLVERSION_TLSv1;
+ } else if(xstrEQ(value, "SSLv2")) {
+ return CURL_SSLVERSION_SSLv2;
+ } else if(xstrEQ(value, "SSLv3")) {
+ return CURL_SSLVERSION_SSLv3;
+ }
+#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7034
+ else if(xstrEQ(value, "TLSv1.0")) {
+ return CURL_SSLVERSION_TLSv1_0;
+ } else if(xstrEQ(value, "TLSv1.1")) {
+ return CURL_SSLVERSION_TLSv1_1;
+ } else if(xstrEQ(value, "TLSv1.2")) {
+ return CURL_SSLVERSION_TLSv1_2;
+ }
+#endif
+#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7052
+ else if(xstrEQ(value, "TLSv1.3")) {
+ return CURL_SSLVERSION_TLSv1_3;
+ }
+#endif
+ return -1;
+}
+
+static int load_repository(
+ DavConfig *config,
+ DavCfgRepository **list_begin,
+ DavCfgRepository **list_end,
+ xmlNode *reponode)
+{
+ DavCfgRepository *repo = dav_repository_new(config);
+ repo->node = reponode;
+
+ // add repo config from child nodes
+ xmlNode *node = reponode->children;
+ int ret = 0;
+ while(node && !ret) {
+ if(node->type == XML_ELEMENT_NODE) {
+ ret = repo_add_config(config, repo, node);
+ }
+ node = node->next;
+ }
+
+ // success: add repo to the configuration, error: free repo
+ if(ret) {
+ return 1;
+ } else {
+ cx_linked_list_add(
+ (void**)list_begin,
+ (void**)list_end,
+ offsetof(DavCfgRepository, prev),
+ offsetof(DavCfgRepository, next),
+ repo);
+ }
+
+ return 0;
+}
+
+static xmlNode* addXmlNode(xmlNode *node, const char *name, cxmutstr content) {
+ xmlNode *text1 = xmlNewDocText(node->doc, BAD_CAST "\t\t");
+ xmlAddChild(node, text1);
+
+ cxmutstr ctn = cx_strdup(cx_strcast(content));
+ xmlNode *newNode = xmlNewChild(node, NULL, BAD_CAST name, BAD_CAST ctn.ptr);
+ free(ctn.ptr);
+
+ xmlNode *text2 = xmlNewDocText(node->doc, BAD_CAST "\n");
+ xmlAddChild(node, text2);
+
+ return newNode;
+}
+
+void dav_config_add_repository(DavConfig *config, DavCfgRepository *repo) {
+ if(repo->node) {
+ fprintf(stderr, "Error: dav_config_add_repository: node already exists\n");
+ return;
+ }
+
+ xmlNode *repoNode = xmlNewNode(NULL, BAD_CAST "repository");
+ xmlNode *rtext1 = xmlNewDocText(config->doc, BAD_CAST "\n");
+ xmlAddChild(repoNode, rtext1);
+ repo->node = repoNode;
+ if(repo->name.value.ptr) {
+ repo->name.node = addXmlNode(repoNode, "name", repo->name.value);
+ }
+ if(repo->url.value.ptr) {
+ repo->url.node = addXmlNode(repoNode, "url", repo->url.value);
+ }
+ if(repo->user.value.ptr) {
+ repo->user.node = addXmlNode(repoNode, "user", repo->user.value);
+ }
+ if(repo->password.value.ptr) {
+ repo->password.node = addXmlNode(repoNode, "password", repo->password.value);
+ }
+
+ if(repo->stored_user.value.ptr) {
+ repo->stored_user.node = addXmlNode(repoNode, "stored-user", repo->stored_user.value);
+ }
+ if(repo->default_key.value.ptr) {
+ repo->default_key.node = addXmlNode(repoNode, "default-key", repo->default_key.value);
+ }
+ if(repo->cert.value.ptr) {
+ repo->cert.node = addXmlNode(repoNode, "cert", repo->cert.value);
+ }
+
+ // TODO: implement booleans
+
+ // indent closing tag
+ xmlNode *rtext2 = xmlNewDocText(config->doc, BAD_CAST "\t");
+ xmlAddChild(repoNode, rtext2);
+
+ // add repository to internal list
+ DavCfgRepository **list_begin = &config->repositories;
+ cx_linked_list_add(
+ (void**)list_begin,
+ NULL,
+ offsetof(DavCfgRepository, prev),
+ offsetof(DavCfgRepository, next),
+ repo);
+
+ // add repository element to the xml document
+ xmlNode *xml_root = xmlDocGetRootElement(config->doc);
+
+ xmlNode *text1 = xmlNewDocText(config->doc, BAD_CAST "\n\t");
+ xmlAddChild(xml_root, text1);
+
+ xmlAddChild(xml_root, repoNode);
+
+ xmlNode *text2 = xmlNewDocText(config->doc, BAD_CAST "\n");
+ xmlAddChild(xml_root, text2);
+}
+
+DavCfgRepository* dav_repository_new(DavConfig *config) {
+ DavCfgRepository *repo = cxMalloc(config->mp->allocator, sizeof(DavCfgRepository));
+ memset(repo, 0, sizeof(DavCfgRepository));
+ repo->decrypt_name.value = false;
+ repo->decrypt_content.value = true;
+ repo->decrypt_properties.value = false;
+ repo->verification.value = true;
+ repo->ssl_version.value = CURL_SSLVERSION_DEFAULT;
+ repo->authmethods.value = CURLAUTH_BASIC;
+ return repo;
+}
+
+void dav_repository_free(DavConfig *config, DavCfgRepository *repo) {
+ // TODO
+}
+
+void dav_repository_remove_and_free(DavConfig *config, DavCfgRepository *repo) {
+ if(repo->prev) {
+ repo->prev->next = repo->next;
+ }
+ if(repo->next) {
+ repo->next->prev = repo->prev;
+ }
+
+ if(repo->node) {
+ // TODO: remove newline after repo node
+
+ xmlUnlinkNode(repo->node);
+ xmlFreeNode(repo->node);
+ }
+}
+
+int dav_repository_get_flags(DavCfgRepository *repo) {
+ int flags = 0;
+
+ DavBool encrypt_content = FALSE;
+ DavBool encrypt_name = FALSE;
+ DavBool encrypt_properties = FALSE;
+ DavBool decrypt_content = FALSE;
+ DavBool decrypt_name = FALSE;
+ DavBool decrypt_properties = FALSE;
+ if(repo->full_encryption.value) {
+ encrypt_content = TRUE;
+ encrypt_name = TRUE;
+ encrypt_properties = TRUE;
+ decrypt_content = TRUE;
+ decrypt_name = TRUE;
+ decrypt_properties = TRUE;
+ } else if(repo->content_encryption.value) {
+ encrypt_content = TRUE;
+ decrypt_content = TRUE;
+ }
+
+ if(decrypt_content) {
+ flags |= DAV_SESSION_DECRYPT_CONTENT;
+ }
+ if(decrypt_name) {
+ flags |= DAV_SESSION_DECRYPT_NAME;
+ }
+ if(decrypt_properties) {
+ flags |= DAV_SESSION_DECRYPT_PROPERTIES;
+ }
+ if(encrypt_content) {
+ flags |= DAV_SESSION_ENCRYPT_CONTENT;
+ }
+ if(encrypt_name) {
+ flags |= DAV_SESSION_ENCRYPT_NAME;
+ }
+ if(encrypt_properties) {
+ flags |= DAV_SESSION_ENCRYPT_PROPERTIES;
+ }
+ return flags;
+}
+
+void dav_repository_set_url(DavConfig *config, DavCfgRepository *repo, cxstring newurl) {
+ if(repo->url.value.ptr) {
+ cxFree(config->mp->allocator, repo->url.value.ptr);
+ }
+ repo->url.value = cx_strdup_a(config->mp->allocator, newurl);
+}
+
+void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password) {
+ const CxAllocator *a = config->mp->allocator;
+ if(user.length > 0) {
+ repo->user.value = cx_strdup_a(a, user);
+ }
+ if(password.length > 0) {
+ char *pwenc = util_base64encode(password.ptr, password.length);
+ repo->password.value = cx_strdup_a(a, cx_str(pwenc));
+ free(pwenc);
+ }
+}
+
+cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo) {
+ cxmutstr pw = { NULL, 0 };
+ if(repo->password.value.ptr) {
+ pw = cx_mutstr(util_base64decode(repo->password.value.ptr));
+ }
+ return pw;
+}
+
+
+void dav_config_add_key(DavConfig *config, DavCfgKey *key) {
+ cx_linked_list_add(
+ (void**)&config->keys,
+ NULL,
+ offsetof(DavCfgKey, prev),
+ offsetof(DavCfgKey, next),
+ key);
+
+ if(key->node) {
+ fprintf(stderr, "Error: dav_config_add_key: node already exists\n");
+ return;
+ }
+
+ xmlNode *keyNode = xmlNewNode(NULL, BAD_CAST "key");
+ xmlNode *rtext1 = xmlNewDocText(config->doc, BAD_CAST "\n");
+ xmlAddChild(keyNode, rtext1);
+ key->node = keyNode;
+
+ if(key->name.value.ptr) {
+ key->name.node = addXmlNode(keyNode, "name", key->name.value);
+ }
+ const char *type = dav_config_keytype_str(key->type);
+ if(type) {
+ key->type_node = addXmlNode(keyNode, "type", cx_mutstr((char*)type));
+ }
+ if(key->file.value.ptr) {
+ key->file.node = addXmlNode(keyNode, "file", key->file.value);
+ }
+
+ // indent closing tag
+ xmlNode *rtext2 = xmlNewDocText(config->doc, BAD_CAST "\t");
+ xmlAddChild(keyNode, rtext2);
+
+ // add key element to the xml document
+ xmlNode *xml_root = xmlDocGetRootElement(config->doc);
+
+ xmlNode *text1 = xmlNewDocText(config->doc, BAD_CAST "\n\t");
+ xmlAddChild(xml_root, text1);
+
+ xmlAddChild(xml_root, keyNode);
+
+ xmlNode *text2 = xmlNewDocText(config->doc, BAD_CAST "\n");
+ xmlAddChild(xml_root, text2);
+}
+
+DavCfgKey* dav_key_new(DavConfig *config) {
+ DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey));
+ memset(key, 0, sizeof(DavCfgKey));
+ key->type = DAV_KEY_TYPE_AES256;
+ return key;
+}
+
+void dav_key_remove_and_free(DavConfig *config, DavCfgKey *key) {
+ cx_linked_list_remove(
+ (void**)&config->keys,
+ NULL,
+ offsetof(DavCfgKey, prev),
+ offsetof(DavCfgKey, next),
+ key);
+ if(key->node) {
+ // TODO: remove newline after key node
+
+ xmlUnlinkNode(key->node);
+ xmlFreeNode(key->node);
+ }
+}
+
+
+static int load_key(
+ DavConfig *config,
+ DavCfgKey **list_begin,
+ DavCfgKey **list_end,
+ xmlNode *keynode)
+{
+ xmlNode *node = keynode->children;
+ DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey));
+ memset(key, 0, sizeof(DavCfgKey));
+ key->type = DAV_KEY_TYPE_AES256;
+ key->node = keynode;
+
+ int error = 0;
+ while(node) {
+ if(node->type == XML_ELEMENT_NODE) {
+ if(xstreq(node->name, "name")) {
+ dav_cfg_string_set_node_value(config, &key->name, node);
+ } else if(xstreq(node->name, "file")) {
+ dav_cfg_string_set_node_value(config, &key->file, node);
+ } else if(xstreq(node->name, "type")) {
+ const char *value = util_xml_get_text(node);
+ key->type_node = node;
+ if(!strcmp(value, "aes128")) {
+ key->type = DAV_KEY_TYPE_AES128;
+ } else if(!strcmp(value, "aes256")) {
+ key->type = DAV_KEY_TYPE_AES256;
+ } else {
+ print_error(node->line, "unknown key type %s\n", value);
+ error = 1;
+ }
+ } else {
+ key->unknown_elements++;
+ }
+
+ }
+ node = node->next;
+ }
+
+ if(!key->name.value.ptr) {
+ error = 1;
+ }
+
+ if(!error) {
+ error = 0;
+ size_t expected_length = 0;
+ if(key->type == DAV_KEY_TYPE_AES128) {
+ expected_length = 16;
+ }
+ if(key->type == DAV_KEY_TYPE_AES256) {
+ expected_length = 32;
+ }
+ /*
+ if(key->length < expected_length) {
+ print_error(keynode->line, "key %s is too small (%zu < %zu)\n",
+ key->name,
+ key->length,
+ expected_length);
+ error = 1;
+ }
+
+ // add key to context
+ if(!error) {
+ cxMapPut(keys, cx_hash_key_str(key->name), key);
+ dav_context_add_key(context, key);
+ }
+ */
+ }
+
+ // cleanup
+ if(error) {
+ return 1;
+ } else {
+ // add key to the configuration
+ cx_linked_list_add(
+ (void**)list_begin,
+ (void**)list_end,
+ offsetof(DavCfgKey, prev),
+ offsetof(DavCfgKey, next),
+ key);
+
+ return 0;
+ }
+}
+
+static int load_proxy(
+ DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode, int type)
+{
+ const char *stype;
+ if(type == DAV_HTTPS_PROXY) {
+ stype = "https";
+ } else if(type == DAV_HTTP_PROXY) {
+ stype = "http";
+ } else {
+ fprintf(stderr, "unknown proxy type\n");
+ return 1;
+ }
+
+ if(!proxy) {
+ // no xml error - so report this directly via fprintf
+ fprintf(stderr, "no memory reserved for %s proxy.\n", stype);
+ return 1;
+ }
+
+ xmlNode *node = proxynode->children;
+ int ret = 0;
+ while(node && !ret) {
+ if(node->type == XML_ELEMENT_NODE) {
+ int reportmissingvalue = 0;
+ if(xstreq(node->name, "url")) {
+ reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->url, node);
+ } else if(xstreq(node->name, "user")) {
+ reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->user, node);
+ } else if(xstreq(node->name, "password")) {
+ reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->password, node);
+ } else if(xstreq(node->name, "no")) {
+ reportmissingvalue = dav_cfg_string_set_node_value(config, &proxy->noproxy, node);
+ } else {
+ proxy->unknown_elements++;
+ }
+
+ if (reportmissingvalue) {
+ print_error(node->line,
+ "missing value for proxy configuration element: %s\n",
+ node->name);
+ ret = 1;
+ break;
+ }
+ }
+ node = node->next;
+ }
+
+ if(!ret && !proxy->url.value.ptr) {
+ print_error(proxynode->line, "missing url for %s proxy.\n", stype);
+ return 1;
+ }
+
+ return ret;
+}
+
+static char* get_attr_content(xmlNode *node) {
+ // TODO: remove code duplication (util_xml_get_text)
+ while(node) {
+ if(node->type == XML_TEXT_NODE) {
+ return (char*)node->content;
+ }
+ node = node->next;
+ }
+ return NULL;
+}
+
+static int load_namespace(
+ DavConfig *config,
+ DavCfgNamespace **list_begin,
+ DavCfgNamespace **list_end,
+ xmlNode *node)
+{
+ const char *prefix = NULL;
+ const char *uri = NULL;
+ xmlAttr *attr = node->properties;
+ while(attr) {
+ if(attr->type == XML_ATTRIBUTE_NODE) {
+ char *value = get_attr_content(attr->children);
+ if(!value) {
+ print_error(
+ node->line,
+ "missing value for attribute %s\n", (char*)attr->name);
+ return 1;
+ }
+ if(xstreq(attr->name, "prefix")) {
+ prefix = value;
+ } else if(xstreq(attr->name, "uri")) {
+ uri = value;
+ } else {
+ print_error(
+ node->line,
+ "unexpected attribute %s\n", (char*)attr->name);
+ return 1;
+ }
+ }
+ attr = attr->next;
+ }
+
+ if(!prefix) {
+ print_error(node->line, "missing prefix attribute\n");
+ return 1;
+ }
+ if(!uri) {
+ print_error(node->line, "missing uri attribute\n");
+ return 1;
+ }
+
+ DavCfgNamespace *ns = cxMalloc(config->mp->allocator, sizeof(DavCfgNamespace));
+ memset(ns, 0, sizeof(DavCfgNamespace));
+ ns->node = node;
+ ns->prefix = cx_strdup_a(config->mp->allocator, cx_str(prefix));
+ ns->uri = cx_strdup_a(config->mp->allocator, cx_str(uri));
+ cx_linked_list_add(
+ (void**)list_begin,
+ (void**)list_end,
+ offsetof(DavCfgNamespace, prev),
+ offsetof(DavCfgNamespace, next),
+ ns);
+
+ return 0;
+}
+
+static int load_secretstore(DavConfig *config, xmlNode *node) {
+ // currently only one secretstore is supported
+
+ if(config->secretstore) {
+ return 1;
+ }
+
+ config->secretstore = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgSecretStore));
+
+ node = node->children;
+ int error = 0;
+ while(node) {
+ if(node->type == XML_ELEMENT_NODE) {
+ if(xstreq(node->name, "unlock-command")) {
+ dav_cfg_string_set_node_value(config, &config->secretstore->unlock_cmd, node);
+ } else if(xstreq(node->name, "lock-command")) {
+ dav_cfg_string_set_node_value(config, &config->secretstore->lock_cmd, node);
+ }
+ }
+ node = node->next;
+ }
+
+ return error;
+}
+
+
+
+
+
+DavCfgRepository* dav_config_get_repository(DavConfig *config, cxstring name) {
+ if(!config) {
+ return NULL;
+ }
+ DavCfgRepository *repo = config->repositories;
+ while(repo) {
+ if(!cx_strcmp(cx_strcast(repo->name.value), name)) {
+ return repo;
+ }
+ repo = repo->next;
+ }
+ return NULL;
+}
+
+DavCfgRepository* dav_config_url2repo(DavConfig *config, const char *url, char **path) {
+ cxmutstr p;
+ DavCfgRepository *repo = dav_config_url2repo_s(config, cx_str(url), &p);
+ *path = p.ptr;
+ return repo;
+}
+
+DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path) {
+ path->ptr = NULL;
+ path->length = 0;
+
+ int s;
+ if(cx_strprefix(url, CX_STR("http://"))) {
+ s = 7;
+ } else if(cx_strprefix(url, CX_STR("https://"))) {
+ s = 8;
+ } else {
+ s = 1;
+ }
+
+ // split URL into repository and path
+ cxstring r = cx_strsubs(url, s);
+ cxstring p = cx_strchr(r, '/');
+ r = cx_strsubsl(url, 0, url.length-p.length);
+ if(p.length == 0) {
+ p = cx_strn("/", 1);
+ }
+
+ DavCfgRepository *repo = dav_config_get_repository(config, r);
+ if(repo) {
+ *path = cx_strdup(p);
+ } else {
+ // TODO: who is responsible for freeing this repository?
+ // how can the callee know, if he has to call free()?
+ repo = dav_repository_new(config);
+ repo->name.value = cx_strdup_a(config->mp->allocator, CX_STR(""));
+ if(url.ptr[url.length-1] == '/') {
+ repo->url.value = cx_strdup_a(config->mp->allocator, url);
+ *path = cx_strdup(CX_STR("/"));
+ } else if (cx_strchr(url, '/').length > 0) {
+ // TODO: fix the following workaround after
+ // fixing the inconsistent behavior of util_url_*()
+ cxstring repo_url = util_url_base_s(url);
+ repo->url.value = cx_strdup_a(config->mp->allocator, repo_url);
+ *path = cx_strdup(util_url_path_s(url));
+ } else {
+ repo->url.value = cx_strdup(url);
+ *path = cx_strdup(CX_STR("/"));
+ }
+ }
+
+ return repo;
+}
+
+int dav_config_keytype(DavCfgKeyType type) {
+ switch(type) {
+ default: break;
+ case DAV_KEY_TYPE_AES256: return DAV_KEY_AES256;
+ case DAV_KEY_TYPE_AES128: return DAV_KEY_AES128;
+ }
+ return 0;
+}
+
+const char* dav_config_keytype_str(DavCfgKeyType type) {
+ switch(type) {
+ default: break;
+ case DAV_KEY_TYPE_AES256: return "aes256";
+ case DAV_KEY_TYPE_AES128: return "aes128";
+ }
+ return NULL;
+}
+
+int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey) {
+ for(DavCfgKey *key=config->keys;key;key=key->next) {
+ char *file = cx_strdup_m(key->file.value).ptr;
+ cxmutstr keycontent = loadkey(file);
+ free(file);
+
+ // TODO: check key length
+
+ if(!keycontent.ptr) {
+ return 1;
+ }
+
+ DavKey *davkey = calloc(1, sizeof(DavKey));
+ davkey->name = cx_strdup_m(key->name.value).ptr;
+ davkey->type = dav_config_keytype(key->type);
+ davkey->data = keycontent.ptr;
+ davkey->length = keycontent.length;
+
+ dav_context_add_key(ctx, davkey);
+ }
+ return 0;
+}
+
+int dav_config_register_namespaces(DavConfig *config, DavContext *ctx) {
+ DavCfgNamespace *ns = config->namespaces;
+ while(ns) {
+ dav_add_namespace(ctx, ns->prefix.ptr, ns->uri.ptr);
+ ns = ns->next;
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+#include "utils.h"
+
+#include "crypto.h"
+
+/* -------------------- OpenSSL Crypto Functions -------------------- */
+#ifdef DAV_USE_OPENSSL
+
+#if OPENSSL_VERSION_NUMBER < 0x10000000L
+
+static EVP_CIPHER_CTX* create_evp_cipher_ctx() {
+ EVP_CIPHER_CTX *ctx = malloc(sizeof(EVP_CIPHER_CTX));
+ EVP_CIPHER_CTX_init(ctx);
+ return ctx;
+}
+
+static void free_evp_cipher_ctx(EVP_CIPHER_CTX *ctx) {
+ EVP_CIPHER_CTX_cleanup(ctx);
+ free(ctx);
+}
+
+#define EVP_CIPHER_CTX_new() create_evp_cipher_ctx()
+#define EVP_CIPHER_CTX_free(ctx) free_evp_cipher_ctx(ctx)
+
+#endif
+
+int dav_rand_bytes(unsigned char *buf, size_t len) {
+ return !RAND_bytes(buf, len);
+}
+
+AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) {
+ AESDecrypter *dec = calloc(1, sizeof(AESDecrypter));
+ SHA256_Init(&dec->sha256);
+ dec->stream = stream;
+ dec->write = write_func;
+ dec->key = key;
+ dec->init = 0;
+ dec->ivpos = 0;
+
+ return dec;
+}
+
+void aes_decrypter_init(AESDecrypter *dec) {
+ //EVP_CIPHER_CTX_init(&dec->ctx);
+ dec->ctx = EVP_CIPHER_CTX_new();
+ dec->init = 1;
+ if(dec->key->type == DAV_KEY_AES128) {
+ EVP_DecryptInit_ex(
+ dec->ctx,
+ EVP_aes_128_cbc(),
+ NULL,
+ dec->key->data,
+ dec->ivtmp);
+ } else if(dec->key->type == DAV_KEY_AES256) {
+ EVP_DecryptInit_ex(
+ dec->ctx,
+ EVP_aes_256_cbc(),
+ NULL,
+ dec->key->data,
+ dec->ivtmp);
+ } else {
+ fprintf(stderr, "unknown key type\n");
+ exit(-1);
+ }
+}
+
+size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) {
+ int len = s*n;
+ if(!dec->init) {
+ size_t m = 16 - dec->ivpos;
+ size_t cp = m > len ? len : m;
+ memcpy(dec->ivtmp + dec->ivpos, buf, cp);
+ dec->ivpos += cp;
+ if(dec->ivpos >= 16) {
+ aes_decrypter_init(dec);
+ }
+ if(len == cp) {
+ return len;
+ } else {
+ buf = (char*)buf + cp;
+ len -= cp;
+ }
+ }
+
+ int outlen = len + 16;
+ unsigned char *out = malloc(outlen);
+ EVP_DecryptUpdate(dec->ctx, out, &outlen, buf, len);
+ ssize_t wlen = dec->write(out, 1, outlen, dec->stream);
+ SHA256_Update(&dec->sha256, out, wlen);
+ free(out);
+ return (s*n) / s;
+}
+
+void aes_decrypter_shutdown(AESDecrypter *dec) {
+ if(dec->init) {
+ void *out = malloc(128);
+ int len = 0;
+ EVP_DecryptFinal_ex(dec->ctx, out, &len);
+ dec->write(out, 1, len, dec->stream);
+ SHA256_Update(&dec->sha256, out, len);
+ free(out);
+ //EVP_CIPHER_CTX_cleanup(&dec->ctx);
+ EVP_CIPHER_CTX_free(dec->ctx);
+ }
+}
+
+void aes_decrypter_close(AESDecrypter *dec) {
+ free(dec);
+}
+
+
+AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
+ unsigned char *iv = malloc(16);
+ if(!RAND_bytes(iv, 16)) {
+ free(iv);
+ return NULL;
+ }
+
+ AESEncrypter *enc = malloc(sizeof(AESEncrypter));
+ SHA256_Init(&enc->sha256);
+ enc->stream = stream;
+ enc->read = read_func;
+ enc->seek = seek_func;
+ enc->tmp = NULL;
+ enc->tmplen = 0;
+ enc->tmpoff = 0;
+ enc->end = 0;
+ enc->iv = iv;
+ enc->ivlen = 16;
+
+ //EVP_CIPHER_CTX_init(&enc->ctx);
+ enc->ctx = EVP_CIPHER_CTX_new();
+ if(key->type == DAV_KEY_AES128) {
+ EVP_EncryptInit_ex(enc->ctx, EVP_aes_128_cbc(), NULL, key->data, enc->iv);
+ } else if(key->type == DAV_KEY_AES256) {
+ EVP_EncryptInit_ex(enc->ctx, EVP_aes_256_cbc(), NULL, key->data, enc->iv);
+ } else {
+ fprintf(stderr, "unknown key type\n");
+ exit(-1);
+ }
+ return enc;
+}
+
+size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) {
+ size_t len = s*n;
+ if(enc->tmp) {
+ size_t tmp_diff = enc->tmplen - enc->tmpoff;
+ size_t cp_len = tmp_diff > len ? len : tmp_diff;
+ memcpy(buf, enc->tmp + enc->tmpoff, cp_len);
+ enc->tmpoff += cp_len;
+ if(enc->tmpoff >= enc->tmplen) {
+ free(enc->tmp);
+ enc->tmp = NULL;
+ enc->tmplen = 0;
+ enc->tmpoff = 0;
+ }
+ return cp_len / s;
+ }
+
+ if(enc->end) {
+ return 0;
+ }
+
+ void *in = malloc(len);
+ size_t in_len = enc->read(in, 1, len, enc->stream);
+
+ SHA256_Update(&enc->sha256, in, in_len);
+
+ unsigned char *out = NULL;
+ int outlen = 0;
+ size_t ivl = enc->ivlen;
+ if(in_len != 0) {
+ outlen = len + 32;
+ out = malloc(outlen + ivl);
+ if(ivl > 0) {
+ memcpy(out, enc->iv, ivl);
+ }
+ EVP_EncryptUpdate(enc->ctx, out + ivl, &outlen, in, in_len);
+ // I think we don't need this
+ /*
+ if(in_len != len) {
+ int newoutlen = 16;
+ EVP_EncryptFinal_ex(enc->ctx, out + ivl + outlen, &newoutlen);
+ outlen += newoutlen;
+ enc->end = 1;
+ }
+ */
+ } else {
+ out = malloc(16);
+ EVP_EncryptFinal_ex(enc->ctx, out, &outlen);
+ enc->end = 1;
+ }
+ enc->tmp = (char*)out;
+ enc->tmplen = outlen + ivl;
+ enc->tmpoff = 0;
+
+ if(enc->ivlen > 0) {
+ enc->ivlen = 0;
+ }
+
+ free(in);
+
+ return aes_read(buf, s, n, enc);
+}
+
+void aes_encrypter_close(AESEncrypter *enc) {
+ if(enc->tmp) {
+ free(enc->tmp);
+ }
+ if(enc->iv) {
+ free(enc->iv);
+ }
+ //EVP_CIPHER_CTX_cleanup(&enc->ctx);
+ EVP_CIPHER_CTX_free(enc->ctx);
+ free(enc);
+}
+
+int aes_encrypter_reset(AESEncrypter *enc, curl_off_t offset, int origin) {
+ if(origin != SEEK_SET || offset != 0 || !enc->seek) {
+ return CURL_SEEKFUNC_CANTSEEK;
+ }
+
+ enc->ivlen = 16;
+ if(enc->seek(enc->stream, 0, SEEK_SET) != 0) {
+ return CURL_SEEKFUNC_FAIL;
+ }
+ return CURL_SEEKFUNC_OK;
+}
+
+
+char* aes_encrypt(const char *in, size_t len, DavKey *key) {
+ unsigned char iv[16];
+ if(!RAND_bytes(iv, 16)) {
+ return NULL;
+ }
+
+ //EVP_CIPHER_CTX ctx;
+ //EVP_CIPHER_CTX_init(&ctx);
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ if(key->type == DAV_KEY_AES128) {
+ EVP_EncryptInit_ex(
+ ctx,
+ EVP_aes_128_cbc(),
+ NULL,
+ (unsigned char*)key->data,
+ iv);
+ } else if(key->type == DAV_KEY_AES256) {
+ EVP_EncryptInit_ex(
+ ctx,
+ EVP_aes_256_cbc(),
+ NULL,
+ (unsigned char*)key->data,
+ iv);
+ } else {
+ //EVP_CIPHER_CTX_cleanup(&ctx);
+ EVP_CIPHER_CTX_free(ctx);
+ return NULL;
+ }
+
+ //int len = strlen(in);
+ int buflen = len + 64;
+ unsigned char *buf = calloc(1, buflen);
+ memcpy(buf, iv, 16);
+
+ int l = buflen - 16;
+ EVP_EncryptUpdate(ctx, buf + 16, &l, (unsigned char*)in, len);
+
+ int f = 0;
+ EVP_EncryptFinal_ex(ctx, buf + 16 + l, &f);
+ char *out = util_base64encode((char*)buf, 16 + l + f);
+ free(buf);
+ EVP_CIPHER_CTX_free(ctx);
+ //EVP_CIPHER_CTX_cleanup(&ctx);
+
+ return out;
+}
+
+char* aes_decrypt(const char *in, size_t *length, DavKey *key) {
+ int len;
+ unsigned char *buf = (unsigned char*)util_base64decode_len(in, &len);
+
+ //EVP_CIPHER_CTX ctx;
+ //EVP_CIPHER_CTX_init(&ctx);
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ if(key->type == DAV_KEY_AES128) {
+ EVP_DecryptInit_ex(
+ ctx,
+ EVP_aes_128_cbc(),
+ NULL,
+ key->data,
+ buf);
+ } else if(key->type == DAV_KEY_AES256) {
+ EVP_DecryptInit_ex(
+ ctx,
+ EVP_aes_256_cbc(),
+ NULL,
+ key->data,
+ buf);
+ } else {
+ //EVP_CIPHER_CTX_cleanup(&ctx);
+ EVP_CIPHER_CTX_free(ctx);
+ return NULL;
+ }
+
+ unsigned char *out = malloc(len + 1);
+ int outlen = len;
+ unsigned char *in_buf = buf + 16;
+ int inlen = len - 16;
+ int f = 0;
+
+ EVP_DecryptUpdate(ctx, out, &outlen, in_buf, inlen);
+ EVP_DecryptFinal_ex(ctx, out + outlen, &f);
+ out[outlen + f] = '\0';
+ free(buf);
+ //EVP_CIPHER_CTX_cleanup(&ctx);
+ EVP_CIPHER_CTX_free(ctx);
+
+ *length = outlen + f;
+ return (char*)out;
+}
+
+
+void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf){
+ SHA256_Final((unsigned char*)buf, sha256);
+}
+
+char* dav_create_hash(const char *data, size_t len) {
+ unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+ DAV_SHA_CTX ctx;
+ SHA256_Init(&ctx);
+ SHA256_Update(&ctx, data, len);
+ SHA256_Final(hash, &ctx);
+ return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
+}
+
+DAV_SHA_CTX* dav_hash_init(void) {
+ DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX));
+ SHA256_Init(ctx);
+ return ctx;
+}
+
+void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
+ SHA256_Update(ctx, data, len);
+}
+
+void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
+ SHA256_Final(buf, ctx);
+ free(ctx);
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static int crypto_pw2key_error = 0;
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
+ if(!crypto_pw2key_error) {
+ fprintf(stderr, "Error: password key derivation not supported on this platform: openssl to old\n");
+ crypto_pw2key_error = 1;
+ }
+ return 0;
+}
+
+#else
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
+ if(!password) {
+ return NULL;
+ }
+ size_t len = strlen(password);
+ if(len == 0) {
+ return NULL;
+ }
+
+ // setup key data and length
+ unsigned char keydata[32];
+ int keylen = 32;
+ switch(enc) {
+ case DAV_KEY_AES128: keylen = 16; break;
+ case DAV_KEY_AES256: keylen = 32; break;
+ default: return NULL;
+ }
+
+ // generate key
+ switch(pwfunc) {
+ case DAV_PWFUNC_PBKDF2_SHA256: {
+ PKCS5_PBKDF2_HMAC(
+ password,
+ len,
+ salt,
+ saltlen,
+ DAV_CRYPTO_ITERATION_COUNT,
+ EVP_sha256(),
+ keylen,
+ keydata);
+ break;
+ }
+ case DAV_PWFUNC_PBKDF2_SHA512: {
+ PKCS5_PBKDF2_HMAC(
+ password,
+ len,
+ salt,
+ saltlen,
+ DAV_CRYPTO_ITERATION_COUNT,
+ EVP_sha512(),
+ keylen,
+ keydata);
+ break;
+ }
+ default: return NULL;
+ }
+
+ // create DavKey with generated data
+ DavKey *key = malloc(sizeof(DavKey));
+ key->data = malloc(keylen);
+ key->length = keylen;
+ key->name = NULL;
+ key->type = enc;
+ memcpy(key->data, keydata, keylen);
+ return key;
+}
+#endif
+
+#endif
+
+
+/* -------------------- Apple Crypto Functions -------------------- */
+#ifdef DAV_CRYPTO_COMMON_CRYPTO
+
+#define RANDOM_BUFFER_LENGTH 256
+static char randbuf[RANDOM_BUFFER_LENGTH];
+static int rbufpos = RANDOM_BUFFER_LENGTH;
+
+int dav_rand_bytes(unsigned char *buf, size_t len) {
+ if(len + rbufpos > RANDOM_BUFFER_LENGTH) {
+ int devr = open("/dev/urandom", O_RDONLY);
+ if(devr == -1) {
+ return 1;
+ }
+
+ if(read(devr, randbuf, RANDOM_BUFFER_LENGTH) < RANDOM_BUFFER_LENGTH) {
+ close(devr);
+ return 1;
+ }
+
+ rbufpos = 0;
+ if(len > RANDOM_BUFFER_LENGTH) {
+ int err = 0;
+ if(read(devr, buf, len) < len) {
+ err = 1;
+ }
+ close(devr);
+ return err;
+ }
+
+ close(devr);
+ }
+
+ char *r = randbuf;
+ memcpy(buf, r + rbufpos, len);
+ rbufpos += len;
+
+ return 0;
+}
+
+AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) {
+ AESDecrypter *dec = calloc(1, sizeof(AESDecrypter));
+ CC_SHA256_Init(&dec->sha256);
+ dec->stream = stream;
+ dec->write = write_func;
+ dec->key = key;
+ dec->init = 0;
+ dec->ivpos = 0;
+
+ return dec;
+}
+
+
+void aes_decrypter_init(AESDecrypter *dec) {
+ //EVP_CIPHER_CTX_init(&dec->ctx);
+ dec->init = 1;
+
+ CCCryptorRef cryptor;
+ CCCryptorStatus status;
+ if(dec->key->type == DAV_KEY_AES128) {
+ status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, dec->key->data, dec->key->length, dec->ivtmp, &cryptor);
+ } else if(dec->key->type == DAV_KEY_AES256) {
+ status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, dec->key->data, dec->key->length, dec->ivtmp, &cryptor);
+ } else {
+ fprintf(stderr, "unknown key type\n");
+ exit(-1);
+ }
+ dec->ctx = cryptor;
+}
+
+size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) {
+ int len = s*n;
+ if(!dec->init) {
+ size_t n = 16 - dec->ivpos;
+ size_t cp = n > len ? len : n;
+ memcpy(dec->ivtmp + dec->ivpos, buf, cp);
+ dec->ivpos += cp;
+ if(dec->ivpos >= 16) {
+ aes_decrypter_init(dec);
+ }
+ if(len == cp) {
+ return len;
+ } else {
+ buf = (char*)buf + cp;
+ len -= cp;
+ }
+ }
+
+ int outlen = len + 16;
+ unsigned char *out = malloc(outlen);
+
+ CCCryptorStatus status;
+ size_t avail = outlen;
+ size_t moved = 0;
+ status = CCCryptorUpdate(dec->ctx, buf, len, out, avail, &moved);
+
+ ssize_t wlen = dec->write(out, 1, moved, dec->stream);
+ CC_SHA256_Update(&dec->sha256, out, wlen);
+ free(out);
+ return (s*n) / s;
+}
+
+void aes_decrypter_shutdown(AESDecrypter *dec) {
+ if(dec->init) {
+ void *out = malloc(128);
+ size_t len = 0;
+ //EVP_DecryptFinal_ex(dec->ctx, out, &len);
+ CCCryptorFinal(dec->ctx, out, 128, &len);
+
+
+ dec->write(out, 1, len, dec->stream);
+ CC_SHA256_Update(&dec->sha256, out, len);
+ free(out);
+ //EVP_CIPHER_CTX_cleanup(&dec->ctx);
+ //EVP_CIPHER_CTX_free(dec->ctx);
+ }
+}
+
+void aes_decrypter_close(AESDecrypter *dec) {
+
+}
+
+AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
+ unsigned char *iv = malloc(16);
+ if(dav_rand_bytes(iv, 16)) {
+ return NULL;
+ }
+
+ CCCryptorRef cryptor;
+ CCCryptorStatus status;
+ if(key->type == DAV_KEY_AES128) {
+ status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor);
+ } else if(key->type == DAV_KEY_AES256) {
+ status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor);
+ } else {
+ free(iv);
+ return NULL;
+ }
+
+ AESEncrypter *enc = malloc(sizeof(AESEncrypter));
+ enc->ctx = cryptor;
+ CC_SHA256_Init(&enc->sha256);
+ enc->stream = stream;
+ enc->read = read_func;
+ enc->seek = seek_func;
+ enc->tmp = NULL;
+ enc->tmplen = 0;
+ enc->tmpoff = 0;
+ enc->end = 0;
+ enc->iv = iv;
+ enc->ivlen = 16;
+
+ return enc;
+}
+
+size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) {
+ size_t len = s*n;
+ if(enc->tmp) {
+ size_t tmp_diff = enc->tmplen - enc->tmpoff;
+ size_t cp_len = tmp_diff > len ? len : tmp_diff;
+ memcpy(buf, enc->tmp + enc->tmpoff, cp_len);
+ enc->tmpoff += cp_len;
+ if(enc->tmpoff >= enc->tmplen) {
+ free(enc->tmp);
+ enc->tmp = NULL;
+ enc->tmplen = 0;
+ enc->tmpoff = 0;
+ }
+ return cp_len / s;
+ }
+
+ if(enc->end) {
+ return 0;
+ }
+
+ void *in = malloc(len);
+ size_t in_len = enc->read(in, 1, len, enc->stream);
+
+ CC_SHA256_Update(&enc->sha256, in, in_len);
+
+ unsigned char *out = NULL;
+ size_t outlen = 0;
+ size_t ivl = enc->ivlen;
+ if(in_len != 0) {
+ outlen = len + 32;
+ out = malloc(outlen + ivl);
+ if(ivl > 0) {
+ memcpy(out, enc->iv, ivl);
+ }
+
+ CCCryptorStatus status;
+ size_t avail = outlen;
+ status = CCCryptorUpdate(enc->ctx, in, in_len, out + ivl, avail, &outlen);
+ // TODO: check if this still works
+ /*
+ if(in_len != len) {
+ size_t newoutlen = 16;
+ status = CCCryptorFinal(enc->ctx, out + ivl + outlen, 16, &newoutlen);
+ outlen += newoutlen;
+ enc->end = 1;
+ }
+ */
+ } else {
+ out = malloc(32);
+ CCCryptorStatus status;
+ size_t avail = outlen;
+ status = CCCryptorFinal(enc->ctx, out, 32, &outlen);
+ enc->end = 1;
+ }
+ enc->tmp = (char*)out;
+ enc->tmplen = outlen + ivl;
+ enc->tmpoff = 0;
+
+ if(enc->ivlen > 0) {
+ enc->ivlen = 0;
+ }
+
+ free(in);
+
+ return aes_read(buf, s, n, enc);
+}
+
+int aes_encrypter_reset(AESEncrypter *enc, curl_off_t offset, int origin) {
+ if(origin != SEEK_SET || offset != 0 || !enc->seek) {
+ return CURL_SEEKFUNC_CANTSEEK;
+ }
+
+ enc->ivlen = 16;
+ if(enc->seek(enc->stream, 0, SEEK_SET) != 0) {
+ return CURL_SEEKFUNC_FAIL;
+ }
+ return CURL_SEEKFUNC_OK;
+}
+
+void aes_encrypter_close(AESEncrypter *enc) {
+ if(enc->tmp) {
+ free(enc->tmp);
+ }
+ if(enc->iv) {
+ free(enc->iv);
+ }
+ // TODO: cleanup cryptor
+ free(enc);
+}
+
+char* aes_encrypt(const char *in, size_t len, DavKey *key) {
+ unsigned char iv[16];
+ if(dav_rand_bytes(iv, 16)) {
+ return NULL;
+ }
+
+ CCCryptorRef cryptor;
+ CCCryptorStatus status;
+ if(key->type == DAV_KEY_AES128) {
+ status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor);
+ } else if(key->type == DAV_KEY_AES256) {
+ status = CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, key->data, key->length, iv, &cryptor);
+ } else {
+ return NULL;
+ }
+
+ if(status != kCCSuccess) {
+ return NULL;
+ }
+
+ int buflen = len + 64;
+ char *buf = calloc(1, buflen);
+ memcpy(buf, iv, 16);
+
+ int pos = 16;
+ size_t avail = buflen - 16;
+ size_t moved;
+ char *out = buf + 16;
+
+ status = CCCryptorUpdate(cryptor, in,
+ len, out, avail,
+ &moved);
+ if(status != kCCSuccess) {
+ free(buf);
+ return NULL;
+ }
+
+ pos += moved;
+ avail -= moved;
+ out += moved;
+
+ status = CCCryptorFinal(cryptor, out, avail, &moved);
+ if(status != kCCSuccess) {
+ free(buf);
+ return NULL;
+ }
+
+ pos += moved;
+
+ char *b64enc = util_base64encode(buf, pos);
+ free(buf);
+
+ return b64enc;
+}
+
+char* aes_decrypt(const char *in, size_t *len, DavKey *key) {
+ int inlen;
+ unsigned char *buf = (unsigned char*)util_base64decode_len(in, &inlen);
+
+ CCCryptorRef cryptor;
+ CCCryptorStatus status;
+ if(key->type == DAV_KEY_AES128) {
+ status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding, key->data, key->length, buf, &cryptor);
+ } else if(key->type == DAV_KEY_AES256) {
+ status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES, kCCOptionPKCS7Padding, key->data, key->length, buf, &cryptor);
+ } else {
+ free(buf);
+ return NULL;
+ }
+
+ if(status != kCCSuccess) {
+ free(buf);
+ return NULL;
+ }
+
+ char *out = malloc(inlen + 1);
+ size_t outavail = inlen;
+ size_t outlen = 0;
+
+ unsigned char *inbuf = buf + 16;
+ inlen -= 16;
+
+ size_t moved = 0;
+ status = CCCryptorUpdate(cryptor, inbuf, inlen, out, outavail, &moved);
+ if(status != kCCSuccess) {
+ free(buf);
+ free(out);
+ // TODO cryptor
+ return NULL;
+ }
+
+ outlen += moved;
+ outavail -= moved;
+
+ status = CCCryptorFinal(cryptor, out + outlen, outavail, &moved);
+ if(status != kCCSuccess) {
+ free(buf);
+ free(out);
+ // TODO cryptor
+ return NULL;
+ }
+
+ outlen += moved;
+ out[outlen] = 0;
+
+ *len = outlen;
+ return out;
+}
+
+void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf) {
+ CC_SHA256_Final(buf, sha256);
+}
+
+char* dav_create_hash(const char *data, size_t len) {
+ unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+ CC_SHA256((const unsigned char*)data, len, hash);
+ return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
+}
+
+DAV_SHA_CTX* dav_hash_init(void) {
+ DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX));
+ CC_SHA256_Init(ctx);
+ return ctx;
+}
+
+void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
+ CC_SHA256_Update(ctx, data, len);
+}
+
+void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
+ CC_SHA256_Final(buf, ctx);
+ free(ctx);
+}
+
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
+ if(!password) {
+ return NULL;
+ }
+ size_t len = strlen(password);
+ if(len == 0) {
+ return NULL;
+ }
+
+ // setup key data and length
+ unsigned char keydata[32];
+ int keylen = 32;
+ switch(enc) {
+ case DAV_KEY_AES128: keylen = 16; break;
+ case DAV_KEY_AES256: keylen = 32; break;
+ default: return NULL;
+ }
+
+ // generate key
+ switch(pwfunc) {
+ case DAV_PWFUNC_PBKDF2_SHA256: {
+ int result = CCKeyDerivationPBKDF(
+ kCCPBKDF2,
+ password,
+ len,
+ salt,
+ saltlen,
+ kCCPRFHmacAlgSHA256,
+ DAV_CRYPTO_ITERATION_COUNT,
+ keydata,
+ keylen);
+ if(result) {
+ return NULL;
+ }
+ break;
+ }
+ case DAV_PWFUNC_PBKDF2_SHA512: {
+ int result = CCKeyDerivationPBKDF(
+ kCCPBKDF2,
+ password,
+ len,
+ salt,
+ saltlen,
+ kCCPRFHmacAlgSHA512,
+ DAV_CRYPTO_ITERATION_COUNT,
+ keydata,
+ keylen);
+ if(result) {
+ return NULL;
+ }
+ break;
+ }
+ default: return NULL;
+ }
+
+ // create DavKey with generated data
+ DavKey *key = malloc(sizeof(DavKey));
+ key->data = malloc(keylen);
+ key->length = keylen;
+ key->name = NULL;
+ key->type = enc;
+ memcpy(key->data, keydata, keylen);
+ return key;
+}
+
+#endif
+
+/* -------------------- Windows Crypto Functions -------------------- */
+#ifdef DAV_CRYPTO_CNG
+
+static void cng_cleanup(BCRYPT_ALG_HANDLE hAesAlg, BCRYPT_KEY_HANDLE hKey, BCRYPT_HASH_HANDLE hHash, void *pbObject) {
+ if(hAesAlg) {
+ BCryptCloseAlgorithmProvider(hAesAlg,0);
+ }
+ if(hKey) {
+ BCryptDestroyKey(hKey);
+ }
+ if(hHash) {
+ BCryptDestroyHash(hHash);
+ }
+ if(pbObject) {
+ free(pbObject);
+ }
+}
+
+static int cng_init_key(BCRYPT_ALG_HANDLE *alg, BCRYPT_KEY_HANDLE *key, void **keyobj, DavKey *aesKey) {
+ BCRYPT_ALG_HANDLE hAesAlg = NULL;
+ BCRYPT_KEY_HANDLE hKey = NULL;
+
+ void *pbKeyObject = NULL;
+ ULONG keyObjectLength = 0;
+
+ ULONG result = 0;
+
+ // check DavKey and get AES key length
+ if(!aesKey) {
+ return 1;
+ }
+
+ ULONG aesKeyLength = 0;
+ if(aesKey->type == DAV_KEY_AES128) {
+ aesKeyLength = 16;
+ } else if(aesKey->type == DAV_KEY_AES256) {
+ aesKeyLength = 32;
+ }
+ if(aesKeyLength > aesKey->length || !aesKey->data) {
+ // invalid DavKey
+ return 1;
+ }
+
+ // initialize BCrypt stuff
+ if(BCryptOpenAlgorithmProvider(&hAesAlg, BCRYPT_AES_ALGORITHM, NULL, 0)) {
+ fprintf(stderr, "Error: BCryptOpenAlgorithmProvider failed\n");
+ return 1;
+ }
+
+ if(BCryptGetProperty(hAesAlg, BCRYPT_OBJECT_LENGTH, (PUCHAR)&keyObjectLength, sizeof(DWORD), &result, 0)) {
+ fprintf(stderr, "Error: BCrypt: Cannot get BCRYPT_OBJECT_LENGTH\n");
+ cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject);
+ return 1;
+ }
+
+ if(BCryptSetProperty(hAesAlg, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0)) {
+ fprintf(stderr, "Error: BCrypt: Cannot set CBC mode\n");
+ cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject);
+ return 1;
+ }
+
+ pbKeyObject = calloc(1, keyObjectLength);
+ if(!pbKeyObject) {
+ cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject);
+ return 1;
+ }
+
+ // init key
+ if(BCryptGenerateSymmetricKey(hAesAlg, &hKey, pbKeyObject, keyObjectLength, aesKey->data, aesKeyLength, 0)) {
+ fprintf(stderr, "Error: BCrypt: Cannot set key\n");
+ cng_cleanup(hAesAlg, hKey, NULL, pbKeyObject);
+ return 1;
+ }
+
+ *alg = hAesAlg;
+ *key = hKey;
+ *keyobj = pbKeyObject;
+
+ return 0;
+}
+
+static int cng_hash_init(WinBCryptSHACTX *ctx) {
+ if(BCryptOpenAlgorithmProvider(&ctx->hAlg, BCRYPT_SHA256_ALGORITHM, NULL, 0)) {
+ fprintf(stderr, "Error: BCryptOpenAlgorithmProvider failed\n");
+ return 1;
+ }
+
+ ULONG hashObjectLen;
+ ULONG result;
+ if(BCryptGetProperty(ctx->hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&hashObjectLen, sizeof(DWORD), &result, 0)) {
+ cng_cleanup(ctx->hAlg, NULL, NULL, NULL);
+ return 1;
+ }
+
+ ctx->pbHashObject = calloc(1, hashObjectLen);
+
+ if(BCryptCreateHash(ctx->hAlg, &ctx->hHash, ctx->pbHashObject, hashObjectLen, NULL, 0, 0)) {
+ cng_cleanup(ctx->hAlg, NULL, ctx->hHash, ctx->pbHashObject);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int dav_rand_bytes(unsigned char *buf, size_t len) {
+ if(BCryptGenRandom(NULL, (unsigned char*)buf, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG)) {
+ return 1;
+ }
+ return 0;
+}
+
+AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func) {
+ AESDecrypter *dec = calloc(1, sizeof(AESDecrypter));
+ if(!dec) {
+ return NULL;
+ }
+ if(cng_hash_init(&dec->sha256)) {
+ free(dec);
+ return NULL;
+ }
+
+ dec->stream = stream;
+ dec->write = write_func;
+ dec->key = key;
+ dec->init = 0;
+ dec->ivpos = 0;
+
+ return dec;
+}
+
+static void aes_decrypter_init(AESDecrypter *dec) {
+ if(cng_init_key(&dec->ctx.hAlg, &dec->ctx.hKey, &dec->ctx.pbKeyObject, dec->key)) {
+ fprintf(stderr, "Error: cng_init_key failed\n");
+ exit(-1);
+ }
+ // copy iv
+ memcpy(dec->ctx.pbIV, dec->ivtmp, 16);
+}
+
+size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec) {
+ int len = s*n;
+ if(!dec->init) {
+ dec->init = 1;
+
+ size_t n = 16 - dec->ivpos;
+ size_t cp = n > len ? len : n;
+ memcpy(dec->ivtmp + dec->ivpos, buf, cp);
+ dec->ivpos += cp;
+ if(dec->ivpos >= 16) {
+ aes_decrypter_init(dec);
+ }
+ if(len == cp) {
+ return len;
+ } else {
+ buf = (char*)buf + cp;
+ len -= cp;
+ }
+ }
+
+ // the cipher text must be a multiply of 16
+ // remaining bytes are stored in ctx.buf and must be added to cibuf
+ // the next time
+ size_t cbufalloc = len + 64;
+ ULONG clen = 0;
+ char *cbuf = malloc(cbufalloc);
+
+ // add previous remaining bytes
+ if(dec->ctx.buflen > 0) {
+ memcpy(cbuf, dec->ctx.buf, dec->ctx.buflen);
+ clen = dec->ctx.buflen;
+ }
+ // add current bytes
+ memcpy(cbuf + clen, buf, len);
+ clen += len;
+
+ // check if the message fits the blocksize
+ int remaining = clen % 16;
+ if(remaining == 0) {
+ // decrypt last block next time, or in aes_decrypter_shutdown
+ // this makes sure, that shutdown always decrypts the last block
+ // with BCRYPT_BLOCK_PADDING flag
+ remaining = 16;
+ }
+
+ // add remaining bytes to ctx.buf for the next aes_write run
+ clen -= remaining;
+ memcpy(dec->ctx.buf, cbuf + clen, remaining);
+ dec->ctx.buflen = remaining;
+
+ // ready to decrypt the message
+ ULONG outlen = clen + 32;
+
+ // decrypt
+ if(clen > 0) {
+ unsigned char* out = malloc(outlen);
+
+ ULONG enc_len = 0;
+ ULONG status = BCryptDecrypt(dec->ctx.hKey, cbuf, clen, NULL, dec->ctx.pbIV, 16, out, outlen, &enc_len, 0);
+ if(status > 0) {
+ fprintf(stderr, "Error: BCryptDecrypt failed: 0x%X\n", status);
+ free(out);
+ free(cbuf);
+ return 0;
+ }
+ outlen = enc_len;
+
+ // write decrypted data to the output stream and update the hash
+ dec->write(out, 1, outlen, dec->stream);
+ BCryptHashData(dec->sha256.hHash, out, outlen, 0);
+
+ free(out);
+ }
+
+ free(cbuf);
+
+ return (s*n) / s;
+}
+
+void aes_decrypter_shutdown(AESDecrypter *dec) {
+ if(dec->init && dec->ctx.buflen > 0) {
+ ULONG outlen = 64;
+ char out[64];
+ if(BCryptDecrypt(dec->ctx.hKey, dec->ctx.buf, dec->ctx.buflen, NULL, dec->ctx.pbIV, 16, out, outlen, &outlen, BCRYPT_BLOCK_PADDING)) {
+ fprintf(stderr, "Error: BCryptDecrypt failed\n");
+ return;
+ }
+ dec->write(out, 1, outlen, dec->stream);
+ BCryptHashData(dec->sha256.hHash, out, outlen, 0);
+ }
+}
+
+void aes_decrypter_close(AESDecrypter *dec) {
+ cng_cleanup(dec->ctx.hAlg, dec->ctx.hKey, NULL, dec->ctx.pbKeyObject);
+ cng_cleanup(dec->sha256.hAlg, NULL, dec->sha256.hHash, dec->sha256.pbHashObject);
+ free(dec);
+}
+
+AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
+ unsigned char *iv = malloc(16);
+ if(dav_rand_bytes(iv, 16)) {
+ free(iv);
+ return NULL;
+ }
+
+ AESEncrypter *enc = calloc(1, sizeof(AESEncrypter));
+ if(cng_hash_init(&enc->sha256)) {
+ free(iv);
+ free(enc);
+ return NULL;
+ }
+
+ enc->stream = stream;
+ enc->read = read_func;
+ enc->seek = seek_func;
+ enc->tmp = NULL;
+ enc->tmplen = 0;
+ enc->tmpoff = 0;
+ enc->end = 0;
+ enc->iv = iv;
+ enc->ivlen = 0;
+
+ if(cng_init_key(&enc->ctx.hAlg, &enc->ctx.hKey, &enc->ctx.pbKeyObject, key)) {
+ fprintf(stderr, "Error: cng_init_key failed\n");
+ exit(-1);
+ }
+
+ enc->ctx.buflen = 0;
+ memcpy(enc->ctx.pbIV, iv, 16);
+
+ return enc;
+}
+
+size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc) {
+ size_t len = s*n;
+ size_t nread = 0;
+
+ if(enc->tmp) {
+ // the temp buffer contains bytes that are already encrypted, but
+ // the last aes_read had not enough read buffer space
+
+ // in case we have a tmp buf, we just return this
+ size_t tmp_diff = enc->tmplen - enc->tmpoff;
+ size_t cp_len = tmp_diff > len ? len : tmp_diff;
+ memcpy(buf, enc->tmp + enc->tmpoff, cp_len);
+ enc->tmpoff += cp_len;
+ if(enc->tmpoff >= enc->tmplen) {
+ free(enc->tmp);
+ enc->tmp = NULL;
+ enc->tmplen = 0;
+ enc->tmpoff = 0;
+ }
+ return cp_len / s;
+ }
+
+ if(enc->ivlen < 16) {
+ size_t copy_iv_len = 16 - enc->ivlen;
+ copy_iv_len = len > copy_iv_len ? copy_iv_len : len;
+
+ memcpy(buf, enc->iv, copy_iv_len);
+ (char*)buf += copy_iv_len;
+ len -= copy_iv_len;
+ nread = copy_iv_len;
+
+ enc->ivlen += copy_iv_len;
+
+ if(len == 0) {
+ return copy_iv_len / s;
+ }
+ }
+
+ if(enc->end) {
+ return 0;
+ }
+
+ size_t remaining = len % 16;
+ len -= remaining;
+
+ if(len > 256) {
+ len -= 16; // optimization for avoiding tmp buffer usage
+ }
+
+ size_t inalloc = len;
+ ULONG inlen = 0;
+ unsigned char *in = malloc(inalloc);
+
+ // fill the input buffer
+ while(inlen < inalloc) {
+ size_t r = enc->read(in + inlen, 1, inalloc - inlen, enc->stream);
+ if(r == 0) {
+ enc->end = 1;
+ break;
+ }
+ inlen += r;
+ }
+
+ if(inlen == 0) {
+ return nread / s;
+ }
+
+ // hash read data
+ BCryptHashData(enc->sha256.hHash, in, inlen, 0);
+
+ // create output buffer
+ ULONG outalloc = inlen + 16;
+ ULONG outlen = 0;
+ char *out = malloc(outalloc);
+
+ // encrypt
+ int flags = 0;
+ if(inlen % 16 != 0) {
+ enc->end = 1;
+ }
+ if(enc->end) {
+ flags = BCRYPT_BLOCK_PADDING;
+ }
+ if(BCryptEncrypt(enc->ctx.hKey, in, inlen, NULL, enc->ctx.pbIV, 16, out, outalloc, &outlen, flags)) {
+ fprintf(stderr, "Error: BCryptEncrypt failed\n");
+ }
+
+ // check if the output fits in buf, if not, save the remaining bytes in tmp
+ if(outlen > len) {
+ size_t tmplen = outlen - len;
+ char *tmp = malloc(tmplen);
+ memcpy(tmp, out+len, tmplen);
+
+ enc->tmp = tmp;
+ enc->tmplen = tmplen;
+ enc->tmpoff = 0;
+
+ outlen = len;
+ }
+
+ // fill read buffer and return
+ memcpy(buf, out, outlen);
+ nread += outlen;
+
+ free(in);
+ free(out);
+
+ return nread / s;
+}
+
+void aes_encrypter_close(AESEncrypter *enc) {
+ enc->end = 1;
+}
+
+int aes_encrypter_reset(AESEncrypter *enc, curl_off_t offset, int origin) {
+ if(origin != SEEK_SET || offset != 0 || !enc->seek) {
+ return CURL_SEEKFUNC_CANTSEEK;
+ }
+
+ enc->ivlen = 0;
+ memcpy(enc->ctx.pbIV, enc->iv, 16);
+ if(enc->seek(enc->stream, 0, SEEK_SET) != 0) {
+ return CURL_SEEKFUNC_FAIL;
+ }
+ return CURL_SEEKFUNC_OK;
+}
+
+char* aes_encrypt(const char *in, size_t len, DavKey *key) {
+ // create random IV
+ char iv[16];
+ if(dav_rand_bytes(iv, 16)) {
+ return NULL;
+ }
+
+ // initialize bcrypt stuff
+ BCRYPT_ALG_HANDLE hAlg = NULL;
+ BCRYPT_KEY_HANDLE hKey = NULL;
+ void *pbKeyObject = NULL;
+ if(cng_init_key(&hAlg, &hKey, &pbKeyObject, key)) {
+ return NULL;
+ }
+
+ // create output buffer
+ ULONG outlen = len + 128;
+ char *out = malloc(outlen);
+
+ // the output must start with the IV
+ memcpy(out, iv, 16);
+ char *encbuf = out + 16;
+ ULONG enclen = outlen - 16;
+ ULONG encoutlen = 0;
+
+ // encrypt
+ if(BCryptEncrypt(hKey, (PUCHAR)in, len, NULL, (PUCHAR)iv, 16, encbuf, enclen, &encoutlen, BCRYPT_BLOCK_PADDING)) {
+ fprintf(stderr, "Error: BCryptEncrypt failed\n");
+ cng_cleanup(hAlg, hKey, NULL, pbKeyObject);
+ free(out);
+ return NULL;
+ }
+
+ outlen = encoutlen + 16; // length of encrypted data + 16 bytes IV
+
+ // base64 encode
+ char *outstr = util_base64encode(out, outlen);
+
+ cng_cleanup(hAlg, hKey, NULL, pbKeyObject);
+ free(out);
+
+ return outstr;
+}
+
+char* aes_decrypt(const char *in, size_t *len, DavKey *key) {
+ BCRYPT_ALG_HANDLE hAlg = NULL;
+ BCRYPT_KEY_HANDLE hKey = NULL;
+ void *pbKeyObject = NULL;
+ if(cng_init_key(&hAlg, &hKey, &pbKeyObject, key)) {
+ return NULL;
+ }
+
+ int inlen;
+ unsigned char *buf = (unsigned char*)util_base64decode_len(in, &inlen);
+ if(inlen < 16 || !buf) {
+ cng_cleanup(hAlg, hKey, NULL, pbKeyObject);
+ if(buf) {
+ free(buf);
+ }
+ return NULL;
+ }
+
+ // encrypted data starts with IV
+ char iv[16];
+ memcpy(iv, buf, 16);
+
+ // decrypt data
+ char *data = buf + 16; // encrypted data starts after IV
+ size_t datalen = inlen - 16;
+
+ // create output buffer
+ ULONG outlen = inlen;
+ char *out = malloc(outlen + 1);
+
+ // decrypt
+ if(BCryptDecrypt(hKey, data, datalen, NULL, iv, 16, out, outlen, &outlen, BCRYPT_BLOCK_PADDING)) {
+ cng_cleanup(hAlg, hKey, NULL, pbKeyObject);
+ free(out);
+ free(buf);
+ return NULL;
+ }
+
+ // decrypt finished, return
+ out[outlen] = 0;
+ *len = (size_t)outlen;
+ return out;
+}
+
+void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf) {
+ BCryptFinishHash(sha256->hHash, buf, DAV_SHA256_DIGEST_LENGTH, 0);
+}
+
+
+char* dav_create_hash(const char *data, size_t len) {
+ unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+ DAV_SHA_CTX *ctx = dav_hash_init();
+ if(ctx) {
+ dav_hash_update(ctx, data, len);
+ dav_hash_final(ctx, hash);
+ }
+ return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
+}
+
+DAV_SHA_CTX* dav_hash_init(void) {
+ DAV_SHA_CTX *ctx = malloc(sizeof(DAV_SHA_CTX));
+ if(!ctx) {
+ return NULL;
+ }
+ if(cng_hash_init(ctx)) {
+ free(ctx);
+ return NULL;
+ }
+ return ctx;
+}
+
+void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len) {
+ BCryptHashData(ctx->hHash, (PUCHAR)data, len, 0);
+}
+
+void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf) {
+ BCryptFinishHash(ctx->hHash, (PUCHAR)buf, DAV_SHA256_DIGEST_LENGTH, 0);
+
+ // cleanup
+ cng_cleanup(ctx->hAlg, NULL, ctx->hHash, ctx->pbHashObject);
+ free(ctx);
+}
+
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc) {
+ if(!password) {
+ return NULL;
+ }
+ size_t len = strlen(password);
+ if(len == 0) {
+ return NULL;
+ }
+
+ // setup key data and length
+ unsigned char keydata[128];
+ int keylen = 32;
+ switch(enc) {
+ case DAV_KEY_AES128: keylen = 16; break;
+ case DAV_KEY_AES256: keylen = 32; break;
+ default: return NULL;
+ }
+
+ LPCWSTR algid;
+ switch(pwfunc) {
+ case DAV_PWFUNC_PBKDF2_SHA256: algid = BCRYPT_SHA256_ALGORITHM; break;
+ case DAV_PWFUNC_PBKDF2_SHA512: algid = BCRYPT_SHA512_ALGORITHM; break;
+ default: return NULL;
+ }
+
+ // open algorithm provider
+ BCRYPT_ALG_HANDLE hAlg;
+ ULONG status = BCryptOpenAlgorithmProvider(&hAlg, algid, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG);
+ if(status > 0) {
+ fprintf(stderr, "Error: dav_pw2key: BCryptOpenAlgorithmProvider failed: 0x%X\n", (unsigned int)status);
+ return NULL;
+ }
+
+ // derive key
+ status = BCryptDeriveKeyPBKDF2(
+ hAlg,
+ (PUCHAR)password,
+ len,
+ (PUCHAR)salt,
+ saltlen,
+ DAV_CRYPTO_ITERATION_COUNT,
+ keydata,
+ 128,
+ 0);
+
+ BCryptCloseAlgorithmProvider(hAlg,0);
+
+ if(status) {
+ fprintf(stderr, "Error: dav_pw2key: BCryptDeriveKeyPBKDF2 failed: 0x%X\n", (unsigned int)status);
+ return NULL;
+ }
+
+ // create DavKey with generated data
+ DavKey *key = malloc(sizeof(DavKey));
+ key->data = malloc(keylen);
+ key->length = keylen;
+ key->name = NULL;
+ key->type = enc;
+ memcpy(key->data, keydata, keylen);
+ return key;
+}
+#endif
+
+
+
+CxBuffer* aes_encrypt_buffer(CxBuffer *in, DavKey *key) {
+ CxBuffer *encbuf = cxBufferCreate(NULL, in->size, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ if(!encbuf) {
+ return NULL;
+ }
+
+ AESEncrypter *enc = aes_encrypter_new(
+ key,
+ in,
+ (dav_read_func)cxBufferRead,
+ NULL);
+ if(!enc) {
+ cxBufferFree(encbuf);
+ return NULL;
+ }
+
+ char buf[1024];
+ size_t r;
+ while((r = aes_read(buf, 1, 1024, enc)) > 0) {
+ cxBufferWrite(buf, 1, r, encbuf);
+ }
+ aes_encrypter_close(enc);
+
+ encbuf->pos = 0;
+ return encbuf;
+}
+
+CxBuffer* aes_decrypt_buffer(CxBuffer *in, DavKey *key) {
+ CxBuffer *decbuf = cxBufferCreate(NULL, in->size, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ if(!decbuf) {
+ return NULL;
+ }
+ AESDecrypter *dec = aes_decrypter_new(
+ key,
+ decbuf,
+ (dav_write_func)cxBufferWrite);
+ if(!dec) {
+ cxBufferFree(decbuf);
+ return NULL;
+ }
+
+ aes_write(in->space, 1, in->size, dec);
+ aes_decrypter_shutdown(dec);
+ aes_decrypter_close(dec);
+ decbuf->pos = 0;
+ return decbuf;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DAV_CRYPTO_H
+#define DAV_CRYPTO_H
+
+#include "webdav.h"
+#include <cx/string.h>
+
+#ifdef __APPLE__
+/* macos */
+
+#define DAV_CRYPTO_COMMON_CRYPTO
+
+#define DAV_AES_CTX CCCryptorRef
+#define DAV_SHA_CTX CC_SHA256_CTX
+#define DAV_SHA256_DIGEST_LENGTH 32
+
+#include <CommonCrypto/CommonCrypto.h>
+#include <CommonCrypto/CommonDigest.h>
+
+#elif defined(_WIN32)
+
+#define DAV_CRYPTO_CNG
+
+#include <windows.h>
+#include <bcrypt.h>
+
+typedef struct WinBCryptCTX {
+ BCRYPT_ALG_HANDLE hAlg;
+ BCRYPT_KEY_HANDLE hKey;
+ void *pbKeyObject;
+ unsigned char pbIV[16];
+
+ unsigned char buf[16];
+ ULONG buflen;
+} WinBCryptCTX;
+
+typedef struct WinBCryptSHACTX {
+ BCRYPT_ALG_HANDLE hAlg;
+ BCRYPT_HASH_HANDLE hHash;
+ void *pbHashObject;
+} WinBCryptSHACTX;
+
+#define DAV_AES_CTX WinBCryptCTX
+#define DAV_SHA_CTX WinBCryptSHACTX
+#define DAV_SHA256_DIGEST_LENGTH 32
+
+#else
+/* unix/linux */
+
+#define DAV_USE_OPENSSL
+
+#define DAV_AES_CTX EVP_CIPHER_CTX*
+#define DAV_SHA_CTX SHA256_CTX
+#define DAV_SHA256_DIGEST_LENGTH 32
+
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
+#if defined(__sun) && defined(__SunOS_5_10)
+#include <sha2.h>
+#define SHA256_Init SHA256Init
+#define SHA256_Update SHA256Update
+#define SHA256_Final SHA256Final
+#else
+#include <openssl/sha.h>
+#endif
+
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DAV_PWFUNC_PBKDF2_SHA256 0
+#define DAV_PWFUNC_PBKDF2_SHA512 1
+
+#define DAV_CRYPTO_ITERATION_COUNT 4000
+
+typedef struct {
+ DAV_AES_CTX ctx;
+ DAV_SHA_CTX sha256;
+ void *stream;
+ dav_write_func write;
+ DavKey *key;
+ int init;
+ unsigned char ivtmp[16];
+ size_t ivpos;
+} AESDecrypter;
+
+typedef struct {
+ DAV_AES_CTX ctx;
+ DAV_SHA_CTX sha256;
+ void *iv;
+ size_t ivlen;
+ void *stream;
+ dav_read_func read;
+ dav_seek_func seek;
+ char *tmp;
+ size_t tmplen;
+ size_t tmpoff;
+ int end;
+} AESEncrypter;
+
+typedef struct DavHashContext DavHashContext;
+
+int dav_rand_bytes(unsigned char *buf, size_t len);
+
+AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func);
+size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec);
+void aes_decrypter_shutdown(AESDecrypter *dec);
+void aes_decrypter_close(AESDecrypter *dec);
+
+AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func);
+size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc);
+void aes_encrypter_close(AESEncrypter *enc);
+int aes_encrypter_reset(AESEncrypter *enc, curl_off_t offset, int origin);
+
+char* aes_encrypt(const char *in, size_t len, DavKey *key);
+char* aes_decrypt(const char *in, size_t *len, DavKey *key);
+
+void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf);
+
+char* dav_create_hash(const char *data, size_t len);
+
+DAV_SHA_CTX* dav_hash_init(void);
+void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len);
+void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf);
+
+DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc);
+
+CxBuffer* aes_encrypt_buffer(CxBuffer *in, DavKey *key);
+CxBuffer* aes_decrypt_buffer(CxBuffer *in, DavKey *key);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAV_CRYPTO_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include <cx/utils.h>
+#include <cx/map.h>
+#include <cx/hash_map.h>
+#include <cx/printf.h>
+#include <cx/mempool.h>
+
+#include "davqlexec.h"
+#include "utils.h"
+#include "methods.h"
+#include "session.h"
+#include "resource.h"
+
+DavQLArgList* dav_ql_get_args(DavQLStatement *st, va_list ap) {
+ DavQLArgList *args = malloc(sizeof(DavQLArgList));
+ if(!args) {
+ return NULL;
+ }
+ args->first = NULL;
+
+ if(!st->args) {
+ args->first = NULL;
+ args->current = NULL;
+ return args;
+ }
+
+ DavQLArg *cur = NULL;
+ CxIterator i = cxListIterator(st->args);
+ cx_foreach(void*, data, i) {
+ intptr_t type = (intptr_t)data;
+ DavQLArg *arg = calloc(1, sizeof(DavQLArg));
+ if(!arg) {
+ dav_ql_free_arglist(args);
+ return NULL;
+ }
+ arg->type = type;
+ switch(type) {
+ case 'd': {
+ arg->value.d = va_arg(ap, int);
+ break;
+ }
+ case 'u': {
+ arg->value.u = va_arg(ap, unsigned int);
+ break;
+ }
+ case 's': {
+ arg->value.s = va_arg(ap, char*);
+ break;
+ }
+ case 't': {
+ arg->value.t = va_arg(ap, time_t);
+ break;
+ }
+ default: {
+ free(arg);
+ dav_ql_free_arglist(args);
+ return NULL;
+ }
+ }
+ if(cur) {
+ cur->next = arg;
+ } else {
+ args->first = arg;
+ }
+ cur = arg;
+ }
+ args->current = args->first;
+ return args;
+}
+
+void dav_ql_free_arglist(DavQLArgList *args) {
+ DavQLArg *arg = args->first;
+ while(arg) {
+ DavQLArg *next = arg->next;
+ free(arg);
+ arg = next;
+ }
+ free(args);
+}
+
+static DavQLArg* arglist_get(DavQLArgList *args) {
+ DavQLArg *a = args->current;
+ if(a) {
+ args->current = a->next;
+ }
+ return a;
+}
+
+int dav_ql_getarg_int(DavQLArgList *args) {
+ DavQLArg *a = arglist_get(args);
+ if(a && a->type == 'd') {
+ return a->value.d;
+ }
+ return 0;
+}
+
+unsigned int dav_ql_getarg_uint(DavQLArgList *args) {
+ DavQLArg *a = arglist_get(args);
+ if(a && a->type == 'u') {
+ return a->value.u;
+ }
+ return 0;
+}
+
+char* dav_ql_getarg_str(DavQLArgList *args) {
+ DavQLArg *a = arglist_get(args);
+ if(a && a->type == 's') {
+ return a->value.s;
+ }
+ return "";
+}
+
+time_t dav_ql_getarg_time(DavQLArgList *args) {
+ DavQLArg *a = arglist_get(args);
+ if(a && a->type == 't') {
+ return a->value.t;
+ }
+ return 0;
+}
+
+
+DavResult dav_statement_exec(DavSession *sn, DavQLStatement *st, ...) {
+ va_list ap;
+ va_start(ap, st);
+ DavResult result = dav_statement_execv(sn, st, ap);
+ va_end(ap);
+ return result;
+}
+
+DavResult dav_statement_execv(DavSession *sn, DavQLStatement *st, va_list ap) {
+ DavResult result;
+ result.result = NULL;
+ result.status = 1;
+
+ // make sure the statement was successfully parsed
+ if(st->type == DAVQL_ERROR) {
+ return result;
+ }
+
+ if(st->type == DAVQL_SELECT) {
+ return dav_exec_select(sn, st, ap);
+ } else {
+ // TODO
+ }
+
+ return result;
+}
+
+cxmutstr dav_format_string(const CxAllocator *a, cxstring fstr, DavQLArgList *ap, davqlerror_t *error) {
+ CxBuffer buf;
+ cxBufferInit(&buf, NULL, 128, a, CX_BUFFER_AUTO_EXTEND);
+
+ int placeholder = 0;
+ for(int i=0;i<fstr.length;i++) {
+ char c = fstr.ptr[i];
+ if(placeholder) {
+ if(c == '%') {
+ // no placeholder, %% transposes to %
+ cxBufferPut(&buf, c);
+ } else {
+ // detect placeholder type and insert arg
+ int err = 0;
+ switch(c) {
+ case 's': {
+ char *arg = dav_ql_getarg_str(ap);
+ cxBufferPutString(&buf, arg);
+ break;
+ }
+ case 'd': {
+ int arg = dav_ql_getarg_int(ap);
+ cx_bprintf(&buf, "%d", arg);
+ break;
+ }
+ case 'u': {
+ unsigned int arg = dav_ql_getarg_uint(ap);
+ cx_bprintf(&buf, "%u", arg);
+ break;
+ }
+ case 't': {
+ // time arguments not supported for strings
+ err = 1;
+ break;
+ }
+ default: {
+ *error = DAVQL_UNKNOWN_FORMATCHAR;
+ err = 1;
+ }
+ }
+ if(err) {
+ cxBufferDestroy(&buf);
+ return (cxmutstr){NULL,0};
+ }
+ }
+ placeholder = 0;
+ } else {
+ if(c == '%') {
+ placeholder = 1;
+ } else {
+ cxBufferPut(&buf, c);
+ }
+ }
+ }
+ if(cxBufferPut(&buf, '\0')) {
+ *error = DAVQL_OOM;
+ cxBufferDestroy(&buf);
+ return (cxmutstr){NULL, 0};
+ }
+ *error = DAVQL_OK;
+
+ return cx_mutstrn(buf.space, buf.size-1);
+}
+
+static int fl_add_properties(DavSession *sn, const CxAllocator *a, CxMap *map, DavQLExpression *expression) {
+ if(!expression) {
+ return 0;
+ }
+
+ if(expression->type == DAVQL_IDENTIFIER) {
+ DavProperty *property = cxMalloc(a, sizeof(DavProperty));
+
+ char *name;
+ DavNamespace *ns = dav_get_property_namespace(
+ sn->context,
+ cx_strdup_a(a, expression->srctext).ptr,
+ &name);
+ if(!ns) {
+ return -1;
+ }
+
+ property->ns = ns;
+ property->name = name;
+ property->value = NULL;
+
+ cxMapPut(map, cx_hash_key(expression->srctext.ptr, expression->srctext.length), property);
+ }
+
+ if(expression->left) {
+ if(fl_add_properties(sn, a, map, expression->left)) {
+ return -1;
+ }
+ }
+ if(expression->right) {
+ if(fl_add_properties(sn, a, map, expression->right)) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static CxBuffer* fieldlist2propfindrequest(DavSession *sn, const CxAllocator *a, CxList *fields, int *isallprop) {
+ CxMap *properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
+ *isallprop = 0;
+
+ CxIterator i = cxListIterator(fields);
+ cx_foreach(DavQLField*, field, i) {
+ if(!cx_strcmp(field->name, CX_STR("*"))) {
+ cxMapDestroy(properties);
+ *isallprop = 1;
+ return create_allprop_propfind_request();
+ } else if(!cx_strcmp(field->name, CX_STR("-"))) {
+ cxMapDestroy(properties);
+ return create_propfind_request(sn, NULL, "propfind", 0);
+ } else {
+ if(fl_add_properties(sn, a, properties, field->expr)) {
+ // TODO: set error
+ cxMapDestroy(properties);
+ return NULL;
+ }
+ }
+ }
+
+ i = cxMapIteratorValues(properties);
+ CxList *list = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ cx_foreach(DavProperty*, value, i) {
+ cxListAdd(list, value);
+ }
+
+ CxBuffer *reqbuf = create_propfind_request(sn, list, "propfind", 0);
+ cxListDestroy(list);
+ cxMapDestroy(properties);
+ return reqbuf;
+}
+
+static int reset_properties(DavSession *sn, DavResult *result, DavResource *res, CxList *fields) {
+ CxMap *new_properties = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 32);
+ DavResourceData *data = (DavResourceData*)res->data;
+
+ // add basic properties
+ void *value;
+
+ cxmutstr cl_keystr = dav_property_key("DAV:", "getcontentlength");
+ CxHashKey cl_key = cx_hash_key(cl_keystr.ptr, cl_keystr.length);
+ value = cxMapGet(data->properties, cl_key);
+ if(value) {
+ cxMapPut(new_properties, cl_key, value);
+ }
+
+ cxmutstr cd_keystr = dav_property_key("DAV:", "creationdate");
+ CxHashKey cd_key = cx_hash_key(cd_keystr.ptr, cd_keystr.length);
+ value = cxMapGet(data->properties, cd_key);
+ if(value) {
+ cxMapPut(new_properties, cd_key, value);
+ }
+
+ cxmutstr lm_keystr = dav_property_key("DAV:", "getlastmodified");
+ CxHashKey lm_key = cx_hash_key(lm_keystr.ptr, lm_keystr.length);
+ value = cxMapGet(data->properties, lm_key);
+ if(value) {
+ cxMapPut(new_properties, lm_key, value);
+ }
+
+ cxmutstr ct_keystr = dav_property_key("DAV:", "getcontenttype");
+ CxHashKey ct_key = cx_hash_key(ct_keystr.ptr, ct_keystr.length);
+ value = cxMapGet(data->properties, ct_key);
+ if(value) {
+ cxMapPut(new_properties, ct_key, value);
+ }
+
+ cxmutstr rt_keystr = dav_property_key("DAV:", "resourcetype");
+ CxHashKey rt_key = cx_hash_key(rt_keystr.ptr, rt_keystr.length);
+ value = cxMapGet(data->properties, rt_key);
+ if(value) {
+ cxMapPut(new_properties, rt_key, value);
+ }
+
+ cxmutstr cn_keystr = dav_property_key(DAV_NS, "crypto-name");
+ CxHashKey cn_key = cx_hash_key(cn_keystr.ptr, cn_keystr.length);
+ value = cxMapGet(data->properties, cn_key);
+ if(value) {
+ cxMapPut(new_properties, cn_key, value);
+ }
+
+ cxmutstr ck_keystr = dav_property_key(DAV_NS, "crypto-key");
+ CxHashKey ck_key = cx_hash_key(ck_keystr.ptr, ck_keystr.length);
+ value = cxMapGet(data->properties, ck_key);
+ if(value) {
+ cxMapPut(new_properties, ck_key, value);
+ }
+
+ cxmutstr ch_keystr = dav_property_key(DAV_NS, "crypto-hash");
+ CxHashKey ch_key = cx_hash_key(ch_keystr.ptr, ch_keystr.length);
+ value = cxMapGet(data->properties, ch_key);
+ if(value) {
+ cxMapPut(new_properties, ch_key, value);
+ }
+
+ // add properties from field list
+ if(fields) {
+ CxIterator i = cxListIterator(fields);
+ cx_foreach(DavCompiledField*, field, i) {
+ DavQLStackObj field_result;
+ if(!dav_exec_expr(field->code, res, &field_result)) {
+ cxmutstr str;
+ str.ptr = NULL;
+ str.length = 0;
+ DavXmlNode *node = NULL;
+ if(field_result.type == 0) {
+ str = cx_asprintf_a(
+ sn->mp->allocator,
+ "%" PRId64,
+ field_result.data.integer);
+ } else if(field_result.type == 1) {
+ if(field_result.data.string) {
+ str = cx_strdup_a(sn->mp->allocator, cx_strn(
+ field_result.data.string,
+ field_result.length));
+ }
+ } else if(field_result.type == 2) {
+ node = dav_copy_node(field_result.data.node);
+ } else {
+ // unknown type
+ // TODO: error
+ resource_free_properties(sn, new_properties);
+ return -1;
+ }
+ if(str.ptr) {
+ node = dav_session_malloc(sn, sizeof(DavXmlNode));
+ memset(node, 0, sizeof(DavXmlNode));
+ node->type = DAV_XML_TEXT;
+ node->content = str.ptr;
+ node->contentlength = str.length;
+ }
+ if(node) {
+ cxmutstr key = dav_property_key(field->ns, field->name);
+
+ DavNamespace *namespace = dav_session_malloc(sn, sizeof(DavNamespace));
+ namespace->prefix = NULL;
+ namespace->name = dav_session_strdup(sn, field->ns);
+
+ DavProperty *prop = dav_session_malloc(sn, sizeof(DavProperty));
+ prop->name = dav_session_strdup(sn, field->name);
+ prop->ns = namespace;
+ prop->value = node;
+
+ cxMapPut(new_properties, cx_hash_key(key.ptr, key.length), prop);
+ free(key.ptr);
+ }
+ } else {
+ // TODO: error
+ resource_free_properties(sn, new_properties);
+ return -1;
+ }
+ }
+ }
+
+ cxMapRemove(data->properties, cl_key);
+ cxMapRemove(data->properties, cd_key);
+ cxMapRemove(data->properties, lm_key);
+ cxMapRemove(data->properties, ct_key);
+ cxMapRemove(data->properties, rt_key);
+ cxMapRemove(data->properties, cn_key);
+ cxMapRemove(data->properties, ck_key);
+ cxMapRemove(data->properties, ch_key);
+
+ resource_free_properties(sn, data->properties);
+ data->properties = new_properties;
+
+ free(cl_keystr.ptr);
+ free(cd_keystr.ptr);
+ free(lm_keystr.ptr);
+ free(ct_keystr.ptr);
+ free(rt_keystr.ptr);
+ free(cn_keystr.ptr);
+ free(ck_keystr.ptr);
+ free(ch_keystr.ptr);
+
+ return 0;
+}
+
+/*
+ * execute a davql select statement
+ */
+DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap) {
+ CxMempool *mp = cxBasicMempoolCreate(128);
+ DavResult result;
+ result.result = NULL;
+ result.status = 1;
+
+ DavQLArgList *args = dav_ql_get_args(st, ap);
+ if(!args) {
+ return result;
+ }
+ cxMempoolRegister(mp, args, (cx_destructor_func)dav_ql_free_arglist);
+
+ int isallprop;
+ CxBuffer *rqbuf = fieldlist2propfindrequest(sn, mp->allocator, st->fields, &isallprop);
+ if(!rqbuf) {
+ cxMempoolDestroy(mp);
+ return result;
+ }
+ cxMempoolRegister(mp, rqbuf, (cx_destructor_func)cxBufferFree);
+
+ // compile field list
+ CxList *cfieldlist = cxLinkedListCreate(mp->allocator, NULL, CX_STORE_POINTERS);
+ if(st->fields) {
+ CxIterator i = cxListIterator(st->fields);
+ cx_foreach(DavQLField*, field, i) {
+ if(cx_strcmp(field->name, CX_STR("*")) && cx_strcmp(field->name, CX_STR("-"))) {
+ // compile field expression
+ CxBuffer *code = dav_compile_expr(
+ sn->context,
+ mp->allocator,
+ field->expr,
+ args);
+ if(!code) {
+ // TODO: set error string
+ return result;
+ }
+ DavCompiledField *cfield = cxMalloc(
+ mp->allocator,
+ sizeof(DavCompiledField));
+
+ char *ns;
+ char *name;
+ dav_get_property_namespace_str(
+ sn->context,
+ cx_strdup_a(mp->allocator, field->name).ptr,
+ &ns,
+ &name);
+ if(!ns || !name) {
+ // TODO: set error string
+ return result;
+ }
+ cfield->ns = ns;
+ cfield->name = name;
+ cfield->code = code;
+ cxListAdd(cfieldlist, cfield);
+ }
+ }
+ }
+
+ // get path string
+ davqlerror_t error;
+ cxmutstr path = dav_format_string(mp->allocator, st->path, args, &error);
+ if(error) {
+ // TODO: cleanup
+ cxMempoolDestroy(mp);
+ return result;
+ }
+
+ int depth = st->depth == DAV_DEPTH_PLACEHOLDER ?
+ dav_ql_getarg_int(args) : st->depth;
+
+ CxBuffer *where = dav_compile_expr(sn->context, mp->allocator, st->where, args);
+ if(st->where && !where) {
+ // TODO: cleanup
+ cxMempoolDestroy(mp);
+ return result;
+ }
+
+ // compile order criterion
+ CxList *ordercr = NULL;
+ if(st->orderby) {
+ ordercr = cxLinkedListCreate(mp->allocator, NULL, sizeof(DavOrderCriterion));
+ CxIterator i = cxListIterator(st->orderby);
+ cx_foreach(DavQLOrderCriterion*, oc, i) {
+ DavQLExpression *column = oc->column;
+ //printf("%.*s %s\n", column->srctext.length, column->srctext.ptr, oc->descending ? "desc" : "asc");
+ if(column->type == DAVQL_IDENTIFIER) {
+ // TODO: remove code duplication (add_cmd)
+ davqlresprop_t resprop;
+ cxstring propertyname = cx_strchr(column->srctext, ':');
+ if(propertyname.length > 0) {
+ char *ns;
+ char *name;
+ dav_get_property_namespace_str(
+ sn->context,
+ cx_strdup_a(mp->allocator, column->srctext).ptr,
+ &ns,
+ &name);
+ if(ns && name) {
+ DavOrderCriterion cr;
+ cr.type = 1;
+ cxmutstr keystr = dav_property_key_a(mp->allocator, ns, name);
+ cr.column.property = cx_hash_key(keystr.ptr, keystr.length);
+ cr.descending = oc->descending;
+ cxListAdd(ordercr, &cr);
+ } else {
+ // error
+ // TODO: cleanup
+ cxMempoolDestroy(mp);
+ return result;
+ }
+ } else if(dav_identifier2resprop(column->srctext, &resprop)) {
+ DavOrderCriterion cr;
+ cr.type = 0;
+ cr.column.resprop = resprop;
+ cr.descending = oc->descending;
+ cxListAdd(ordercr, &cr);
+ } else {
+ // error
+ // TODO: cleanup
+ cxMempoolDestroy(mp);
+ return result;
+ }
+
+ } else if(column->type == DAVQL_NUMBER) {
+ // TODO: implement
+ fprintf(stderr, "order by number not supported\n");
+ return result;
+ } else {
+ // something is broken
+ // TODO: cleanup
+ cxMempoolDestroy(mp);
+ return result;
+ }
+ }
+ }
+
+ DavResource *selroot = dav_resource_new(sn, path.ptr);
+
+ CxList *stack = cxLinkedListCreateSimple(sizeof(DavQLRes));
+ // initialize the stack with the requested resource
+ DavQLRes res;
+ res.resource = selroot;
+ res.depth = 0;
+ cxListInsert(stack, 0, &res);
+
+ // reuseable response buffer
+ CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, mp->allocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ if(!rpbuf) {
+ // TODO: cleanup
+ cxMempoolDestroy(mp);
+ return result;
+ }
+
+ result.result = selroot;
+ result.status = 0;
+
+ // do a propfind request for each resource on the stack
+ while(cxListSize(stack) > 0) {
+ DavQLRes *sr_ptr = cxListAt(stack, 0); // get first element from the stack
+ DavResource *root = sr_ptr->resource;
+ int res_depth = sr_ptr->depth;
+ cxListRemove(stack, 0); // remove first element
+
+ util_set_url(sn, dav_resource_get_href(root));
+ CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf);
+ long http_status = 0;
+ curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
+ //printf("rpbuf: %s\n%.*s\n\n", root->href, (int)rpbuf->size, rpbuf->space);
+ //fflush(stdout);
+
+ if(ret == CURLE_OK && http_status == 207) {
+ // in case of an redirect we have to adjust resource->href
+ dav_set_effective_href(sn, root);
+
+ // propfind request successful, now parse the response
+ char *url = "http://url/";
+ PropfindParser *parser = create_propfind_parser(rpbuf, url);
+ if(!parser) {
+ result.status = -1;
+ break;
+ }
+
+ ResponseTag response;
+ int r;
+ while((r = get_propfind_response(parser, &response)) != 0) {
+ if(r == -1) {
+ // error
+ result.status = -1;
+ // TODO: free resources
+ cleanup_response(&response);
+ break;
+ }
+
+ // the propfind multistatus response contains responses
+ // for the requested resource and all childs
+ // determine if the response is a child or not
+ if(hrefeq(sn, root->href, response.href)) {
+ // response is the currently requested resource
+ // and not a child
+
+ // add properties
+ add_properties(root, &response);
+ cleanup_response(&response);
+
+ if(root == selroot) {
+ // The current root is the root of the select query.
+ // In this case we have to check the where clause.
+ // If root is not selroot, the where clause was
+ // already checked for the resource before it was
+ // added to the stack.
+ DavQLStackObj where_result;
+ if(!dav_exec_expr(where, root, &where_result)) {
+ if(where_result.data.integer != 0) {
+ if(!reset_properties(sn, &result, root, cfieldlist)) {
+ continue;
+ }
+ result.status = -1;
+ }
+ }
+ result.result = NULL;
+ result.status = -1;
+ dav_resource_free_all(selroot);
+ cxListDestroy(stack);
+ break;
+ }
+ } else {
+ DavResource *child = response2resource(
+ sn,
+ &response,
+ root->path);
+ cleanup_response(&response);
+ // check where clause
+ DavQLStackObj where_result;
+ if(!dav_exec_expr(where, child, &where_result)) {
+ if(where_result.data.integer != 0) {
+ if(!reset_properties(sn, &result, child, cfieldlist)) {
+ //resource_add_child(root, child);
+ resource_add_ordered_child(root, child, ordercr);
+ if(child->iscollection &&
+ (depth < 0 || depth > res_depth+1))
+ {
+ DavQLRes rs;
+ rs.resource = child;
+ rs.depth = res_depth + 1;
+ cxListInsert(stack, 0, &rs);
+ }
+ } else {
+ dav_resource_free(child);
+ }
+ } else {
+ dav_resource_free(child);
+ }
+ }
+ }
+ }
+ destroy_propfind_parser(parser);
+ } else {
+ dav_session_set_error(sn, ret, http_status);
+ result.result = NULL;
+ result.status = -1;
+ dav_resource_free_all(selroot);
+ break;
+ }
+
+ // reset response buffer
+ cxBufferSeek(rpbuf, SEEK_SET, 0);
+ }
+
+ cxMempoolDestroy(mp);
+ return result;
+}
+
+static int count_func_args(DavQLExpression *expr) {
+ int count = 0;
+ DavQLExpression *arg = expr->right;
+ while(arg) {
+ count++;
+ if(arg->op == DAVQL_ARGLIST) {
+ arg = arg->right;
+ } else {
+ break;
+ }
+ }
+ return count;
+}
+
+int dav_identifier2resprop(cxstring src, davqlresprop_t *prop) {
+ if(!cx_strcmp(src, CX_STR("name"))) {
+ *prop = DAVQL_RES_NAME;
+ } else if(!cx_strcmp(src, CX_STR("path"))) {
+ *prop = DAVQL_RES_PATH;
+ } else if(!cx_strcmp(src, CX_STR("href"))) {
+ *prop = DAVQL_RES_HREF;
+ } else if(!cx_strcmp(src, CX_STR("contentlength"))) {
+ *prop = DAVQL_RES_CONTENTLENGTH;
+ } else if(!cx_strcmp(src, CX_STR("contenttype"))) {
+ *prop = DAVQL_RES_CONTENTTYPE;
+ } else if(!cx_strcmp(src, CX_STR("creationdate"))) {
+ *prop = DAVQL_RES_CREATIONDATE;
+ } else if(!cx_strcmp(src, CX_STR("lastmodified"))) {
+ *prop = DAVQL_RES_LASTMODIFIED;
+ } else if(!cx_strcmp(src, CX_STR("iscollection"))) {
+ *prop = DAVQL_RES_ISCOLLECTION;
+ } else {
+ return 0;
+ }
+ return 1;
+}
+
+static int add_cmd(DavContext *ctx, const CxAllocator *a, CxBuffer *bcode, DavQLExpression *expr, DavQLArgList *ap) {
+ if(!expr) {
+ return 0;
+ }
+
+ int numcmd = 1;
+ DavQLCmd cmd;
+ memset(&cmd, 0, sizeof(DavQLCmd));
+ davqlerror_t error;
+
+ cxstring src = expr->srctext;
+ switch(expr->type) {
+ default: break;
+ case DAVQL_NUMBER: {
+ cmd.type = DAVQL_CMD_INT;
+ if(src.ptr[0] == '%') {
+ cmd.data.integer = dav_ql_getarg_int(ap);
+ } else if(util_strtoint(src.ptr, &cmd.data.integer)) {
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ } else {
+ // error
+ return -1;
+ }
+
+ break;
+ }
+ case DAVQL_STRING: {
+ cmd.type = DAVQL_CMD_STRING;
+ cmd.data.string = dav_format_string(a, src, ap, &error);
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_TIMESTAMP: {
+ if(src.ptr[0] == '%') {
+ cmd.type = DAVQL_CMD_TIMESTAMP;
+ cmd.data.timestamp = dav_ql_getarg_time(ap);
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ } else {
+ // error
+ return -1;
+ }
+ break;
+ }
+ case DAVQL_IDENTIFIER: {
+ cxstring propertyname = cx_strchr(src, ':');
+ cmd.type = DAVQL_CMD_RES_IDENTIFIER;
+ if(propertyname.length > 0) {
+ cmd.type = DAVQL_CMD_PROP_IDENTIFIER;
+ char *ns;
+ char *name;
+ dav_get_property_namespace_str(
+ ctx,
+ cx_strdup_a(a, src).ptr,
+ &ns,
+ &name);
+ if(ns && name) {
+ cmd.data.property.ns = ns;
+ cmd.data.property.name = name;
+ } else {
+ // error
+ return -1;
+ }
+ } else if(!dav_identifier2resprop(src, &cmd.data.resprop)) {
+ if(!cx_strcmp(src, CX_STR("true"))) {
+ cmd.type = DAVQL_CMD_INT;
+ cmd.data.integer = 1;
+ } else if(!cx_strcmp(src, CX_STR("false"))) {
+ cmd.type = DAVQL_CMD_INT;
+ cmd.data.integer = 0;
+ } else {
+ // error, unknown identifier
+ return -1;
+ }
+ }
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_UNARY: {
+ numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+ switch(expr->op) {
+ case DAVQL_ADD: {
+ // noop
+ numcmd = 0;
+ break;
+ }
+ case DAVQL_SUB: {
+ cmd.type = DAVQL_CMD_OP_UNARY_SUB;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_NEG: {
+ cmd.type = DAVQL_CMD_OP_UNARY_NEG;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ default: break;
+ }
+ break;
+ }
+ case DAVQL_BINARY: {
+ numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+ numcmd += add_cmd(ctx, a, bcode, expr->right, ap);
+ switch(expr->op) {
+ case DAVQL_ADD: {
+ cmd.type = DAVQL_CMD_OP_BINARY_ADD;
+ break;
+ }
+ case DAVQL_SUB: {
+ cmd.type = DAVQL_CMD_OP_BINARY_SUB;
+ break;
+ }
+ case DAVQL_MUL: {
+ cmd.type = DAVQL_CMD_OP_BINARY_MUL;
+ break;
+ }
+ case DAVQL_DIV: {
+ cmd.type = DAVQL_CMD_OP_BINARY_DIV;
+ break;
+ }
+ case DAVQL_AND: {
+ cmd.type = DAVQL_CMD_OP_BINARY_AND;
+ break;
+ }
+ case DAVQL_OR: {
+ cmd.type = DAVQL_CMD_OP_BINARY_OR;
+ break;
+ }
+ case DAVQL_XOR: {
+ cmd.type = DAVQL_CMD_OP_BINARY_XOR;
+ break;
+ }
+ default: break;
+ }
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_LOGICAL: {
+ if(expr->left && expr->right && expr->op != DAVQL_LOR) {
+ numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+ numcmd += add_cmd(ctx, a, bcode, expr->right, ap);
+ }
+
+ switch(expr->op) {
+ case DAVQL_NOT: {
+ numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+ cmd.type = DAVQL_CMD_OP_LOGICAL_NOT;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_LAND: {
+ cmd.type = DAVQL_CMD_OP_LOGICAL_AND;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_LOR: {
+ int nleft = add_cmd(ctx, a, bcode, expr->left, ap);
+
+ cmd.type = DAVQL_CMD_OP_LOGICAL_OR_L;
+ DavQLCmd *or_l = (DavQLCmd*)(bcode->space + bcode->pos);
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+
+ int nright = add_cmd(ctx, a, bcode, expr->right, ap);
+ or_l->data.integer = nright + 1;
+
+ cmd.type = DAVQL_CMD_OP_LOGICAL_OR;
+ cmd.data.integer = 0;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+
+ numcmd += nleft + nright;
+ break;
+ }
+ case DAVQL_LXOR: {
+ cmd.type = DAVQL_CMD_OP_LOGICAL_XOR;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_EQ: {
+ cmd.type = DAVQL_CMD_OP_EQ;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_NEQ: {
+ cmd.type = DAVQL_CMD_OP_NEQ;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_LT: {
+ cmd.type = DAVQL_CMD_OP_LT;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_GT: {
+ cmd.type = DAVQL_CMD_OP_GT;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_LE: {
+ cmd.type = DAVQL_CMD_OP_LE;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_GE: {
+ cmd.type = DAVQL_CMD_OP_GE;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_LIKE: {
+ cmd.type = DAVQL_CMD_OP_LIKE;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ case DAVQL_UNLIKE: {
+ cmd.type = DAVQL_CMD_OP_UNLIKE;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+ break;
+ }
+ default: break;
+ }
+ break;
+ }
+ case DAVQL_FUNCCALL: {
+ switch(expr->op) {
+ case DAVQL_CALL: {
+ int nright = add_cmd(ctx, a, bcode, expr->right, ap);
+ // TODO: count args
+ DavQLExpression *funcid = expr->left;
+ if(!funcid && funcid->type != DAVQL_IDENTIFIER) {
+ // fail
+ return -1;
+ }
+
+ // numargs
+ cmd.type = DAVQL_CMD_INT;
+ cmd.data.integer = count_func_args(expr);
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+
+ // TODO: resolve function name
+ cmd.type = DAVQL_CMD_CALL;
+ cmd.data.func = NULL;
+ cxBufferWrite(&cmd, sizeof(cmd), 1, bcode);
+
+ numcmd = 2;
+ numcmd += nright;
+ break;
+ }
+ case DAVQL_ARGLIST: {
+ numcmd = 0;
+ numcmd += add_cmd(ctx, a, bcode, expr->left, ap);
+ numcmd += add_cmd(ctx, a, bcode, expr->right, ap);
+ break;
+ }
+ default: break;
+ }
+ break;
+ }
+ }
+ return numcmd;
+}
+
+CxBuffer* dav_compile_expr(DavContext *ctx, const CxAllocator *a, DavQLExpression *lexpr, DavQLArgList *ap) {
+ CxBuffer *bcode = cxBufferCreate(NULL, 512, a, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ if(!bcode) {
+ return NULL;
+ }
+
+ if(add_cmd(ctx, a, bcode, lexpr, ap) <= 0) {
+ cxBufferFree(bcode);
+ return NULL;
+ }
+
+ return bcode;
+}
+
+static int cmd_str_cmp(DavQLStackObj obj1, DavQLStackObj obj2, davqlcmdtype_t cmd) {
+ cxmutstr s1m = obj1.type == 1 ?
+ cx_mutstrn(obj1.data.string, obj1.length) :
+ cx_asprintf("%" PRId64, obj1.data.integer);
+ cxmutstr s2m = obj1.type == 1 ?
+ cx_mutstrn(obj2.data.string, obj2.length) :
+ cx_asprintf("%" PRId64, obj2.data.integer);
+
+ cxstring s1 = cx_strcast(s1m);
+ cxstring s2 = cx_strcast(s2m);
+
+ int res = 0;
+ switch(cmd) {
+ case DAVQL_CMD_OP_EQ: {
+ res = cx_strcmp(s1, s2) == 0;
+ break;
+ }
+ case DAVQL_CMD_OP_NEQ: {
+ res = cx_strcmp(s1, s2) != 0;
+ break;
+ }
+ case DAVQL_CMD_OP_LT: {
+ res = cx_strcmp(s1, s2) < 0;
+ break;
+ }
+ case DAVQL_CMD_OP_GT: {
+ res = cx_strcmp(s1, s2) > 0;
+ break;
+ }
+ case DAVQL_CMD_OP_LE: {
+ res = cx_strcmp(s1, s2) <= 0;
+ break;
+ }
+ case DAVQL_CMD_OP_GE: {
+ res = cx_strcmp(s1, s2) >= 0;
+ break;
+ }
+ default: break;
+ }
+
+ if(obj1.type == 0) {
+ free(s1m.ptr);
+ }
+ if(obj2.type == 0) {
+ free(s2m.ptr);
+ }
+
+ return res;
+}
+
+int dav_exec_expr(CxBuffer *bcode, DavResource *res, DavQLStackObj *result) {
+ if(!bcode) {
+ result->type = 0;
+ result->length = 0;
+ result->data.integer = 1;
+ return 0;
+ }
+
+ size_t count = bcode->pos / sizeof(DavQLCmd);
+ DavQLCmd *cmds = (DavQLCmd*)bcode->space;
+
+ // create execution stack
+ size_t stsize = 64;
+ size_t stpos = 0;
+ DavQLStackObj *stack = calloc(stsize, sizeof(DavQLStackObj));
+#define DAVQL_PUSH(obj) \
+ if(stpos == stsize) { \
+ stsize += 64; \
+ DavQLStackObj *stack_newptr; \
+ stack_newptr = realloc(stack, stsize * sizeof(DavQLStackObj)); \
+ if(stack_newptr) { \
+ stack = stack_newptr; \
+ } else { \
+ free(stack); \
+ return -1; \
+ }\
+ } \
+ stack[stpos++] = obj;
+#define DAVQL_PUSH_INT(intval) \
+ { \
+ DavQLStackObj intobj; \
+ intobj.type = 0; \
+ intobj.length = 0; \
+ intobj.data.integer = intval; \
+ DAVQL_PUSH(intobj); \
+ }
+#define DAVQL_POP() stack[--stpos]
+
+ DavQLStackObj obj;
+ int ret = 0;
+ for(size_t i=0;i<count;i++) {
+ DavQLCmd cmd = cmds[i];
+ switch(cmd.type) {
+ case DAVQL_CMD_INT: {
+ //printf("int %lld\n", cmd.data.integer);
+ obj.type = 0;
+ obj.length = 0;
+ obj.data.integer = cmd.data.integer;
+ DAVQL_PUSH(obj);
+ break;
+ }
+ case DAVQL_CMD_STRING: {
+ //printf("string \"%.*s\"\n", cmd.data.string.length, cmd.data.string.ptr);
+ obj.type = 1;
+ obj.length = cmd.data.string.length;
+ obj.data.string = cmd.data.string.ptr;
+ DAVQL_PUSH(obj);
+ break;
+ }
+ case DAVQL_CMD_TIMESTAMP: {
+ //printf("timestamp %d\n", cmd.data.timestamp);
+ obj.type = 0;
+ obj.length = 0;
+ obj.data.integer = (int64_t)cmd.data.timestamp;
+ DAVQL_PUSH(obj);
+ break;
+ }
+ case DAVQL_CMD_RES_IDENTIFIER: {
+ //char *rid[8] = {"name", "path", "href", "contentlength", "contenttype", "creationdate", "lastmodified", "iscollection"};
+ //printf("resprop %s\n", rid[cmd.data.resprop]);
+ switch(cmd.data.resprop) {
+ case DAVQL_RES_NAME: {
+ obj.type = 1;
+ obj.length = strlen(res->name);
+ obj.data.string = res->name;
+ break;
+ }
+ case DAVQL_RES_PATH: {
+ obj.type = 1;
+ obj.length = strlen(res->path);
+ obj.data.string = res->path;
+ break;
+ }
+ case DAVQL_RES_HREF: {
+ obj.type = 1;
+ obj.length = strlen(res->href);
+ obj.data.string = res->href;
+ break;
+ }
+ case DAVQL_RES_CONTENTLENGTH: {
+ obj.type = 0;
+ obj.length = 0;
+ obj.data.integer = res->contentlength;
+ break;
+ }
+ case DAVQL_RES_CONTENTTYPE: {
+ obj.type = 1;
+ obj.length = strlen(res->contenttype);
+ obj.data.string = res->contenttype;
+ break;
+ }
+ case DAVQL_RES_CREATIONDATE: {
+ obj.type = 0;
+ obj.length = 0;
+ obj.data.integer = res->creationdate;
+ break;
+ }
+ case DAVQL_RES_LASTMODIFIED: {
+ obj.type = 0;
+ obj.length = 0;
+ obj.data.integer = res->lastmodified;
+ break;
+ }
+ case DAVQL_RES_ISCOLLECTION: {
+ obj.type = 0;
+ obj.length = 0;
+ obj.data.integer = res->iscollection;
+ break;
+ }
+ }
+ DAVQL_PUSH(obj);
+ break;
+ }
+ case DAVQL_CMD_PROP_IDENTIFIER: {
+ //printf("property %s:%s\n", cmd.data.property.ns, cmd.data.property.name);
+ //char *value = dav_get_string_property_ns(res, cmd.data.property.ns, cmd.data.property.name);
+ DavXmlNode *value = dav_get_property_ns(res, cmd.data.property.ns, cmd.data.property.name);
+ if(dav_xml_isstring(value)) {
+ obj.type = 1;
+ obj.length = (uint32_t)value->contentlength;
+ obj.data.string = value->content;
+ } else {
+ obj.type = 2;
+ obj.length = 0;
+ obj.data.node = value;
+ }
+ DAVQL_PUSH(obj);
+ break;
+ }
+ //case DAVQL_CMD_OP_UNARY_ADD: {
+ // printf("uadd\n");
+ // break;
+ //}
+ case DAVQL_CMD_OP_UNARY_SUB: {
+ //printf("usub\n");
+ obj = DAVQL_POP();
+ if(obj.type == 0) {
+ obj.data.integer = -obj.data.integer;
+ DAVQL_PUSH(obj);
+ } else {
+ ret = -1;
+ i = count; // end loop
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_UNARY_NEG: {
+ //printf("uneg\n");
+ obj = DAVQL_POP();
+ if(obj.type == 0) {
+ obj.data.integer = obj.data.integer == 0 ? 1 : 0;
+ DAVQL_PUSH(obj);
+ } else {
+ ret = -1;
+ i = count; // end loop
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_BINARY_ADD: {
+ //printf("add\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer + obj2.data.integer);
+ } else {
+ // TODO: string concat
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_BINARY_SUB: {
+ //printf("sub\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer - obj2.data.integer);
+ } else {
+ // error
+ ret = -1;
+ i = count; // end loop
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_BINARY_MUL: {
+ //printf("mul\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer * obj2.data.integer);
+ } else {
+ // error
+ ret = -1;
+ i = count; // end loop
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_BINARY_DIV: {
+ //printf("div\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer / obj2.data.integer);
+ } else {
+ // error
+ ret = -1;
+ i = count; // end loop
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_BINARY_AND: {
+ //printf("and\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer & obj2.data.integer);
+ } else {
+ // error
+ ret = -1;
+ i = count; // end loop
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_BINARY_OR: {
+ //printf("or\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer | obj2.data.integer);
+ } else {
+ // error
+ ret = -1;
+ i = count; // end loop
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_BINARY_XOR: {
+ //printf("xor\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer ^ obj2.data.integer);
+ } else {
+ // error
+ ret = -1;
+ i = count; // end loop
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_LOGICAL_NOT: {
+ //printf("not\n");
+ break;
+ }
+ case DAVQL_CMD_OP_LOGICAL_AND: {
+ //printf("land\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0);
+ int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0);
+ DAVQL_PUSH_INT(v1 && v2);
+ break;
+ }
+ case DAVQL_CMD_OP_LOGICAL_OR_L: {
+ //printf("or_l %d\n", cmd.data.integer);
+ DavQLStackObj obj1 = stack[stpos];
+ if((obj1.type == 0 && obj1.data.integer) || (obj1.type == 1 && obj1.data.string)) {
+ stpos--;
+ DAVQL_PUSH_INT(1);
+ i += cmd.data.integer; // jump, skip right subtree of 'or'
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_LOGICAL_OR: {
+ //printf("or\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0);
+ int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0);
+ DAVQL_PUSH_INT(v1 || v2);
+ break;
+ }
+ case DAVQL_CMD_OP_LOGICAL_XOR: {
+ //printf("lxor\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0);
+ int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0);
+ DAVQL_PUSH_INT(!v1 != !v2);
+ break;
+ }
+ case DAVQL_CMD_OP_EQ: {
+ //printf("eq\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer == obj2.data.integer);
+ } else {
+ DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_NEQ: {
+ //printf("neq\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer != obj2.data.integer);
+ } else {
+ DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_LT: {
+ //printf("lt\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer < obj2.data.integer);
+ } else {
+ DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_GT: {
+ //printf("gt\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer > obj2.data.integer);
+ } else {
+ DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_LE: {
+ //printf("le\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer <= obj2.data.integer);
+ } else {
+ DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_GE: {
+ //printf("ge\n");
+ DavQLStackObj obj2 = DAVQL_POP();
+ DavQLStackObj obj1 = DAVQL_POP();
+ if(obj1.type == 0 && obj2.type == 0) {
+ DAVQL_PUSH_INT(obj1.data.integer >= obj2.data.integer);
+ } else {
+ DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type));
+ }
+ break;
+ }
+ case DAVQL_CMD_OP_LIKE: {
+ //printf("like\n");
+ break;
+ }
+ case DAVQL_CMD_OP_UNLIKE: {
+ //printf("unlike\n");
+ break;
+ }
+ case DAVQL_CMD_CALL: {
+ //printf("call %x\n", cmd.data.func);
+ break;
+ }
+ }
+ }
+
+ if(stpos == 1) {
+ *result = stack[0];
+ } else {
+ ret = -1;
+ }
+ free(stack);
+
+ return ret;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef DAVQLEXEC_H
+#define DAVQLEXEC_H
+
+#include <stdarg.h>
+#include "davqlparser.h"
+#include "webdav.h"
+
+#include <cx/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DavQLCmd DavQLCmd;
+typedef struct DavQLStackObj DavQLStackObj;
+typedef struct DavQLRes DavQLRes;
+
+typedef struct DavQLArg DavQLArg;
+typedef struct DavQLArgList DavQLArgList;
+
+typedef void*(*davql_func)(); // TODO: interface?
+
+struct DavQLArg {
+ int type;
+ union DavQLArgValue{
+ int d;
+ unsigned int u;
+ char *s;
+ time_t t;
+ } value;
+ DavQLArg *next;
+};
+
+struct DavQLArgList {
+ DavQLArg *first;
+ DavQLArg *current;
+};
+
+typedef enum {
+ DAVQL_OK = 0,
+ DAVQL_UNSUPPORTED_FORMATCHAR,
+ DAVQL_UNKNOWN_FORMATCHAR,
+ DAVQL_OOM
+} davqlerror_t;
+
+typedef enum {
+ DAVQL_CMD_INT = 0,
+ DAVQL_CMD_STRING,
+ DAVQL_CMD_TIMESTAMP,
+ DAVQL_CMD_RES_IDENTIFIER,
+ DAVQL_CMD_PROP_IDENTIFIER,
+ //DAVQL_CMD_OP_UNARY_ADD,
+ DAVQL_CMD_OP_UNARY_SUB,
+ DAVQL_CMD_OP_UNARY_NEG,
+ DAVQL_CMD_OP_BINARY_ADD,
+ DAVQL_CMD_OP_BINARY_SUB,
+ DAVQL_CMD_OP_BINARY_MUL,
+ DAVQL_CMD_OP_BINARY_DIV,
+ DAVQL_CMD_OP_BINARY_AND,
+ DAVQL_CMD_OP_BINARY_OR,
+ DAVQL_CMD_OP_BINARY_XOR,
+ DAVQL_CMD_OP_LOGICAL_NOT,
+ DAVQL_CMD_OP_LOGICAL_AND,
+ DAVQL_CMD_OP_LOGICAL_OR_L,
+ DAVQL_CMD_OP_LOGICAL_OR,
+ DAVQL_CMD_OP_LOGICAL_XOR,
+ DAVQL_CMD_OP_EQ,
+ DAVQL_CMD_OP_NEQ,
+ DAVQL_CMD_OP_LT,
+ DAVQL_CMD_OP_GT,
+ DAVQL_CMD_OP_LE,
+ DAVQL_CMD_OP_GE,
+ DAVQL_CMD_OP_LIKE,
+ DAVQL_CMD_OP_UNLIKE,
+ DAVQL_CMD_CALL
+} davqlcmdtype_t;
+
+typedef enum {
+ DAVQL_RES_NAME = 0,
+ DAVQL_RES_PATH,
+ DAVQL_RES_HREF,
+ DAVQL_RES_CONTENTLENGTH,
+ DAVQL_RES_CONTENTTYPE,
+ DAVQL_RES_CREATIONDATE,
+ DAVQL_RES_LASTMODIFIED,
+ DAVQL_RES_ISCOLLECTION
+} davqlresprop_t;
+
+struct DavQLCmd {
+ davqlcmdtype_t type;
+ union DavQLCmdData {
+ int64_t integer;
+ cxmutstr string;
+ time_t timestamp;
+ davqlresprop_t resprop;
+ DavPropName property;
+ davql_func func;
+ } data;
+};
+
+struct DavQLStackObj {
+ int32_t type; // 0: int, 1: string, 2: xml
+ uint32_t length;
+ union DavQLStackData {
+ int64_t integer;
+ char *string;
+ DavXmlNode *node;
+ } data;
+};
+
+struct DavQLRes {
+ DavResource *resource;
+ int depth;
+};
+
+typedef struct DavCompiledField {
+ char *ns;
+ char *name;
+ CxBuffer *code;
+} DavCompiledField;
+
+typedef struct DavOrderCriterion {
+ int type; // 0: resprop, 1: property
+ union DavQLColumn {
+ davqlresprop_t resprop;
+ CxHashKey property;
+ } column;
+ _Bool descending;
+} DavOrderCriterion;
+
+DavQLArgList* dav_ql_get_args(DavQLStatement *st, va_list ap);
+void dav_ql_free_arglist(DavQLArgList *args);
+
+int dav_ql_getarg_int(DavQLArgList *args);
+unsigned int dav_ql_getarg_uint(DavQLArgList *args);
+char* dav_ql_getarg_str(DavQLArgList *args);
+time_t dav_ql_getarg_time(DavQLArgList *args);
+
+DavResult dav_statement_exec(DavSession *sn, DavQLStatement *st, ...);
+DavResult dav_statement_execv(DavSession *sn, DavQLStatement *st, va_list ap);
+
+CxBuffer* dav_path_string(cxmutstr src, DavQLArgList *args, davqlerror_t *error);
+cxmutstr dav_format_string(const CxAllocator *a, cxstring fstr, DavQLArgList *ap, davqlerror_t *error);
+
+DavResult dav_exec_select(DavSession *sn, DavQLStatement *st, va_list ap);
+
+int dav_identifier2resprop(cxstring src, davqlresprop_t *prop);
+
+CxBuffer* dav_compile_expr(DavContext *ctx, const CxAllocator *a, DavQLExpression *lexpr, DavQLArgList *ap);
+
+int dav_exec_expr(CxBuffer *bcode, DavResource *res, DavQLStackObj *result);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAVQLEXEC_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "davqlparser.h"
+#include <cx/utils.h>
+#include <cx/linked_list.h>
+#include <cx/printf.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#define sfmtarg(s) ((int)(s).length), (s).ptr
+
+// ------------------------------------------------------------------------
+// D E B U G E R
+// ------------------------------------------------------------------------
+
+static const char* _map_querytype(davqltype_t type) {
+ switch(type) {
+ case DAVQL_ERROR: return "ERROR";
+ case DAVQL_SELECT: return "SELECT";
+ case DAVQL_SET: return "SET";
+ default: return "unknown";
+ }
+}
+
+static const char* _map_exprtype(davqlexprtype_t type) {
+ switch(type) {
+ case DAVQL_UNDEFINED_TYPE: return "undefined";
+ case DAVQL_NUMBER: return "NUMBER";
+ case DAVQL_STRING: return "STRING";
+ case DAVQL_TIMESTAMP: return "TIMESTAMP";
+ case DAVQL_IDENTIFIER: return "IDENTIFIER";
+ case DAVQL_UNARY: return "UNARY";
+ case DAVQL_BINARY: return "BINARY";
+ case DAVQL_LOGICAL: return "LOGICAL";
+ case DAVQL_FUNCCALL: return "FUNCCALL";
+ default: return "unknown";
+ }
+}
+
+static const char* _map_specialfield(int info) {
+ switch(info) {
+ case 0: return "";
+ case 1: return "with wildcard";
+ case 2: return "(resource data only)";
+ default: return "with mysterious identifier";
+ }
+}
+
+static const char* _map_operator(davqloperator_t op) {
+ // don't use string array, because enum values may change
+ switch(op) {
+ case DAVQL_NOOP: return "no operator";
+ case DAVQL_CALL: return "function call"; case DAVQL_ARGLIST: return ",";
+ case DAVQL_ADD: return "+"; case DAVQL_SUB: return "-";
+ case DAVQL_MUL: return "*"; case DAVQL_DIV: return "/";
+ case DAVQL_AND: return "&"; case DAVQL_OR: return "|";
+ case DAVQL_XOR: return "^"; case DAVQL_NEG: return "~";
+ case DAVQL_NOT: return "NOT"; case DAVQL_LAND: return "AND";
+ case DAVQL_LOR: return "OR"; case DAVQL_LXOR: return "XOR";
+ case DAVQL_EQ: return "="; case DAVQL_NEQ: return "!=";
+ case DAVQL_LT: return "<"; case DAVQL_GT: return ">";
+ case DAVQL_LE: return "<="; case DAVQL_GE: return ">=";
+ case DAVQL_LIKE: return "LIKE"; case DAVQL_UNLIKE: return "UNLIKE";
+ default: return "unknown";
+ }
+}
+
+static void dav_debug_ql_fnames_print(DavQLStatement *stmt) {
+ if (stmt->fields) {
+ printf("Field names: ");
+ CxIterator i = cxListIterator(stmt->fields);
+ cx_foreach(DavQLField *, f, i) {
+ printf("%.*s, ", (int)f->name.length, f->name.ptr);
+ }
+ printf("\b\b \b\b\n");
+ }
+}
+
+static void dav_debug_ql_stmt_print(DavQLStatement *stmt) {
+ // Basic information
+ size_t fieldcount = stmt->fields ? cxListSize(stmt->fields) : 0;
+ int specialfield = 0;
+ if (fieldcount > 0) {
+ DavQLField* firstfield = (DavQLField*)cxListAt(stmt->fields, 0);
+ if (firstfield->expr->type == DAVQL_IDENTIFIER) {
+ switch (firstfield->expr->srctext.ptr[0]) {
+ case '*': specialfield = 1; break;
+ case '-': specialfield = 2; break;
+ }
+ }
+ }
+ if (specialfield) {
+ fieldcount--;
+ }
+ printf("Statement: %.*s\nType: %s\nField count: %zu %s\n",
+ (int)stmt->srctext.length, stmt->srctext.ptr,
+ _map_querytype(stmt->type),
+ fieldcount,
+ _map_specialfield(specialfield));
+
+ dav_debug_ql_fnames_print(stmt);
+ printf("Path: %.*s\nHas where clause: %s\n",
+ (int)stmt->path.length, stmt->path.ptr,
+ stmt->where ? "yes" : "no");
+
+ // WITH attributes
+ if (stmt->depth == DAV_DEPTH_INFINITY) {
+ printf("Depth: infinity\n");
+ } else if (stmt->depth == DAV_DEPTH_PLACEHOLDER) {
+ printf("Depth: placeholder\n");
+ } else {
+ printf("Depth: %d\n", stmt->depth);
+ }
+
+ // order by clause
+ printf("Order by: ");
+ if (stmt->orderby) {
+ CxIterator i = cxListIterator(stmt->orderby);
+ cx_foreach(DavQLOrderCriterion*, critdata, i) {
+ printf("%.*s %s%s", (int)critdata->column->srctext.length, critdata->column->srctext.ptr,
+ critdata->descending ? "desc" : "asc",
+ i.index+1 < cxListSize(stmt->orderby) ? ", " : "\n");
+ }
+ } else {
+ printf("nothing\n");
+ }
+
+ // error messages
+ if (stmt->errorcode) {
+ printf("\nError code: %d\nError: %s\n",
+ stmt->errorcode, stmt->errormessage);
+ }
+}
+
+static int dav_debug_ql_expr_selected(DavQLExpression *expr) {
+ if (!expr) {
+ printf("Currently no expression selected.\n");
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+static void dav_debug_ql_expr_print(DavQLExpression *expr) {
+ if (dav_debug_ql_expr_selected(expr)) {
+ cxstring empty = CX_STR("(empty)");
+ printf(
+ "Text: %.*s\nType: %s\nOperator: %s\n",
+ sfmtarg(expr->srctext),
+ _map_exprtype(expr->type),
+ _map_operator(expr->op));
+ if (expr->left || expr->right) {
+ printf("Left hand: %.*s\nRight hand: %.*s\n",
+ sfmtarg(expr->left?expr->left->srctext:empty),
+ sfmtarg(expr->right?expr->right->srctext:empty));
+ }
+ }
+}
+
+static void dav_debug_ql_field_print(DavQLField *field) {
+ if (field) {
+ printf("Name: %.*s\n", sfmtarg(field->name));
+ if (field->expr) {
+ dav_debug_ql_expr_print(field->expr);
+ } else {
+ printf("No expression.\n");
+ }
+ } else {
+ printf("No field selected.\n");
+ }
+}
+
+static void dav_debug_ql_tree_print(DavQLExpression *expr, int depth) {
+ if (expr) {
+ if (expr->left) {
+ printf("%*c%s\n", depth, ' ', _map_operator(expr->op));
+ dav_debug_ql_tree_print(expr->left, depth+1);
+ dav_debug_ql_tree_print(expr->right, depth+1);
+ } else if (expr->type == DAVQL_UNARY) {
+ printf("%*c%s %.*s\n", depth, ' ', _map_operator(expr->op),
+ sfmtarg(expr->srctext));
+ } else {
+ printf("%*c%.*s\n", depth, ' ', sfmtarg(expr->srctext));
+ }
+ }
+}
+
+#define DQLD_CMD_Q 0
+#define DQLD_CMD_PS 1
+#define DQLD_CMD_PE 2
+#define DQLD_CMD_PF 3
+#define DQLD_CMD_PT 4
+#define DQLD_CMD_F 10
+#define DQLD_CMD_W 11
+#define DQLD_CMD_O 12
+#define DQLD_CMD_L 21
+#define DQLD_CMD_R 22
+#define DQLD_CMD_N 23
+#define DQLD_CMD_P 24
+#define DQLD_CMD_H 100
+
+static int dav_debug_ql_command() {
+ printf("> ");
+
+ char buffer[8];
+ fgets(buffer, 8, stdin);
+ // discard remaining chars
+ if (!strchr(buffer, '\n')) {
+ int chr;
+ while ((chr = fgetc(stdin) != '\n') && chr != EOF);
+ }
+
+ if (!strcmp(buffer, "q\n")) {
+ return DQLD_CMD_Q;
+ } else if (!strcmp(buffer, "ps\n")) {
+ return DQLD_CMD_PS;
+ } else if (!strcmp(buffer, "pe\n")) {
+ return DQLD_CMD_PE;
+ } else if (!strcmp(buffer, "pf\n")) {
+ return DQLD_CMD_PF;
+ } else if (!strcmp(buffer, "pt\n")) {
+ return DQLD_CMD_PT;
+ } else if (!strcmp(buffer, "l\n")) {
+ return DQLD_CMD_L;
+ } else if (!strcmp(buffer, "r\n")) {
+ return DQLD_CMD_R;
+ } else if (!strcmp(buffer, "h\n")) {
+ return DQLD_CMD_H;
+ } else if (!strcmp(buffer, "f\n")) {
+ return DQLD_CMD_F;
+ } else if (!strcmp(buffer, "w\n")) {
+ return DQLD_CMD_W;
+ } else if (!strcmp(buffer, "o\n")) {
+ return DQLD_CMD_O;
+ } else if (!strcmp(buffer, "n\n")) {
+ return DQLD_CMD_N;
+ } else if (!strcmp(buffer, "p\n")) {
+ return DQLD_CMD_P;
+ } else {
+ return -1;
+ }
+}
+
+void dav_debug_statement(DavQLStatement *stmt) {
+ if (!stmt) {
+ fprintf(stderr, "Debug DavQLStatement failed: null pointer");
+ return;
+ }
+
+ printf("Starting DavQL debugger (type 'h' for help)...\n\n");
+ dav_debug_ql_stmt_print(stmt);
+
+ if (stmt->errorcode) {
+ return;
+ }
+
+ DavQLExpression *examineexpr = NULL;
+ CxList *examineelem = NULL;
+ int examineclause = 0;
+
+ while(1) {
+ int cmd = dav_debug_ql_command();
+ switch (cmd) {
+ case DQLD_CMD_Q: return;
+ case DQLD_CMD_PS: dav_debug_ql_stmt_print(stmt); break;
+ case DQLD_CMD_PE: dav_debug_ql_expr_print(examineexpr); break;
+ case DQLD_CMD_PT: dav_debug_ql_tree_print(examineexpr, 1); break;
+ case DQLD_CMD_PF: dav_debug_ql_fnames_print(stmt); break;
+ case DQLD_CMD_F:
+ examineclause = DQLD_CMD_F;
+ examineelem = stmt->fields;
+ if (stmt->fields && cxListSize(stmt->fields) > 0) {
+ DavQLField* field = cxListAt(stmt->fields, 0);
+ examineexpr = field->expr;
+ dav_debug_ql_field_print(field);
+ } else {
+ examineexpr = NULL;
+ }
+ break;
+ case DQLD_CMD_W:
+ examineclause = 0; examineelem = NULL;
+ examineexpr = stmt->where;
+ dav_debug_ql_expr_print(examineexpr);
+ break;
+ case DQLD_CMD_O:
+ examineclause = DQLD_CMD_O;
+ examineelem = stmt->orderby;
+ examineexpr = stmt->orderby && cxListSize(stmt->orderby) > 0 ?
+ ((DavQLOrderCriterion*)cxListAt(stmt->orderby, 0))->column : NULL;
+ dav_debug_ql_expr_print(examineexpr);
+ break;
+ case DQLD_CMD_N:
+ case DQLD_CMD_P:
+ printf("TODO: port code to ucx 3\n");
+ /*
+ if (examineelem) {
+ CxList *newelem = (cmd == DQLD_CMD_N ?
+ examineelem->next : examineelem->prev);
+ if (newelem) {
+ examineelem = newelem;
+ if (examineclause == DQLD_CMD_O) {
+ examineexpr = ((DavQLOrderCriterion*)
+ examineelem->data)->column;
+ dav_debug_ql_expr_print(examineexpr);
+ } else if (examineclause == DQLD_CMD_F) {
+ DavQLField* field = (DavQLField*)examineelem->data;
+ examineexpr = field->expr;
+ dav_debug_ql_field_print(field);
+ } else {
+ printf("Examining unknown clause type.");
+ }
+ } else {
+ printf("Reached end of list.\n");
+ }
+ } else {
+ printf("Currently not examining an expression list.\n");
+ }
+ */
+ break;
+ case DQLD_CMD_L:
+ if (dav_debug_ql_expr_selected(examineexpr)) {
+ if (examineexpr->left) {
+ examineexpr = examineexpr->left;
+ dav_debug_ql_expr_print(examineexpr);
+ } else {
+ printf("There is no left subtree.\n");
+ }
+ }
+ break;
+ case DQLD_CMD_R:
+ if (dav_debug_ql_expr_selected(examineexpr)) {
+ if (examineexpr->right) {
+ examineexpr = examineexpr->right;
+ dav_debug_ql_expr_print(examineexpr);
+ } else {
+ printf("There is no right subtree.\n");
+ }
+ }
+ break;
+ case DQLD_CMD_H:
+ printf(
+ "\nCommands:\n"
+ "ps: print statement information\n"
+ "o: examine order by clause\n"
+ "f: examine field list\n"
+ "pf: print field names\n"
+ "w: examine where clause\n"
+ "n: examine next expression "
+ "(in order by clause or field list)\n"
+ "p: examine previous expression "
+ "(in order by clause or field list)\n"
+ "q: quit\n\n"
+ "\nExpression examination:\n"
+ "pe: print expression information\n"
+ "pt: print full syntax tree of current (sub-)expression\n"
+ "l: enter left subtree\n"
+ "r: enter right subtree\n");
+ break;
+ default: printf("unknown command\n");
+ }
+ }
+}
+
+// ------------------------------------------------------------------------
+// P A R S E R
+// ------------------------------------------------------------------------
+
+#define _error_context "(%.*s[->]%.*s%.*s)"
+#define _error_invalid "invalid statement"
+#define _error_out_of_memory "out of memory"
+#define _error_unexpected_token "unexpected token " _error_context
+#define _error_invalid_token "invalid token " _error_context
+#define _error_missing_path "expected path " _error_context
+#define _error_missing_from "expecting FROM keyword " _error_context
+#define _error_missing_at "expecting AT keyword " _error_context
+#define _error_missing_by "expecting BY keyword " _error_context
+#define _error_missing_as "expecting alias ('as <identifier>') " _error_context
+#define _error_missing_identifier "expecting identifier " _error_context
+#define _error_missing_par "missing closed parenthesis " _error_context
+#define _error_missing_assign "expecting assignment ('=') " _error_context
+#define _error_missing_where "SET statements must have a WHERE clause or " \
+ "explicitly use ANYWHERE " _error_context
+#define _error_invalid_depth "invalid depth " _error_context
+#define _error_missing_expr "missing expression " _error_context
+#define _error_invalid_expr "invalid expression " _error_context
+#define _error_invalid_unary_op "invalid unary operator " _error_context
+#define _error_invalid_logical_op "invalid logical operator " _error_context
+#define _error_invalid_fmtspec "invalid format specifier " _error_context
+#define _error_invalid_string "string expected " _error_context
+#define _error_invalid_order_criterion "invalid order criterion " _error_context
+
+#define token_sstr(token) ((token)->value)
+
+static void dav_error_in_context(int errorcode, const char *errormsg,
+ DavQLStatement *stmt, DavQLToken *token) {
+
+ // we try to achieve two things: get as many information as possible
+ // and recover the concrete source string (and not the token strings)
+ cxstring emptystring = CX_STR("");
+ cxstring prev = token->prev ? (token->prev->prev ?
+ token_sstr(token->prev->prev) : token_sstr(token->prev))
+ : emptystring;
+ cxstring tokenstr = token_sstr(token);
+ cxstring next = token->next ? (token->next->next ?
+ token_sstr(token->next->next) : token_sstr(token->next))
+ : emptystring;
+
+ int lp = prev.length == 0 ? 0 : tokenstr.ptr-prev.ptr;
+ const char *pn = tokenstr.ptr + tokenstr.length;
+ int ln = next.ptr+next.length - pn;
+
+ stmt->errorcode = errorcode;
+ stmt->errormessage = cx_asprintf(errormsg,
+ lp, prev.ptr,
+ sfmtarg(tokenstr),
+ ln, pn).ptr;
+}
+
+#define dqlsec_alloc_failed(ptr, stmt) \
+ if (!(ptr)) do { \
+ (stmt)->errorcode = DAVQL_ERROR_OUT_OF_MEMORY; \
+ return 0; \
+ } while(0)
+#define dqlsec_malloc(stmt, ptr, type) \
+ dqlsec_alloc_failed(ptr = malloc(sizeof(type)), stmt)
+#define dqlsec_mallocz(stmt, ptr, type) \
+ dqlsec_alloc_failed(ptr = calloc(1, sizeof(type)), stmt)
+
+
+// special symbols are single tokens - the % sign MUST NOT be a special symbol
+static const char *special_token_symbols = ",()+-*/&|^~=!<>";
+
+static _Bool iskeyword(DavQLToken *token) {
+ cxstring keywords[] ={CX_STR("select"), CX_STR("set"), CX_STR("from"), CX_STR("at"), CX_STR("as"),
+ CX_STR("where"), CX_STR("anywhere"), CX_STR("like"), CX_STR("unlike"), CX_STR("and"),
+ CX_STR("or"), CX_STR("not"), CX_STR("xor"), CX_STR("with"), CX_STR("infinity"),
+ CX_STR("order"), CX_STR("by"), CX_STR("asc"), CX_STR("desc")
+ };
+ for (int i = 0 ; i < sizeof(keywords)/sizeof(cxstring) ; i++) {
+ if (!cx_strcasecmp(token->value, keywords[i])) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static _Bool islongoperator(DavQLToken *token) {
+ cxstring operators[] = {CX_STR("and"), CX_STR("or"), CX_STR("not"), CX_STR("xor"),
+ CX_STR("like"), CX_STR("unlike")
+ };
+ for (int i = 0 ; i < sizeof(operators)/sizeof(cxstring) ; i++) {
+ if (!cx_strcasecmp(token->value, operators[i])) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int dav_stmt_add_field(DavQLStatement *stmt, DavQLField *field) {
+ if(!stmt->fields) {
+ stmt->fields = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ if(!stmt->fields) {
+ stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+ return 1;
+ }
+ }
+
+ if(cxListAdd(stmt->fields, field)) {
+ stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static void tokenlist_free(DavQLToken *tokenlist) {
+ DavQLToken *token = tokenlist;
+ while(token) {
+ DavQLToken *next = token->next;
+ free(token);
+ token = next;
+ }
+}
+
+static int dav_parse_add_token(DavQLToken **begin, DavQLToken **end, DavQLToken *token) {
+
+ // determine token class (order of if-statements is very important!)
+ char firstchar = token->value.ptr[0];
+
+ if (isdigit(firstchar)) {
+ token->tokenclass = DAVQL_TOKEN_NUMBER;
+ // check, if all characters are digits
+ for (size_t i = 1 ; i < token->value.length ; i++) {
+ if (!isdigit(token->value.ptr[i])) {
+ token->tokenclass = DAVQL_TOKEN_INVALID;
+ break;
+ }
+ }
+ } else if (firstchar == '%') {
+ token->tokenclass = DAVQL_TOKEN_FMTSPEC;
+ } else if (token->value.length == 1) {
+ switch (firstchar) {
+ case '(': token->tokenclass = DAVQL_TOKEN_OPENP; break;
+ case ')': token->tokenclass = DAVQL_TOKEN_CLOSEP; break;
+ case ',': token->tokenclass = DAVQL_TOKEN_COMMA; break;
+ default:
+ token->tokenclass = strchr(special_token_symbols, firstchar) ?
+ DAVQL_TOKEN_OPERATOR : DAVQL_TOKEN_IDENTIFIER;
+ }
+ } else if (islongoperator(token)) {
+ token->tokenclass = DAVQL_TOKEN_OPERATOR;
+ } else if (firstchar == '\'') {
+ token->tokenclass = DAVQL_TOKEN_STRING;
+ } else if (firstchar == '`') {
+ token->tokenclass = DAVQL_TOKEN_IDENTIFIER;
+ } else if (iskeyword(token)) {
+ token->tokenclass = DAVQL_TOKEN_KEYWORD;
+ } else {
+ token->tokenclass = DAVQL_TOKEN_IDENTIFIER;
+ // TODO: check for illegal characters
+ }
+
+ // remove quotes (extreme cool feature)
+ if (token->tokenclass == DAVQL_TOKEN_STRING ||
+ (token->tokenclass == DAVQL_TOKEN_IDENTIFIER && firstchar == '`')) {
+
+ char lastchar = token->value.ptr[token->value.length-1];
+ if (firstchar == lastchar) {
+ token->value.ptr++;
+ token->value.length -= 2;
+ } else {
+ token->tokenclass = DAVQL_TOKEN_INVALID;
+ }
+ }
+
+ cx_linked_list_add((void**)begin, (void**)end, offsetof(DavQLToken, prev), offsetof(DavQLToken, next), token);
+ return 0;
+}
+
+
+
+static DavQLToken* dav_parse_tokenize(cxstring src) {
+#define alloc_token() do {token = calloc(1, sizeof(DavQLToken));\
+ if(!token) {tokenlist_free(tokens_begin); return NULL;}} while(0)
+#define add_token() if(dav_parse_add_token(&tokens_begin, &tokens_end, token)) return NULL;
+
+ DavQLToken *tokens_begin = NULL;
+ DavQLToken *tokens_end = NULL;
+
+ DavQLToken *token = NULL;
+
+ char insequence = '\0';
+ for (size_t i = 0 ; i < src.length ; i++) {
+ // quoted strings / identifiers are a single token
+ if (src.ptr[i] == '\'' || src.ptr[i] == '`') {
+ if (src.ptr[i] == insequence) {
+ // lookahead for escaped string quotes
+ if (src.ptr[i] == '\'' && i+2 < src.length &&
+ src.ptr[i+1] == src.ptr[i] && src.ptr[i+2] == src.ptr[i]) {
+ token->value.length += 3;
+ i += 2;
+ } else {
+ // add quoted token to list
+ token->value.length++;
+ add_token();
+ token = NULL;
+ insequence = '\0';
+ }
+ } else if (insequence == '\0') {
+ insequence = src.ptr[i];
+ // always create new token for quoted strings
+ if (token) {
+ add_token();
+ }
+ alloc_token();
+ token->value.ptr = src.ptr + i;
+ token->value.length = 1;
+ } else {
+ // add other kind of quotes to token
+ token->value.length++;
+ }
+ } else if (insequence) {
+ token->value.length++;
+ } else if (isspace(src.ptr[i])) {
+ // add token before spaces to list (if any)
+ if (token) {
+ add_token();
+ token = NULL;
+ }
+ } else if (strchr(special_token_symbols, src.ptr[i])) {
+ // add token before special symbol to list (if any)
+ if (token) {
+ add_token();
+ token = NULL;
+ }
+ // add special symbol as single token to list
+ alloc_token();
+ token->value.ptr = src.ptr + i;
+ token->value.length = 1;
+ add_token();
+ // set tokenizer ready to read more tokens
+ token = NULL;
+ } else {
+ // if this is a new token, create memory for it
+ if (!token) {
+ alloc_token();
+ token->value.ptr = src.ptr + i;
+ token->value.length = 0;
+ }
+ // extend token length when reading more bytes
+ token->value.length++;
+ }
+ }
+
+ if (token) {
+ add_token();
+ }
+
+ alloc_token();
+ token->tokenclass = DAVQL_TOKEN_END;
+ token->value = CX_STR("");
+
+ cx_linked_list_add((void**)&tokens_begin, (void**)&tokens_end, offsetof(DavQLToken, prev), offsetof(DavQLToken, next), token);
+ return tokens_begin;
+#undef alloc_token
+#undef add_token
+}
+
+static void dav_free_expression(DavQLExpression *expr) {
+ if (expr) {
+ if (expr->left) {
+ dav_free_expression(expr->left);
+ }
+ if (expr->right) {
+ dav_free_expression(expr->right);
+ }
+ free(expr);
+ }
+}
+
+static void dav_free_field(DavQLField *field) {
+ dav_free_expression(field->expr);
+ free(field);
+}
+
+static void dav_free_order_criterion(DavQLOrderCriterion *crit) {
+ if (crit->column) { // do it null-safe though column is expected to be set
+ dav_free_expression(crit->column);
+ }
+}
+
+#define token_is(token, expectedclass) (token && \
+ (token->tokenclass == expectedclass))
+
+#define tokenvalue_is(token, expectedvalue) (token && \
+ !cx_strcasecmp(token->value, cx_str(expectedvalue)))
+
+typedef int(*exprparser_f)(DavQLStatement*,DavQLToken*,DavQLExpression*);
+
+static int dav_parse_binary_expr(DavQLStatement* stmt, DavQLToken* token,
+ DavQLExpression* expr, exprparser_f parseL, char* opc, int* opv,
+ exprparser_f parseR) {
+
+ if (!token) {
+ return 0;
+ }
+
+ int total_consumed = 0, consumed;
+
+ // save temporarily on stack (copy to heap later on)
+ DavQLExpression left, right;
+
+ // RULE: LEFT, [Operator, RIGHT]
+ memset(&left, 0, sizeof(DavQLExpression));
+ consumed = parseL(stmt, token, &left);
+ if (!consumed || stmt->errorcode) {
+ return 0;
+ }
+ total_consumed += consumed;
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+
+ char *op;
+ if (token_is(token, DAVQL_TOKEN_OPERATOR) &&
+ (op = strchr(opc, token_sstr(token).ptr[0]))) {
+ expr->op = opv[op-opc];
+ expr->type = DAVQL_BINARY;
+ total_consumed++;
+ token = token->next;
+ memset(&right, 0, sizeof(DavQLExpression));
+ consumed = parseR(stmt, token, &right);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (!consumed) {
+ dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+ _error_missing_expr, stmt, token);
+ return 0;
+ }
+ total_consumed += consumed;
+ }
+
+ if (expr->op == DAVQL_NOOP) {
+ memcpy(expr, &left, sizeof(DavQLExpression));
+ } else {
+ dqlsec_malloc(stmt, expr->left, DavQLExpression);
+ memcpy(expr->left, &left, sizeof(DavQLExpression));
+ dqlsec_malloc(stmt, expr->right, DavQLExpression);
+ memcpy(expr->right, &right, sizeof(DavQLExpression));
+
+ expr->srctext.ptr = expr->left->srctext.ptr;
+ expr->srctext.length =
+ expr->right->srctext.ptr -
+ expr->left->srctext.ptr + expr->right->srctext.length;
+ }
+
+ return total_consumed;
+}
+
+static void fmt_args_add(DavQLStatement *stmt, void *data) {
+ if(!stmt->args) {
+ stmt->args = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ }
+ cxListAdd(stmt->args, data);
+}
+
+static void dav_add_fmt_args(DavQLStatement *stmt, cxstring str) {
+ int placeholder = 0;
+ for (size_t i=0;i<str.length;i++) {
+ char c = str.ptr[i];
+ if (placeholder) {
+ if (c != '%') {
+ fmt_args_add(stmt, (void*)(intptr_t)c);
+ }
+ placeholder = 0;
+ } else if (c == '%') {
+ placeholder = 1;
+ }
+ }
+}
+
+static int dav_parse_literal(DavQLStatement* stmt, DavQLToken* token,
+ DavQLExpression* expr) {
+
+ expr->srctext = token_sstr(token);
+ if (token_is(token, DAVQL_TOKEN_NUMBER)) {
+ expr->type = DAVQL_NUMBER;
+ } else if (token_is(token, DAVQL_TOKEN_STRING)) {
+ expr->type = DAVQL_STRING;
+ // check for format specifiers and add args
+ dav_add_fmt_args(stmt, expr->srctext);
+ } else if (token_is(token, DAVQL_TOKEN_TIMESTAMP)) {
+ expr->type = DAVQL_TIMESTAMP;
+ } else if (token_is(token, DAVQL_TOKEN_FMTSPEC)
+ && expr->srctext.length == 2) {
+ switch (expr->srctext.ptr[1]) {
+ case 'd': expr->type = DAVQL_NUMBER; break;
+ case 's': expr->type = DAVQL_STRING; break;
+ case 't': expr->type = DAVQL_TIMESTAMP; break;
+ default:
+ dav_error_in_context(DAVQL_ERROR_INVALID_FMTSPEC,
+ _error_invalid_fmtspec, stmt, token);
+ return 0;
+ }
+ // add fmtspec type to query arg list
+ fmt_args_add(stmt, (void*)(intptr_t)expr->srctext.ptr[1]);
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+
+// forward declaration
+static int dav_parse_expression(DavQLStatement* stmt, DavQLToken* token,
+ DavQLExpression* expr);
+
+static int dav_parse_arglist(DavQLStatement* stmt, DavQLToken* token,
+ DavQLExpression* expr) {
+
+ expr->srctext.ptr = token_sstr(token).ptr;
+ expr->srctext.length = 0;
+ expr->left = expr->right = NULL; // in case we fail, we want them to be sane
+
+ int total_consumed = 0;
+
+ // RULE: Expression, {",", Expression};
+ DavQLExpression *arglist = expr;
+ DavQLExpression arg;
+ const char *lastchar = expr->srctext.ptr;
+ int consumed;
+ do {
+ memset(&arg, 0, sizeof(DavQLExpression));
+ consumed = dav_parse_expression(stmt, token, &arg);
+ if (consumed) {
+ lastchar = arg.srctext.ptr + arg.srctext.length;
+ total_consumed += consumed;
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+ // look ahead for a comma
+ if (token_is(token, DAVQL_TOKEN_COMMA)) {
+ total_consumed++;
+ token = token->next;
+ /* we have more arguments, so put the current argument to the
+ * left subtree and create a new node to the right
+ */
+ dqlsec_malloc(stmt, arglist->left, DavQLExpression);
+ memcpy(arglist->left, &arg, sizeof(DavQLExpression));
+ arglist->srctext.ptr = arg.srctext.ptr;
+ arglist->op = DAVQL_ARGLIST;
+ arglist->type = DAVQL_FUNCCALL;
+ dqlsec_mallocz(stmt, arglist->right, DavQLExpression);
+ arglist = arglist->right;
+ } else {
+ // this was the last argument, so write it to the current node
+ memcpy(arglist, &arg, sizeof(DavQLExpression));
+ consumed = 0;
+ }
+ }
+ } while (consumed && !stmt->errorcode);
+
+ // recover source text
+ arglist = expr;
+ while (arglist && arglist->type == DAVQL_FUNCCALL) {
+ arglist->srctext.length = lastchar - arglist->srctext.ptr;
+ arglist = arglist->right;
+ }
+
+ return total_consumed;
+}
+
+static int dav_parse_funccall(DavQLStatement* stmt, DavQLToken* token,
+ DavQLExpression* expr) {
+
+ // RULE: Identifier, "(", ArgumentList, ")";
+ if (token_is(token, DAVQL_TOKEN_IDENTIFIER) &&
+ token_is(token->next, DAVQL_TOKEN_OPENP)) {
+
+ expr->type = DAVQL_FUNCCALL;
+ expr->op = DAVQL_CALL;
+
+ dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+ expr->left->type = DAVQL_IDENTIFIER;
+ expr->left->srctext = token_sstr(token);
+ expr->right = NULL;
+
+ token = token->next->next;
+
+ DavQLExpression arg;
+ int argtokens = dav_parse_arglist(stmt, token, &arg);
+ if (stmt->errorcode) {
+ // if an error occurred while parsing the arglist, return now
+ return 2;
+ }
+ if (argtokens) {
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), argtokens);
+ dqlsec_malloc(stmt, expr->right, DavQLExpression);
+ memcpy(expr->right, &arg, sizeof(DavQLExpression));
+ } else {
+ // arg list may be empty
+ expr->right = NULL;
+ }
+
+ if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+ return 3 + argtokens;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_PAR, _error_missing_par,
+ stmt, token);
+ return 2; // it MUST be a function call, but it is invalid
+ }
+ } else {
+ return 0;
+ }
+}
+
+static int dav_parse_unary_expr(DavQLStatement* stmt, DavQLToken* token,
+ DavQLExpression* expr) {
+
+ DavQLToken *firsttoken = token; // save for srctext recovery
+
+ DavQLExpression* atom = expr;
+ int total_consumed = 0;
+
+ // optional unary operator
+ if (token_is(token, DAVQL_TOKEN_OPERATOR)) {
+ char *op = strchr("+-~", token_sstr(token).ptr[0]);
+ if (op) {
+ expr->type = DAVQL_UNARY;
+ switch (*op) {
+ case '+': expr->op = DAVQL_ADD; break;
+ case '-': expr->op = DAVQL_SUB; break;
+ case '~': expr->op = DAVQL_NEG; break;
+ }
+ dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+ atom = expr->left;
+ total_consumed++;
+ token = token->next;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_INVALID_UNARY_OP,
+ _error_invalid_unary_op, stmt, token);
+ return 0;
+ }
+ }
+
+ // RULE: (ParExpression | AtomicExpression)
+ if (token_is(token, DAVQL_TOKEN_OPENP)) {
+ token = token->next; total_consumed++;
+ // RULE: "(", Expression, ")"
+ int consumed = dav_parse_expression(stmt, token, atom);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (!consumed) {
+ dav_error_in_context(DAVQL_ERROR_INVALID_EXPR,
+ _error_invalid_expr, stmt, token);
+ return 0;
+ }
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+ total_consumed += consumed;
+ if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+ token = token->next; total_consumed++;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_PAR,
+ _error_missing_par, stmt, token);
+ return 0;
+ }
+ } else {
+ // RULE: FunctionCall
+ int consumed = dav_parse_funccall(stmt, token, atom);
+ if (consumed) {
+ total_consumed += consumed;
+ } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+ // RULE: Identifier
+ total_consumed++;
+ atom->type = DAVQL_IDENTIFIER;
+ atom->srctext = token_sstr(token);
+ } else {
+ // RULE: Literal
+ total_consumed += dav_parse_literal(stmt, token, atom);
+ }
+ }
+
+ // recover source text
+ expr->srctext.ptr = token_sstr(firsttoken).ptr;
+ if (total_consumed > 0) {
+ cxstring lasttoken =
+ token_sstr((DavQLToken*)cx_linked_list_at(token, 0, offsetof(DavQLToken, next), total_consumed-1));
+ expr->srctext.length =
+ lasttoken.ptr - expr->srctext.ptr + lasttoken.length;
+ } else {
+ // the expression should not be used anyway, but we want to be safe
+ expr->srctext.length = 0;
+ }
+
+
+ return total_consumed;
+}
+
+static int dav_parse_bitexpr(DavQLStatement* stmt, DavQLToken* token,
+ DavQLExpression* expr) {
+
+ return dav_parse_binary_expr(stmt, token, expr,
+ dav_parse_unary_expr,
+ "&|^", (int[]){DAVQL_AND, DAVQL_OR, DAVQL_XOR},
+ dav_parse_bitexpr);
+}
+
+static int dav_parse_multexpr(DavQLStatement* stmt, DavQLToken* token,
+ DavQLExpression* expr) {
+
+ return dav_parse_binary_expr(stmt, token, expr,
+ dav_parse_bitexpr,
+ "*/", (int[]){DAVQL_MUL, DAVQL_DIV},
+ dav_parse_multexpr);
+}
+
+static int dav_parse_expression(DavQLStatement* stmt, DavQLToken* token,
+ DavQLExpression* expr) {
+
+ return dav_parse_binary_expr(stmt, token, expr,
+ dav_parse_multexpr,
+ "+-", (int[]){DAVQL_ADD, DAVQL_SUB},
+ dav_parse_expression);
+}
+
+static int dav_parse_named_field(DavQLStatement *stmt, DavQLToken *token,
+ DavQLField *field) {
+ int total_consumed = 0, consumed;
+
+ // RULE: Expression, " as ", Identifier;
+ DavQLExpression *expr;
+ dqlsec_mallocz(stmt, expr, DavQLExpression);
+ consumed = dav_parse_expression(stmt, token, expr);
+ if (stmt->errorcode) {
+ dav_free_expression(expr);
+ return 0;
+ }
+ if (expr->type == DAVQL_UNDEFINED_TYPE) {
+ dav_free_expression(expr);
+ dav_error_in_context(DAVQL_ERROR_INVALID_EXPR,
+ _error_invalid_expr, stmt, token);
+ return 0;
+ }
+
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+ total_consumed += consumed;
+
+ if (token_is(token, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(token, "as")) {
+ token = token->next; total_consumed++;
+ } else {
+ dav_free_expression(expr);
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_as, stmt, token);
+ return 0;
+ }
+
+ if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+ field->name = token_sstr(token);
+ field->expr = expr;
+ return total_consumed + 1;
+ } else {
+ dav_free_expression(expr);
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_identifier, stmt, token);
+ return 0;
+ }
+}
+
+static int dav_parse_fieldlist(DavQLStatement *stmt, DavQLToken *token) {
+
+ // RULE: "-"
+ if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "-")) {
+ DavQLField *field;
+ dqlsec_malloc(stmt, field, DavQLField);
+ if(dav_stmt_add_field(stmt, field)) {
+ free(field);
+ return 0;
+ }
+ dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+ field->expr->type = DAVQL_IDENTIFIER;
+ field->expr->srctext = field->name = token_sstr(token);
+ return 1;
+ }
+
+ // RULE: "*", {",", NamedExpression}
+ if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "*")) {
+ DavQLField *field;
+ dqlsec_malloc(stmt, field, DavQLField);
+ if(dav_stmt_add_field(stmt, field)) {
+ free(field);
+ return 0;
+ }
+ dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+ field->expr->type = DAVQL_IDENTIFIER;
+ field->expr->srctext = field->name = token_sstr(token);
+
+ int total_consumed = 0;
+ int consumed = 1;
+
+ do {
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+ total_consumed += consumed;
+
+ if (token_is(token, DAVQL_TOKEN_COMMA)) {
+ total_consumed++; token = token->next;
+ DavQLField localfield;
+ consumed = dav_parse_named_field(stmt, token, &localfield);
+ if (!stmt->errorcode && consumed) {
+ DavQLField *add_field;
+ dqlsec_malloc(stmt, add_field, DavQLField);
+ memcpy(add_field, &localfield, sizeof(DavQLField));
+ if(dav_stmt_add_field(stmt, add_field)) {
+ free(add_field);
+ return 0;
+ }
+ }
+ } else {
+ consumed = 0;
+ }
+ } while (consumed > 0);
+
+ return total_consumed;
+ }
+
+ // RULE: FieldExpression, {",", FieldExpression}
+ {
+ int total_consumed = 0, consumed;
+ do {
+ // RULE: NamedField | Identifier
+ DavQLField localfield;
+ consumed = dav_parse_named_field(stmt, token, &localfield);
+ if (consumed) {
+ DavQLField *field;
+ dqlsec_malloc(stmt, field, DavQLField);
+ memcpy(field, &localfield, sizeof(DavQLField));
+ if(dav_stmt_add_field(stmt, field)) {
+ free(field);
+ return 0;
+ }
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+ total_consumed += consumed;
+ } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER)
+ // look ahead, if the field is JUST the identifier
+ && (token_is(token->next, DAVQL_TOKEN_COMMA) ||
+ tokenvalue_is(token->next, "from"))) {
+
+ DavQLField *field;
+ dqlsec_malloc(stmt, field, DavQLField);
+ dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+ field->expr->type = DAVQL_IDENTIFIER;
+ field->expr->srctext = field->name = token_sstr(token);
+ if(dav_stmt_add_field(stmt, field)) {
+ free(field);
+ return 0;
+ }
+
+ consumed = 1;
+ total_consumed++;
+ token = token->next;
+
+ // we found a valid solution, so erase any errors
+ stmt->errorcode = 0;
+ if (stmt->errormessage) {
+ free(stmt->errormessage);
+ stmt->errormessage = NULL;
+ }
+ } else {
+ // dav_parse_named_field has already thrown a good error
+ consumed = 0;
+ }
+
+ // field has been parsed, now try to get a comma
+ if (consumed) {
+ consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0;
+ if (consumed) {
+ token = token->next;
+ total_consumed++;
+ }
+ }
+ } while (consumed);
+
+ return total_consumed;
+ }
+}
+
+// forward declaration
+static int dav_parse_logical_expr(DavQLStatement *stmt, DavQLToken *token,
+ DavQLExpression *expr);
+
+static int dav_parse_bool_prim(DavQLStatement *stmt, DavQLToken *token,
+ DavQLExpression *expr) {
+
+ expr->type = DAVQL_LOGICAL;
+ expr->srctext = token_sstr(token);
+
+ int total_consumed = 0;
+
+ DavQLExpression bexpr;
+ memset(&bexpr, 0, sizeof(DavQLExpression));
+ total_consumed = dav_parse_expression(stmt, token, &bexpr);
+ if (!total_consumed || stmt->errorcode) {
+ return 0;
+ }
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), total_consumed);
+
+ DavQLToken* optok = token;
+ // RULE: Expression, (" like " | " unlike "), String
+ if (token_is(optok, DAVQL_TOKEN_OPERATOR) && (tokenvalue_is(optok,
+ "like") || tokenvalue_is(optok, "unlike"))) {
+
+ total_consumed++;
+ token = token->next;
+ if (token_is(token, DAVQL_TOKEN_STRING)) {
+ expr->op = tokenvalue_is(optok, "like") ?
+ DAVQL_LIKE : DAVQL_UNLIKE;
+ dqlsec_malloc(stmt, expr->left, DavQLExpression);
+ memcpy(expr->left, &bexpr, sizeof(DavQLExpression));
+ dqlsec_mallocz(stmt, expr->right, DavQLExpression);
+ expr->right->type = DAVQL_STRING;
+ expr->right->srctext = token_sstr(token);
+ expr->srctext.length = expr->right->srctext.ptr -
+ expr->srctext.ptr + expr->right->srctext.length;
+
+ // fmt args
+ dav_add_fmt_args(stmt, expr->right->srctext);
+
+ return total_consumed + 1;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_INVALID_STRING,
+ _error_invalid_string, stmt, token);
+ return 0;
+ }
+ }
+ // RULE: Expression, Comparison, Expression
+ else if (token_is(optok, DAVQL_TOKEN_OPERATOR) && (
+ tokenvalue_is(optok, "=") || tokenvalue_is(optok, "!") ||
+ tokenvalue_is(optok, "<") || tokenvalue_is(optok, ">"))) {
+
+ total_consumed++;
+ token = token->next;
+
+ if (tokenvalue_is(optok, "=")) {
+ expr->op = DAVQL_EQ;
+ } else {
+ if (tokenvalue_is(token, "=")) {
+ if (tokenvalue_is(optok, "!")) {
+ expr->op = DAVQL_NEQ;
+ } else if (tokenvalue_is(optok, "<")) {
+ expr->op = DAVQL_LE;
+ } else if (tokenvalue_is(optok, ">")) {
+ expr->op = DAVQL_GE;
+ }
+ total_consumed++;
+ token = token->next;
+ } else {
+ if (tokenvalue_is(optok, "<")) {
+ expr->op = DAVQL_LT;
+ } else if (tokenvalue_is(optok, ">")) {
+ expr->op = DAVQL_GT;
+ }
+ }
+ }
+
+ DavQLExpression rexpr;
+ memset(&rexpr, 0, sizeof(DavQLExpression));
+ int consumed = dav_parse_expression(stmt, token, &rexpr);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (!consumed) {
+ dav_error_in_context(
+ DAVQL_ERROR_MISSING_EXPR, _error_missing_expr,
+ stmt, token);
+ return 0;
+ }
+
+ total_consumed += consumed;
+ dqlsec_malloc(stmt, expr->left, DavQLExpression);
+ memcpy(expr->left, &bexpr, sizeof(DavQLExpression));
+ dqlsec_malloc(stmt, expr->right, DavQLExpression);
+ memcpy(expr->right, &rexpr, sizeof(DavQLExpression));
+
+ expr->srctext.length = expr->right->srctext.ptr -
+ expr->srctext.ptr + expr->right->srctext.length;
+
+ return total_consumed;
+ }
+ // RULE: FunctionCall | Identifier;
+ else if (bexpr.type == DAVQL_FUNCCALL || bexpr.type == DAVQL_IDENTIFIER) {
+ memcpy(expr, &bexpr, sizeof(DavQLExpression));
+
+ return total_consumed;
+ } else {
+ return 0;
+ }
+}
+
+static int dav_parse_bool_expr(DavQLStatement *stmt, DavQLToken *token,
+ DavQLExpression *expr) {
+
+ // RULE: "not ", LogicalExpression
+ if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "not")) {
+ expr->type = DAVQL_LOGICAL;
+ expr->op = DAVQL_NOT;
+ dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+ expr->srctext = token_sstr(token);
+
+ token = token->next;
+ int consumed = dav_parse_bool_expr(stmt, token, expr->left);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (consumed) {
+ cxstring lasttok = token_sstr((DavQLToken*)cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed-1));
+ expr->srctext.length =
+ lasttok.ptr - expr->srctext.ptr + lasttok.length;
+ return consumed + 1;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+ _error_missing_expr, stmt, token);
+ return 0;
+ }
+ }
+ // RULE: "(", LogicalExpression, ")"
+ else if (token_is(token, DAVQL_TOKEN_OPENP)) {
+ int consumed = dav_parse_logical_expr(stmt, token->next, expr);
+ if (consumed) {
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+
+ if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+ token = token->next;
+ return consumed + 2;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_PAR, _error_missing_par,
+ stmt, token);
+ return 0;
+ }
+ } else {
+ // don't handle errors here, we can also try a boolean primary
+ stmt->errorcode = 0;
+ if (stmt->errormessage) {
+ free(stmt->errormessage);
+ }
+ }
+ }
+
+ // RULE: BooleanPrimary
+ return dav_parse_bool_prim(stmt, token, expr);
+}
+
+static int dav_parse_logical_expr(DavQLStatement *stmt, DavQLToken *token,
+ DavQLExpression *expr) {
+
+ DavQLToken *firsttoken = token;
+ int total_consumed = 0;
+
+ // RULE: BooleanLiteral, [LogicalOperator, LogicalExpression];
+ DavQLExpression left, right;
+ memset(&left, 0, sizeof(DavQLExpression));
+ int consumed = dav_parse_bool_expr(stmt, token, &left);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (!consumed) {
+ dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+ _error_missing_expr, stmt, token);
+ return 0;
+ }
+ total_consumed += consumed;
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+
+ if (token_is(token, DAVQL_TOKEN_OPERATOR)) {
+ expr->type = DAVQL_LOGICAL;
+
+ davqloperator_t op = DAVQL_NOOP;
+ if (tokenvalue_is(token, "and")) {
+ op = DAVQL_LAND;
+ } else if (tokenvalue_is(token, "or")) {
+ op = DAVQL_LOR;
+ } else if (tokenvalue_is(token, "xor")) {
+ op = DAVQL_LXOR;
+ }
+
+ if (op == DAVQL_NOOP) {
+ dav_error_in_context(DAVQL_ERROR_INVALID_LOGICAL_OP,
+ _error_invalid_logical_op, stmt, token);
+ return 0;
+ } else {
+ expr->op = op;
+ total_consumed++;
+ token = token->next;
+
+ memset(&right, 0, sizeof(DavQLExpression));
+ consumed = dav_parse_logical_expr(stmt, token, &right);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (!consumed) {
+ dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+ _error_missing_expr, stmt, token);
+ return 0;
+ }
+ total_consumed += consumed;
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+
+ dqlsec_malloc(stmt, expr->left, DavQLExpression);
+ memcpy(expr->left, &left, sizeof(DavQLExpression));
+ dqlsec_malloc(stmt, expr->right, DavQLExpression);
+ memcpy(expr->right, &right, sizeof(DavQLExpression));
+ }
+ } else {
+ memcpy(expr, &left, sizeof(DavQLExpression));
+ }
+
+ // set type and recover source text
+ if (total_consumed > 0) {
+ expr->srctext.ptr = token_sstr(firsttoken).ptr;
+ cxstring lasttok = token_sstr((DavQLToken*)cx_linked_list_at(firsttoken, 0, offsetof(DavQLToken, next), total_consumed-1));
+ expr->srctext.length = lasttok.ptr-expr->srctext.ptr+lasttok.length;
+ }
+
+ return total_consumed;
+}
+
+static int dav_parse_where_clause(DavQLStatement *stmt, DavQLToken *token) {
+ dqlsec_mallocz(stmt, stmt->where, DavQLExpression);
+
+ return dav_parse_logical_expr(stmt, token, stmt->where);
+}
+
+static int dav_parse_with_clause(DavQLStatement *stmt, DavQLToken *token) {
+
+ int total_consumed = 0;
+
+ // RULE: "depth", "=", (Number | "infinity")
+ if (tokenvalue_is(token, "depth")) {
+ token = token->next; total_consumed++;
+ if (tokenvalue_is(token, "=")) {
+ token = token->next; total_consumed++;
+ if (tokenvalue_is(token, "infinity")) {
+ stmt->depth = DAV_DEPTH_INFINITY;
+ token = token->next; total_consumed++;
+ } else {
+ DavQLExpression *depthexpr;
+ dqlsec_mallocz(stmt, depthexpr, DavQLExpression);
+
+ int consumed = dav_parse_expression(stmt, token, depthexpr);
+
+ if (consumed) {
+ if (depthexpr->type == DAVQL_NUMBER) {
+ if (depthexpr->srctext.ptr[0] == '%') {
+ stmt->depth = DAV_DEPTH_PLACEHOLDER;
+ } else {
+ cxstring depthstr = depthexpr->srctext;
+ char *conv = malloc(depthstr.length+1);
+ if (!conv) {
+ dav_free_expression(depthexpr);
+ stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+ return 0;
+ }
+ char *chk;
+ memcpy(conv, depthstr.ptr, depthstr.length);
+ conv[depthstr.length] = '\0';
+ stmt->depth = strtol(conv, &chk, 10);
+ if (*chk || stmt->depth < -1) {
+ dav_error_in_context(DAVQL_ERROR_INVALID_DEPTH,
+ _error_invalid_depth, stmt, token);
+ }
+ free(conv);
+ }
+ total_consumed += consumed;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_INVALID_DEPTH,
+ _error_invalid_depth, stmt, token);
+ }
+ }
+
+ dav_free_expression(depthexpr);
+ }
+ }
+ }
+
+ return total_consumed;
+}
+
+static int dav_parse_order_crit(DavQLStatement *stmt, DavQLToken *token,
+ DavQLOrderCriterion *crit) {
+
+ // RULE: (Identifier | Number), [" asc"|" desc"];
+ DavQLExpression expr;
+ memset(&expr, 0, sizeof(DavQLExpression));
+ int consumed = dav_parse_expression(stmt, token, &expr);
+ if (stmt->errorcode || !consumed) {
+ return 0;
+ }
+
+ if (expr.type != DAVQL_IDENTIFIER && expr.type != DAVQL_NUMBER) {
+ dav_error_in_context(DAVQL_ERROR_INVALID_ORDER_CRITERION,
+ _error_invalid_order_criterion, stmt, token);
+ return 0;
+ }
+
+ dqlsec_malloc(stmt, crit->column, DavQLExpression);
+ memcpy(crit->column, &expr, sizeof(DavQLExpression));
+
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+ if (token_is(token, DAVQL_TOKEN_KEYWORD) && (
+ tokenvalue_is(token, "asc") || tokenvalue_is(token, "desc"))) {
+
+ crit->descending = tokenvalue_is(token, "desc");
+
+ return consumed+1;
+ } else {
+ crit->descending = 0;
+ return consumed;
+ }
+}
+
+static int dav_parse_orderby_clause(DavQLStatement *stmt, DavQLToken *token) {
+
+ int total_consumed = 0, consumed;
+
+ DavQLOrderCriterion crit;
+
+ if(!stmt->orderby) {
+ stmt->orderby = cxLinkedListCreateSimple(sizeof(DavQLOrderCriterion));
+ if(!stmt->orderby) {
+ return 0;
+ }
+ }
+
+ // RULE: OrderByCriterion, {",", OrderByCriterion};
+ do {
+ consumed = dav_parse_order_crit(stmt, token, &crit);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (!consumed) {
+ dav_error_in_context(DAVQL_ERROR_MISSING_EXPR, _error_missing_expr,
+ stmt, token);
+ return 0;
+ }
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+ total_consumed += consumed;
+
+ if(cxListAdd(stmt->orderby, &crit)) {
+ stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+ return 0;
+ }
+
+ if (token_is(token, DAVQL_TOKEN_COMMA)) {
+ total_consumed++;
+ token = token->next;
+ } else {
+ consumed = 0;
+ }
+ } while (consumed);
+
+ return total_consumed;
+}
+
+
+static int dav_parse_assignments(DavQLStatement *stmt, DavQLToken *token) {
+
+ // RULE: Assignment, {",", Assignment}
+ int total_consumed = 0, consumed;
+ do {
+ // RULE: Identifier, "=", Expression
+ if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+
+ // Identifier
+ DavQLField *field;
+ dqlsec_malloc(stmt, field, DavQLField);
+ field->name = token_sstr(token);
+ total_consumed++;
+ token = token->next;
+
+ // "="
+ if (!token_is(token, DAVQL_TOKEN_OPERATOR)
+ || !tokenvalue_is(token, "=")) {
+ dav_free_field(field);
+
+ dav_error_in_context(DAVQL_ERROR_MISSING_ASSIGN,
+ _error_missing_assign, stmt, token);
+ return total_consumed;
+ }
+ total_consumed++;
+ token = token->next;
+
+ // Expression
+ dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+ consumed = dav_parse_expression(stmt, token, field->expr);
+ if (stmt->errorcode) {
+ dav_free_field(field);
+ return total_consumed;
+ }
+ token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+ total_consumed += consumed;
+
+ // Add assignment to list and check if there's another one
+ if(dav_stmt_add_field(stmt, field)) {
+ free(field);
+ return 0;
+ }
+ consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0;
+ if (consumed) {
+ token = token->next;
+ total_consumed++;
+ }
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_identifier, stmt, token);
+ return total_consumed;
+ }
+ } while (consumed);
+
+ return total_consumed;
+}
+
+static int dav_parse_path(DavQLStatement *stmt, DavQLToken *tokens) {
+ if (token_is(tokens, DAVQL_TOKEN_STRING)) {
+ stmt->path = token_sstr(tokens);
+ tokens = tokens->next;
+ return 1;
+ } else if (token_is(tokens, DAVQL_TOKEN_OPERATOR)
+ && tokenvalue_is(tokens, "/")) {
+ stmt->path = token_sstr(tokens);
+ tokens = tokens->next;
+ int consumed = 1;
+ while (!token_is(tokens, DAVQL_TOKEN_KEYWORD) &&
+ !token_is(tokens, DAVQL_TOKEN_END)) {
+ cxstring toksstr = token_sstr(tokens);
+ stmt->path.length = toksstr.ptr-stmt->path.ptr+toksstr.length;
+ tokens = tokens->next;
+ consumed++;
+ }
+ return consumed;
+ } else if (token_is(tokens, DAVQL_TOKEN_FMTSPEC) &&
+ tokenvalue_is(tokens, "%s")) {
+ stmt->path = token_sstr(tokens);
+ tokens = tokens->next;
+ fmt_args_add(stmt, (void*)(intptr_t)'s');
+ return 1;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_path, stmt, tokens);
+ return 0;
+ }
+}
+
+/**
+ * Parser of a select statement.
+ * @param stmt the statement object that shall contain the syntax tree
+ * @param tokens the token list
+ */
+static void dav_parse_select_statement(DavQLStatement *stmt, DavQLToken *tokens) {
+ stmt->type = DAVQL_SELECT;
+
+ // Consume field list
+ tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_fieldlist(stmt, tokens));
+ if (stmt->errorcode) {
+ return;
+ }
+
+ // Consume FROM keyword
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "from")) {
+ tokens = tokens->next;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_from, stmt, tokens);
+ return;
+ }
+
+ // Consume path
+ tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_path(stmt, tokens));
+ if (stmt->errorcode) {
+ return;
+ }
+ //dav_add_fmt_args(stmt, stmt->path); // add possible path args
+
+ // Consume with clause (if any)
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "with")) {
+ tokens = tokens->next;
+ tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+ dav_parse_with_clause(stmt, tokens));
+ }
+ if (stmt->errorcode) {
+ return;
+ }
+
+ // Consume where clause (if any)
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "where")) {
+ tokens = tokens->next;
+ tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+ dav_parse_where_clause(stmt, tokens));
+ } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "anywhere")) {
+ // useless, but the user may want to explicitly express his intent
+ tokens = tokens->next;
+ stmt->where = NULL;
+ }
+ if (stmt->errorcode) {
+ return;
+ }
+
+ // Consume order by clause (if any)
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "order")) {
+ tokens = tokens->next;
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "by")) {
+ tokens = tokens->next;
+ tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+ dav_parse_orderby_clause(stmt, tokens));
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_by, stmt, tokens);
+ return;
+ }
+ }
+ if (stmt->errorcode) {
+ return;
+ }
+
+
+ if (tokens) {
+ if (token_is(tokens, DAVQL_TOKEN_INVALID)) {
+ dav_error_in_context(DAVQL_ERROR_INVALID_TOKEN,
+ _error_invalid_token, stmt, tokens);
+ } else if (!token_is(tokens, DAVQL_TOKEN_END)) {
+ dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN,
+ _error_unexpected_token, stmt, tokens);
+ }
+ }
+}
+
+static void dav_parse_set_statement(DavQLStatement *stmt, DavQLToken *tokens) {
+ stmt->type = DAVQL_SET;
+
+ // Consume assignments
+ tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_assignments(stmt, tokens));
+ if (stmt->errorcode) {
+ return;
+ }
+
+ // Consume AT keyword
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "at")) {
+ tokens = tokens->next;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_at, stmt, tokens);
+ return;
+ }
+
+ // Consume path
+ tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_path(stmt, tokens));
+ if (stmt->errorcode) {
+ return;
+ }
+
+ // Consume with clause (if any)
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "with")) {
+ tokens = tokens->next;
+ tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+ dav_parse_with_clause(stmt, tokens));
+ }
+ if (stmt->errorcode) {
+ return;
+ }
+
+ // Consume mandatory where clause (or anywhere keyword)
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "where")) {
+ tokens = tokens->next;
+ tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+ dav_parse_where_clause(stmt, tokens));
+ } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "anywhere")) {
+ // no-op, but we want the user to be explicit about this
+ tokens = tokens->next;
+ stmt->where = NULL;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_where, stmt, tokens);
+ return;
+ }
+}
+
+DavQLStatement* dav_parse_statement(cxstring srctext) {
+ DavQLStatement *stmt = calloc(1, sizeof(DavQLStatement));
+
+ // if we can't even get enough memory for the statement object or an error
+ // message, we can simply die without returning anything
+ if (!stmt) {
+ return NULL;
+ }
+ char *oommsg = strdup(_error_out_of_memory);
+ if (!oommsg) {
+ free(stmt);
+ return NULL;
+ }
+
+ // default values
+ stmt->type = -1;
+ stmt->depth = 1;
+
+ // save trimmed source text
+ stmt->srctext = cx_strtrim(srctext);
+
+ if (stmt->srctext.length) {
+ // tokenization
+ DavQLToken* tokens = dav_parse_tokenize(stmt->srctext);
+
+ if (tokens) {
+ // use first token to determine query type
+
+ if (tokenvalue_is(tokens, "select")) {
+ dav_parse_select_statement(stmt, tokens->next);
+ } else if (tokenvalue_is(tokens, "set")) {
+ dav_parse_set_statement(stmt, tokens->next);
+ } else {
+ stmt->type = DAVQL_ERROR;
+ stmt->errorcode = DAVQL_ERROR_INVALID;
+ stmt->errormessage = strdup(_error_invalid);
+ }
+
+ // free token data
+ tokenlist_free(tokens);
+ } else {
+ stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ stmt->type = DAVQL_ERROR;
+ stmt->errorcode = DAVQL_ERROR_INVALID;
+ stmt->errormessage = strdup(_error_invalid);
+ }
+
+ if (stmt->errorcode == DAVQL_ERROR_OUT_OF_MEMORY) {
+ stmt->type = DAVQL_ERROR;
+ stmt->errormessage = oommsg;
+ } else {
+ free(oommsg);
+ }
+
+ return stmt;
+}
+
+void dav_free_statement(DavQLStatement *stmt) {
+ if(stmt->fields) {
+ cxDefineDestructor(stmt->fields, dav_free_field);
+ cxListDestroy(stmt->fields);
+ }
+
+ if (stmt->where) {
+ dav_free_expression(stmt->where);
+ }
+ if (stmt->errormessage) {
+ free(stmt->errormessage);
+ }
+
+ if(stmt->orderby) {
+ cxDefineDestructor(stmt->orderby, dav_free_order_criterion);
+ cxListDestroy(stmt->orderby);
+ }
+ if(stmt->args) {
+ cxListDestroy(stmt->args);
+ }
+ free(stmt);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DAVQLPARSER_H
+#define DAVQLPARSER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <cx/string.h>
+#include <cx/list.h>
+
+/**
+ * Enumeration of possible statement types.
+ */
+typedef enum {DAVQL_ERROR, DAVQL_SELECT, DAVQL_SET} davqltype_t;
+
+/**
+ * Enumeration of possible token classes.
+ */
+typedef enum {
+ DAVQL_TOKEN_INVALID, DAVQL_TOKEN_KEYWORD,
+ DAVQL_TOKEN_IDENTIFIER, DAVQL_TOKEN_FMTSPEC,
+ DAVQL_TOKEN_STRING, DAVQL_TOKEN_NUMBER, DAVQL_TOKEN_TIMESTAMP,
+ DAVQL_TOKEN_COMMA, DAVQL_TOKEN_OPENP, DAVQL_TOKEN_CLOSEP,
+ DAVQL_TOKEN_OPERATOR, DAVQL_TOKEN_END
+} davqltokenclass_t;
+
+/**
+ * Enumeration of possible expression types.
+ */
+typedef enum {
+ DAVQL_UNDEFINED_TYPE,
+ DAVQL_NUMBER, DAVQL_STRING, DAVQL_TIMESTAMP, DAVQL_IDENTIFIER,
+ DAVQL_UNARY, DAVQL_BINARY, DAVQL_LOGICAL, DAVQL_FUNCCALL
+} davqlexprtype_t;
+
+/**
+ * Enumeration of possible expression operators.
+ */
+typedef enum {
+ DAVQL_NOOP, DAVQL_CALL, DAVQL_ARGLIST, // internal representations
+ DAVQL_ADD, DAVQL_SUB, DAVQL_MUL, DAVQL_DIV,
+ DAVQL_AND, DAVQL_OR, DAVQL_XOR, DAVQL_NEG, // airthmetic
+ DAVQL_NOT, DAVQL_LAND, DAVQL_LOR, DAVQL_LXOR, // logical
+ DAVQL_EQ, DAVQL_NEQ, DAVQL_LT, DAVQL_GT, DAVQL_LE, DAVQL_GE,
+ DAVQL_LIKE, DAVQL_UNLIKE // comparisons
+} davqloperator_t;
+
+typedef struct DavQLToken DavQLToken;
+struct DavQLToken {
+ davqltokenclass_t tokenclass;
+ cxstring value;
+ DavQLToken *prev;
+ DavQLToken *next;
+};
+
+/**
+ * An expression within a DAVQL query.
+ */
+typedef struct _davqlexpr DavQLExpression;
+
+/**
+ * The structure for type DavQLExpression.
+ */
+struct _davqlexpr {
+ /**
+ * The original expression text.
+ * Contains the literal value, if type is LITERAL.
+ */
+ cxstring srctext;
+ /**
+ * The expression type.
+ */
+ davqlexprtype_t type;
+ /**
+ * Operator.
+ */
+ davqloperator_t op;
+ /**
+ * Left or single operand.
+ * <code>NULL</code> for literals or identifiers.
+ */
+ DavQLExpression *left;
+ /**
+ * Right operand.
+ * <code>NULL</code> for literals, identifiers or unary expressions.
+ */
+ DavQLExpression *right;
+};
+
+/**
+ * A tuple representing an order criterion.
+ */
+typedef struct {
+ /**
+ * The column.
+ */
+ DavQLExpression *column;
+ /**
+ * True, if the result shall be sorted descending, false otherwise.
+ * Default is false (ascending).
+ */
+ _Bool descending;
+} DavQLOrderCriterion;
+
+/**
+ * A tuple representing a field.
+ */
+typedef struct {
+ /**
+ * The field name.
+ * <ul>
+ * <li>SELECT: the identifier or an alias name</li>
+ * <li>SET: the identifier</li>
+ * </ul>
+ */
+ cxstring name;
+ /**
+ * The field expression.
+ * <ul>
+ * <li>SELECT: the queried property (identifier) or an expression</li>
+ * <li>SET: the expression for the value to be set</li>
+ * </ul>
+ */
+ DavQLExpression *expr;
+} DavQLField;
+
+/**
+ * Query statement object.
+ * Contains the binary information about the parsed query.
+ *
+ * The grammar for a DavQLStatement is:
+ *
+ * <pre>
+ * Keyword = "select" | "set" | "from" | "at" | "as"
+ * | "where" | "anywhere" | "like" | "unlike"
+ * | "and" | "or" | "not" | "xor" | "with" | "infinity"
+ * | "order" | "by" | "asc" | "desc";
+ *
+ * Expression = AddExpression;
+ * AddExpression = MultExpression, [AddOperator, AddExpression];
+ * MultExpression = BitwiseExpression, [MultOperator, MultExpression];
+ * BitwiseExpression = UnaryExpression, [BitwiseOperator, BitwiseExpression];
+ * UnaryExpression = [UnaryOperator], (ParExpression | AtomicExpression);
+ * AtomicExpression = FunctionCall | Identifier | Literal;
+ * ParExpression = "(", Expression, ")";
+ *
+ * BitwiseOperator = "&" | "|" | "^";
+ * MultOperator = "*" | "/";
+ * AddOperator = "+" | "-";
+ * UnaryOperator = "+" | "-" | "~";
+ *
+ * FunctionCall = Identifier, "(", [ArgumentList], ")";
+ * ArgumentList = Expression, {",", Expression};
+ * Identifier = IdentifierChar - ?Digit?, {IdentifierChar}
+ * | "`", ?Character? - "`", {?Character? - "`"}, "`";
+ * IdentifierChar = ?Character? - (" "|",");
+ * Literal = Number | String | Timestamp;
+ * Number = ?Digit?, {?Digit?} | "%d";
+ * String = "'", {?Character? - "'" | "'''"} , "'" | "%s";
+ * Timestamp = "%t"; // TODO: maybe introduce a real literal
+ *
+ * LogicalExpression = BooleanExpression, [LogicalOperator, LogicalExpression];
+ * BooleanExpression = "not ", BooleanExpression
+ * | "(", LogicalExpression, ")"
+ * | BooleanPrimary;
+ * BooleanPrimary = Expression, (" like " | " unlike "), String
+ * | Expression, Comparison, Expression
+ * | FunctionCall | Identifier;
+ *
+ * LogicalOperator = " and " | " or " | " xor ";
+ * Comparison = | "=" | "<" | ">" | "<=" | ">=" | "!=";
+ *
+ * FieldExpressions = "-"
+ * | "*", {",", NamedField}
+ * | FieldExpression, {",", FieldExpression};
+ * FieldExpression = NamedField | Identifier;
+ * NamedField = Expression, " as ", Identifier;
+ *
+ * Assignments = Assignment, {",", Assignment};
+ * Assignment = Identifier, "=", Expression;
+ *
+ * Path = String
+ * | "/", [PathNode, {"/", PathNode}], ["/"];
+ * PathNode = {{?Character? - "/"} - Keyword};
+ *
+ * WithClause = "depth", "=", (Number | "infinity");
+ *
+ * OrderByClause = OrderByCriterion, {",", OrderByCriterion};
+ * OrderByCriterion = (Identifier | Number), [" asc"|" desc"];
+ *
+ * </pre>
+ *
+ * Note: mandatory spaces are part of the grammar. But you may also insert an
+ * arbitrary amount of optional spaces between two symbols if they are not part
+ * of an literal, identifier or the path.
+ *
+ * <b>SELECT:</b>
+ * <pre>
+ * SelectStatement = "select ", FieldExpressions,
+ * " from ", Path,
+ * [" with ", WithClause],
+ * [(" where ", LogicalExpression) | " anywhere"],
+ * [" order by ", OrderByClause];
+ * </pre>
+ *
+ * <b>SET:</b>
+ * <pre>
+ * SetStatement = "set ",Assignments,
+ * " at ", Path,
+ * [" with ", WithClause],
+ * (" where ", LogicalExpression) | " anywhere";
+ * </pre>
+ *
+ */
+typedef struct {
+ /**
+ * The original query text.
+ */
+ cxstring srctext;
+ /**
+ * The statement type.
+ */
+ davqltype_t type;
+ /**
+ * Error code, if any error occurred. Zero otherwise.
+ */
+ int errorcode;
+ /**
+ * Error message, if any error occurred.
+ */
+ char* errormessage;
+ /**
+ * The list of DavQLFields.
+ */
+ CxList* fields;
+ /**
+ * A string that denotes the queried path.
+ */
+ cxstring path;
+ /**
+ * Logical expression for selection.
+ * <code>NULL</code>, if there is no where clause.
+ */
+ DavQLExpression* where;
+ /**
+ * The list of DavQLOrderCriterions.
+ * This is <code>NULL</code> for SET queries and may be <code>NULL</code>
+ * if the result doesn't need to be sorted.
+ */
+ CxList* orderby;
+ /**
+ * The recursion depth for the statement.
+ * Defaults to 1.
+ * Magic numbers are DAV_DEPTH_INFINITY for infinity and
+ * DAV_DEPTH_PLACEHOLDER for a placeholder.
+ */
+ int depth;
+ /**
+ * A list of all required arguments
+ */
+ CxList* args;
+} DavQLStatement;
+
+/** Infinity recursion depth for a DavQLStatement. */
+#define DAV_DEPTH_INFINITY -1
+
+/** Depth needs to be specified at runtime. */
+#define DAV_DEPTH_PLACEHOLDER -2
+
+/** Unexpected token. */
+#define DAVQL_ERROR_UNEXPECTED_TOKEN 1
+
+/** A token has been found, for which no token class is applicable. */
+#define DAVQL_ERROR_INVALID_TOKEN 2
+
+/** A token that has been expected was not found. */
+#define DAVQL_ERROR_MISSING_TOKEN 11
+
+/** An expression has been expected, but was not found. */
+#define DAVQL_ERROR_MISSING_EXPR 12
+
+/** A closed parenthesis ')' is missing. */
+#define DAVQL_ERROR_MISSING_PAR 13
+
+/** An assignment operator '=' is missing. */
+#define DAVQL_ERROR_MISSING_ASSIGN 14
+
+/** The type of the expression could not be determined. */
+#define DAVQL_ERROR_INVALID_EXPR 21
+
+/** An operator has been found for an unary expression, but it is invalid. */
+#define DAVQL_ERROR_INVALID_UNARY_OP 22
+
+/** An operator has been found for a logical expression, but it is invalid. */
+#define DAVQL_ERROR_INVALID_LOGICAL_OP 23
+
+/** Invalid format specifier. */
+#define DAVQL_ERROR_INVALID_FMTSPEC 24
+
+/** A string has been expected. */
+#define DAVQL_ERROR_INVALID_STRING 25
+
+/** The order criterion is invalid (must be an identifier or field index). */
+#define DAVQL_ERROR_INVALID_ORDER_CRITERION 26
+
+/** The depth is invalid. */
+#define DAVQL_ERROR_INVALID_DEPTH 101
+
+/** Nothing about the statement seems legit. */
+#define DAVQL_ERROR_INVALID -1
+
+/** A call to malloc or calloc failed. */
+#define DAVQL_ERROR_OUT_OF_MEMORY -2
+
+/**
+ * Starts an interactive debugger for a DavQLStatement.
+ *
+ * @param stmt the statement to debug
+ */
+void dav_debug_statement(DavQLStatement *stmt);
+
+/**
+ * Parses a statement.
+ * @param stmt the sstr_t containing the statement
+ * @return a DavQLStatement object
+ */
+DavQLStatement* dav_parse_statement(cxstring stmt);
+
+/**
+ * Implicitly converts a cstr to a sstr_t and calls dav_parse_statement.
+ */
+#define dav_parse_cstr_statement(stmt) dav_parse_statement(cx_str(stmt))
+
+/**
+ * Frees a DavQLStatement.
+ * @param stmt the statement object to free
+ */
+void dav_free_statement(DavQLStatement *stmt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAVQLPARSER_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "utils.h"
+#include "methods.h"
+#include "crypto.h"
+#include "session.h"
+#include "xml.h"
+
+#include <cx/utils.h>
+#include <cx/printf.h>
+#include <cx/hash_map.h>
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+
+
+int dav_buffer_seek(CxBuffer *b, curl_off_t offset, int origin) {
+ return cxBufferSeek(b, offset, origin) == 0 ? 0:CURL_SEEKFUNC_CANTSEEK;
+}
+
+/* ----------------------------- PROPFIND ----------------------------- */
+
+CURLcode do_propfind_request(
+ DavSession *sn,
+ CxBuffer *request,
+ CxBuffer *response)
+{
+ CURL *handle = sn->handle;
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PROPFIND");
+
+ // always try to get information about possible children
+ int depth = 1;
+
+ int maxretry = 2;
+
+ struct curl_slist *headers = NULL;
+ CURLcode ret = 0;
+
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead);
+ curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
+ curl_easy_setopt(handle, CURLOPT_READDATA, request);
+ curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
+ curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+ CxMap *respheaders = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
+ cxDefineDestructor(respheaders, free);
+ util_capture_header(handle, respheaders);
+
+ for(int i=0;i<maxretry;i++) {
+ if (depth == 1) {
+ headers = curl_slist_append(headers, "Depth: 1");
+ } else if (depth == -1) {
+ headers = curl_slist_append(headers, "Depth: infinity");
+ } else {
+ headers = curl_slist_append(headers, "Depth: 0");
+ }
+ headers = curl_slist_append(headers, "Content-Type: text/xml");
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+
+ // reset buffers and perform request
+ request->pos = 0;
+ response->size = response->pos = 0;
+ ret = dav_session_curl_perform_buf(sn, request, response, NULL);
+ curl_slist_free_all(headers);
+ headers = NULL;
+
+ /*
+ * Handle two cases:
+ * 1. We communicate with IIS and get a X-MSDAVEXT_Error: 589831
+ * => try with depth 0 next time, it's not a collection
+ * 2. Other cases
+ * => the server handled our request and we can stop requesting
+ */
+ char *msdavexterror;
+ msdavexterror = cxMapGet(respheaders, cx_hash_key_str("x-msdavext_error"));
+ int iishack = depth == 1 &&
+ msdavexterror && !strncmp(msdavexterror, "589831;", 7);
+
+ if(iishack) {
+ depth = 0;
+ } else {
+ break;
+ }
+ }
+
+ // deactivate header capturing and free captured map
+ util_capture_header(handle, NULL);
+ cxMapDestroy(respheaders);
+
+ return ret;
+}
+
+CxBuffer* create_allprop_propfind_request(void) {
+ CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ cxstring s;
+
+ s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ s = CX_STR("<D:propfind xmlns:D=\"DAV:\">\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ s = CX_STR("<D:allprop/></D:propfind>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ return buf;
+}
+
+CxBuffer* create_cryptoprop_propfind_request(void) {
+ CxBuffer *buf = cxBufferCreate(NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ cxstring s;
+
+ s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ s = CX_STR("<D:propfind xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ s = CX_STR("<D:prop><idav:crypto-prop/></D:prop></D:propfind>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ return buf;
+}
+
+CxBuffer* create_propfind_request(DavSession *sn, CxList *properties, char *rootelm, DavBool nocrypt) {
+ CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ cxstring s;
+
+ int add_crypto_name = 1;
+ int add_crypto_key = 1;
+ int add_crypto_hash = 1;
+ char *crypto_ns = "idav";
+ CxMap *namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8);
+ if(properties) {
+ CxIterator i = cxListIterator(properties);
+ cx_foreach(DavProperty*, p, i) {
+ if(strcmp(p->ns->name, "DAV:")) {
+ cxMapPut(namespaces, cx_hash_key_str(p->ns->prefix), p->ns);
+ }
+
+ // if the properties list contains the idav properties crypto-name
+ // and crypto-key, mark them as existent
+ if(!strcmp(p->ns->name, DAV_NS)) {
+ if(!strcmp(p->name, "crypto-name")) {
+ add_crypto_name = 0;
+ crypto_ns = p->ns->prefix;
+ } else if(!strcmp(p->name, "crypto-key")) {
+ add_crypto_key = 0;
+ crypto_ns = p->ns->prefix;
+ } else if(!strcmp(p->name, "crypto-hash")) {
+ add_crypto_hash = 0;
+ crypto_ns = p->ns->prefix;
+ }
+ }
+ }
+ }
+
+ DavNamespace idav_ns;
+ if(add_crypto_name && add_crypto_key && DAV_CRYPTO(sn) && !nocrypt) {
+ idav_ns.prefix = "idav";
+ idav_ns.name = DAV_NS;
+ cxMapPut(namespaces, cx_hash_key_str("idav"), &idav_ns);
+ }
+
+ s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ // write root element and namespaces
+ cx_bprintf(buf, "<D:%s xmlns:D=\"DAV:\"", rootelm);
+
+ CxIterator mapi = cxMapIteratorValues(namespaces);
+ cx_foreach(DavNamespace*, ns, mapi) {
+ s = CX_STR(" xmlns:");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = cx_str(ns->prefix);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR("=\"");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = cx_str(ns->name);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR("\"");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ }
+ s = CX_STR(">\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ // default properties
+ s = CX_STR("<D:prop>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ s = CX_STR("<D:creationdate />\n<D:getlastmodified />\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ s = CX_STR("<D:getcontentlength />\n<D:getcontenttype />\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ s = CX_STR("<D:resourcetype />\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ // crypto properties
+ if(DAV_CRYPTO(sn) && !nocrypt) {
+ if(add_crypto_name) {
+ cxBufferPut(buf, '<');
+ cxBufferPutString(buf, crypto_ns);
+ s = CX_STR(":crypto-name />\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ }
+ if(add_crypto_key) {
+ cxBufferPut(buf, '<');
+ cxBufferPutString(buf, crypto_ns);
+ s = CX_STR(":crypto-key />\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ }
+ if(add_crypto_hash) {
+ cxBufferPut(buf, '<');
+ cxBufferPutString(buf, crypto_ns);
+ s = CX_STR(":crypto-hash />\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ }
+ }
+
+ // extra properties
+ if(properties) {
+ CxIterator i = cxListIterator(properties);
+ cx_foreach(DavProperty*, prop, i) {
+ s = CX_STR("<");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = cx_str(prop->ns->prefix);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR(":");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = cx_str(prop->name);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR(" />\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ }
+ }
+
+ // end
+ cx_bprintf(buf, "</D:prop>\n</D:%s>\n", rootelm);
+
+ cxMapDestroy(namespaces);
+ return buf;
+}
+
+CxBuffer* create_basic_propfind_request(void) {
+ CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ cxstring s;
+
+ s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ s = CX_STR("<D:propfind xmlns:D=\"DAV:\" xmlns:i=\"");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR(DAV_NS);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR("\" >\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ // properties
+ s = CX_STR("<D:prop>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR("<D:resourcetype />\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR("<i:crypto-key />\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR("<i:crypto-name />\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR("<i:crypto-hash />\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR("</D:prop>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ // end
+ s = CX_STR("</D:propfind>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ return buf;
+}
+
+PropfindParser* create_propfind_parser(CxBuffer *response, char *url) {
+ PropfindParser *parser = malloc(sizeof(PropfindParser));
+ if(!parser) {
+ return NULL;
+ }
+ parser->document = xmlReadMemory(response->space, response->pos, url, NULL, 0);
+ parser->current = NULL;
+ if(parser->document) {
+ xmlNode *xml_root = xmlDocGetRootElement(parser->document);
+ if(xml_root) {
+ xmlNode *node = xml_root->children;
+ while(node) {
+ // find first response tag
+ if(node->type == XML_ELEMENT_NODE) {
+ if(xstreq(node->name, "response")) {
+ parser->current = node;
+ break;
+ }
+ }
+ node = node->next;
+ }
+ return parser;
+ } else {
+ xmlFreeDoc(parser->document);
+ }
+ }
+ free(parser);
+ return NULL;
+}
+
+void destroy_propfind_parser(PropfindParser *parser) {
+ if(parser->document) {
+ xmlFreeDoc(parser->document);
+ }
+ free(parser);
+}
+
+int get_propfind_response(PropfindParser *parser, ResponseTag *result) {
+ if(parser->current == NULL) {
+ return 0;
+ }
+
+ char *href = NULL;
+ int iscollection = 0;
+ char *crypto_name = NULL; // name set by crypto-name property
+ char *crypto_key = NULL;
+
+ result->properties = cxLinkedListCreateSimple(CX_STORE_POINTERS); // xmlNode list
+
+ xmlNode *node = parser->current->children;
+ while(node) {
+ if(node->type == XML_ELEMENT_NODE) {
+ if(xstreq(node->name, "href")) {
+ xmlNode *href_node = node->children;
+ if(href_node->type != XML_TEXT_NODE) {
+ // error
+ return -1;
+ }
+ href = (char*)href_node->content;
+ } else if(xstreq(node->name, "propstat")) {
+ xmlNode *n = node->children;
+ xmlNode *prop_node = NULL;
+ int ok = 0;
+ // get the status code
+ while(n) {
+ if(n->type == XML_ELEMENT_NODE) {
+ if(xstreq(n->name, "prop")) {
+ prop_node = n;
+ } else if(xstreq(n->name, "status")) {
+ xmlNode *status_node = n->children;
+ if(status_node->type != XML_TEXT_NODE) {
+ // error
+ return -1;
+ }
+ cxstring status_str = cx_str((char*)status_node->content);
+ if(status_str.length < 13) {
+ // error
+ return -1;
+ }
+ status_str = cx_strsubsl(status_str, 9, 3);
+ if(!cx_strcmp(status_str, CX_STR("200"))) {
+ ok = 1;
+ }
+ }
+ }
+ n = n->next;
+ }
+ // if status is ok, get all properties
+ if(ok) {
+ n = prop_node->children;
+ while(n) {
+ if(n->type == XML_ELEMENT_NODE) {
+ cxListAdd(result->properties, n);
+ if(xstreq(n->name, "resourcetype")) {
+ if(parse_resource_type(n)) {
+ iscollection = TRUE;
+ }
+ } else if(xstreq(n->ns->href, DAV_NS)) {
+ if(xstreq(n->name, "crypto-name")) {
+ crypto_name = util_xml_get_text(n);
+ } else if(xstreq(n->name, "crypto-key")) {
+ crypto_key = util_xml_get_text(n);
+ }
+ }
+ }
+ n = n->next;
+ }
+ }
+ }
+ }
+ node = node->next;
+ }
+
+ result->href = util_url_path(href);
+ result->iscollection = iscollection;
+ result->crypto_name = crypto_name;
+ result->crypto_key = crypto_key;
+
+ // find next response tag
+ xmlNode *next = parser->current->next;
+ while(next) {
+ if(next->type == XML_ELEMENT_NODE) {
+ if(xstreq(next->name, "response")) {
+ break;
+ }
+ }
+ next = next->next;
+ }
+ parser->current = next;
+
+ return 1;
+}
+
+void cleanup_response(ResponseTag *result) {
+ if(result) {
+ cxListDestroy(result->properties);
+ }
+}
+
+int hrefeq(DavSession *sn, const char *href1, const char *href2) {
+ cxmutstr href_s = cx_mutstr(util_url_decode(sn, href1));
+ cxmutstr href_r = cx_mutstr(util_url_decode(sn, href2));
+ int ret = 0;
+ if(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) {
+ ret = 1;
+ } else if(href_s.length == href_r.length + 1) {
+ if(href_s.ptr[href_s.length-1] == '/') {
+ href_s.length--;
+ if(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) {
+ ret = 1;
+ }
+ }
+ } else if(href_r.length == href_s.length + 1) {
+ if(href_r.ptr[href_r.length-1] == '/') {
+ href_r.length--;
+ if(!cx_strcmp(cx_strcast(href_s), cx_strcast(href_r))) {
+ ret = 1;
+ }
+ }
+ }
+
+ free(href_s.ptr);
+ free(href_r.ptr);
+
+ return ret;
+}
+
+
+DavResource* parse_propfind_response(DavSession *sn, DavResource *root, CxBuffer *response) {
+ char *url = NULL;
+ curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &url);
+ if(!root) {
+ printf("methods.c: TODO: remove\n");
+ root = dav_resource_new_href(sn, util_url_path(url)); // TODO: remove
+ }
+
+ //printf("%.*s\n\n", response->size, response->space);
+ xmlDoc *doc = xmlReadMemory(response->space, response->size, url, NULL, 0);
+ if(!doc) {
+ // TODO: free stuff
+ sn->error = DAV_ERROR;
+ return NULL;
+ }
+
+ xmlNode *xml_root = xmlDocGetRootElement(doc);
+ xmlNode *node = xml_root->children;
+ while(node) {
+ if(node->type == XML_ELEMENT_NODE) {
+ if(xstreq(node->name, "response")) {
+ parse_response_tag(root, node);
+ }
+ }
+ node = node->next;
+ }
+ xmlFreeDoc(doc);
+
+ return root;
+}
+
+DavResource* response2resource(DavSession *sn, ResponseTag *response, char *parent_path) {
+ // create resource
+ char *name = NULL;
+ DavKey *key = NULL;
+ if(DAV_DECRYPT_NAME(sn) && response->crypto_name && (key = dav_context_get_key(sn->context, response->crypto_key))) {
+ if(!response->crypto_key) {
+ sn->error = DAV_ERROR;
+ dav_session_set_errstr(sn, "Missing crypto-key property");
+ return NULL;
+ }
+ name = util_decrypt_str_k(sn, response->crypto_name, key);
+ if(!name) {
+ sn->error = DAV_ERROR;
+ dav_session_set_errstr(sn, "Cannot decrypt resource name");
+ return NULL;
+ }
+ } else {
+ cxstring resname = cx_str(util_resource_name(response->href));
+ int nlen = 0;
+ char *uname = curl_easy_unescape(
+ sn->handle,
+ resname.ptr,
+ resname.length,
+ &nlen);
+ name = dav_session_strdup(sn, uname);
+ curl_free(uname);
+ }
+
+ char *href = dav_session_strdup(sn, response->href);
+ DavResource *res = NULL;
+ if(parent_path) {
+ res = dav_resource_new_full(sn, parent_path, name, href);
+ } else {
+ res = dav_resource_new_href(sn, href);
+ }
+ dav_session_free(sn, name);
+
+ add_properties(res, response);
+ return res;
+}
+
+void add_properties(DavResource *res, ResponseTag *response) {
+ res->iscollection = response->iscollection;
+
+ int decrypt_props = DAV_ENCRYPT_PROPERTIES(res->session);
+ xmlNode *crypto_prop = NULL;
+ char *crypto_key = NULL;
+
+ // add properties
+ if(response->properties) {
+ CxIterator i = cxListIterator(response->properties);
+ cx_foreach(xmlNode*, prop, i) {
+ resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, prop->children);
+
+ if (decrypt_props &&
+ prop->children &&
+ prop->children->type == XML_TEXT_NODE &&
+ xstreq(prop->ns->href, DAV_NS))
+ {
+ if(xstreq(prop->name, "crypto-prop")) {
+ crypto_prop = prop;
+ } else if(xstreq(prop->name, "crypto-key")) {
+ crypto_key = util_xml_get_text(prop);
+ }
+ }
+ }
+ }
+
+ if(crypto_prop && crypto_key) {
+ char *crypto_prop_content = util_xml_get_text(crypto_prop);
+ DavKey *key = dav_context_get_key(res->session->context, crypto_key);
+ if(crypto_prop_content) {
+ CxMap *cprops = parse_crypto_prop_str(res->session, key, crypto_prop_content);
+ resource_set_crypto_properties(res, cprops);
+ }
+ }
+
+ set_davprops(res);
+}
+
+int parse_response_tag(DavResource *resource, xmlNode *node) {
+ DavSession *sn = resource->session;
+
+ //DavResource *res = resource;
+ DavResource *res = NULL;
+ const char *href = NULL;
+ CxList *properties = cxLinkedListCreateSimple(CX_STORE_POINTERS); // xmlNode list
+ char *crypto_name = NULL; // name set by crypto-name property
+ char *crypto_key = NULL;
+
+ int iscollection = 0; // TODO: remove
+
+ node = node->children;
+ while(node) {
+ if(node->type == XML_ELEMENT_NODE) {
+ if(xstreq(node->name, "href")) {
+ xmlNode *href_node = node->children;
+ if(href_node->type != XML_TEXT_NODE) {
+ // error
+ sn->error = DAV_ERROR;
+ return 1;
+ }
+ //char *href = (char*)href_node->content;
+ href = util_url_path((const char*)href_node->content);
+
+ char *href_s = util_url_decode(resource->session, href);
+ char *href_r = util_url_decode(resource->session, resource->href);
+
+ if(hrefeq(sn, href_s, href_r)) {
+ res = resource;
+ }
+
+ free(href_s);
+ free(href_r);
+ } else if(xstreq(node->name, "propstat")) {
+ xmlNode *n = node->children;
+ xmlNode *prop_node = NULL;
+ int ok = 0;
+ // get the status code
+ while(n) {
+ if(n->type == XML_ELEMENT_NODE) {
+ if(xstreq(n->name, "prop")) {
+ prop_node = n;
+ } else if(xstreq(n->name, "status")) {
+ xmlNode *status_node = n->children;
+ if(status_node->type != XML_TEXT_NODE) {
+ sn->error = DAV_ERROR;
+ return 1;
+ }
+ cxstring status_str = cx_str((char*)status_node->content);
+ if(status_str.length < 13) {
+ sn->error = DAV_ERROR;
+ return 1;
+ }
+ status_str = cx_strsubsl(status_str, 9, 3);
+ if(!cx_strcmp(status_str, CX_STR("200"))) {
+ ok = 1;
+ }
+ }
+ }
+ n = n->next;
+ }
+ // if status is ok, get all properties
+ if(ok) {
+ n = prop_node->children;
+ while(n) {
+ if(n->type == XML_ELEMENT_NODE) {
+ cxListAdd(properties, n);
+ if(xstreq(n->name, "resourcetype")) {
+ if(parse_resource_type(n)) {
+ iscollection = TRUE;
+ }
+ } else if(n->ns && xstreq(n->ns->href, DAV_NS)) {
+ if(xstreq(n->name, "crypto-name")) {
+ crypto_name = util_xml_get_text(n);
+ } else if(xstreq(n->name, "crypto-key")) {
+ crypto_key = util_xml_get_text(n);
+ }
+ }
+ }
+ n = n->next;
+ }
+ }
+ }
+ }
+
+ node = node->next;
+ }
+
+ if(!res) {
+ // create new resource object
+ char *name = NULL;
+ if(DAV_DECRYPT_NAME(sn) && crypto_name) {
+ if(!crypto_key) {
+ sn->error = DAV_ERROR;
+ dav_session_set_errstr(sn, "Missing crypto-key property");
+ return -1;
+ }
+ name = util_decrypt_str(sn, crypto_name, crypto_key);
+ if(!name) {
+ sn->error = DAV_ERROR;
+ dav_session_set_errstr(sn, "Cannot decrypt resource name");
+ return -1;
+ }
+ } else {
+ cxstring resname = cx_str(util_resource_name(href));
+ int nlen = 0;
+ char *uname = curl_easy_unescape(
+ sn->handle,
+ resname.ptr,
+ resname.length,
+ &nlen);
+ name = dav_session_strdup(sn, uname);
+ curl_free(uname);
+ }
+
+ char *href_cp = dav_session_strdup(sn, href);
+ res = dav_resource_new_full(sn, resource->path, name, href_cp);
+
+ dav_session_free(sn, name);
+ }
+ res->iscollection = iscollection;
+
+ // add properties
+ int decrypt_props = DAV_ENCRYPT_PROPERTIES(res->session);
+ xmlNode *crypto_prop = NULL;
+
+ CxIterator i = cxListIterator(properties);
+ cx_foreach(xmlNode*, prop, i) {
+ if(!prop->ns) {
+ continue;
+ }
+ resource_add_property(res, (char*)prop->ns->href, (char*)prop->name, prop->children);
+
+ if (decrypt_props &&
+ prop->children &&
+ prop->children->type == XML_TEXT_NODE &&
+ xstreq(prop->ns->href, DAV_NS))
+ {
+ if(xstreq(prop->name, "crypto-prop")) {
+ crypto_prop = prop;
+ }
+ }
+ }
+ cxListDestroy(properties);
+
+ if(crypto_prop && crypto_key) {
+ char *crypto_prop_content = util_xml_get_text(crypto_prop);
+ DavKey *key = dav_context_get_key(res->session->context, crypto_key);
+ if(crypto_prop_content && key) {
+ CxMap *cprops = parse_crypto_prop_str(res->session, key, crypto_prop_content);
+ resource_set_crypto_properties(res, cprops);
+ }
+ }
+
+
+ set_davprops(res);
+ if(res != resource) {
+ resource_add_child(resource, res);
+ }
+
+ return 0;
+}
+
+void set_davprops(DavResource *res) {
+ char *cl = dav_get_string_property_ns(res, "DAV:", "getcontentlength");
+ char *ct = dav_get_string_property_ns(res, "DAV:", "getcontenttype");
+ char *cd = dav_get_string_property_ns(res, "DAV:", "creationdate");
+ char *lm = dav_get_string_property_ns(res, "DAV:", "getlastmodified");
+
+ res->contenttype = ct;
+ if(cl) {
+ char *end = NULL;
+ res->contentlength = strtoull(cl, &end, 0);
+ }
+ res->creationdate = util_parse_creationdate(cd);
+ res->lastmodified = util_parse_lastmodified(lm);
+}
+
+int parse_resource_type(xmlNode *node) {
+ int collection = FALSE;
+ xmlNode *c = node->children;
+ while(c) {
+ if(c->type == XML_ELEMENT_NODE) {
+ if(xstreq(c->ns->href, "DAV:") && xstreq(c->name, "collection")) {
+ collection = TRUE;
+ break;
+ }
+ }
+ c = c->next;
+ }
+ return collection;
+}
+
+
+/* ----------------------------- PROPPATCH ----------------------------- */
+
+CURLcode do_proppatch_request(
+ DavSession *sn,
+ char *lock,
+ CxBuffer *request,
+ CxBuffer *response)
+{
+ CURL *handle = sn->handle;
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PROPPATCH");
+
+ struct curl_slist *headers = NULL;
+ headers = curl_slist_append(headers, "Content-Type: text/xml");
+ if(lock) {
+ char *url = NULL;
+ curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+ char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+ headers = curl_slist_append(headers, ltheader);
+ free(ltheader);
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+ }
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead);
+ curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
+ curl_easy_setopt(handle, CURLOPT_READDATA, request);
+ curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
+ curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+
+ cxBufferSeek(request, 0, SEEK_SET);
+ CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
+ curl_slist_free_all(headers);
+
+ //printf("proppatch: \n%.*s\n", request->size, request->space);
+
+ return ret;
+}
+
+CxBuffer* create_proppatch_request(DavResourceData *data) {
+ CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ cxstring s;
+
+ CxMap *namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8);
+ cxDefineDestructor(namespaces, free);
+
+ {
+ char prefix[8];
+ int pfxnum = 0;
+ if (data->set && cxListSize(data->set) > 0) {
+ CxIterator i = cxListIterator(data->set);
+ cx_foreach(DavProperty*, p, i) {
+ if (strcmp(p->ns->name, "DAV:")) {
+ snprintf(prefix, 8, "x%d", pfxnum++);
+ cxMapPut(namespaces, cx_hash_key_str(p->ns->name), strdup(prefix));
+ }
+ }
+ }
+ if (data->remove && cxListSize(data->remove) > 0) {
+ CxIterator i = cxListIterator(data->remove);
+ cx_foreach(DavProperty*, p, i) {
+ if (strcmp(p->ns->name, "DAV:")) {
+ snprintf(prefix, 8, "x%d", pfxnum++);
+ cxMapPut(namespaces, cx_hash_key_str(p->ns->name), strdup(prefix));
+ }
+ }
+ }
+ }
+
+ s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ // write root element and namespaces
+ s = CX_STR("<D:propertyupdate xmlns:D=\"DAV:\"");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ CxIterator mapi = cxMapIterator(namespaces);
+ cx_foreach(CxMapEntry*, entry, mapi) {
+ s = CX_STR(" xmlns:");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = cx_str(entry->value);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR("=\"");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = cx_strn(entry->key->data, entry->key->len);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR("\"");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ }
+ s = CX_STR(">\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ if(data->set) {
+ s = CX_STR("<D:set>\n<D:prop>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ CxIterator i = cxListIterator(data->set);
+ cx_foreach(DavProperty*, property, i) {
+ char *prefix = cxMapGet(namespaces, cx_hash_key_str(property->ns->name));
+ if(!prefix) {
+ prefix = "D";
+ }
+
+ // begin tag
+ s = CX_STR("<");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = cx_str(prefix);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR(":");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = cx_str(property->name);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR(">");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ // content
+ DavXmlNode *content = property->value;
+ if(content->type == DAV_XML_TEXT && !content->next) {
+ cxBufferWrite(content->content, 1, content->contentlength, buf);
+ } else {
+ dav_print_node(buf, (cx_write_func)cxBufferWrite, namespaces, content);
+ }
+
+ // end tag
+ s = CX_STR("</");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = cx_str(prefix);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR(":");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = cx_str(property->name);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR(">\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ }
+ s = CX_STR("</D:prop>\n</D:set>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ }
+ if(data->remove) {
+ s = CX_STR("<D:remove>\n<D:prop>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ CxIterator i = cxListIterator(data->remove);
+ cx_foreach(DavProperty*, property, i) {
+ char *prefix = cxMapGet(namespaces, cx_hash_key_str(property->ns->name));
+
+ s = CX_STR("<");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = cx_str(prefix);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR(":");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = cx_str(property->name);
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ s = CX_STR(" />\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ }
+ s = CX_STR("</D:prop>\n</D:remove>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ }
+
+ s = CX_STR("</D:propertyupdate>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ // cleanup namespace map
+ cxMapDestroy(namespaces);
+
+ return buf;
+}
+
+CxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash) {
+ CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ cxstring s;
+
+ s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ s = CX_STR("<D:propertyupdate xmlns:D=\"DAV:\" xmlns:idav=\"" DAV_NS "\">\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ s = CX_STR("<D:set>\n<D:prop>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ if(DAV_ENCRYPT_NAME(sn)) {
+ s = CX_STR("<idav:crypto-name>");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ char *crname = aes_encrypt(name, strlen(name), key);
+ cxBufferPutString(buf, crname);
+ free(crname);
+ s = CX_STR("</idav:crypto-name>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ }
+
+ s = CX_STR("<idav:crypto-key>");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ cxBufferPutString(buf, key->name);
+ s = CX_STR("</idav:crypto-key>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ if(hash) {
+ s = CX_STR("<idav:crypto-hash>");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ cxBufferPutString(buf, hash);
+ s = CX_STR("</idav:crypto-hash>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+ }
+
+ s = CX_STR("</D:prop>\n</D:set>\n</D:propertyupdate>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ return buf;
+}
+
+/* ----------------------------- PUT ----------------------------- */
+
+static size_t dummy_write(void *buf, size_t s, size_t n, void *data) {
+ //fwrite(buf, s, n, stdout);
+ return s*n;
+}
+
+CURLcode do_put_request(DavSession *sn, char *lock, DavBool create, void *data, dav_read_func read_func, dav_seek_func seek_func, size_t length) {
+ CURL *handle = sn->handle;
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL);
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+
+ // clear headers
+ struct curl_slist *headers = NULL;
+ if(lock) {
+ char *url = NULL;
+ curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+ char *ltheader = NULL;
+ if(create) {
+ url = util_parent_path(url);
+ ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+ free(url);
+ } else {
+ ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+ }
+ headers = curl_slist_append(headers, ltheader);
+ free(ltheader);
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+ }
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+
+ CxBuffer *buf = NULL;
+ if(!read_func) {
+ buf = cxBufferCreate(data, length, cxDefaultAllocator, 0);
+ buf->size = length;
+ data = buf;
+ read_func = (dav_read_func)cxBufferRead;
+ curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length);
+ } else if(length == 0) {
+ headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
+ curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)-1);
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+ } else {
+ curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length);
+ }
+
+ curl_easy_setopt(handle, CURLOPT_READFUNCTION, read_func);
+ curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, seek_func);
+ curl_easy_setopt(handle, CURLOPT_SEEKDATA, data);
+ curl_easy_setopt(handle, CURLOPT_READDATA, data);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+
+ CURLcode ret = dav_session_curl_perform(sn, NULL);
+ curl_slist_free_all(headers);
+ if(buf) {
+ cxBufferFree(buf);
+ }
+
+ return ret;
+}
+
+CURLcode do_delete_request(DavSession *sn, char *lock, CxBuffer *response) {
+ CURL *handle = sn->handle;
+ struct curl_slist *headers = NULL;
+ if(lock) {
+ char *url = NULL;
+ curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+ char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+ headers = curl_slist_append(headers, ltheader);
+ free(ltheader);
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+ } else {
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
+ }
+
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "DELETE");
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+
+ CURLcode ret = dav_session_curl_perform(sn, NULL);
+ curl_slist_free_all(headers);
+ return ret;
+}
+
+CURLcode do_mkcol_request(DavSession *sn, char *lock) {
+ CURL *handle = sn->handle;
+ struct curl_slist *headers = NULL;
+ if(lock) {
+ char *url = NULL;
+ curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+ url = util_parent_path(url);
+ char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+ free(url);
+ headers = curl_slist_append(headers, ltheader);
+ free(ltheader);
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+ } else {
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
+ }
+
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "MKCOL");
+ curl_easy_setopt(handle, CURLOPT_PUT, 0L);
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+
+ CURLcode ret = dav_session_curl_perform(sn, NULL);
+ curl_slist_free_all(headers);
+ return ret;
+}
+
+
+CURLcode do_head_request(DavSession *sn) {
+ CURL *handle = sn->handle;
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "HEAD");
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+ curl_easy_setopt(handle, CURLOPT_NOBODY, 1L);
+
+ // clear headers
+ struct curl_slist *headers = NULL;
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+
+ CURLcode ret = dav_session_curl_perform(sn, NULL);
+ curl_easy_setopt(handle, CURLOPT_NOBODY, 0L);
+ return ret;
+}
+
+
+CURLcode do_copy_move_request(DavSession *sn, char *dest, char *lock, DavBool copy, DavBool override) {
+ CURL *handle = sn->handle;
+ if(copy) {
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "COPY");
+ } else {
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "MOVE");
+ }
+ curl_easy_setopt(handle, CURLOPT_PUT, 0L);
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+
+ struct curl_slist *headers = NULL;
+ if(lock) {
+ char *url = NULL;
+ curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &url);
+ char *ltheader = cx_asprintf("If: <%s> (<%s>)", url, lock).ptr;
+ headers = curl_slist_append(headers, ltheader);
+ free(ltheader);
+ }
+ //cxstring deststr = ucx_sprintf("Destination: %s", dest);
+ cxmutstr deststr = cx_strcat(2, CX_STR("Destination: "), cx_str(dest));
+ headers = curl_slist_append(headers, deststr.ptr);
+ if(override) {
+ headers = curl_slist_append(headers, "Overwrite: T");
+ } else {
+ headers = curl_slist_append(headers, "Overwrite: F");
+ }
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+
+ CURLcode ret = dav_session_curl_perform(sn, NULL);
+ free(deststr.ptr);
+ curl_slist_free_all(headers);
+ headers = NULL;
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+ return ret;
+}
+
+
+CxBuffer* create_lock_request(void) {
+ CxBuffer *buf = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ cxstring s;
+
+ s = CX_STR("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ s = CX_STR("<D:lockinfo xmlns:D=\"DAV:\">\n"
+ "<D:lockscope><D:exclusive/></D:lockscope>\n"
+ "<D:locktype><D:write/></D:locktype>\n"
+ "<D:owner><D:href>http://davutils.org/libidav/</D:href></D:owner>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ s = CX_STR("</D:lockinfo>\n");
+ cxBufferWrite(s.ptr, 1, s.length, buf);
+
+ return buf;
+}
+
+int parse_lock_response(DavSession *sn, CxBuffer *response, LockDiscovery *lock) {
+ lock->locktoken = NULL;
+ lock->timeout = NULL;
+
+ xmlDoc *doc = xmlReadMemory(response->space, response->size, NULL, NULL, 0);
+ if(!doc) {
+ sn->error = DAV_ERROR;
+ return -1;
+ }
+
+ char *timeout = NULL;
+ char *locktoken = NULL;
+
+ int ret = -1;
+ xmlNode *xml_root = xmlDocGetRootElement(doc);
+ DavBool lockdiscovery = 0;
+ if(xml_root) {
+ xmlNode *node = xml_root->children;
+ while(node) {
+ if(node->type == XML_ELEMENT_NODE) {
+ if(xstreq(node->name, "lockdiscovery")) {
+ node = node->children;
+ lockdiscovery = 1;
+ continue;
+ }
+
+ if(xstreq(node->name, "activelock")) {
+ node = node->children;
+ continue;
+ }
+
+ if(lockdiscovery) {
+ if(xstreq(node->name, "timeout")) {
+ timeout = util_xml_get_text(node);
+ } else if(xstreq(node->name, "locktoken")) {
+ xmlNode *n = node->children;
+ while(n) {
+ if(xstreq(n->name, "href")) {
+ locktoken = util_xml_get_text(n);
+ break;
+ }
+ n = n->next;
+ }
+ }
+ }
+ }
+ node = node->next;
+ }
+ }
+
+ if(timeout && locktoken) {
+ lock->timeout = strdup(timeout);
+ lock->locktoken = strdup(locktoken);
+ ret = 0;
+ }
+
+ xmlFreeDoc(doc);
+ return ret;
+}
+
+CURLcode do_lock_request(DavSession *sn, CxBuffer *request, CxBuffer *response, time_t timeout) {
+ CURL *handle = sn->handle;
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "LOCK");
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
+ request->pos = 0;
+
+ // clear headers
+ struct curl_slist *headers = NULL;
+
+ if(timeout != 0) {
+ cxmutstr thdr;
+ if(timeout < 0) {
+ thdr = cx_asprintf("%s", "Timeout: Infinite");
+ } else {
+ thdr = cx_asprintf("Timeout: Second-%u", (unsigned int)timeout);
+ }
+ headers = curl_slist_append(headers, thdr.ptr);
+ free(thdr.ptr);
+ }
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead);
+ curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
+ curl_easy_setopt(handle, CURLOPT_READDATA, request);
+ curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
+ curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+
+ CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
+
+ if(headers) {
+ curl_slist_free_all(headers);
+ }
+
+ return ret;
+}
+
+CURLcode do_unlock_request(DavSession *sn, char *locktoken) {
+ CURL *handle = sn->handle;
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "UNLOCK");
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+
+ // set lock-token header
+ cxmutstr ltheader = cx_asprintf("Lock-Token: <%s>", locktoken);
+ struct curl_slist *headers = curl_slist_append(NULL, ltheader.ptr);
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+
+ CURLcode ret = dav_session_curl_perform(sn, NULL);
+ curl_slist_free_all(headers);
+ free(ltheader.ptr);
+
+ return ret;
+}
+
+CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken) {
+ CURL *handle = sn->handle;
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, method);
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
+
+ // set lock-token header
+ cxmutstr ltheader;
+ struct curl_slist *headers = NULL;
+ if(locktoken) {
+ ltheader = cx_asprintf("Lock-Token: <%s>", locktoken);
+ headers = curl_slist_append(NULL, ltheader.ptr);
+ }
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+
+ CURLcode ret = dav_session_curl_perform(sn, NULL);
+ if(locktoken) {
+ curl_slist_free_all(headers);
+ free(ltheader.ptr);
+ }
+
+ return ret;
+}
+
+
+CURLcode do_report_request(DavSession *sn, CxBuffer *request, CxBuffer *response) {
+ CURL *handle = sn->handle;
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "REPORT");
+
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(handle, CURLOPT_READFUNCTION, cxBufferRead);
+ curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, cxBufferSeek);
+ curl_easy_setopt(handle, CURLOPT_READDATA, request);
+ curl_easy_setopt(handle, CURLOPT_SEEKDATA, request);
+ curl_easy_setopt(handle, CURLOPT_INFILESIZE, request->size);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cxBufferWrite);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
+
+ struct curl_slist *headers = NULL;
+ headers = curl_slist_append(headers, "Content-Type: text/xml");
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+
+ request->pos = 0;
+ response->size = response->pos = 0;
+ CURLcode ret = dav_session_curl_perform_buf(sn, request, response, NULL);
+
+ curl_slist_free_all(headers);
+
+ return ret;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef METHODS_H
+#define METHODS_H
+
+#include "webdav.h"
+#include "resource.h"
+
+#include <cx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct PropfindParser PropfindParser;
+typedef struct ResponseTag ResponseTag;
+typedef struct LockDiscovery LockDiscovery;
+
+struct PropfindParser {
+ xmlDoc *document;
+ xmlNode *current;
+};
+
+struct ResponseTag {
+ const char *href;
+ int iscollection;
+ CxList *properties;
+ const char *crypto_name;
+ const char *crypto_key;
+};
+
+struct LockDiscovery {
+ char *timeout;
+ char *locktoken;
+};
+
+int dav_buffer_seek(CxBuffer *b, curl_off_t offset, int origin);
+
+CURLcode do_propfind_request(
+ DavSession *sn,
+ CxBuffer *request,
+ CxBuffer *response);
+
+CURLcode do_proppatch_request(
+ DavSession *sn,
+ char *lock,
+ CxBuffer *request,
+ CxBuffer *response);
+
+CURLcode do_put_request(
+ DavSession *sn,
+ char *lock,
+ DavBool create,
+ void *data,
+ dav_read_func read_func,
+ dav_seek_func seek_func,
+ size_t length);
+
+CxBuffer* create_allprop_propfind_request(void);
+CxBuffer* create_cryptoprop_propfind_request(void);
+CxBuffer* create_propfind_request(DavSession *sn, CxList *properties, char *rootelm, DavBool nocrypt);
+CxBuffer* create_basic_propfind_request(void);
+
+PropfindParser* create_propfind_parser(CxBuffer *response, char *url);
+void destroy_propfind_parser(PropfindParser *parser);
+int get_propfind_response(PropfindParser *parser, ResponseTag *result);
+void cleanup_response(ResponseTag *result);
+
+int hrefeq(DavSession *sn, const char *href1, const char *href2);
+DavResource* response2resource(DavSession *sn, ResponseTag *response, char *parent_path);
+void add_properties(DavResource *res, ResponseTag *response);
+
+DavResource* parse_propfind_response(DavSession *sn, DavResource *root, CxBuffer *response);
+int parse_response_tag(DavResource *resource, xmlNode *node);
+void set_davprops(DavResource *res);
+
+/*
+ * parses the content of a resourcetype element
+ * returns 1 if the resourcetype is a collection, 0 otherwise
+ */
+int parse_resource_type(xmlNode *node);
+
+CxBuffer* create_proppatch_request(DavResourceData *data);
+CxBuffer* create_crypto_proppatch_request(DavSession *sn, DavKey *key, const char *name, const char *hash);
+
+CURLcode do_delete_request(DavSession *sn, char *lock, CxBuffer *response);
+
+CURLcode do_mkcol_request(DavSession *sn, char *lock);
+
+CURLcode do_head_request(DavSession *sn);
+
+CURLcode do_copy_move_request(DavSession *sn, char *dest, char *lock, DavBool copy, DavBool override);
+
+CxBuffer* create_lock_request(void);
+int parse_lock_response(DavSession *sn, CxBuffer *response, LockDiscovery *lock);
+CURLcode do_lock_request(DavSession *sn, CxBuffer *request, CxBuffer *response, time_t timeout);
+CURLcode do_unlock_request(DavSession *sn, char *locktoken);
+
+CURLcode do_simple_request(DavSession *sn, char *method, char *locktoken);
+
+CURLcode do_report_request(DavSession *sn, CxBuffer *request, CxBuffer *response);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* METHODS_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "pwdstore.h"
+
+#include "utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cx/utils.h>
+#include <cx/hash_map.h>
+
+#ifdef _WIN32
+#include <winsock.h>
+#pragma comment(lib, "Ws2_32.lib")
+#else
+#include <netinet/in.h>
+#endif
+
+
+static pwdstore_pwinput_func pw_input = (pwdstore_pwinput_func)pwdstore_default_pwinput;
+static void *pw_input_data = "Master password: ";
+
+char * pwdstore_default_pwinput(char *prompt) {
+ return util_password_input(prompt);
+}
+
+void pwdstore_set_pwinput_func(pwdstore_pwinput_func func, void *userdata) {
+ pw_input = func;
+ pw_input_data = userdata;
+}
+
+PwdStore* pwdstore_open(const char *file) {
+ FILE *in = fopen(file, "r");
+ if(!in) {
+ return NULL;
+ }
+
+ CxBuffer *buf = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ cx_stream_copy(in, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
+ fclose(in);
+
+ if(buf->size < PWDS_HEADER_SIZE || buf->space[0] != PWDS_MAGIC_CHAR) {
+ cxBufferFree(buf);
+ return NULL;
+ }
+
+ PwdStore *p = malloc(sizeof(PwdStore));
+ p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+ p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+ p->content = buf;
+ p->key = NULL;
+ p->unlock_cmd = NULL;
+ p->lock_cmd = NULL;
+ p->encoffset = PWDS_HEADER_SIZE;
+ p->isdecrypted = 0;
+
+ if(pwdstore_getindex(p)) {
+ pwdstore_free(p);
+ return NULL;
+ }
+
+ return p;
+}
+
+PwdStore* pwdstore_new(void) {
+ PwdStore *p = calloc(1, sizeof(PwdStore));
+ p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+ p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+ p->content = cxBufferCreate(NULL, PWDS_HEADER_SIZE, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ PWDS_MAGIC(p) = PWDS_MAGIC_CHAR;
+ PWDS_VERSION(p) = 1;
+ PWDS_ENC(p) = DAV_KEY_AES256;
+ PWDS_PWFUNC(p) = DAV_PWFUNC_PBKDF2_SHA256;
+ dav_rand_bytes((unsigned char*)p->content->space+4, 16);
+ p->isdecrypted = 1;
+ p->encoffset = PWDS_HEADER_SIZE;
+ return p;
+}
+
+PwdStore* pwdstore_clone(PwdStore *p) {
+ CxBuffer *newbuffer = calloc(1, sizeof(CxBuffer));
+ *newbuffer = *p->content;
+ newbuffer->space = malloc(p->content->capacity);
+ memcpy(newbuffer->space, p->content->space, p->content->capacity);
+
+ DavKey *key = NULL;
+ if(p->key) {
+ key = malloc(sizeof(DavKey));
+ key->data = malloc(p->key->length);
+ memcpy(key->data, p->key->data, p->key->length);
+ key->length = p->key->length;
+ key->type = p->key->type;
+ key->name = NULL;
+ }
+
+ PwdStore *newp = calloc(1, sizeof(PwdStore));
+ newp->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+ newp->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ newp->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ newp->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+ newp->content = newbuffer;
+ newp->key = key;
+ newp->unlock_cmd = p->unlock_cmd ? strdup(p->unlock_cmd) : NULL;
+ newp->lock_cmd = p->lock_cmd ? strdup(p->lock_cmd) : NULL;
+ newp->encoffset = p->encoffset;
+ newp->isdecrypted = p->isdecrypted;
+
+ CxIterator i = cxMapIterator(p->ids);
+ cx_foreach(CxMapEntry *, e, i) {
+ PwdEntry *entry = e->value;
+ pwdstore_put(newp, entry->id, entry->user, entry->password);
+ }
+
+ i = cxMapIterator(p->index);
+ cx_foreach(CxMapEntry *, e, i) {
+ PwdIndexEntry *entry = e->value;
+ CxList *locations = NULL;
+ if(entry->locations) {
+ locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ CxIterator li = cxListIterator(entry->locations);
+ cx_foreach(char *, location, li) {
+ cxListAdd(locations, strdup(location));
+ }
+ }
+ pwdstore_put_index(newp, strdup(entry->id), locations);
+ }
+
+ return newp;
+}
+
+static int readval(CxBuffer *in, char **val, int allowzero) {
+ // value = length string
+ // length = uint32
+ // string = bytes
+
+ *val = NULL;
+
+ // get length
+ uint32_t length = 0;
+ if(cxBufferRead(&length, 1, sizeof(uint32_t), in) != sizeof(uint32_t)) {
+ return 0;
+ }
+ length = ntohl(length); // convert from BE to host byte order
+ if(length == 0) {
+ if(allowzero) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ if(length > PWDSTORE_MAX_LEN) {
+ return 0;
+ }
+
+ // get value
+ char *value = malloc(length + 1);
+ value[length] = 0;
+ if(cxBufferRead(value, 1, length, in) != length) {
+ free(value);
+ return 0;
+ }
+
+ *val = value;
+ return 1;
+}
+
+static int read_indexentry(PwdStore *p, CxBuffer *in) {
+ // read type of index element
+ int type = cxBufferGet(in);
+ if(type == EOF || type != 0) {
+ // only type 0 supported yet
+ return 0;
+ }
+
+ char *id = NULL;
+ CxList *locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ cxDefineDestructor(locations, free);
+
+ // get id (required)
+ int ret = 0;
+ if(readval(in, &id, FALSE)) {
+ // get locations
+ char *location = NULL;
+ while((ret = readval(in, &location, TRUE)) == 1) {
+ if(!location) {
+ break;
+ }
+ cxListAdd(locations, location);
+ }
+ }
+
+ if(ret) {
+ pwdstore_put_index(p, id, locations);
+ if(cxListSize(locations) == 0) {
+ cxListDestroy(locations);
+ }
+ } else {
+ if(id) free(id);
+ cxListDestroy(locations);
+ }
+
+ return ret;
+}
+
+static int read_pwdentry(PwdStore *p, CxBuffer *in) {
+ int type = cxBufferGet(in);
+ if(type == EOF || type != 0) {
+ // only type 0 supported yet
+ return 0;
+ }
+
+ char *id = NULL;
+ char *user = NULL;
+ char *password = NULL;
+
+ int ret = 0;
+ if(readval(in, &id, FALSE)) {
+ if(readval(in, &user, FALSE)) {
+ if(readval(in, &password, FALSE)) {
+ pwdstore_put(p, id, user, password);
+ ret = 1;
+ }
+ }
+ }
+
+ if(id) free(id);
+ if(user) free(user);
+ if(password) free(password);
+
+ return ret;
+}
+
+static void remove_list_entries(PwdStore *s, const char *id) {
+ CxIterator i = cxListMutIterator(s->locations);
+ cx_foreach(PwdIndexEntry*, ie, i) {
+ if(!strcmp(ie->id, id)) {
+ cxIteratorFlagRemoval(i);
+ cxIteratorNext(i);
+ break;
+ }
+ }
+ i = cxListMutIterator(s->noloc);
+ cx_foreach(PwdIndexEntry*, ie, i) {
+ if(!strcmp(ie->id, id)) {
+ cxIteratorFlagRemoval(i);
+ cxIteratorNext(i);
+ break;
+ }
+ }
+}
+
+void pwdstore_remove_entry(PwdStore *s, const char *id) {
+ remove_list_entries(s, id);
+
+ CxHashKey key = cx_hash_key_str(id);
+ PwdIndexEntry *i = cxMapRemoveAndGet(s->index, key);
+ PwdEntry *e = cxMapRemoveAndGet(s->ids, key);
+
+ if(i) {
+ if(i->locations) {
+ cxListDestroy(i->locations);
+ }
+ free(i->id);
+ free(i);
+ }
+ if(e) {
+ free(e->id);
+ free(e->user);
+ free(e->password);
+ free(e);
+ }
+}
+
+int pwdstore_getindex(PwdStore *s) {
+ uint32_t netindexlen;
+
+ // set the position to the last 4 bytes of the header
+ // for reading index length
+ s->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t);
+
+ // read indexlen and convert to host byte order
+ if(cxBufferRead(&netindexlen, 1, sizeof(uint32_t), s->content) != sizeof(uint32_t)) {
+ return 1;
+ }
+ uint32_t indexlen = ntohl(netindexlen);
+
+ // integer overflow check
+ if(UINT32_MAX - PWDS_HEADER_SIZE < indexlen) {
+ return 1;
+ }
+ if(s->content->size < PWDS_HEADER_SIZE + indexlen) {
+ return 1;
+ }
+ // encrypted content starts after the index content
+ s->encoffset = PWDS_HEADER_SIZE + indexlen;
+
+ // the index starts after the header
+ CxBuffer *index = cxBufferCreate(s->content->space+PWDS_HEADER_SIZE, indexlen, cxDefaultAllocator, 0);
+ index->size = indexlen;
+
+ // read index
+ while(read_indexentry(s, index)) {}
+
+ // free index buffer structure (not the content)
+ cxBufferFree(index);
+
+ return 0;
+}
+
+int pwdstore_decrypt(PwdStore *p) {
+ if(!p->key) {
+ return 1;
+ }
+ if(p->isdecrypted) {
+ return 0;
+ }
+
+ // decrypt contet
+ size_t encsz = p->content->size - p->encoffset;
+ CxBuffer *enc = cxBufferCreate(p->content->space + p->encoffset, encsz, cxDefaultAllocator, 0);
+ enc->size = encsz;
+ enc->size = p->content->size - p->encoffset;
+ CxBuffer *content = aes_decrypt_buffer(enc, p->key);
+ cxBufferFree(enc);
+ if(!content) {
+ return 1;
+ }
+
+ while(read_pwdentry(p, content)) {}
+
+ cxBufferFree(content);
+
+ p->isdecrypted = 1;
+
+ return 0;
+}
+
+int pwdstore_setpassword(PwdStore *p, const char *password) {
+ DavKey *key = dav_pw2key(
+ password,
+ (unsigned char*)(p->content->space + 4),
+ 16,
+ PWDS_PWFUNC(p),
+ PWDS_ENC(p));
+ if(!key) {
+ return 1;
+ }
+
+ p->key = key;
+ return 0;
+}
+
+void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc) {
+ PWDS_ENC(p) = enc;
+ PWDS_PWFUNC(p) = pwfunc;
+}
+
+void pwdstore_free_entry(PwdEntry *e) {
+ if(e->id) free(e->id);
+ if(e->user) free(e->user);
+ if(e->password) free(e->password);
+ free(e);
+}
+
+void pwdstore_free(PwdStore* p) {
+ cxDefineDestructor(p->ids, pwdstore_free_entry);
+ cxMapDestroy(p->ids);
+
+ cxListDestroy(p->locations);
+
+ if(p->content) {
+ cxBufferFree(p->content);
+ }
+
+ free(p);
+}
+
+int pwdstore_has_id(PwdStore *s, const char *id) {
+ return cxMapGet(s->index, cx_hash_key_str(id)) ? 1 : 0;
+}
+
+PwdEntry* pwdstore_get(PwdStore *p, const char *id) {
+ PwdEntry *e = cxMapGet(p->ids, cx_hash_key_str(id));
+ if(e && e->user && e->password) {
+ return e;
+ } else {
+ return NULL;
+ }
+}
+
+void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password) {
+ PwdEntry *entry = malloc(sizeof(PwdEntry));
+ entry->id = strdup(id);
+ entry->user = strdup(username);
+ entry->password = strdup(password);
+ cxMapPut(p->ids, cx_hash_key_str(id), entry);
+}
+
+void pwdstore_put_index(PwdStore *p, char *id, CxList *locations) {
+ PwdIndexEntry *e = cxMapGet(p->index, cx_hash_key_str(id));
+ if(e) {
+ return;
+ }
+ PwdIndexEntry *newentry = malloc(sizeof(PwdIndexEntry));
+ newentry->id = id;
+ if(locations && cxListSize(locations) > 0) {
+ newentry->locations = locations;
+ cxListAdd(p->locations, newentry);
+ } else {
+ newentry->locations = NULL;
+ cxListAdd(p->noloc, newentry);
+ }
+ cxMapPut(p->index, cx_hash_key_str(id), newentry);
+}
+
+void write_index_entry(CxBuffer *out, PwdIndexEntry *e) {
+ uint32_t idlen = strlen(e->id);
+ uint32_t netidlen = htonl(idlen);
+
+ cxBufferPut(out, 0); // type
+
+ cxBufferWrite(&netidlen, 1, sizeof(uint32_t), out);
+ cxBufferWrite(e->id, 1, idlen, out);
+
+ if(e->locations) {
+ CxIterator i = cxListIterator(e->locations);
+ cx_foreach(char *, location, i) {
+ uint32_t locationlen = strlen(location);
+ uint32_t netlocationlen = htonl(locationlen);
+
+ cxBufferWrite(&netlocationlen, 1, sizeof(uint32_t), out);
+ cxBufferWrite(location, 1, locationlen, out);
+ }
+ }
+
+ uint32_t terminate = 0;
+ cxBufferWrite(&terminate, 1, sizeof(uint32_t), out);
+}
+
+int pwdstore_store(PwdStore *p, const char *file) {
+ if(!p->key) {
+ return 1;
+ }
+
+ CxBuffer *index = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ CxBuffer *content = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+
+ // create index
+ CxIterator i = cxListIterator(p->noloc);
+ cx_foreach(PwdIndexEntry*, e, i) {
+ write_index_entry(index, e);
+ }
+ i = cxListIterator(p->locations);
+ cx_foreach(PwdIndexEntry*, e, i) {
+ write_index_entry(index, e);
+ }
+
+ i = cxMapIteratorValues(p->ids);
+ cx_foreach(PwdEntry*, value, i) {
+ if(!value->id || !value->user || !value->password) {
+ continue;
+ }
+
+ uint32_t idlen = strlen(value->id);
+ uint32_t ulen = strlen(value->user);
+ uint32_t plen = strlen(value->password);
+ uint32_t netidlen = htonl(idlen);
+ uint32_t netulen = htonl(ulen);
+ uint32_t netplen = htonl(plen);
+
+ // content buffer
+ cxBufferPut(content, 0); // type
+
+ cxBufferWrite(&netidlen, 1, sizeof(uint32_t), content);
+ cxBufferWrite(value->id, 1, idlen, content);
+ cxBufferWrite(&netulen, 1, sizeof(uint32_t), content);
+ cxBufferWrite(value->user, 1, ulen, content);
+ cxBufferWrite(&netplen, 1, sizeof(uint32_t), content);
+ cxBufferWrite(value->password, 1, plen, content);
+ }
+
+ content->pos = 0;
+ CxBuffer *enc = aes_encrypt_buffer(content, p->key);
+
+ p->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t);
+ p->content->size = PWDS_HEADER_SIZE;
+
+ // add index after header
+ uint32_t netindexlen = htonl((uint32_t)index->size);
+ cxBufferWrite(&netindexlen, 1, sizeof(uint32_t), p->content);
+ cxBufferWrite(index->space, 1, index->size, p->content);
+
+ // add encrypted buffer
+ cxBufferWrite(enc->space, 1, enc->size, p->content);
+
+ cxBufferFree(enc);
+
+ FILE *out = fopen(file, "w");
+ if(!out) {
+ return 1;
+ }
+ fwrite(p->content->space, 1, p->content->size, out);
+ fclose(out);
+
+ return 0;
+}
+
+int pwdstore_decrypt_secrets(PwdStore *secrets) {
+ if(!pw_input) {
+ return 1;
+ }
+
+ char *ps_password = NULL;
+ if(secrets->unlock_cmd && strlen(secrets->unlock_cmd) > 0) {
+ CxBuffer *cmd_out = cxBufferCreate(NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ if(!util_exec_command(secrets->unlock_cmd, cmd_out)) {
+ // command successful, get first line from output without newline
+ // and use that as password for the secretstore
+ size_t len = 0;
+ for(size_t i=0;i<=cmd_out->size;i++) {
+ if(i == cmd_out->size || cmd_out->space[i] == '\n') {
+ len = i;
+ break;
+ }
+ }
+ if(len > 0) {
+ ps_password = malloc(len + 1);
+ memcpy(ps_password, cmd_out->space, len);
+ ps_password[len] = 0;
+ }
+ }
+ cxBufferFree(cmd_out);
+ }
+
+ if(!ps_password) {
+ ps_password = pw_input(pw_input_data);
+ if(!ps_password) {
+ return 1;
+ }
+ }
+
+ int err = pwdstore_setpassword(secrets, ps_password);
+ free(ps_password);
+ if(err) {
+ fprintf(stderr, "Error: cannot create key from password\n");
+ return 1;
+ }
+ if(pwdstore_decrypt(secrets)) {
+ fprintf(stderr, "Error: cannot decrypt secrets store\n");
+ return 1;
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIBIDAV_PWDSTORE_H
+#define LIBIDAV_PWDSTORE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include <cx/map.h>
+#include <cx/buffer.h>
+#include <cx/linked_list.h>
+#include "crypto.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PWDSTORE_MAX_LEN 4096
+
+/*
+ * File Format:
+ *
+ * file = header, index, enc_content
+ * header = magic, version, enc, pwfunc, salt, indexlen
+ * magic = 1 byte
+ * version = 1 byte
+ * enc = 1 byte
+ * pwfunc = 1 byte
+ * salt = 16 bytes
+ * indexlen = uint32
+ * index = { itype length id locations zero }
+ * enc_content = iv bytes
+ * iv = 16 bytes
+ * content = { entry }
+ * entry = itype length id length username length password
+ * length = uint32
+ * zero = 4 zero bytes
+ * itype = 1 byte
+ * id = string
+ * locations = { length string }
+ * username = string
+ * password = string
+ *
+ * The content is AES encrypted with a key derived from a password
+ * and the salt. The first 16 bytes are the aes iv.
+ *
+ * All integers are big endian
+ */
+
+#define PWDS_HEADER_SIZE 24
+
+typedef struct PwdStore PwdStore;
+typedef struct PwdEntry PwdEntry;
+typedef struct PwdIndexEntry PwdIndexEntry;
+
+struct PwdStore {
+ /*
+ * map of all credentials
+ * key is the username
+ * value is PwdEntry*
+ */
+ CxMap *ids;
+
+ /*
+ * list of all credentials with location
+ * value is PwdIndexEntry*
+ */
+ CxList *locations;
+
+ /*
+ * list of all credentials without location
+ * value is PwdIndexEntry*
+ */
+ CxList *noloc;
+
+ /*
+ * index map that contains all elements from the lists
+ * 'locations' and 'noloc'
+ */
+ CxMap *index;
+
+ /*
+ * a buffer containing the complete file content
+ */
+ CxBuffer *content;
+
+ /*
+ * key used for encryption/decryption
+ */
+ DavKey *key;
+
+ /*
+ * optional shell command, that is used for getting the master password
+ */
+ char *unlock_cmd;
+
+ /*
+ * optional shell command, that is exected when the secretstore is closed
+ */
+ char *lock_cmd;
+
+ /*
+ * start offset of the encrypted buffer
+ */
+ uint32_t encoffset;
+
+ /*
+ * indicates if the PwdStore is decrypted with pwdstore_decrypt
+ */
+ uint8_t isdecrypted;
+};
+
+#define PWDS_MAGIC(p) (p)->content->space[0]
+#define PWDS_VERSION(p) (p)->content->space[1]
+#define PWDS_ENC(p) (p)->content->space[2]
+#define PWDS_PWFUNC(p) (p)->content->space[3]
+
+#define PWDS_MAGIC_CHAR 'P'
+
+struct PwdEntry {
+ char *id;
+ char *user;
+ char *password;
+};
+
+struct PwdIndexEntry {
+ char *id;
+ CxList *locations;
+};
+
+/*
+ * opens the password store
+ * the content is still encrypted and must be decrypted using pwdstore_decrypt
+ */
+PwdStore* pwdstore_open(const char *file);
+
+PwdStore* pwdstore_new(void);
+
+PwdStore* pwdstore_clone(PwdStore *p);
+
+/*
+ * decrypts the password store with the previously set password
+ */
+int pwdstore_decrypt(PwdStore *p);
+
+int pwdstore_setpassword(PwdStore *p, const char *password);
+
+void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc);
+
+void pwdstore_free_entry(PwdEntry *e);
+void pwdstore_free(PwdStore* p);
+
+int pwdstore_has_id(PwdStore *s, const char *id);
+
+PwdEntry* pwdstore_get(PwdStore *p, const char *id);
+
+void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password);
+void pwdstore_put_index(PwdStore *p, char *id, CxList *locations);
+
+void pwdstore_remove_entry(PwdStore *s, const char *id);
+
+int pwdstore_store(PwdStore *p, const char *file);
+
+
+int pwdstore_decrypt_secrets(PwdStore *secrets);
+
+
+
+
+
+
+typedef char*(*pwdstore_pwinput_func)(void *userdata);
+
+void pwdstore_set_pwinput_func(pwdstore_pwinput_func func, void *userdata);
+
+char * pwdstore_default_pwinput(char *prompt);
+
+
+
+/* private */
+int pwdstore_getindex(PwdStore *s);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBIDAV_PWDSTORE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <libxml/tree.h>
+
+#include "utils.h"
+#include "session.h"
+#include "methods.h"
+#include "crypto.h"
+#include <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/hash_map.h>
+#include <cx/printf.h>
+#include <cx/mempool.h>
+#include <cx/array_list.h>
+
+#include "resource.h"
+#include "xml.h"
+#include "davqlexec.h"
+
+#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
+
+DavResource* dav_resource_new(DavSession *sn, const char *path) {
+ //char *href = util_url_path(url);
+ //DavResource *res = dav_resource_new_href(sn, href);
+ char *parent = util_parent_path(path);
+ const char *name = util_resource_name(path);
+ char *href = dav_session_create_plain_href(sn, path);
+
+ DavResource *res = dav_resource_new_full(sn, parent, name, href);
+ free(parent);
+ return res;
+}
+
+DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, const char *name) {
+ char *path = util_concat_path(parent->path, name);
+ char *href = dav_session_create_plain_href(sn, path);
+ DavResource *res = dav_resource_new_full(sn, parent->path, name, href);
+ free(path);
+ return res;
+}
+
+
+DavResource* dav_resource_new_href(DavSession *sn, const char *href) {
+ DavResource *res = cxCalloc(sn->mp->allocator, 1, sizeof(DavResource));
+ res->session = sn;
+
+ // set name, path and href
+ resource_set_info(res, href);
+
+ // initialize resource data
+ res->data = resource_data_new(sn);
+
+ return res;
+}
+
+DavResource* dav_resource_new_full(DavSession *sn, const char *parent_path, const char *name, char *href) {
+ cxstring n = cx_str(name);
+ // the name must not contain path separators
+ if(n.length > 0 && href) {
+ for(int i=0;i<n.length-1;i++) {
+ char c = n.ptr[i];
+ if(c == '/' || c == '\\') {
+ n = cx_str(util_resource_name(href));
+ break;
+ }
+ }
+ }
+ // remove trailing '/'
+ if(n.length > 0 && n.ptr[n.length-1] == '/') {
+ n.length--;
+ }
+
+ DavResource *res = cxCalloc(sn->mp->allocator, 1, sizeof(DavResource));
+ res->session = sn;
+
+ // set name, path and href
+ res->name = cx_strdup_a(sn->mp->allocator, n).ptr;
+
+ char *path = util_concat_path(parent_path, name);
+ res->path = dav_session_strdup(sn, path);
+
+ res->href = href;
+
+ // initialize resource data
+ res->data = resource_data_new(sn);
+
+ // cache href/path
+ if(href) {
+ dav_session_cache_path(sn, cx_str(path), cx_str(href));
+ }
+ free(path);
+
+ return res;
+}
+
+void resource_free_properties(DavSession *sn, CxMap *properties) {
+ if(!properties) return;
+
+ CxIterator i = cxMapIteratorValues(properties);
+ cx_foreach(DavProperty*, property, i) {
+ // TODO: free everything
+ dav_session_free(sn, property);
+ }
+ cxMapDestroy(properties);
+}
+
+void dav_resource_free(DavResource *res) {
+ DavSession *sn = res->session;
+
+ dav_session_free(sn, res->name);
+ dav_session_free(sn, res->path);
+ if(res->href) {
+ dav_session_free(sn, res->href);
+ }
+
+ DavResourceData *data = res->data;
+ resource_free_properties(sn, data->properties);
+ resource_free_properties(sn, data->crypto_properties);
+
+ if(data->set) {
+ CxIterator i = cxListIterator(data->set);
+ cx_foreach(DavProperty *, p, i) {
+ dav_session_free(sn, p->ns->name);
+ if(p->ns->prefix) {
+ dav_session_free(sn, p->ns->prefix);
+ }
+ dav_session_free(sn, p->ns);
+
+ dav_session_free(sn, p->name);
+ dav_free_xml_node_sn(sn, p->value);
+ dav_session_free(sn, p);
+ }
+ }
+
+ if(data->remove) {
+ CxIterator i = cxListIterator(data->remove);
+ cx_foreach(DavProperty *, p, i) {
+ dav_session_free(sn, p->ns->name);
+ if(p->ns->prefix) {
+ dav_session_free(sn, p->ns->prefix);
+ }
+ dav_session_free(sn, p->ns);
+
+ dav_session_free(sn, p->name);
+ dav_session_free(sn, p);
+ }
+ }
+
+ if(data->crypto_set) {
+ CxIterator i = cxListIterator(data->crypto_set);
+ cx_foreach(DavProperty *, p, i) {
+ dav_session_free(sn, p->ns->name);
+ if(p->ns->prefix) {
+ dav_session_free(sn, p->ns->prefix);
+ }
+ dav_session_free(sn, p->ns);
+
+ dav_session_free(sn, p->name);
+ dav_free_xml_node_sn(sn, p->value);
+ dav_session_free(sn, p);
+ }
+ }
+
+ if(data->crypto_remove) {
+ CxIterator i = cxListIterator(data->crypto_remove);
+ cx_foreach(DavProperty *, p, i) {
+ dav_session_free(sn, p->ns->name);
+ if(p->ns->prefix) {
+ dav_session_free(sn, p->ns->prefix);
+ }
+ dav_session_free(sn, p->ns);
+
+ dav_session_free(sn, p->name);
+ dav_session_free(sn, p);
+ }
+ }
+
+ if(!data->read && data->content) {
+ dav_session_free(sn, data->content);
+ }
+ dav_session_free(sn, data);
+
+ dav_session_free(sn, res);
+}
+
+void dav_resource_free_all(DavResource *res) {
+ DavResource *child = res->children;
+ dav_resource_free(res);
+ while(child) {
+ DavResource *next = child->next;
+ dav_resource_free_all(child);
+ child = next;
+ }
+}
+
+void resource_set_href(DavResource *res, cxstring href) {
+ res->href = cx_strdup_a(res->session->mp->allocator, href).ptr;
+}
+
+void resource_set_info(DavResource *res, const char *href_str) {
+ char *url_str = NULL;
+ curl_easy_getinfo(res->session->handle, CURLINFO_EFFECTIVE_URL, &url_str);
+ cxstring name = cx_str(util_resource_name(href_str));
+ cxstring href = cx_str(href_str);
+
+ cxstring base_href = cx_str(util_url_path(res->session->base_url));
+ cxstring path = cx_strsubs(href, base_href.length - 1);
+
+ const CxAllocator *a = res->session->mp->allocator;
+ CURL *handle = res->session->handle;
+
+ int nlen = 0;
+ char *uname = curl_easy_unescape(handle, name.ptr, name.length , &nlen);
+ int plen = 0;
+ char *upath = curl_easy_unescape(handle, path.ptr, path.length, &plen);
+
+ res->name = cx_strdup_a(a, cx_strn(uname, nlen)).ptr;
+ res->href = cx_strdup_a(a, href).ptr;
+ res->path = cx_strdup_a(a, cx_strn(upath, plen)).ptr;
+
+ curl_free(uname);
+ curl_free(upath);
+}
+
+DavResourceData* resource_data_new(DavSession *sn) {
+ DavResourceData *data = cxMalloc(
+ sn->mp->allocator,
+ sizeof(DavResourceData));
+ if(!data) {
+ return NULL;
+ }
+ data->properties = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 32);
+ data->crypto_properties = NULL;
+ data->set = NULL;
+ data->remove = NULL;
+ data->crypto_set = NULL;
+ data->crypto_remove = NULL;
+ data->read = NULL;
+ data->content = NULL;
+ data->seek = NULL;
+ data->length = 0;
+ return data;
+}
+
+char* dav_resource_get_href(DavResource *resource) {
+ if(!resource->href) {
+ resource->href = dav_session_get_href(
+ resource->session,
+ resource->path);
+ }
+ return resource->href;
+}
+
+void resource_add_prop(DavResource *res, const char *ns, const char *name, DavXmlNode *val) {
+ DavSession *sn = res->session;
+
+ DavNamespace *namespace = dav_session_malloc(sn, sizeof(DavNamespace));
+ namespace->prefix = NULL;
+ namespace->name = dav_session_strdup(sn, ns);
+
+ DavProperty *prop = dav_session_malloc(sn, sizeof(DavProperty));
+ prop->name = dav_session_strdup(sn, name);
+ prop->ns = namespace;
+ prop->value = val;
+
+ cxmutstr keystr = dav_property_key(ns, name);
+ CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
+ cxMapPut(((DavResourceData*)res->data)->properties, key, prop);
+ free(keystr.ptr);
+}
+
+void resource_add_property(DavResource *res, const char *ns, const char *name, xmlNode *val) {
+ if(!val) {
+ return;
+ }
+
+ resource_add_prop(res, ns, name, dav_convert_xml(res->session, val));
+}
+
+void resource_add_string_property(DavResource *res, char *ns, char *name, char *val) {
+ if(!val) {
+ return;
+ }
+
+ resource_add_prop(res, ns, name, dav_text_node(res->session, val));
+}
+
+void resource_set_crypto_properties(DavResource *res, CxMap *cprops) {
+ DavResourceData *data = res->data;
+ resource_free_properties(res->session, data->crypto_properties);
+ data->crypto_properties = cprops;
+}
+
+DavXmlNode* resource_get_property(DavResource *res, const char *ns, const char *name) {
+ cxmutstr keystr = dav_property_key(ns, name);
+ CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
+ DavXmlNode *ret = resource_get_property_k(res, key);
+ free(keystr.ptr);
+
+ return ret;
+}
+
+DavXmlNode* resource_get_encrypted_property(DavResource *res, const char *ns, const char *name) {
+ cxmutstr keystr = dav_property_key(ns, name);
+ CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
+ DavXmlNode *ret = resource_get_encrypted_property_k(res, key);
+ free(keystr.ptr);
+
+ return ret;
+}
+
+DavXmlNode* resource_get_property_k(DavResource *res, CxHashKey key) {
+ DavResourceData *data = (DavResourceData*)res->data;
+ DavProperty *property = cxMapGet(data->properties, key);
+
+ return property ? property->value : NULL;
+}
+
+DavXmlNode* resource_get_encrypted_property_k(DavResource *res, CxHashKey key) {
+ DavResourceData *data = (DavResourceData*)res->data;
+ DavProperty *property = cxMapGet(data->crypto_properties, key);
+
+ return property ? property->value : NULL;
+}
+
+cxmutstr dav_property_key(const char *ns, const char *name) {
+ return dav_property_key_a(cxDefaultAllocator, ns, name);
+}
+
+cxmutstr dav_property_key_a(const CxAllocator *a, const char *ns, const char *name) {
+ cxstring ns_str = cx_str(ns);
+ cxstring name_str = cx_str(name);
+
+ return cx_strcat_a(a, 4, ns_str, CX_STR("\0"), name_str, CX_STR("\0"));
+}
+
+
+
+
+void resource_add_child(DavResource *parent, DavResource *child) {
+ child->next = NULL;
+ if(parent->children) {
+ DavResource *last = parent->children;
+ while(last->next) {
+ last = last->next;
+ }
+ last->next = child;
+ child->prev = last;
+ } else {
+ child->prev = NULL;
+ parent->children = child;
+ }
+ child->parent = parent;
+}
+
+static int resource_cmp(DavResource *res1, DavResource *res2, DavOrderCriterion *cr) {
+ if(!(res1 && res2)) {
+ return 0;
+ }
+
+ int ret;
+ if(cr->type == 0) {
+ switch(cr->column.resprop) {
+ case DAVQL_RES_NAME: {
+ ret = strcmp(res1->name, res2->name);
+ break;
+ }
+ case DAVQL_RES_PATH: {
+ ret = strcmp(res1->path, res2->path);
+ break;
+ }
+ case DAVQL_RES_HREF: {
+ ret = strcmp(res1->href, res2->href);
+ break;
+ }
+ case DAVQL_RES_CONTENTLENGTH: {
+ int c = res1->contentlength == res2->contentlength;
+ ret = c ? 0 : (res1->contentlength < res2->contentlength?-1:1);
+ break;
+ }
+ case DAVQL_RES_CONTENTTYPE: {
+ ret = strcmp(res1->contenttype, res2->contenttype);
+ break;
+ }
+ case DAVQL_RES_CREATIONDATE: {
+ int c = res1->creationdate == res2->creationdate;
+ ret = c ? 0 : (res1->creationdate < res2->creationdate?-1:1);
+ break;
+ }
+ case DAVQL_RES_LASTMODIFIED: {
+ int c = res1->lastmodified == res2->lastmodified;
+ ret = c ? 0 : (res1->lastmodified < res2->lastmodified?-1:1);
+ break;
+ }
+ case DAVQL_RES_ISCOLLECTION: {
+ int c = res1->iscollection == res2->iscollection;
+ ret = c ? 0 : (res1->iscollection < res2->iscollection?-1:1);
+ break;
+ }
+ default: ret = 0;
+ }
+ } else if(cr->type == 1) {
+ DavXmlNode *xvalue1 = resource_get_property_k(res1, cr->column.property);
+ DavXmlNode *xvalue2 = resource_get_property_k(res2, cr->column.property);
+ char *value1 = dav_xml_getstring(xvalue1);
+ char *value2 = dav_xml_getstring(xvalue2);
+ if(!value1) {
+ ret = value2 ? -1 : 0;
+ } else if(!value2) {
+ ret = value1 ? 1 : 0;
+ } else {
+ ret = strcmp(value1, value2);
+ }
+ } else {
+ return 0;
+ }
+
+ return cr->descending ? -ret : ret;
+}
+
+void resource_add_ordered_child(DavResource *parent, DavResource *child, CxList *ordercr) {
+ if(!ordercr) {
+ resource_add_child(parent, child);
+ return;
+ }
+
+ child->parent = parent;
+
+ if(!parent->children) {
+ child->next = NULL;
+ child->prev = NULL;
+ parent->children = child;
+ } else {
+ DavResource *resource = parent->children;
+ while(resource) {
+ int r = 0;
+ CxIterator i = cxListIterator(ordercr);
+ cx_foreach(DavOrderCriterion*, cr, i) {
+ r = resource_cmp(child, resource, cr);
+ if(r != 0) {
+ break;
+ }
+ }
+
+ if(r < 0) {
+ // insert child before resource
+ child->prev = resource->prev;
+ child->next = resource;
+ if(resource->prev) {
+ resource->prev->next = child;
+ } else {
+ parent->children = child;
+ }
+ resource->prev = child;
+ break;
+ } if(!resource->next) {
+ // append child
+ child->prev = resource;
+ child->next = NULL;
+ resource->next = child;
+ break;
+ } else {
+ resource = resource->next;
+ }
+ }
+ }
+}
+
+char* dav_get_string_property(DavResource *res, char *name) {
+ char *pns;
+ char *pname;
+ dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+ if(!pns || !pname) {
+ return NULL;
+ }
+ return dav_get_string_property_ns(res, pns, pname);
+}
+
+char* dav_get_string_property_ns(DavResource *res, char *ns, char *name) {
+ DavXmlNode *prop = dav_get_property_ns(res, ns, name);
+ if(!prop) {
+ return NULL;
+ }
+ return dav_xml_getstring(prop);
+}
+
+DavXmlNode* dav_get_property(DavResource *res, char *name) {
+ char *pns;
+ char *pname;
+ dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+ if(!pns || !pname) {
+ return NULL;
+ }
+ return dav_get_property_ns(res, pns, pname);
+}
+
+static DavXmlNode* get_property_ns(DavResource *res, DavBool encrypted, const char *ns, const char *name) {
+ if(!ns || !name) {
+ return NULL;
+ }
+
+ DavResourceData *data = res->data;
+
+ DavXmlNode *property = NULL;
+ CxList *remove_list = NULL;
+ CxList *set_list = NULL;
+
+ if(encrypted) {
+ // check if crypto_properties because it will only be created
+ // if the resource has encrypted properties
+ if(!data->crypto_properties) {
+ return NULL;
+ }
+ property = resource_get_encrypted_property(res, ns, name);
+ remove_list = data->crypto_remove;
+ set_list = data->crypto_set;
+ } else {
+ property = resource_get_property(res, ns, name);
+ remove_list = data->remove;
+ set_list = data->set;
+ }
+
+ // resource_get_property only returns persistent properties
+ // check the remove and set list
+ if(property && remove_list) {
+ // if the property is in the remove list, we return NULL
+ CxIterator i = cxListIterator(remove_list);
+ cx_foreach(DavProperty*, p, i) {
+ if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) {
+ return NULL;
+ }
+ }
+ }
+
+ // the set list contains property updates
+ // we return an updated property if possible
+ if(set_list) {
+ CxIterator i = cxListIterator(set_list);
+ cx_foreach(DavProperty*, p, i) {
+ if(!strcmp(p->name, name) && !strcmp(p->ns->name, ns)) {
+ return p->value; // TODO: fix
+ }
+ }
+ }
+
+ // no property update
+
+ return property;
+}
+
+DavXmlNode* dav_get_property_ns(DavResource *res, const char *ns, const char *name) {
+ DavXmlNode *property_value = get_property_ns(res, FALSE, ns, name);
+
+ if(!property_value && DAV_DECRYPT_PROPERTIES(res->session)) {
+ property_value = get_property_ns(res, TRUE, ns, name);
+ }
+
+ return property_value;
+}
+
+DavXmlNode* dav_get_encrypted_property_ns(DavResource *res, const char *ns, const char *name) {
+ return get_property_ns(res, TRUE, ns, name);
+}
+
+static DavProperty* createprop(DavSession *sn, const char *ns, const char *name) {
+ DavProperty *property = dav_session_malloc(sn, sizeof(DavProperty));
+ property->name = dav_session_strdup(sn, name);
+ property->value = NULL;
+
+ DavNamespace *namespace = dav_session_malloc(sn, sizeof(DavNamespace));
+ namespace->prefix = NULL;
+ namespace->name = dav_session_strdup(sn, ns);
+
+ property->ns = namespace;
+
+ return property;
+}
+
+int dav_set_string_property(DavResource *res, char *name, char *value) {
+ char *pns;
+ char *pname;
+ dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+ if(!pns) {
+ res->session->errorstr = "Property namespace not found";
+ return 1;
+ }
+ dav_set_string_property_ns(res, pns, pname, value);
+ return 0;
+}
+
+static int add2propertylist(const CxAllocator *a, CxList **list, DavProperty *property) {
+ if(!*list) {
+ CxList *newlist = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS);
+ if(!newlist) {
+ return 1;
+ }
+ *list = newlist;
+ }
+ cxListAdd(*list, property);
+ return 0;
+}
+
+void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
+ DavSession *sn = res->session;
+ const CxAllocator *a = res->session->mp->allocator;
+ DavResourceData *data = res->data;
+
+ DavProperty *property = createprop(res->session, ns, name);
+ property->value = dav_text_node(res->session, value);
+
+ if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+ add2propertylist(a, &data->crypto_set, property);
+ } else {
+ add2propertylist(a, &data->set, property);
+ }
+}
+
+void dav_set_property(DavResource *res, char *name, DavXmlNode *value) {
+ char *pns;
+ char *pname;
+ dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+ dav_set_property_ns(res, pns, pname, value);
+}
+
+void dav_set_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value) {
+ DavSession *sn = res->session;
+ const CxAllocator *a = sn->mp->allocator;
+ DavResourceData *data = res->data;
+
+ DavProperty *property = createprop(sn, ns, name);
+ // TODO: this function should copy the value
+ // but we also need a function, that doesn't create a copy
+ property->value = value;
+
+ if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+ add2propertylist(a, &data->crypto_set, property);
+ } else {
+ add2propertylist(a, &data->set, property);
+ }
+}
+
+void dav_remove_property(DavResource *res, char *name) {
+ char *pns;
+ char *pname;
+ dav_get_property_namespace_str(res->session->context, name, &pns, &pname);
+ dav_remove_property_ns(res, pns, pname);
+}
+
+void dav_remove_property_ns(DavResource *res, char *ns, char *name) {
+ DavSession *sn = res->session;
+ DavResourceData *data = res->data;
+ const CxAllocator *a = res->session->mp->allocator;
+
+ DavProperty *property = createprop(res->session, ns, name);
+
+ if(DAV_ENCRYPT_PROPERTIES(sn) && dav_namespace_is_encrypted(sn->context, ns)) {
+ add2propertylist(a, &data->crypto_remove, property);
+ } else {
+ add2propertylist(a, &data->remove, property);
+ }
+}
+
+void dav_set_encrypted_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value) {
+ const CxAllocator *a = res->session->mp->allocator;
+ DavResourceData *data = res->data;
+
+ DavProperty *property = createprop(res->session, ns, name);
+ property->value = value; // TODO: copy node?
+
+ add2propertylist(a, &data->crypto_set, property);
+}
+
+void dav_set_encrypted_string_property_ns(DavResource *res, char *ns, char *name, char *value) {
+ const CxAllocator *a = res->session->mp->allocator;
+ DavResourceData *data = res->data;
+
+ DavProperty *property = createprop(res->session, ns, name);
+ property->value = dav_text_node(res->session, value);
+
+ add2propertylist(a, &data->crypto_set, property);
+}
+
+void dav_remove_encrypted_property_ns(DavResource *res, char *ns, char *name) {
+ DavResourceData *data = res->data;
+ const CxAllocator *a = res->session->mp->allocator;
+
+ DavProperty *property = createprop(res->session, ns, name);
+
+ add2propertylist(a, &data->crypto_remove, property);
+}
+
+static int compare_propname(const void *a, const void *b) {
+ const DavPropName *p1 = a;
+ const DavPropName *p2 = b;
+
+ int result = strcmp(p1->ns, p2->ns);
+ if(result) {
+ return result;
+ } else {
+ return strcmp(p1->name, p2->name);
+ }
+}
+
+DavPropName* dav_get_property_names(DavResource *res, size_t *count) {
+ DavResourceData *data = res->data;
+
+ *count = cxMapSize(data->properties);
+ DavPropName *names = dav_session_calloc(
+ res->session,
+ *count,
+ sizeof(DavPropName));
+
+
+ CxIterator i = cxMapIteratorValues(data->properties);
+ cx_foreach(DavProperty*, value, i) {
+ DavPropName *name = &names[i.index];
+ name->ns = value->ns->name;
+ name->name = value->name;
+ }
+
+ qsort(names, *count, sizeof(DavPropName), compare_propname);
+
+ return names;
+}
+
+
+void dav_set_content(DavResource *res, void *stream, dav_read_func read_func, dav_seek_func seek_func) {
+ DavResourceData *data = res->data;
+ data->content = stream;
+ data->read = read_func;
+ data->seek = seek_func;
+ data->length = 0;
+}
+
+void dav_set_content_data(DavResource *res, char *content, size_t length) {
+ DavSession *sn = res->session;
+ DavResourceData *data = res->data;
+ data->content = dav_session_malloc(sn, length);
+ memcpy(data->content, content, length);
+ data->read = NULL;
+ data->seek = NULL;
+ data->length = length;
+}
+
+void dav_set_content_length(DavResource *res, size_t length) {
+ DavResourceData *data = res->data;
+ data->length = length;
+}
+
+
+int dav_load(DavResource *res) {
+ CxBuffer *rqbuf = create_allprop_propfind_request();
+ int ret = dav_propfind(res->session, res, rqbuf);
+ cxBufferFree(rqbuf);
+ return ret;
+}
+
+int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop) {
+ CxMempool *mp = cxMempoolCreate(64, NULL);
+ const CxAllocator *a = mp->allocator;
+
+ CxList *proplist = cxArrayListCreate(a, NULL, sizeof(DavProperty), numprop);
+ for(size_t i=0;i<numprop;i++) {
+ DavProperty p;
+ p.name = properties[i].name;
+ p.ns = cxMalloc(a, sizeof(DavNamespace));
+ p.ns->name = properties[i].ns;
+ if(!strcmp(properties[i].ns, "DAV:")) {
+ p.ns->prefix = "D";
+ } else {
+ p.ns->prefix = cx_asprintf_a(a, "x%d", (int)i).ptr;
+ }
+ p.value = NULL;
+ cxListAdd(proplist, &p);
+ }
+
+ CxBuffer *rqbuf = create_propfind_request(res->session, proplist, "propfind", 0);
+ int ret = dav_propfind(res->session, res, rqbuf);
+ cxBufferFree(rqbuf);
+ cxMempoolDestroy(mp);
+ return ret;
+}
+
+
+static void init_hash_stream(HashStream *hstr, void *stream, dav_read_func readfn, dav_seek_func seekfn) {
+ hstr->sha = NULL;
+ hstr->stream = stream;
+ hstr->read = readfn;
+ hstr->seek = seekfn;
+ hstr->error = 0;
+}
+
+static size_t dav_read_h(void *buf, size_t size, size_t nelm, void *stream) {
+ HashStream *s = stream;
+ if(!s->sha) {
+ s->sha = dav_hash_init();
+ }
+
+ size_t r = s->read(buf, size, nelm, s->stream);
+ dav_hash_update(s->sha, buf, r);
+ return r;
+}
+
+static int dav_seek_h(void *stream, long offset, int whence) {
+ HashStream *s = stream;
+ if(offset == 0 && whence == SEEK_SET) {
+ unsigned char buf[DAV_SHA256_DIGEST_LENGTH];
+ dav_hash_final(s->sha, buf);
+ s->sha = NULL;
+ } else {
+ s->error = 1;
+ }
+ return s->seek(s->stream, offset, whence);
+}
+
+
+int dav_store(DavResource *res) {
+ DavSession *sn = res->session;
+ DavResourceData *data = res->data;
+
+ util_set_url(sn, dav_resource_get_href(res));
+
+ DavLock *lock = dav_get_lock(sn, res->path);
+ char *locktoken = lock ? lock->token : NULL;
+
+ // store content
+ if(data->content) {
+ curl_easy_setopt(sn->handle, CURLOPT_XFERINFOFUNCTION, dav_session_put_progress);
+ curl_easy_setopt(sn->handle, CURLOPT_XFERINFODATA, res);
+ curl_easy_setopt(sn->handle, CURLOPT_NOPROGRESS, 0L);
+
+ int encryption = DAV_ENCRYPT_CONTENT(sn) && sn->key;
+ CURLcode ret;
+ if(encryption) {
+ AESEncrypter *enc = NULL;
+ CxBuffer *buf = NULL;
+ if(data->read) {
+ enc = aes_encrypter_new(
+ sn->key,
+ data->content,
+ data->read,
+ data->seek);
+ } else {
+ buf = cxBufferCreate(data->content, data->length, cxDefaultAllocator, 0);
+ buf->size = data->length;
+ enc = aes_encrypter_new(
+ sn->key,
+ buf,
+ (dav_read_func)cxBufferRead,
+ (dav_seek_func)cxBufferSeek);
+ }
+
+ // put resource
+ ret = do_put_request(
+ sn,
+ locktoken,
+ TRUE,
+ enc,
+ (dav_read_func)aes_read,
+ (dav_seek_func)aes_encrypter_reset,
+ 0);
+
+ // get sha256 hash
+ dav_get_hash(&enc->sha256, (unsigned char*)data->hash);
+ char *enc_hash = aes_encrypt(data->hash, DAV_SHA256_DIGEST_LENGTH, sn->key);
+
+ aes_encrypter_close(enc);
+ if(buf) {
+ cxBufferFree(buf);
+ }
+
+ // add crypto properties
+ // TODO: store the properties later
+ if(resource_add_crypto_info(sn, res->href, res->name, enc_hash)) {
+ free(enc_hash);
+ curl_easy_setopt(sn->handle, CURLOPT_XFERINFOFUNCTION, NULL);
+ curl_easy_setopt(sn->handle, CURLOPT_NOPROGRESS, 1L);
+ return 1;
+ }
+ resource_add_string_property(res, DAV_NS, "crypto-hash", enc_hash);
+ free(enc_hash);
+ } else if((sn->flags & DAV_SESSION_STORE_HASH) == DAV_SESSION_STORE_HASH) {
+ HashStream hstr;
+ CxBuffer *iobuf = NULL;
+ if(!data->read) {
+ iobuf = cxBufferCreate(data->content, data->length, cxDefaultAllocator, 0);
+ iobuf->size = data->length;
+ init_hash_stream(
+ &hstr,
+ iobuf,
+ (dav_read_func)cxBufferRead,
+ (dav_seek_func)cxBufferSeek);
+ } else {
+ init_hash_stream(
+ &hstr,
+ data->content,
+ data->read,
+ data->seek);
+ }
+
+ ret = do_put_request(
+ sn,
+ locktoken,
+ TRUE,
+ &hstr,
+ dav_read_h,
+ (dav_seek_func)dav_seek_h,
+ data->length);
+
+ if(hstr.sha) {
+ dav_hash_final(hstr.sha, (unsigned char*)data->hash);
+ char *hash = util_hexstr((unsigned char*)data->hash, 32);
+ dav_set_string_property_ns(res, DAV_NS, "content-hash", hash);
+ free(hash);
+ }
+ } else {
+ ret = do_put_request(
+ sn,
+ locktoken,
+ TRUE,
+ data->content,
+ data->read,
+ data->seek,
+ data->length);
+ }
+
+ curl_easy_setopt(sn->handle, CURLOPT_XFERINFOFUNCTION, NULL);
+ curl_easy_setopt(sn->handle, CURLOPT_NOPROGRESS, 1L);
+
+ long status = 0;
+ curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &status);
+ if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+ res->session->error = 0;
+ // cleanup node data
+ if(!data->read) {
+ cxFree(sn->mp->allocator, data->content);
+ }
+ data->content = NULL;
+ data->read = NULL;
+ data->length = 0;
+ } else {
+ dav_session_set_error(sn, ret, status);
+ return 1;
+ }
+ }
+
+ // generate crypto-prop content
+ if(DAV_ENCRYPT_PROPERTIES(sn) && sn->key && (data->crypto_set || data->crypto_remove)) {
+ DavResource *crypto_res = dav_resource_new_href(sn, res->href);
+ int ret = 1;
+
+ if(crypto_res) {
+ CxBuffer *rqbuf = create_cryptoprop_propfind_request();
+ ret = dav_propfind(res->session, res, rqbuf);
+ cxBufferFree(rqbuf);
+ }
+
+ if(!ret) {
+ DavXmlNode *crypto_prop_node = dav_get_property_ns(crypto_res, DAV_NS, "crypto-prop");
+ CxMap *crypto_props = parse_crypto_prop(sn, sn->key, crypto_prop_node);
+ if(!crypto_props) {
+ // resource hasn't encrypted properties yet
+ crypto_props = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32); // create new map
+ }
+
+ // remove all properties
+ if(data->crypto_remove) {
+ CxIterator i = cxListIterator(data->crypto_remove);
+ cx_foreach(DavProperty *, property, i) {
+ if(cxMapSize(crypto_props) == 0) {
+ break; // map already empty, can't remove any more
+ }
+
+ cxmutstr key = dav_property_key(property->ns->name, property->name);
+ DavProperty *existing_prop = cxMapGet(crypto_props, cx_hash_key(key.ptr, key.length));
+ if(existing_prop) {
+ // TODO: free existing_prop
+ }
+ free(key.ptr);
+ }
+ }
+
+ // set properties
+ if(data->crypto_set) {
+ CxIterator i = cxListIterator(data->crypto_set);
+ cx_foreach(DavProperty *, property, i) {
+ cxmutstr keystr = dav_property_key(property->ns->name, property->name);
+ CxHashKey key = cx_hash_key(keystr.ptr, keystr.length);
+ DavProperty *existing_prop = cxMapRemoveAndGet(crypto_props, key);
+ cxMapPut(crypto_props, key, property);
+ if(existing_prop) {
+ // TODO: free existing_prop
+ }
+ free(keystr.ptr);
+ }
+ }
+
+ DavXmlNode *crypto_prop_value = create_crypto_prop(sn, crypto_props);
+ if(crypto_prop_value) {
+ DavProperty *new_crypto_prop = createprop(sn, DAV_NS, "crypto-prop");
+ new_crypto_prop->value = crypto_prop_value;
+ add2propertylist(sn->mp->allocator, &data->set, new_crypto_prop);
+ }
+
+ dav_resource_free(crypto_res);
+ }
+
+ if(ret) {
+ return 1;
+ }
+ }
+
+ // store properties
+ int r = 0;
+ sn->error = DAV_OK;
+ if(data->set || data->remove > 0) {
+ CxBuffer *request = create_proppatch_request(data);
+ CxBuffer *response = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ //printf("request:\n%.*s\n\n", request->pos, request->space);
+
+ CURLcode ret = do_proppatch_request(sn, locktoken, request, response);
+ long status = 0;
+ curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status);
+ if(ret == CURLE_OK && status == 207) {
+ //printf("%s\n", response->space);
+ // TODO: parse response
+ // TODO: cleanup node data correctly
+ data->set = NULL;
+ data->remove = NULL;
+ } else {
+ dav_session_set_error(sn, ret, status);
+ r = -1;
+ }
+
+ cxBufferFree(request);
+ cxBufferFree(response);
+ }
+
+ return r;
+}
+
+#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32
+static void set_progressfunc(DavResource *res) {
+ CURL *handle = res->session->handle;
+ curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, dav_session_get_progress);
+ curl_easy_setopt(handle, CURLOPT_XFERINFODATA, res);
+ curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L);
+}
+
+static void unset_progressfunc(DavResource *res) {
+ CURL *handle = res->session->handle;
+ curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, NULL);
+ curl_easy_setopt(handle, CURLOPT_XFERINFODATA, NULL);
+ curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1L);
+}
+#else
+static void set_progressfunc(DavResource *res) {
+
+}
+static void unset_progressfunc(DavResource *res) {
+
+}
+#endif
+
+int dav_get_content(DavResource *res, void *stream, dav_write_func write_fnc) {
+ DavSession *sn = res->session;
+ CURL *handle = sn->handle;
+ util_set_url(res->session, dav_resource_get_href(res));
+
+ // check encryption
+ AESDecrypter *dec = NULL;
+ DavKey *key = NULL;
+ if(DAV_DECRYPT_CONTENT(sn)) {
+ char *keyname = dav_get_string_property_ns(res, DAV_NS, "crypto-key");
+ if(keyname) {
+ key = dav_context_get_key(sn->context, keyname);
+ if(key) {
+ dec = aes_decrypter_new(key, stream, write_fnc);
+ stream = dec;
+ write_fnc = (dav_write_func)aes_write;
+ }
+ }
+ }
+
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, NULL);
+ curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL);
+ curl_easy_setopt(handle, CURLOPT_PUT, 0L);
+ curl_easy_setopt(handle, CURLOPT_UPLOAD, 0L);
+
+ curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_fnc);
+ curl_easy_setopt(handle, CURLOPT_WRITEDATA, stream);
+
+ if(sn->get_progress) {
+ set_progressfunc(res);
+ }
+
+ long status = 0;
+ CURLcode ret = dav_session_curl_perform(sn, &status);
+
+ if(sn->get_progress) {
+ unset_progressfunc(res);
+ }
+
+ char *hash = NULL;
+ if(dec) {
+ aes_decrypter_shutdown(dec); // get final bytes
+
+ // get hash
+ unsigned char sha[DAV_SHA256_DIGEST_LENGTH];
+ dav_get_hash(&dec->sha256, sha);
+ hash = util_hexstr(sha, DAV_SHA256_DIGEST_LENGTH);
+
+ aes_decrypter_close(dec);
+ }
+
+ if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+ int verify_failed = 0;
+ if(DAV_DECRYPT_CONTENT(sn) && key) {
+ // try to verify the content
+ char *res_hash = dav_get_string_property_ns(res, DAV_NS, "crypto-hash");
+
+ if(res_hash) {
+ size_t len = 0;
+ char *dec_hash = aes_decrypt(res_hash, &len, key);
+ char *hex_hash = util_hexstr((unsigned char*)dec_hash, len);
+ if(strcmp(hash, hex_hash)) {
+ verify_failed = 1;
+ }
+ free(dec_hash);
+ free(hex_hash);
+ }
+ }
+ if(hash) {
+ free(hash);
+ }
+
+ if(verify_failed) {
+ res->session->error = DAV_CONTENT_VERIFICATION_ERROR;
+ return 1;
+ }
+
+ res->session->error = DAV_OK;
+ return 0;
+ } else {
+ if(hash) {
+ free(hash);
+ }
+ dav_session_set_error(res->session, ret, status);
+ return 1;
+ }
+}
+
+DavResource* dav_create_child(DavResource *parent, char *name) {
+ DavResource *res = dav_resource_new_child(parent->session, parent, name);
+ if(dav_create(res)) {
+ dav_resource_free(res);
+ return NULL;
+ } else {
+ return res;
+ }
+}
+
+int dav_delete(DavResource *res) {
+ CURL *handle = res->session->handle;
+ util_set_url(res->session, dav_resource_get_href(res));
+
+ DavLock *lock = dav_get_lock(res->session, res->path);
+ char *locktoken = lock ? lock->token : NULL;
+
+ CxBuffer *response = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ CURLcode ret = do_delete_request(res->session, locktoken, response);
+ long status = 0;
+ curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+ int r = 0;
+ if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+ res->session->error = DAV_OK;
+ res->exists = 0;
+
+ // TODO: parse response
+ // TODO: free res
+ } else {
+ dav_session_set_error(res->session, ret, status);
+ r = 1;
+ }
+
+ cxBufferFree(response);
+ return r;
+}
+
+static int create_ancestors(DavSession *sn, char *href, char *path) {
+ CURL *handle = sn->handle;
+ CURLcode code;
+
+ DavLock *lock = dav_get_lock(sn, path);
+ char *locktoken = lock ? lock->token : NULL;
+
+ long status = 0;
+ int ret = 0;
+
+ if(strlen(path) <= 1) {
+ return 0;
+ }
+
+ char *p = util_parent_path(path);
+ char *h = util_parent_path(href);
+
+ for(int i=0;i<2;i++) {
+ util_set_url(sn, h);
+ code = do_mkcol_request(sn, locktoken);
+ curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status);
+ if(status == 201) {
+ // resource successfully created
+ char *name = (char*)util_resource_name(p);
+ int len = strlen(name);
+ if(name[len - 1] == '/') {
+ name[len - 1] = '\0';
+ }
+ if(resource_add_crypto_info(sn, h, name, NULL)) {
+ sn->error = DAV_ERROR;
+ dav_session_set_errstr(sn, "Cannot set crypto properties for ancestor");
+ }
+ break;
+ } else if(status == 405) {
+ // parent already exists
+ break;
+ } else if(status == 409) {
+ // parent doesn't exist
+ if(create_ancestors(sn, h, p)) {
+ ret = 1;
+ break;
+ }
+ } else {
+ dav_session_set_error(sn, code, status);
+ ret = 1;
+ break;
+ }
+ }
+
+ free(p);
+ free(h);
+ return ret;
+}
+
+static int create_resource(DavResource *res, int *status) {
+ DavSession *sn = res->session;
+ CURL *handle = sn->handle;
+ util_set_url(sn, dav_resource_get_href(res));
+
+ DavLock *lock = dav_get_lock(res->session, res->path);
+ char *locktoken = lock ? lock->token : NULL;
+
+ CURLcode code;
+ if(res->iscollection) {
+ code = do_mkcol_request(sn, locktoken);
+ } else {
+ code = do_put_request(sn, locktoken, TRUE, "", NULL, NULL, 0);
+ }
+ long s = 0;
+ curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &s);
+ *status = s;
+ if(code == CURLE_OK && (s >= 200 && s < 300)) {
+ sn->error = DAV_OK;
+ // if the session has encrypted file names, add crypto infos
+ if(!resource_add_crypto_info(sn, res->href, res->name, NULL)) {
+ // do a minimal propfind request
+ CxBuffer *rqbuf = create_propfind_request(sn, NULL, "propfind", 0);
+ int ret = dav_propfind(sn, res, rqbuf);
+ cxBufferFree(rqbuf);
+ return ret;
+ } else {
+ return 1;
+ }
+ } else {
+ dav_session_set_error(sn, code, s);
+ return 1;
+ }
+}
+
+int dav_create(DavResource *res) {
+ int status;
+ if(!create_resource(res, &status)) {
+ // resource successfully created
+ res->exists = 1;
+ return 0;
+ }
+
+ if(status == 403 || status == 409 || status == 404) {
+ // create intermediate collections
+ if(create_ancestors(res->session, res->href, res->path)) {
+ return 1;
+ }
+ }
+
+ return create_resource(res, &status);
+}
+
+int dav_exists(DavResource *res) {
+ if(!dav_load_prop(res, NULL, 0)) {
+ res->exists = 1;
+ return 1;
+ } else {
+ if(res->session->error == DAV_NOT_FOUND) {
+ res->exists = 0;
+ }
+ return 0;
+ }
+}
+
+static int dav_cp_mv_url(DavResource *res, char *desturl, _Bool copy, _Bool override) {
+ DavSession *sn = res->session;
+ CURL *handle = sn->handle;
+ util_set_url(sn, dav_resource_get_href(res));
+
+ DavLock *lock = dav_get_lock(sn, res->path);
+ char *locktoken = lock ? lock->token : NULL;
+
+ CURLcode ret = do_copy_move_request(sn, desturl, locktoken, copy, override);
+
+ long status = 0;
+ curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+ if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+ return 0;
+ } else {
+ dav_session_set_error(sn, ret, status);
+ return 1;
+ }
+}
+
+static int dav_cp_mv(DavResource *res, char *newpath, _Bool copy, _Bool override) {
+ char *dest = dav_session_get_href(res->session, newpath);
+ char *desturl = util_get_url(res->session, dest);
+ dav_session_free(res->session, dest);
+
+ int ret = dav_cp_mv_url(res, desturl, copy, override);
+ free(desturl);
+ return ret;
+}
+
+int dav_copy(DavResource *res, char *newpath) {
+ return dav_cp_mv(res, newpath, true, false);
+}
+
+int dav_move(DavResource *res, char *newpath) {
+ return dav_cp_mv(res, newpath, false, false);
+}
+
+int dav_copy_o(DavResource *res, char *newpath, DavBool override) {
+ return dav_cp_mv(res, newpath, true, override);
+}
+
+int dav_move_o(DavResource *res, char *newpath, DavBool override) {
+ return dav_cp_mv(res, newpath, false, override);
+}
+
+int dav_copyto(DavResource *res, char *url, DavBool override) {
+ return dav_cp_mv_url(res, url, true, override);
+}
+
+int dav_moveto(DavResource *res, char *url, DavBool override) {
+ return dav_cp_mv_url(res, url, false, override);
+}
+
+int dav_lock(DavResource *res) {
+ return dav_lock_t(res, 0);
+}
+
+int dav_lock_t(DavResource *res, time_t timeout) {
+ DavSession *sn = res->session;
+ CURL *handle = sn->handle;
+ util_set_url(sn, dav_resource_get_href(res));
+
+ CxBuffer *request = create_lock_request();
+ CxBuffer *response = cxBufferCreate(NULL, 512, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ CURLcode ret = do_lock_request(sn, request, response, timeout);
+
+ //printf("\nlock\n");
+ //printf("%.*s\n\n", request->size, request->space);
+ //printf("%.*s\n\n", response->size, response->space);
+
+ cxBufferFree(request);
+
+ long status = 0;
+ curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+ if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+ LockDiscovery lock;
+ int parse_error = parse_lock_response(sn, response, &lock);
+ cxBufferFree(response);
+ if(parse_error) {
+ sn->error = DAV_ERROR;
+ return -1;
+ }
+
+ DavLock *l = dav_create_lock(sn, lock.locktoken, lock.timeout);
+ free(lock.locktoken);
+ free(lock.timeout);
+
+ int r = 0;
+ if(res->iscollection) {
+ r = dav_add_collection_lock(sn, res->path, l);
+ } else {
+ r = dav_add_resource_lock(sn, res->path, l);
+ }
+
+ if(r == 0) {
+ return 0;
+ } else {
+ (void)dav_unlock(res);
+ sn->error = DAV_ERROR;
+ dav_destroy_lock(sn, l);
+ return -1;
+ }
+ } else {
+ dav_session_set_error(sn, ret, status);
+ cxBufferFree(response);
+ return -1;
+ }
+}
+
+int dav_unlock(DavResource *res) {
+ DavSession *sn = res->session;
+ CURL *handle = sn->handle;
+ util_set_url(sn, dav_resource_get_href(res));
+
+ DavLock *lock = dav_get_lock(res->session, res->path);
+ if(!lock) {
+ sn->error = DAV_ERROR;
+ return -1;
+ }
+
+ CURLcode ret = do_unlock_request(sn, lock->token);
+ long status = 0;
+ curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+ if(ret == CURLE_OK && (status >= 200 && status < 300)) {
+ dav_remove_lock(sn, res->path, lock);
+ } else {
+ dav_session_set_error(sn, ret, status);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int resource_add_crypto_info(DavSession *sn, const char *href, const char *name, const char *hash) {
+ if(!DAV_IS_ENCRYPTED(sn)) {
+ return 0;
+ }
+
+ CxBuffer *request = create_crypto_proppatch_request(sn, sn->key, name, hash);
+ CxBuffer *response = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+
+ util_set_url(sn, href);
+ // TODO: lock
+ CURLcode ret = do_proppatch_request(sn, NULL, request, response);
+ cxBufferFree(request);
+ long status = 0;
+ curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status);
+ if(ret == CURLE_OK && status == 207) {
+ // TODO: parse response
+ sn->error = DAV_OK;
+ cxBufferFree(response);
+ return 0;
+ } else {
+ dav_session_set_error(sn, ret, status);
+ cxBufferFree(response);
+ return 1;
+ }
+}
+
+/* ----------------------------- crypto-prop ----------------------------- */
+
+DavXmlNode* create_crypto_prop(DavSession *sn, CxMap *properties) {
+ if(!sn->key) {
+ return NULL;
+ }
+
+ CxBuffer *content = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+
+ // create an xml document containing all properties
+ CxMap *nsmap = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8);
+ cxDefineDestructor(nsmap, free);
+ cxMapPut(nsmap, cx_hash_key_str("DAV:"), strdup("D"));
+
+ cxBufferPutString(content, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ cxBufferPutString(content, "<D:prop xmlns:D=\"DAV:\">\n");
+
+ CxIterator i = cxMapIteratorValues(properties);
+ cx_foreach(DavProperty*, prop, i) {
+ DavXmlNode pnode;
+ pnode.type = DAV_XML_ELEMENT;
+ pnode.namespace = prop->ns->name;
+ pnode.name = prop->name;
+ pnode.prev = NULL;
+ pnode.next = NULL;
+ pnode.children = prop->value;
+ pnode.parent = NULL;
+ pnode.attributes = NULL;
+ pnode.content = NULL;
+ pnode.contentlength = 0;
+
+ dav_print_node(content, (cx_write_func)cxBufferWrite, nsmap, &pnode);
+ cxBufferPut(content, '\n');
+ }
+
+ cxBufferPutString(content, "</D:prop>");
+
+ cxMapDestroy(nsmap);
+
+ // encrypt xml document
+ char *crypto_prop_content = aes_encrypt(content->space, content->size, sn->key);
+ cxBufferDestroy(content);
+
+ DavXmlNode *ret = NULL;
+ if(crypto_prop_content) {
+ ret = dav_text_node(sn, crypto_prop_content);
+ free(crypto_prop_content);
+ }
+ return ret;
+}
+
+CxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node) {
+ if(!node || node->type != DAV_XML_TEXT || node->contentlength == 0) {
+ return NULL;
+ }
+
+ return parse_crypto_prop_str(sn, key, node->content);
+}
+
+CxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content) {
+ size_t len = 0;
+ char *dec_str = aes_decrypt(content, &len, key);
+
+ xmlDoc *doc = xmlReadMemory(dec_str, len, NULL, NULL, 0);
+ free(dec_str);
+ if(!doc) {
+ return NULL;
+ }
+
+ int err = 0;
+ xmlNode *xml_root = xmlDocGetRootElement(doc);
+ if(xml_root) {
+ if(
+ !xml_root->ns ||
+ !xstreq(xml_root->name, "prop") ||
+ !xstreq(xml_root->ns->href, "DAV:"))
+ {
+ err = 1;
+ }
+ } else {
+ err = 1;
+ }
+
+ if(err) {
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+
+ // ready to get the properties
+ CxMap *map = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
+ xmlNode *n = xml_root->children;
+ while(n) {
+ if(n->type == XML_ELEMENT_NODE && n->ns && n->ns->href) {
+ DavProperty *property = dav_session_malloc(sn, sizeof(DavProperty));
+ property->name = dav_session_strdup(sn, (const char*)n->name);
+ property->ns = dav_session_malloc(sn, sizeof(DavNamespace));
+ property->ns->name = dav_session_strdup(sn, (const char*)n->ns->href);
+ property->ns->prefix = n->ns->prefix ?
+ dav_session_strdup(sn, (const char*)n->ns->prefix) : NULL;
+ property->value = n->children ? dav_convert_xml(sn, n->children) : NULL;
+
+ cxmutstr propkey = dav_property_key(property->ns->name, property->name);
+ cxMapPut(map, cx_hash_key_cxstr(propkey), property);
+ cx_strfree(&propkey);
+ }
+ n = n->next;
+ }
+
+ xmlFreeDoc(doc);
+ if(cxMapSize(map) == 0) {
+ cxMapDestroy(map);
+ return NULL;
+ }
+ return map;
+}
+
+
+/* ----------------------------- streams ----------------------------- */
+
+static size_t in_write(const char *ptr, size_t size, size_t nitems, void *in_stream) {
+ DavInputStream *in = in_stream;
+ size_t len = size * nitems;
+
+ if(in->alloc < len) {
+ char *newb = realloc(in->buffer, len);
+ if(!newb) {
+ if(in->buffer) free(in->buffer);
+ in->eof = 1;
+ return 0;
+ }
+
+ in->buffer = newb;
+ in->alloc = len;
+ }
+
+ memcpy(in->buffer, ptr, len);
+
+ in->size = len;
+ in->pos = 0;
+
+ return nitems;
+}
+
+/*
+DavInputStream* dav_inputstream_open(DavResource *res) {
+ DavSession *sn = res->session;
+
+ DavInputStream *in = dav_session_malloc(sn, sizeof(DavInputStream));
+ if(!in) {
+ return NULL;
+ }
+ memset(in, 0, sizeof(DavInputStream));
+
+ in->res = res;
+
+ in->c = curl_easy_duphandle(sn->handle);
+ char *url = util_get_url(sn, dav_resource_get_href(res));
+ curl_easy_setopt(in->c, CURLOPT_URL, url);
+ free(url);
+
+ in->m = curl_multi_init();
+
+ curl_easy_setopt(in->c, CURLOPT_HTTPHEADER, NULL);
+ curl_easy_setopt(in->c, CURLOPT_CUSTOMREQUEST, NULL);
+ curl_easy_setopt(in->c, CURLOPT_PUT, 0L);
+ curl_easy_setopt(in->c, CURLOPT_UPLOAD, 0L);
+
+ curl_multi_add_handle(in->m, in->c);
+
+ dav_write_func write_fnc = (dav_write_func)in_write;
+ void *stream = in;
+
+ // check encryption
+ AESDecrypter *dec = NULL;
+ DavKey *key = NULL;
+ if(DAV_DECRYPT_CONTENT(sn)) {
+ char *keyname = dav_get_string_property_ns(res, DAV_NS, "crypto-key");
+ if(keyname) {
+ key = dav_context_get_key(sn->context, keyname);
+ if(key) {
+ dec = aes_decrypter_new(key, stream, write_fnc);
+ stream = dec;
+ write_fnc = (dav_write_func)aes_write;
+ }
+ }
+ }
+
+ curl_easy_setopt(in->c, CURLOPT_WRITEFUNCTION, write_fnc);
+ curl_easy_setopt(in->c, CURLOPT_WRITEDATA, stream);
+
+ in->dec = dec;
+
+ return in;
+}
+
+size_t dav_read(void *buf, size_t size, size_t nitems, DavInputStream *in) {
+ size_t len = in->size - in->pos;
+ size_t rl = size * nitems;
+ if(len > 0) {
+ len = rl > len ? len : rl;
+ len -= len % size;
+ memcpy(buf, in->buffer + in->pos, len);
+ in->pos += len;
+ return len / size;
+ }
+ in->size = 0;
+
+ if(in->eof) {
+ if(in->dec) {
+ aes_decrypter_shutdown(in->dec); // get final bytes
+ aes_decrypter_close(in->dec);
+ in->dec = NULL;
+ } else {
+ return 0;
+ }
+ } else {
+ int running;
+ while(!in->eof && in->size == 0) {
+ CURLMcode r = curl_multi_perform(in->m, &running);
+ if(r != CURLM_OK || running == 0) {
+ in->eof = 1;
+ break;
+ }
+
+ int numfds;
+ if(curl_multi_poll(in->m, NULL, 0, 5000, &numfds) != CURLM_OK) {
+ in->eof = 1;
+ }
+ }
+ }
+
+ return in->size > 0 ? dav_read(buf, size, nitems, in) : 0;
+}
+
+void dav_inputstream_close(DavInputStream *in) {
+ curl_multi_cleanup(in->m);
+ curl_easy_cleanup(in->c);
+ if(in->buffer) free(in->buffer);
+ dav_session_free(in->res->session, in);
+}
+
+
+static size_t out_read(char *ptr, size_t size, size_t nitems, void *out_stream) {
+ DavOutputStream *out = out_stream;
+ size_t len = size * nitems;
+ size_t available = out->size - out->pos;
+ if(available == 0) {
+ return 0;
+ }
+
+ size_t r = len > available ? available : len;
+ r -= r % size;
+ memcpy(ptr, out->buffer + out->pos, r);
+
+ out->pos += r;
+
+ return r / size;
+}
+
+static size_t dummy_write(void *buf, size_t s, size_t n, void *data) {
+ return s*n;
+}
+
+DavOutputStream* dav_outputstream_open(DavResource *res) {
+ DavSession *sn = res->session;
+
+ DavOutputStream *out = dav_session_malloc(sn, sizeof(DavOutputStream));
+ if(!out) {
+ return NULL;
+ }
+ memset(out, 0, sizeof(DavOutputStream));
+
+ out->res = res;
+
+ out->c = curl_easy_duphandle(sn->handle);
+ char *url = util_get_url(sn, dav_resource_get_href(res));
+ curl_easy_setopt(out->c, CURLOPT_URL, url);
+ free(url);
+
+ out->m = curl_multi_init();
+ curl_multi_add_handle(out->m, out->c);
+
+ void *stream = out;
+ dav_read_func read_fnc = (dav_read_func)out_read;
+
+ // if encryption or hashing in enabled, we need a stream wrapper
+ if(DAV_ENCRYPT_CONTENT(sn) && sn->key) {
+ AESEncrypter *enc = aes_encrypter_new(sn->key, out, (dav_read_func)out_read, NULL);
+ out->enc = enc;
+ stream = enc;
+ read_fnc = (dav_read_func)aes_read;
+ } else if((sn->flags & DAV_SESSION_STORE_HASH) == DAV_SESSION_STORE_HASH) {
+ HashStream *hstr = dav_session_malloc(sn, sizeof(HashStream));
+ out->hstr = hstr;
+ init_hash_stream(hstr, out, (dav_read_func)out_read, NULL);
+ stream = hstr;
+ read_fnc = (dav_read_func)dav_read_h;
+ }
+
+ curl_easy_setopt(out->c, CURLOPT_HEADERFUNCTION, NULL);
+ curl_easy_setopt(out->c, CURLOPT_HTTPHEADER, NULL);
+ curl_easy_setopt(out->c, CURLOPT_CUSTOMREQUEST, NULL);
+ curl_easy_setopt(out->c, CURLOPT_PUT, 1L);
+ curl_easy_setopt(out->c, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt(out->c, CURLOPT_READFUNCTION, read_fnc);
+ curl_easy_setopt(out->c, CURLOPT_READDATA, stream);
+ curl_easy_setopt(out->c, CURLOPT_SEEKFUNCTION, NULL);
+ curl_easy_setopt(out->c, CURLOPT_INFILESIZE, -1);
+ curl_easy_setopt(out->c, CURLOPT_INFILESIZE_LARGE, -1L);
+
+ curl_easy_setopt(out->c, CURLOPT_WRITEFUNCTION, dummy_write);
+ curl_easy_setopt(out->c, CURLOPT_WRITEDATA, NULL);
+
+ return out;
+}
+
+size_t dav_write(const void *buf, size_t size, size_t nitems, DavOutputStream *out) {
+ if(out->eof) return 0;
+
+ out->buffer = buf;
+ out->size = size * nitems;
+ out->pos = 0;
+
+ int running;
+ while(!out->eof && (out->size == 0 || out->size - out->pos > 0)) {
+ CURLMcode r = curl_multi_perform(out->m, &running);
+ if(r != CURLM_OK || running == 0) {
+ out->eof = 1;
+ break;
+ }
+
+ int numfds;
+ if(curl_multi_poll(out->m, NULL, 0, 5000, &numfds) != CURLM_OK) {
+ out->eof = 1;
+ }
+ }
+
+ return (out->size - out->pos) / size;
+}
+
+int dav_outputstream_close(DavOutputStream *out) {
+ DavSession *sn = out->res->session;
+ DavResource *res = out->res;
+ DavResourceData *data = res->data;
+
+ int ret = 0;
+
+ dav_write(NULL, 1, 0, out);
+
+ curl_multi_cleanup(out->m);
+ curl_easy_cleanup(out->c);
+
+ int store = 0;
+ if(out->enc) {
+ // get sha256 hash
+ char hash[32];
+ dav_get_hash(&out->enc->sha256, (unsigned char*)data->hash);
+ aes_encrypter_close(out->enc);
+ char *enc_hash = aes_encrypt(hash, DAV_SHA256_DIGEST_LENGTH, sn->key);
+ // add crypto properties
+ if(resource_add_crypto_info(sn, out->res->href, out->res->name, enc_hash)) {
+ ret = 1;
+ }
+ free(enc_hash);
+ } else if(out->hstr) {
+ dav_hash_final(out->hstr->sha, (unsigned char*)data->hash);
+ char *hash = util_hexstr((unsigned char*)data->hash, 32);
+ dav_set_string_property_ns(res, DAV_NS, "content-hash", hash);
+ free(hash);
+ dav_session_free(sn, out->hstr);
+ store = 1;
+ }
+
+ if(store) {
+ ret = dav_store(out->res);
+ }
+
+ dav_session_free(out->res->session, out);
+
+ return ret;
+}
+
+*/
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef RESOURCE_H
+#define RESOURCE_H
+
+#include "webdav.h"
+#include "crypto.h"
+#include <cx/string.h>
+#include <cx/hash_key.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DavResourceData DavResourceData;
+
+struct DavResourceData {
+ CxMap *properties;
+ CxList *set;
+ CxList *remove;
+ CxList *crypto_set;
+ CxList *crypto_remove;
+
+ /*
+ * properties encapsulated in a crypto-prop property or NULL
+ */
+ CxMap *crypto_properties;
+
+ /*
+ * char* or stream
+ */
+ void *content;
+ /*
+ * if NULL, content is a char*
+ */
+ dav_read_func read;
+ /*
+ * curl seek func
+ */
+ dav_seek_func seek;
+ /*
+ * content length
+ */
+ size_t length;
+
+ /*
+ * sha256 content hash
+ */
+ char hash[32];
+};
+
+/*
+ * read wrapper with integrated hashing
+ */
+typedef struct {
+ DAV_SHA_CTX *sha;
+ void *stream;
+ dav_read_func read;
+ dav_seek_func seek;
+ int error;
+} HashStream;
+
+struct DavInputStream {
+ DavResource *res;
+ CURLM *m;
+ CURL *c;
+ AESDecrypter *dec;
+ char *buffer;
+ size_t alloc;
+ size_t size;
+ size_t pos;
+ int eof;
+};
+
+struct DavOutputStream {
+ DavResource *res;
+ CURLM *m;
+ CURL *c;
+ AESEncrypter *enc;
+ HashStream *hstr;
+ const char *buffer;
+ size_t size;
+ size_t pos;
+ int eof;
+};
+
+DavResource* dav_resource_new_full(DavSession *sn, const char *parent_path, const char *name, char *href);
+
+void resource_free_properties(DavSession *sn, CxMap *properties);
+
+void resource_set_href(DavResource *res, cxstring href);
+
+void resource_set_info(DavResource *res, const char *href_str);
+DavResourceData* resource_data_new(DavSession *sn);
+void resource_add_property(DavResource *res, const char *ns, const char *name, xmlNode *val);
+void resource_set_crypto_properties(DavResource *res, CxMap *cprops);
+DavXmlNode* resource_get_property(DavResource *res, const char *ns, const char *name);
+DavXmlNode* resource_get_encrypted_property(DavResource *res, const char *ns, const char *name);
+DavXmlNode* resource_get_property_k(DavResource *res, CxHashKey key);
+DavXmlNode* resource_get_encrypted_property_k(DavResource *res, CxHashKey key);
+void resource_add_child(DavResource *parent, DavResource *child);
+void resource_add_ordered_child(DavResource *parent, DavResource *child, CxList *ordercr);
+int resource_add_crypto_info(DavSession *sn, const char *href, const char *name, const char *hash);
+
+cxmutstr dav_property_key_a(const CxAllocator *a, const char *ns, const char *name);
+
+DavXmlNode* create_crypto_prop(DavSession *sn, CxMap *properties);
+CxMap* parse_crypto_prop(DavSession *sn, DavKey *key, DavXmlNode *node);
+CxMap* parse_crypto_prop_str(DavSession *sn, DavKey *key, const char *content);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RESOURCE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cx/buffer.h>
+#include <cx/mempool.h>
+#include <cx/hash_map.h>
+
+#include "utils.h"
+#include "session.h"
+#include "resource.h"
+#include "methods.h"
+
+DavSession* dav_session_new(DavContext *context, char *base_url) {
+ if(!base_url) {
+ return NULL;
+ }
+ cxstring url = cx_str(base_url);
+ if(url.length == 0) {
+ return NULL;
+ }
+ DavSession *sn = malloc(sizeof(DavSession));
+ memset(sn, 0, sizeof(DavSession));
+ sn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE);
+ sn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE);
+ sn->key = NULL;
+ sn->errorstr = NULL;
+ sn->error = DAV_OK;
+ sn->flags = 0;
+
+ dav_session_set_baseurl(sn, base_url);
+
+ sn->handle = curl_easy_init();
+ curl_easy_setopt(sn->handle, CURLOPT_FOLLOWLOCATION, 1L);
+
+ // lock manager is created on-demand
+ sn->locks = NULL;
+
+ // set proxy
+ DavProxy *proxy = cx_strprefix(url, CX_STR("https")) ? context->https_proxy
+ : context->http_proxy;
+
+ if (proxy->url) {
+ curl_easy_setopt(sn->handle, CURLOPT_PROXY, proxy->url);
+ if (proxy->username) {
+ curl_easy_setopt(sn->handle, CURLOPT_PROXYUSERNAME,
+ proxy->username);
+ if (proxy->password) {
+ curl_easy_setopt(sn->handle, CURLOPT_PROXYPASSWORD,
+ proxy->password);
+ } else {
+ // TODO: prompt
+ }
+ }
+ if(proxy->no_proxy) {
+ curl_easy_setopt(sn->handle, CURLOPT_NOPROXY,
+ proxy->no_proxy);
+ }
+ }
+
+ // set url
+#if LIBCURL_VERSION_NUM >= 0x072D00
+ curl_easy_setopt(sn->handle, CURLOPT_DEFAULT_PROTOCOL, "http");
+#endif
+ curl_easy_setopt(sn->handle, CURLOPT_URL, base_url);
+
+ // add to context
+ dav_context_add_session(context, sn);
+ sn->context = context;
+
+ return sn;
+}
+
+DavSession* dav_session_new_auth(
+ DavContext *context,
+ char *base_url,
+ char *user,
+ char *password)
+{
+ DavSession *sn = dav_session_new(context, base_url);
+ if(!sn) {
+ return NULL;
+ }
+ dav_session_set_auth(sn, user, password);
+ return sn;
+}
+
+DavSession* dav_session_clone(DavSession *sn) {
+ CURL *newhandle = curl_easy_duphandle(sn->handle);
+
+ DavSession *newsn = malloc(sizeof(DavSession));
+ memset(newsn, 0, sizeof(DavSession));
+ newsn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE);
+ newsn->pathcache = cxHashMapCreate(newsn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE);
+ newsn->key = sn->key;
+ newsn->errorstr = NULL;
+ newsn->error = DAV_OK;
+ newsn->flags = 0;
+
+ newsn->handle = newhandle;
+
+ newsn->base_url = cx_strdup_a(newsn->mp->allocator, cx_str(sn->base_url)).ptr;
+ newsn->auth_prompt = sn->auth_prompt;
+ newsn->authprompt_userdata = sn->authprompt_userdata;
+ newsn->logfunc = sn->logfunc;
+ newsn->get_progress = sn->get_progress;
+ newsn->put_progress = sn->put_progress;
+ newsn->progress_userdata = sn->progress_userdata;
+
+ // add to context
+ dav_context_add_session(sn->context, newsn);
+ newsn->context = sn->context;
+
+ return newsn;
+}
+
+void dav_session_set_auth(DavSession *sn, const char *user, const char *password) {
+ if(user && password) {
+ dav_session_set_auth_s(sn, cx_str(user), cx_str(password));
+ }
+}
+
+void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password) {
+ if(user.length > 0 && password.length > 0) {
+ size_t upwdlen = user.length + password.length + 2;
+ char *upwdbuf = malloc(upwdlen);
+ snprintf(upwdbuf, upwdlen, "%.*s:%.*s", (int)user.length, user.ptr, (int)password.length, password.ptr);
+ curl_easy_setopt(sn->handle, CURLOPT_USERPWD, upwdbuf);
+ free(upwdbuf);
+ }
+}
+
+void dav_session_set_baseurl(DavSession *sn, char *base_url) {
+ const CxAllocator *a = sn->mp->allocator;
+ if(sn->base_url) {
+ cxFree(a, sn->base_url);
+ }
+
+ cxstring url = cx_str(base_url);
+ if(url.ptr[url.length - 1] == '/') {
+ cxmutstr url_m = cx_strdup_a(a, cx_str(base_url));
+ sn->base_url = url_m.ptr;
+ } else {
+ char *url_str = cxMalloc(a, url.length + 2);
+ memcpy(url_str, base_url, url.length);
+ url_str[url.length] = '/';
+ url_str[url.length + 1] = '\0';
+ sn->base_url = url_str;
+ }
+}
+
+void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags) {
+ sn->key = key;
+ // TODO: review sanity
+ if(flags != 0) {
+ sn->flags |= flags;
+ } else {
+ sn->flags |= DAV_SESSION_ENCRYPT_CONTENT;
+ }
+}
+
+void dav_session_set_authcallback(DavSession *sn, dav_auth_func func, void *userdata) {
+ sn->auth_prompt = func;
+ sn->authprompt_userdata = userdata;
+}
+
+void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav_progress_func put, void *userdata) {
+ sn->get_progress = get;
+ sn->put_progress = put;
+ sn->progress_userdata = userdata;
+}
+
+CURLcode dav_session_curl_perform(DavSession *sn, long *status) {
+ return dav_session_curl_perform_buf(sn, NULL, NULL, status);
+}
+
+CURLcode dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response, long *status) {
+ CURLcode ret = curl_easy_perform(sn->handle);
+ long http_status;
+ curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
+ if(ret == CURLE_OK) {
+ if(sn->logfunc) {
+ char *log_method;
+ char *log_url;
+ curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &log_url);
+ curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_METHOD , &log_method);
+ char *log_reqbody = NULL;
+ size_t log_reqbodylen = 0;
+ char *log_rpbody = NULL;
+ size_t log_rpbodylen = 0;
+ if(request) {
+ log_reqbody = request->space;
+ log_reqbodylen = request->size;
+ }
+ if(response) {
+ log_rpbody = response->space;
+ log_rpbodylen = response->size;
+ }
+ sn->logfunc(sn, log_method, log_url, log_reqbody, log_reqbodylen, http_status, log_rpbody, log_rpbodylen);
+ }
+
+ if(http_status == 401 && sn->auth_prompt) {
+ if(!sn->auth_prompt(sn, sn->authprompt_userdata)) {
+ if(request) {
+ cxBufferSeek(request, 0, SEEK_SET);
+ }
+ if(response) {
+ cxBufferSeek(response, 0, SEEK_SET);
+ }
+ ret = curl_easy_perform(sn->handle);
+ curl_easy_getinfo(sn->handle, CURLINFO_RESPONSE_CODE, &http_status);
+ }
+ }
+
+ }
+
+ if(status) {
+ *status = http_status;
+ }
+ return ret;
+}
+
+int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
+ DavResource *res = clientp;
+ DavSession *sn = res->session;
+ if(sn->get_progress) {
+ sn->get_progress(res, (int64_t)dltotal, (int64_t)dlnow, sn->progress_userdata);
+ }
+ return 0;
+}
+
+int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
+ DavResource *res = clientp;
+ DavSession *sn = res->session;
+ if(sn->put_progress) {
+ sn->put_progress(res, (int64_t)ultotal, (int64_t)ulnow, sn->progress_userdata);
+ }
+ return 0;
+}
+
+void dav_session_set_error(DavSession *sn, CURLcode c, int status) {
+ if(status > 0) {
+ switch(status) {
+ default: {
+ switch(c) {
+ default: sn->error = DAV_ERROR;
+ }
+ break;
+ }
+ case 401: sn->error = DAV_UNAUTHORIZED; break;
+ case 403: sn->error = DAV_FORBIDDEN; break;
+ case 404: sn->error = DAV_NOT_FOUND; break;
+ case 405: sn->error = DAV_METHOD_NOT_ALLOWED; break;
+ case 407: sn->error = DAV_PROXY_AUTH_REQUIRED; break;
+ case 409: sn->error = DAV_CONFLICT; break;
+ case 412: sn->error = DAV_PRECONDITION_FAILED; break;
+ case 413: sn->error = DAV_REQUEST_ENTITY_TOO_LARGE; break;
+ case 414: sn->error = DAV_REQUEST_URL_TOO_LONG; break;
+ case 423: sn->error = DAV_LOCKED; break;
+ case 511: sn->error = DAV_NET_AUTH_REQUIRED; break;
+ }
+ } else {
+ switch(c) {
+ case CURLE_UNSUPPORTED_PROTOCOL: sn->error = DAV_UNSUPPORTED_PROTOCOL; break;
+ case CURLE_COULDNT_RESOLVE_PROXY: sn->error = DAV_COULDNT_RESOLVE_PROXY; break;
+ case CURLE_COULDNT_RESOLVE_HOST: sn->error = DAV_COULDNT_RESOLVE_HOST; break;
+ case CURLE_COULDNT_CONNECT: sn->error = DAV_COULDNT_CONNECT; break;
+ case CURLE_OPERATION_TIMEDOUT: sn->error = DAV_TIMEOUT; break;
+ case CURLE_SSL_CONNECT_ERROR:
+ case CURLE_PEER_FAILED_VERIFICATION:
+ case CURLE_SSL_ENGINE_NOTFOUND:
+ case CURLE_SSL_ENGINE_SETFAILED:
+ case CURLE_SSL_CERTPROBLEM:
+ case CURLE_SSL_CIPHER:
+//#ifndef CURLE_SSL_CACERT
+// case CURLE_SSL_CACERT:
+//#endif
+ case CURLE_SSL_CACERT_BADFILE:
+ case CURLE_SSL_SHUTDOWN_FAILED:
+ case CURLE_SSL_CRL_BADFILE:
+ case CURLE_SSL_ISSUER_ERROR: sn->error = DAV_SSL_ERROR; break;
+ default: sn->error = DAV_ERROR; break;
+ }
+ }
+ if(c != CURLE_OK) {
+ dav_session_set_errstr(sn, curl_easy_strerror(c));
+ } else {
+ dav_session_set_errstr(sn, NULL);
+ }
+}
+
+void dav_session_set_errstr(DavSession *sn, const char *str) {
+ if(sn->errorstr) {
+ dav_session_free(sn, sn->errorstr);
+ }
+ char *errstr = NULL;
+ if(str) {
+ errstr = dav_session_strdup(sn, str);
+ }
+ sn->errorstr = errstr;
+}
+
+void dav_session_destroy(DavSession *sn) {
+ // remove session from context
+ if (dav_context_remove_session(sn->context, sn)) {
+ fprintf(stderr, "Error: session not found in ctx->sessions\n");
+ dav_session_destructor(sn);
+ }
+}
+
+void dav_session_destructor(DavSession *sn) {
+ cxMempoolDestroy(sn->mp);
+ curl_easy_cleanup(sn->handle);
+ free(sn);
+}
+
+
+void* dav_session_malloc(DavSession *sn, size_t size) {
+ return cxMalloc(sn->mp->allocator, size);
+}
+
+void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size) {
+ return cxCalloc(sn->mp->allocator, nelm, size);
+}
+
+void* dav_session_realloc(DavSession *sn, void *ptr, size_t size) {
+ return cxRealloc(sn->mp->allocator, ptr, size);
+}
+
+void dav_session_free(DavSession *sn, void *ptr) {
+ cxFree(sn->mp->allocator, ptr);
+}
+
+char* dav_session_strdup(DavSession *sn, const char *str) {
+ return cx_strdup_a(sn->mp->allocator, cx_str((char*)str)).ptr;
+}
+
+
+char* dav_session_create_plain_href(DavSession *sn, const char *path) {
+ if(!DAV_ENCRYPT_NAME(sn) && !DAV_DECRYPT_NAME(sn)) {
+ // non encrypted file names
+ char *url = util_path_to_url(sn, path);
+ char *href = dav_session_strdup(sn, util_url_path(url));
+ free(url);
+ return href;
+ } else {
+ return NULL;
+ }
+}
+
+char* dav_session_get_href(DavSession *sn, const char *path) {
+ if(DAV_DECRYPT_NAME(sn) || DAV_ENCRYPT_NAME(sn)) {
+ cxstring p = cx_str(path);
+ CxBuffer href;
+ CxBuffer pbuf;
+ cxBufferInit(&href, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ cxBufferInit(&pbuf, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+
+ int start = 0;
+ int begin = 0;
+
+ // check path cache
+ char *cp = strdup(path);
+ //printf("cp: %s\n", cp);
+ while(strlen(cp) > 1) {
+ char *cached = cxMapGet(sn->pathcache, cx_hash_key_str(cp));
+ if(cached) {
+ start = strlen(cp);
+ begin = start;
+ cxBufferPutString(&href, cached);
+ break;
+ } else {
+ // check, if the parent path is cached
+ char *f = cp;
+ cp = util_parent_path(cp);
+ free(f);
+ }
+ }
+ free(cp);
+ if(href.pos == 0) {
+ // if there are no cached elements we have to add the base url path
+ // to the href buffer
+ cxBufferPutString(&href, util_url_path(sn->base_url));
+ }
+
+ // create resource for name lookup
+ cxmutstr rp = cx_strdup(cx_strn(path, start));
+ DavResource *root = dav_resource_new(sn, rp.ptr);
+ free(rp.ptr);
+ resource_set_href(root, cx_strn(href.space, href.pos));
+
+ // create request buffer for propfind requests
+ CxBuffer *rqbuf = create_basic_propfind_request();
+
+ cxstring remaining = cx_strsubs(p, start);
+ CxStrtokCtx elms = cx_strtok(remaining, CX_STR("/"), INT_MAX);
+ DavResource *res = root;
+ cxBufferPutString(&pbuf, res->path);
+ // iterate over all remaining path elements
+ cxstring elm;
+ while(cx_strtok_next(&elms, &elm)) {
+ if(elm.length > 0) {
+ //printf("elm: %.*s\n", elm.length, elm.ptr);
+ DavResource *child = dav_find_child(sn, res, rqbuf, elm.ptr);
+
+ // if necessary add a path separator
+ if(pbuf.space[pbuf.pos-1] != '/') {
+ if(href.space[href.pos-1] != '/') {
+ cxBufferPut(&href, '/');
+ }
+ cxBufferPut(&pbuf, '/');
+ }
+ // add last path/href to the cache
+ cxstring pp = cx_strn(pbuf.space, pbuf.size);
+ cxstring hh = cx_strn(href.space, href.size);
+ dav_session_cache_path(sn, pp, hh);
+
+ cxBufferWrite(elm.ptr, 1, elm.length, &pbuf);
+ if(child) {
+ // href is already URL encoded, so don't encode again
+ cxBufferPutString(&href, util_resource_name(child->href));
+ res = child;
+ } else if(DAV_ENCRYPT_NAME(sn)) {
+ char *random_name = util_random_str();
+ cxBufferPutString(&href, random_name);
+ free(random_name);
+ } else {
+ // path is not URL encoded, so we have to do this here
+ cxstring resname = cx_str(util_resource_name((const char*)path));
+ // the name of collections ends with
+ // a trailing slash, which MUST NOT be encoded
+ if(resname.ptr[resname.length-1] == '/') {
+ char *esc = curl_easy_escape(sn->handle,
+ resname.ptr, resname.length-1);
+ cxBufferWrite(esc, 1, strlen(esc), &href);
+ cxBufferPut(&href, '/');
+ curl_free(esc);
+ } else {
+ char *esc = curl_easy_escape(sn->handle,
+ resname.ptr, resname.length);
+ cxBufferWrite(esc, 1, strlen(esc), &href);
+ curl_free(esc);
+ }
+ }
+ }
+ }
+
+ // if necessary add a path separator
+ if(p.ptr[p.length-1] == '/') {
+ if(href.space[href.pos-1] != '/') {
+ cxBufferPut(&href, '/');
+ }
+ cxBufferPut(&pbuf, '/');
+ }
+ // add the final path to the cache
+ cxstring pp = cx_strn(pbuf.space, pbuf.size);
+ cxstring hh = cx_strn(href.space, href.size);
+ dav_session_cache_path(sn, pp, hh);
+
+ cxmutstr href_str = cx_strdup_a(
+ sn->mp->allocator,
+ cx_strn(href.space, href.size));
+
+ // cleanup
+ dav_resource_free_all(root);
+ cxBufferFree(rqbuf);
+
+ cxBufferDestroy(&pbuf);
+ cxBufferDestroy(&href);
+
+ return href_str.ptr;
+ } else {
+ return dav_session_create_plain_href(sn, path);
+ }
+}
+
+DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf, const char *name) {
+ if(res && !dav_propfind(sn, res, rqbuf)) {
+ DavResource *child = res->children;
+ while(child) {
+ if(!strcmp(child->name, name)) {
+ return child;
+ }
+ child = child->next;
+ }
+ }
+ return NULL;
+}
+
+void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href) {
+ CxHashKey path_key = cx_hash_key(path.ptr, path.length);
+ char *elm = cxMapGet(sn->pathcache, path_key);
+ if(!elm) {
+ cxmutstr href_s = cx_strdup_a(sn->mp->allocator, href);
+ cxMapPut(sn->pathcache, path_key, href_s.ptr);
+ }
+}
+
+
+DavLock* dav_create_lock(DavSession *sn, const char *token, char *timeout) {
+ DavLock *lock = dav_session_malloc(sn, sizeof(DavLock));
+ lock->path = NULL;
+ lock->token = dav_session_strdup(sn, token);
+
+ // TODO: timeout
+
+ return lock;
+}
+
+void dav_destroy_lock(DavSession *sn, DavLock *lock) {
+ dav_session_free(sn, lock->token);
+ if(lock->path) {
+ dav_session_free(sn, lock->path);
+ }
+ dav_session_free(sn, lock);
+}
+
+
+static int dav_lock_cmp(void const *left, void const *right) {
+ const DavLock *l = left;
+ const DavLock *r = right;
+ return strcmp(l->path, r->path);
+}
+
+static int create_lock_manager(DavSession *sn) {
+ // create lock manager
+ DavLockManager *locks = cxMalloc(sn->mp->allocator, sizeof(DavLockManager));
+ locks->resource_locks = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, 16);
+ locks->collection_locks = cxLinkedListCreate(sn->mp->allocator, dav_lock_cmp, CX_STORE_POINTERS);
+ sn->locks = locks;
+ return 0;
+}
+
+static DavLockManager* get_lock_manager(DavSession *sn) {
+ DavLockManager *locks = sn->locks;
+ if(!locks) {
+ if(create_lock_manager(sn)) {
+ return NULL;
+ }
+ locks = sn->locks;
+ }
+ return locks;
+}
+
+int dav_add_resource_lock(DavSession *sn, const char *path, DavLock *lock) {
+ DavLockManager *locks = get_lock_manager(sn);
+ if(!locks) {
+ return -1;
+ }
+
+ CxHashKey path_key = cx_hash_key_str(path);
+ if(cxMapGet(locks->resource_locks, path_key)) {
+ return -1;
+ }
+
+ cxMapPut(locks->resource_locks, path_key, lock);
+ return 0;
+}
+
+int dav_add_collection_lock(DavSession *sn, const char *path, DavLock *lock) {
+ DavLockManager *locks = get_lock_manager(sn);
+ if(!locks) {
+ return -1;
+ }
+
+ lock->path = dav_session_strdup(sn, path);
+ cxListAdd(locks->collection_locks, lock);
+ cxListSort(locks->collection_locks);
+
+ return 0;
+}
+
+DavLock* dav_get_lock(DavSession *sn, const char *path) {
+ DavLockManager *locks = get_lock_manager(sn);
+ if(!locks) {
+ return NULL;
+ }
+
+ cxstring p = cx_str(path);
+
+ DavLock *lock = cxMapGet(locks->resource_locks, cx_hash_key(p.ptr, p.length));
+ if(lock) {
+ return lock;
+ }
+
+ CxIterator i = cxListIterator(locks->collection_locks);
+ cx_foreach(DavLock*, col_lock, i) {
+ int cmd = strcmp(path, col_lock->path);
+ if(cmd == 0) {
+ return col_lock;
+ } else if(cx_strprefix(p, cx_str(col_lock->path))) {
+ return col_lock;
+ } else if(cmd > 0) {
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+void dav_remove_lock(DavSession *sn, const char *path, DavLock *lock) {
+ DavLockManager *locks = get_lock_manager(sn);
+ if(!locks) {
+ return;
+ }
+
+ if(cxMapRemoveAndGet(locks->resource_locks, cx_hash_key_str(path))) {
+ return;
+ }
+
+ cxListFindRemove(locks->collection_locks, lock);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DAV_SESSION_H
+#define DAV_SESSION_H
+
+#include <cx/buffer.h>
+#include "webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// initial size of the session mempool
+#define DAV_SESSION_MEMPOOL_SIZE 1024
+// initial size of the path cache map
+#define DAV_PATH_CACHE_SIZE 32
+
+#define DAV_ENCRYPT_NAME(sn) \
+ (((sn)->flags & DAV_SESSION_ENCRYPT_NAME) == DAV_SESSION_ENCRYPT_NAME)
+
+#define DAV_DECRYPT_NAME(sn) \
+ (((sn)->flags & DAV_SESSION_DECRYPT_NAME) == DAV_SESSION_DECRYPT_NAME)
+
+#define DAV_ENCRYPT_CONTENT(sn) \
+ (((sn)->flags & DAV_SESSION_ENCRYPT_CONTENT) == DAV_SESSION_ENCRYPT_CONTENT)
+
+#define DAV_DECRYPT_CONTENT(sn) \
+ (((sn)->flags & DAV_SESSION_DECRYPT_CONTENT) == DAV_SESSION_DECRYPT_CONTENT)
+
+#define DAV_IS_ENCRYPTED(sn) \
+ (DAV_ENCRYPT_NAME(sn) || DAV_ENCRYPT_CONTENT(sn))
+
+#define DAV_CRYPTO(sn) \
+ (DAV_ENCRYPT_NAME(sn) || DAV_DECRYPT_NAME(sn) || \
+ DAV_ENCRYPT_CONTENT(sn) || DAV_DECRYPT_CONTENT(sn))
+
+#define DAV_ENCRYPT_PROPERTIES(sn) \
+ (((sn)->flags & DAV_SESSION_ENCRYPT_PROPERTIES) == DAV_SESSION_ENCRYPT_PROPERTIES)
+
+#define DAV_DECRYPT_PROPERTIES(sn) \
+ (((sn)->flags & DAV_SESSION_DECRYPT_PROPERTIES) == DAV_SESSION_DECRYPT_PROPERTIES)
+
+/*
+typedef struct DavPathCacheElement {
+ char *name;
+ char *encrypted_name;
+ int exists;
+} DavPathCacheElement;
+*/
+
+typedef struct DavLock DavLock;
+struct DavLock {
+ char *path;
+ char *token;
+};
+
+typedef struct DavLockManager {
+ CxMap *resource_locks;
+ CxList *collection_locks;
+} DavLockManager;
+
+CURLcode dav_session_curl_perform(DavSession *sn, long *status);
+CURLcode dav_session_curl_perform_buf(DavSession *sn, CxBuffer *request, CxBuffer *response, long *status);
+
+int dav_session_get_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
+int dav_session_put_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
+
+void dav_session_set_error(DavSession *sn, CURLcode c, int status);
+void dav_session_set_errstr(DavSession *sn, const char *str);
+
+char* dav_session_create_plain_href(DavSession *sn, const char *path);
+
+char* dav_session_get_href(DavSession *sn, const char *path);
+
+DavResource* dav_find_child(DavSession *sn, DavResource *res, CxBuffer *rqbuf, const char *name);
+
+void dav_session_cache_path(DavSession *sn, cxstring path, cxstring href);
+
+
+DavLock* dav_create_lock(DavSession *sn, const char *token, char *timeout);
+void dav_destroy_lock(DavSession *sn, DavLock *lock);
+
+int dav_add_resource_lock(DavSession *sn, const char *path, DavLock *lock);
+int dav_add_collection_lock(DavSession *sn, const char *path, DavLock *lock);
+
+DavLock* dav_get_lock(DavSession *sn, const char *path);
+void dav_remove_lock(DavSession *sn, const char *path, DavLock *lock);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAV_SESSION_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/printf.h>
+#include <libxml/tree.h>
+#include <curl/curl.h>
+
+#ifdef _WIN32
+#include <conio.h>
+#define getpasswordchar() getch()
+#define IS_PATH_SEPARATOR(c) (c == '/' || c == '\\')
+#define PATH_SEPARATOR '\\'
+#else
+#include <unistd.h>
+#include <spawn.h>
+#include <sys/wait.h>
+#include <termios.h>
+#define getpasswordchar() getchar()
+#define IS_PATH_SEPARATOR(c) (c == '/')
+#define PATH_SEPARATOR '/'
+#endif
+
+#include "webdav.h"
+#include "utils.h"
+#include "crypto.h"
+#include "session.h"
+
+/*
+#include <openssl/hmac.h>
+#include <openssl/evp.h>
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/rand.h>
+*/
+
+static size_t extractval(cxstring str, char *result, char delim) {
+ size_t n = 0;
+ for(size_t i = 0; i < str.length ; i++) {
+ if(isdigit(str.ptr[i])) {
+ result[n++] = str.ptr[i];
+ } else if(str.ptr[i] != delim) {
+ return 0;
+ }
+ }
+ result[n] = '\0';
+ return n;
+}
+
+static time_t parse_iso8601(char *iso8601str) {
+
+ // safety
+ if(!iso8601str) {
+ return 0;
+ }
+
+ // local vars
+ struct tm tparts;
+ memset(&tparts, 0, sizeof(struct tm));
+ long val;
+ char conv[16];
+
+ // work on the trimmed string
+ cxstring date = cx_strtrim(cx_str(iso8601str));
+
+ cxstring time = cx_strchr(date, 'T');
+ if(time.length == 0) {
+ return 0;
+ }
+ date.length = time.ptr - date.ptr;
+ time.ptr++; time.length--;
+
+ cxstring tzinfo;
+ if((tzinfo = cx_strchr(time, 'Z')).length > 0 ||
+ (tzinfo = cx_strchr(time, '+')).length > 0 ||
+ (tzinfo = cx_strchr(time, '-')).length > 0) {
+
+ time.length = tzinfo.ptr - time.ptr;
+ }
+
+ // parse date
+ if((date.length != 8 && date.length != 10)
+ || extractval(date, conv , '-') != 8) {
+ return 0;
+ }
+ val = atol(conv);
+ if(val < 19000000L) {
+ return 0;
+ }
+ tparts.tm_mday = val % 100;
+ tparts.tm_mon = (val % 10000) / 100 - 1;
+ tparts.tm_year = val / 10000 - 1900;
+
+ // parse time and skip possible fractional seconds
+ cxstring frac;
+ if((frac = cx_strchr(time, '.')).length > 0 ||
+ (frac = cx_strchr(time, ',')).length > 0) {
+ time.length = frac.ptr - time.ptr;
+ }
+ if((time.length != 6 && time.length != 8)
+ || extractval(time, conv , ':') != 6) {
+ return 0;
+ }
+ val = atol(conv);
+ tparts.tm_sec = val % 100;
+ tparts.tm_min = (val % 10000) / 100;
+ tparts.tm_hour = val / 10000;
+
+
+ // parse time zone (if any)
+ if(tzinfo.length == 0) {
+ // local time
+ tparts.tm_isdst = -1;
+ return mktime(&tparts);
+ } else if(!cx_strcmp(tzinfo, cx_str("Z"))) {
+#if defined(__FreeBSD__)
+ return timegm(&tparts);
+#elif defined(_WIN32)
+ return _mkgmtime(&tparts);
+#else
+ return mktime(&tparts) - timezone;
+#endif
+ } else if(tzinfo.ptr[0] == '+' || tzinfo.ptr[0] == '-') {
+ int sign = (tzinfo.ptr[0] == '+') ? -1 : 1;
+
+ if(tzinfo.length > 6) {
+ return 0;
+ } else {
+ tzinfo.ptr++; tzinfo.length--;
+ extractval(tzinfo, conv, ':');
+ val = atol(conv);
+ val = 60 * (val / 100) + (val % 100);
+#if defined(__FreeBSD__)
+ return timegm(&tparts) + (time_t) (60 * val * sign);
+#elif defined(_WIN32)
+ return _mkgmtime(&tparts) + (time_t)(60 * val * sign);
+#else
+ return mktime(&tparts) - timezone + (time_t) (60 * val * sign);
+#endif
+ }
+ } else {
+ return 0;
+ }
+}
+
+
+time_t util_parse_creationdate(char *str) {
+ // parse a ISO-8601 date (rfc-3339)
+ // example: 2012-11-29T21:35:35Z
+ if(!str) {
+ return 0;
+ }
+
+ return parse_iso8601(str);
+}
+
+time_t util_parse_lastmodified(char *str) {
+ // parse a rfc-1123 date
+ // example: Thu, 29 Nov 2012 21:35:35 GMT
+ if(!str) {
+ return 0;
+ } else {
+ time_t result = curl_getdate(str, NULL);
+ if(result == -1) {
+ // fall back to the ISO-8601 format (e.g. Microsoft Sharepoint
+ // illegally uses this format for lastmodified, but also some
+ // users might want to give an ISO-8601 date)
+ return util_parse_creationdate(str);
+ } else {
+ return result;
+ }
+ }
+}
+
+int util_getboolean(const char *v) {
+ if(v[0] == 'T' || v[0] == 't') {
+ return 1;
+ }
+ return 0;
+}
+
+int util_strtouint(const char *str, uint64_t *value) {
+ if (str == NULL || *str == '\0') return 0;
+ char *end;
+ errno = 0;
+ uint64_t val = strtoull(str, &end, 0);
+ if(errno == 0 && *end == '\0') {
+ *value = val;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+int util_strtoint(const char *str, int64_t *value) {
+ if (str == NULL || *str == '\0') return 0;
+ char *end;
+ errno = 0;
+ int64_t val = strtoll(str, &end, 0);
+ if(errno == 0 && *end == '\0') {
+ *value = val;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+int util_szstrtouint(const char *str, uint64_t *value) {
+ if (str == NULL || *str == '\0') return 0;
+ char *end;
+ errno = 0;
+ size_t len = strlen(str);
+ uint64_t val = strtoull(str, &end, 0);
+ if(errno != 0) {
+ return 0;
+ } if(end == str+len) {
+ *value = val;
+ return 1;
+ } else if(end == str+len-1) {
+ uint64_t mul = 1;
+ switch(end[0]) {
+ case 'k':
+ case 'K': mul = 1024; break;
+ case 'm':
+ case 'M': mul = 1024*1024; break;
+ case 'g':
+ case 'G': mul = 1024*1024*1024; break;
+ default: return 0;
+ }
+
+ uint64_t result = 0;
+ if(util_uint_mul(val, mul, &result)) {
+ return 0;
+ }
+ *value = result;
+ return 1;
+ }
+ return 0;
+}
+
+int util_uint_mul(uint64_t a, uint64_t b, uint64_t *result) {
+ if(a == 0 || b == 0) {
+ *result = 0;
+ return 0;
+ }
+ uint64_t r = a * b;
+ if(r / b == a) {
+ *result = r;
+ return 0;
+ } else {
+ *result = 0;
+ return 1;
+ }
+}
+
+cxstring util_url_base_s(cxstring url) {
+ size_t i = 0;
+ if(url.length > 0) {
+ int slmax;
+ if(cx_strprefix(url, cx_str("http://"))) {
+ slmax = 3;
+ } else if(cx_strprefix(url, cx_str("https://"))) {
+ slmax = 3;
+ } else {
+ slmax = 1;
+ }
+ int slashcount = 0;
+ for(i=0;i<url.length;i++) {
+ if(url.ptr[i] == '/') {
+ slashcount++;
+ if(slashcount == slmax) {
+ i++;
+ break;
+ }
+ }
+ }
+ }
+ return cx_strsubsl(url, 0, i);
+}
+
+char* util_url_base(const char *url) {
+ return cx_strdup(util_url_base_s(cx_str(url))).ptr;
+}
+
+#ifdef _WIN32
+#define strncasecmp _strnicmp
+#endif
+
+const char* util_url_path(const char *url) {
+ return util_url_path_s(cx_str(url)).ptr;
+}
+
+cxstring util_url_path_s(cxstring url) {
+ cxstring path = { "", 0 };
+ int slashcount = 0;
+ int slmax;
+ if(url.length > 7 && !strncasecmp(url.ptr, "http://", 7)) {
+ slmax = 3;
+ } else if(url.length > 8 && !strncasecmp(url.ptr, "https://", 8)) {
+ slmax = 3;
+ } else {
+ slmax = 1;
+ }
+ char c;
+ for(int i=0;i<url.length;i++) {
+ c = url.ptr[i];
+ if(c == '/') {
+ slashcount++;
+ if(slashcount == slmax) {
+ path = cx_strsubs(url, i);
+ break;
+ }
+ }
+ }
+ return path;
+}
+
+char* util_url_decode(DavSession *sn, const char *url) {
+ char *unesc = curl_easy_unescape(sn->handle, url, strlen(url), NULL);
+ char *ret = strdup(unesc);
+ curl_free(unesc);
+ return ret;
+}
+
+static size_t util_header_callback(char *buffer, size_t size,
+ size_t nitems, void *data) {
+
+ cxstring sbuffer = cx_strn(buffer, size*nitems);
+
+ CxMap *map = (CxMap*) data;
+
+ // if we get a status line, clear the map and exit
+ if(cx_strprefix(sbuffer, cx_str("HTTP/"))) {
+ // TODO: use new map destructor ucx_map_free_content(map, free);
+ cxMapClear(map);
+ return size*nitems;
+ }
+
+ // if we get the terminating CRLF, just exit
+ if(!cx_strcmp(sbuffer, cx_str("\r\n"))) {
+ return 2;
+ }
+
+ cxstring key = sbuffer;
+ cxstring value = cx_strchr(sbuffer, ':');
+
+ if(value.length == 0) {
+ return 0; // invalid header line
+ }
+
+ key.length = value.ptr - key.ptr;
+ value.ptr++; value.length--;
+
+ cxmutstr key_cp = cx_strdup(cx_strtrim(key));
+ cx_strlower(key_cp);
+ cxmutstr value_cp = cx_strdup(cx_strtrim(value));
+
+ cxMapPut(map, cx_hash_key(key_cp.ptr, key_cp.length), value_cp.ptr);
+
+ free(key_cp.ptr);
+
+ return sbuffer.length;
+}
+
+int util_path_isrelated(const char *path1, const char *path2) {
+ cxstring p1 = cx_str(path1);
+ cxstring p2 = cx_str(path2);
+
+ if(IS_PATH_SEPARATOR(p1.ptr[p1.length-1])) {
+ p1.length--;
+ }
+ if(IS_PATH_SEPARATOR(p2.ptr[p2.length-1])) {
+ p2.length--;
+ }
+
+ if(p2.length < p1.length) {
+ return 0;
+ }
+
+ if(!cx_strcmp(p1, p2)) {
+ return 1;
+ }
+
+ if(cx_strprefix(p2, p1)) {
+ if(IS_PATH_SEPARATOR(p2.ptr[p1.length])) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+#ifdef _WIN32
+int util_path_isabsolut(const char *path) {
+ if(strlen(path) < 3) {
+ return 0;
+ }
+
+ // check if first char is A-Z or a-z
+ char c = path[0];
+ if(!((c >= 65 && c <= 90) || (c >= 97 && c <= 122))) {
+ return 0;
+ }
+
+ if(path[1] == ':' && path[2] == '\\') {
+ return 1;
+ }
+ return 0;
+}
+#else
+int util_path_isabsolut(const char *path) {
+ return path[0] == '/';
+}
+#endif
+
+char* util_path_normalize(const char *path) {
+ size_t len = strlen(path);
+ CxBuffer buf;
+ cxBufferInit(&buf, NULL, len+1, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+
+ if(path[0] == '/') {
+ cxBufferPut(&buf, '/');
+ }
+
+ int add_separator = 0;
+ int seg_start = 0;
+ for(int i=0;i<=len;i++) {
+ char c = path[i];
+ if(IS_PATH_SEPARATOR(c) || c == '\0') {
+ const char *seg_ptr = path+seg_start;
+ int seg_len = i - seg_start;
+ if(IS_PATH_SEPARATOR(seg_ptr[0])) {
+ seg_ptr++;
+ seg_len--;
+ }
+
+ if(seg_len > 0) {
+ cxstring seg = cx_strn(seg_ptr, seg_len);
+ if(!cx_strcmp(seg, CX_STR(".."))) {
+ for(int j=buf.pos;j>=0;j--) {
+ char t = j < buf.pos ? buf.space[j] : 0;
+ if(IS_PATH_SEPARATOR(t) || j == 0) {
+ buf.pos = j;
+ buf.size = j;
+ buf.space[j] = 0;
+ add_separator = IS_PATH_SEPARATOR(t) ? 1 : 0;
+ break;
+ }
+ }
+ } else if(!cx_strcmp(seg, CX_STR("."))) {
+ // ignore
+ } else {
+ if(add_separator) {
+ cxBufferPut(&buf, PATH_SEPARATOR);
+ }
+ cxBufferWrite(seg_ptr, 1, seg_len, &buf);
+ add_separator = 1;
+ }
+ }
+
+ seg_start = i;
+ }
+ }
+
+ cxBufferPut(&buf, 0);
+
+ return buf.space;
+}
+
+static char* create_relative_path(const char *abspath, const char *base) {
+ size_t path_len = strlen(abspath);
+ size_t base_len = strlen(base);
+
+ if(IS_PATH_SEPARATOR(abspath[path_len-1])) {
+ path_len--;
+ }
+ if(IS_PATH_SEPARATOR(base[base_len-1])) {
+ base_len--;
+ }
+ // get base parent
+ for(int i=base_len-1;i>=0;i--) {
+ if(IS_PATH_SEPARATOR(base[i])) {
+ base_len = i+1;
+ break;
+ }
+ }
+
+ size_t max = path_len > base_len ? base_len : path_len;
+
+ // get prefix of abspath and base
+ // this dir is the root of the link
+ size_t last_dir = 0;
+ for(size_t i=0;i<max;i++) {
+ char c = abspath[i];
+ if(c != base[i]) {
+ break;
+ } else if(IS_PATH_SEPARATOR(c)) {
+ last_dir = i;
+ }
+ }
+
+ char *ret = NULL;
+ CxBuffer out;
+ if(last_dir+1 < base_len) {
+ // base is deeper than the link root, we have to go backwards
+ size_t dircount = 0;
+ for(size_t i=last_dir+1;i<base_len;i++) {
+ if(IS_PATH_SEPARATOR(base[i])) {
+ dircount++;
+ }
+ }
+
+ cxBufferInit(&out, NULL, dircount*3+path_len-last_dir, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+
+ for(size_t i=0;i<dircount;i++) {
+ cxBufferPutString(&out, "../");
+ }
+ } else {
+ cxBufferInit(&out, NULL, path_len - last_dir, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ }
+
+ cxBufferPutString(&out, abspath + last_dir + 1);
+ cxBufferPut(&out, 0);
+
+ return out.space;
+}
+
+#ifdef _WIN32
+char* util_create_relative_path(const char *abspath, const char *base) {
+ char *abspath_converted = strdup(abspath);
+ char *base_converted = strdup(base);
+ size_t abs_len = strlen(abspath_converted);
+ size_t base_len = strlen(base_converted);
+
+ for(int i=0;i<abs_len;i++) {
+ if(abspath_converted[i] == '\\') {
+ abspath_converted[i] = '/';
+ }
+ }
+ for(int i=0;i<base_len;i++) {
+ if(base_converted[i] == '\\') {
+ base_converted[i] = '/';
+ }
+ }
+
+ char *ret = create_relative_path(abspath_converted, base_converted);
+ free(abspath_converted);
+ free(base_converted);
+ return ret;
+}
+#else
+char* util_create_relative_path(const char *abspath, const char *base) {
+ return create_relative_path(abspath, base);
+}
+#endif
+
+
+void util_capture_header(CURL *handle, CxMap* map) {
+ if(map) {
+ curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, util_header_callback);
+ curl_easy_setopt(handle, CURLOPT_HEADERDATA, map);
+ } else {
+ curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, NULL);
+ curl_easy_setopt(handle, CURLOPT_HEADERDATA, NULL);
+ }
+}
+
+const char* util_resource_name(const char *url) {
+ cxstring urlstr = cx_str(url);
+ if(urlstr.ptr[urlstr.length-1] == '/') {
+ urlstr.length--;
+ }
+ cxstring resname = cx_strrchr(urlstr, '/');
+ if(resname.length > 1) {
+ return resname.ptr+1;
+ } else {
+ return url;
+ }
+}
+
+const char* util_resource_name_c(const char *url, char pathseparator) {
+ cxstring urlstr = cx_str(url);
+ if(urlstr.ptr[urlstr.length-1] == pathseparator) {
+ urlstr.length--;
+ }
+ cxstring resname = cx_strrchr(urlstr, pathseparator);
+ if(resname.length > 1) {
+ return resname.ptr+1;
+ } else {
+ return url;
+ }
+}
+
+const char* util_path_file_name(const char *url) {
+#ifdef _WIN32
+ return util_resource_name_c(url, '\\');
+#else
+ return util_resource_name_c(url, '/');
+#endif
+}
+
+
+int util_mkdir(char *path, mode_t mode) {
+#ifdef _WIN32
+ return mkdir(path);
+#else
+ return mkdir(path, mode);
+#endif
+}
+
+char* util_concat_path(const char *url_base, const char *p) {
+ cxstring base = cx_str(url_base);
+ cxstring path;
+ if(p) {
+ path = cx_str((char*)p);
+ } else {
+ path = CX_STR("");
+ }
+
+ return util_concat_path_s(base, path).ptr;
+}
+
+cxmutstr util_concat_path_s(cxstring base, cxstring path) {
+ if(!path.ptr) {
+ path = CX_STR("");
+ }
+
+ int add_separator = 0;
+ if(base.length != 0 && base.ptr[base.length-1] == '/') {
+ if(path.ptr[0] == '/') {
+ base.length--;
+ }
+ } else {
+ if(path.length == 0 || path.ptr[0] != '/') {
+ add_separator = 1;
+ }
+ }
+
+ cxmutstr url;
+ if(add_separator) {
+ url = cx_strcat(3, base, CX_STR("/"), path);
+ } else {
+ url = cx_strcat(2, base, path);
+ }
+
+ return url;
+}
+
+cxmutstr util_concat_path_ext(cxstring base, cxstring path, char separator) {
+ if(!path.ptr) {
+ path = CX_STR("");
+ }
+
+ int add_separator = 0;
+ if(base.length != 0 && base.ptr[base.length-1] == separator) {
+ if(path.ptr[0] == separator) {
+ base.length--;
+ }
+ } else {
+ if(path.length == 0 || path.ptr[0] != separator) {
+ add_separator = 1;
+ }
+ }
+
+ cxmutstr url;
+ if(add_separator) {
+ url = cx_strcat(3, base, cx_strn(&separator, 1), path);
+ } else {
+ url = cx_strcat(2, base, path);
+ }
+
+ return url;
+}
+
+cxmutstr util_concat_sys_path(cxstring base, cxstring path) {
+#ifdef _WIN32
+ return util_concat_path_ext(base, path, '\\');
+#else
+ return util_concat_path_ext(base, path, '/');
+#endif
+}
+
+char* util_get_url(DavSession *sn, const char *href) {
+ cxstring base = cx_str(sn->base_url);
+ cxstring href_str = cx_str(href);
+
+ const char *base_path = util_url_path(sn->base_url);
+ base.length -= strlen(base_path);
+
+ cxmutstr url = cx_strcat(2, base, href_str);
+ return url.ptr;
+}
+
+void util_set_url(DavSession *sn, const char *href) {
+ char *url = util_get_url(sn, href);
+ curl_easy_setopt(sn->handle, CURLOPT_URL, url);
+ free(url);
+}
+
+char* util_path_to_url(DavSession *sn, const char *path) {
+ size_t pathlen = path ? strlen(path) : 0;
+ if(pathlen == 0) {
+ return strdup(sn->base_url);
+ }
+
+ CxBuffer url;
+ cxBufferInit(&url, NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+
+ // add base url
+ cxBufferWrite(sn->base_url, 1, strlen(sn->base_url), &url);
+ // remove trailing slash
+ cxBufferSeek(&url, -1, SEEK_CUR);
+
+ cxstring p = cx_strn(path, pathlen);
+
+ CxStrtokCtx tkctx = cx_strtok(p, CX_STR("/"), INT_MAX);
+ cxstring node;
+ while(cx_strtok_next(&tkctx, &node)) {
+ if(node.length > 0) {
+ char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
+ cxBufferPut(&url, '/');
+ cxBufferWrite(esc, 1, strlen(esc), &url);
+ curl_free(esc);
+ }
+ }
+
+ if(path[p.length-1] == '/') {
+ cxBufferPut(&url, '/');
+ }
+ cxBufferPut(&url, 0);
+
+ return url.space;
+}
+
+char* util_parent_path(const char *path) {
+ const char *name = util_resource_name(path);
+ size_t namelen = strlen(name);
+ size_t pathlen = strlen(path);
+ size_t parentlen = pathlen - namelen;
+ char *parent = malloc(parentlen + 1);
+ memcpy(parent, path, parentlen);
+ parent[parentlen] = '\0';
+ return parent;
+}
+
+char* util_sys_parent_path(const char *path) {
+ const char *name = util_path_file_name(path);
+ size_t namelen = strlen(name);
+ size_t pathlen = strlen(path);
+ size_t parentlen = pathlen - namelen;
+ char *parent = malloc(parentlen + 1);
+ memcpy(parent, path, parentlen);
+ parent[parentlen] = '\0';
+ return parent;
+}
+
+char* util_size_str(DavBool iscollection, uint64_t contentlength) {
+ return util_size_str2(iscollection, contentlength, contentlength, 1);
+}
+
+char* util_size_str2(DavBool iscollection, uint64_t contentlength, uint64_t dimension, int precision) {
+ char *str = malloc(16);
+ uint64_t size = contentlength;
+
+ if(iscollection) {
+ str[0] = '\0'; // currently no information for collections
+ } else if(dimension < 0x400) {
+ snprintf(str, 16, "%" PRIu64 " bytes", size);
+ } else if(dimension < 0x100000) {
+ float s = (float)size/0x400;
+ int diff = (s*100 - (int)s*100);
+ if(diff > 90) {
+ diff = 0;
+ s += 0.10f;
+ }
+ if(dimension < 0x2800 && diff != 0) {
+ // size < 10 KiB
+ snprintf(str, 16, "%.*f KiB", precision, s);
+ } else {
+ snprintf(str, 16, "%.0f KiB", s);
+ }
+ } else if(dimension < 0x40000000) {
+ float s = (float)size/0x100000;
+ int diff = (s*100 - (int)s*100);
+ if(diff > 90) {
+ diff = 0;
+ s += 0.10f;
+ }
+ if(dimension < 0xa00000 && diff != 0) {
+ // size < 10 MiB
+ snprintf(str, 16, "%.*f MiB", precision, s);
+ } else {
+ size /= 0x100000;
+ snprintf(str, 16, "%.0f MiB", s);
+ }
+ } else if(dimension < 0x1000000000ULL) {
+ float s = (float)size/0x40000000;
+ int diff = (s*100 - (int)s*100);
+ if(diff > 90) {
+ diff = 0;
+ s += 0.10f;
+ }
+ if(dimension < 0x280000000 && diff != 0) {
+ // size < 10 GiB
+ snprintf(str, 16, "%.*f GiB", precision, s);
+ } else {
+ size /= 0x40000000;
+ snprintf(str, 16, "%.0f GiB", s);
+ }
+ } else {
+ size /= 1024;
+ float s = (float)size/0x40000000;
+ int diff = (s*100 - (int)s*100);
+ if(diff > 90) {
+ diff = 0;
+ s += 0.10f;
+ }
+ if(dimension < 0x280000000 && diff != 0) {
+ // size < 10 TiB
+ snprintf(str, 16, "%.*f TiB", precision, s);
+ } else {
+ size /= 0x40000000;
+ snprintf(str, 16, "%.0f TiB", s);
+ }
+ }
+ return str;
+}
+
+char* util_date_str(time_t tm) {
+ struct tm t;
+ struct tm n;
+ time_t now = time(NULL);
+#ifdef _WIN32
+ memcpy(&t, localtime(&tm), sizeof(struct tm));
+ memcpy(&n, localtime(&now), sizeof(struct tm));
+#else
+ localtime_r(&tm, &t);
+ localtime_r(&now, &n);
+#endif /* _WIN32 */
+ char *str = malloc(16);
+ if(t.tm_year == n.tm_year) {
+ strftime(str, 16, "%b %d %H:%M", &t);
+ } else {
+ strftime(str, 16, "%b %d %Y", &t);
+ }
+ return str;
+}
+
+
+char* util_xml_get_text(const xmlNode *elm) {
+ xmlNode *node = elm->children;
+ while(node) {
+ if(node->type == XML_TEXT_NODE) {
+ return (char*)node->content;
+ }
+ node = node->next;
+ }
+ return NULL;
+}
+
+
+char* util_base64decode(const char *in) {
+ int len = 0;
+ return util_base64decode_len(in, &len);
+}
+
+#define WHITESPACE 64
+#define EQUALS 65
+#define INVALID 66
+static char b64dectable[] = {
+ 66,66,66,66,66,66,66,66,66,66,64,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+ 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,62,66,66,66,63,52,53,
+ 54,55,56,57,58,59,60,61,66,66,66,65,66,66,66, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,66,66,66,66,66,66,26,27,28,
+ 29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,66,66,
+ 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+ 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+ 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+ 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+ 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+ 66,66,66,66,66,66
+};
+char* util_base64decode_len(const char* in, int *outlen) {
+ /* code is mostly from wikibooks */
+
+ if(!in) {
+ *outlen = 0;
+ return NULL;
+ }
+
+ size_t inlen = strlen(in);
+ size_t bufsize = (inlen*3) / 4;
+ char *outbuf = malloc(bufsize+1);
+ *outlen = -1;
+
+ unsigned char *out = (unsigned char*)outbuf;
+
+ const char *end = in + inlen;
+ char iter = 0;
+ uint32_t buf = 0;
+ size_t len = 0;
+
+ while (in < end) {
+ unsigned char c = b64dectable[*in++];
+
+ switch (c) {
+ case WHITESPACE: continue; /* skip whitespace */
+ case INVALID: {
+ /* invalid input */
+ outbuf[0] = 0;
+ return outbuf;
+ }
+ case EQUALS: {
+ /* pad character, end of data */
+ in = end;
+ continue;
+ }
+ default: {
+ buf = buf << 6 | c;
+ iter++; // increment the number of iteration
+ /* If the buffer is full, split it into bytes */
+ if (iter == 4) {
+ if ((len += 3) > bufsize) {
+ /* buffer overflow */
+ outbuf[0] = 0;
+ return outbuf;
+ }
+ *(out++) = (buf >> 16) & 255;
+ *(out++) = (buf >> 8) & 255;
+ *(out++) = buf & 255;
+ buf = 0; iter = 0;
+
+ }
+ }
+ }
+ }
+
+ if (iter == 3) {
+ if ((len += 2) > bufsize) {
+ /* buffer overflow */
+ outbuf[0] = 0;
+ return outbuf;
+ }
+ *(out++) = (buf >> 10) & 255;
+ *(out++) = (buf >> 2) & 255;
+ }
+ else if (iter == 2) {
+ if (++len > bufsize) {
+ /* buffer overflow */
+ outbuf[0] = 0;
+ return outbuf;
+ }
+ *(out++) = (buf >> 4) & 255;
+ }
+
+ *outlen = len; /* modify to reflect the actual output size */
+ outbuf[len] = 0;
+ return outbuf;
+}
+
+
+static char* b64enctable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+char* util_base64encode(const char *in, size_t len) {
+ // calculate length of base64 output and create buffer
+ size_t outlen = 4 * ((len + 2) / 3);
+ int pad = len % 3;
+
+ char *out = malloc(outlen + 1);
+ out[outlen] = 0;
+ size_t pos = 0;
+
+ // encode blocks of 3 bytes
+ size_t i;
+ size_t blockend = len - pad;
+ for(i=0;i<blockend;i++) {
+ unsigned char b1 = in[i++];
+ unsigned char b2 = in[i++];
+ unsigned char b3 = in[i];
+ uint32_t inb = b1 << 16 | (b2 << 8) | b3;
+ out[pos++] = b64enctable[(inb >> 18) & 63];
+ out[pos++] = b64enctable[(inb >> 12) & 63];
+ out[pos++] = b64enctable[(inb >> 6) & 63];
+ out[pos++] = b64enctable[(inb) & 63];
+ }
+
+ // encode last bytes
+ if(pad > 0) {
+ char p[3] = {0, 0, 0};
+ for(int j=0;i<len;i++) {
+ p[j++] = in[i];
+ }
+ unsigned char b1 = p[0];
+ unsigned char b2 = p[1];
+ unsigned char b3 = p[2];
+ uint32_t inb = (b1 << 16) | (b2 << 8) | b3;
+ out[pos++] = b64enctable[(inb >> 18) & 63];
+ out[pos++] = b64enctable[(inb >> 12) & 63];
+ out[pos++] = b64enctable[(inb >> 6) & 63];
+ out[pos++] = b64enctable[(inb) & 63];
+ for(int k=outlen-1;k>=outlen-(3-pad);k--) {
+ out[k] = '=';
+ }
+ }
+
+ return out;
+}
+
+char* util_encrypt_str(DavSession *sn, const char *str, const char *key) {
+ DavKey *k = dav_context_get_key(sn->context, key);
+ if(!k) {
+ sn->error = DAV_ERROR;
+ cxmutstr err = cx_asprintf("Key %s not found", key);
+ dav_session_set_errstr(sn, err.ptr);
+ free(err.ptr);
+ return NULL;
+ }
+
+ return util_encrypt_str_k(sn, str, k);
+}
+
+char* util_encrypt_str_k(DavSession *sn, const char *str, DavKey *key) {
+ char *enc_str = aes_encrypt(str, strlen(str), key);
+ char *ret_str = dav_session_strdup(sn, enc_str);
+ free(enc_str);
+ return ret_str;
+}
+
+char* util_decrypt_str(DavSession *sn, const char *str, const char *key) {
+ DavKey *k = dav_context_get_key(sn->context, key);
+ if(!k) {
+ sn->error = DAV_ERROR;
+ cxmutstr err = cx_asprintf("Key %s not found", key);
+ dav_session_set_errstr(sn, err.ptr);
+ free(err.ptr);
+ return NULL;
+ }
+
+ return util_decrypt_str_k(sn, str, k);
+}
+
+char* util_decrypt_str_k(DavSession *sn, const char *str, DavKey *key) {
+ size_t len = 0;
+ char *dec_str = aes_decrypt(str, &len, key);
+ char *ret_str = dav_session_strdup(sn, dec_str);
+ free(dec_str);
+ return ret_str;
+}
+
+char* util_random_str() {
+ unsigned char *str = malloc(25);
+ str[24] = '\0';
+
+ cxstring t = CX_STR(
+ "01234567890"
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ const unsigned char *table = (const unsigned char*)t.ptr;
+
+#ifdef DAV_USE_OPENSSL
+ RAND_bytes(str, 24);
+#else
+ dav_rand_bytes(str, 24);
+#endif
+ for(int i=0;i<24;i++) {
+ int c = str[i] % t.length;
+ str[i] = table[c];
+ }
+
+ return (char*)str;
+}
+
+/*
+ * gets a substring from 0 to the appearance of the token
+ * tokens are separated by space
+ * sets sub to the substring and returns the remaining string
+ */
+// TODO: remove if it isn't used
+/*
+sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) {
+ int i;
+ int token_start = -1;
+ int token_end = -1;
+ for(i=0;i<=str.length;i++) {
+ int c;
+ if(i == str.length) {
+ c = ' ';
+ } else {
+ c = str.ptr[i];
+ }
+ if(c < 33) {
+ if(token_start != -1) {
+ token_end = i;
+ size_t len = token_end - token_start;
+ sstr_t tk = sstrsubsl(str, token_start, len);
+ //printf("token: {%.*s}\n", token.length, token.ptr);
+ if(!sstrcmp(tk, token)) {
+ *sub = sstrtrim(sstrsubsl(str, 0, token_start));
+ break;
+ }
+ token_start = -1;
+ token_end = -1;
+ }
+ } else {
+ if(token_start == -1) {
+ token_start = i;
+ }
+ }
+ }
+
+ if(i < str.length) {
+ return sstrtrim(sstrsubs(str, i));
+ } else {
+ str.ptr = NULL;
+ str.length = 0;
+ return str;
+ }
+}
+*/
+
+cxmutstr util_readline(FILE *stream) {
+ CxBuffer buf;
+ cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+
+ int c;
+ while((c = fgetc(stream)) != EOF) {
+ if(c == '\n') {
+ break;
+ }
+ cxBufferPut(&buf, c);
+ }
+
+ cxmutstr str = cx_strdup(cx_strtrim(cx_strn(buf.space, buf.size)));
+ cxBufferDestroy(&buf);
+ return str;
+}
+
+char* util_password_input(char *prompt) {
+ fprintf(stderr, "%s", prompt);
+ fflush(stderr);
+
+#ifndef _WIN32
+ // hide terminal input
+ struct termios oflags, nflags;
+ if(isatty(fileno(stdin))) {
+ tcgetattr(fileno(stdin), &oflags);
+ nflags = oflags;
+ nflags.c_lflag &= ~ECHO;
+ nflags.c_lflag |= ECHONL;
+ if (tcsetattr(fileno(stdin), TCSANOW, &nflags) != 0) {
+ perror("tcsetattr");
+ }
+ }
+
+#endif
+
+ // read password input
+ CxBuffer buf;
+ cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ int c = 0;
+ while((c = getpasswordchar()) != EOF) {
+ if(c == '\n' || c == '\r') {
+ break;
+ }
+ cxBufferPut(&buf, c);
+ }
+ cxBufferPut(&buf, 0);
+ fflush(stdin);
+
+#ifndef _WIN32
+ // restore terminal settings
+ if (isatty(fileno(stdin)) && tcsetattr(fileno(stdin), TCSANOW, &oflags) != 0) {
+ perror("tcsetattr");
+ }
+#endif
+
+ return buf.space;
+}
+
+int util_exec_command(char *command, CxBuffer *outbuf) {
+#ifdef _WIN32
+ fprintf(stderr, "util_exec_command unsupported\n");
+ return 1;
+#else
+
+ int pout[2];
+ if(pipe(pout)) {
+ perror("pipe");
+ return 1;
+ }
+
+ int ret = 0;
+
+ // close stdin and stderr, use pipe for stdout
+ posix_spawn_file_actions_t actions;
+ posix_spawn_file_actions_init(&actions);
+ posix_spawn_file_actions_addclose(&actions, 0);
+ posix_spawn_file_actions_adddup2(&actions, pout[1], 1);
+ posix_spawn_file_actions_addclose(&actions, 2);
+
+ char *args[4];
+ args[0] = "sh";
+ args[1] = "-c";
+ args[2] = command;
+ args[3] = NULL;
+
+ pid_t pid; // child pid
+ ret = posix_spawn(&pid, "/bin/sh", &actions, NULL, args, NULL);
+
+ close(pout[1]);
+
+ if(!ret) {
+ ssize_t r;
+ char buf[1024];
+ while((r = read(pout[0], buf, 1024)) > 0) {
+ cxBufferWrite(buf, 1, r, outbuf);
+ }
+ }
+
+ // wait for child process
+ ret = 1;
+ waitpid(pid, &ret, 0);
+
+ posix_spawn_file_actions_destroy(&actions);
+ close(pout[0]);
+
+ return ret;
+#endif
+}
+
+char* util_hexstr(const unsigned char *data, size_t len) {
+ size_t buflen = 2*len + 4;
+ CxBuffer buf;
+ cxBufferInit(&buf, NULL, buflen + 1, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ for(int i=0;i<len;i++) {
+ cx_bprintf(&buf, "%02x", data[i]);
+ }
+ cxBufferPut(&buf, 0);
+ return buf.space;
+}
+
+void util_remove_trailing_pathseparator(char *path) {
+ size_t len = strlen(path);
+ if(len < 2) {
+ return;
+ }
+
+ if(path[len-1] == '/') {
+ path[len-1] = '\0';
+ }
+}
+
+char* util_file_hash(const char *path) {
+ FILE *in = fopen(path, "r");
+ if(!in) {
+ return NULL;
+ }
+
+ DAV_SHA_CTX *sha = dav_hash_init();
+ char *buf = malloc(16384);
+
+ size_t r;
+ while((r = fread(buf, 1, 16384, in)) > 0) {
+ dav_hash_update(sha, buf, r);
+ }
+
+ unsigned char hash[DAV_SHA256_DIGEST_LENGTH];
+ dav_hash_final(sha, hash);
+ free(buf);
+ fclose(in);
+
+ return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH);
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <io.h>
+#endif /* _WIN32 */
+
+#include <sys/types.h>
+#include <libxml/tree.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+
+#include <curl/curl.h>
+#include "webdav.h"
+
+#ifdef _WIN32
+#ifndef mode_t
+#define mode_t int
+#endif
+#endif
+
+#ifndef S_IRWXG
+/* if one is not defined, the others are probably also not defined */
+#define S_IRWXU 0700
+#define S_IRWXG 070
+#define S_IRGRP 040
+#define S_IWGRP 020
+#define S_IXGRP 010
+#define S_IRWXO 07
+#define S_IROTH 04
+#define S_IWOTH 02
+#define S_IXOTH 01
+#endif /* S_IRWXG */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+time_t util_parse_creationdate(char *str);
+time_t util_parse_lastmodified(char *str);
+
+int util_mkdir(char *path, mode_t mode);
+
+char* util_url_base(const char *url);
+cxstring util_url_base_s(cxstring url);
+const char* util_url_path(const char *url);
+cxstring util_url_path_s(cxstring url);
+char* util_url_decode(DavSession *sn, const char *url);
+const char* util_resource_name(const char *url);
+const char* util_resource_name_c(const char *url, char pathseparator);
+const char* util_path_file_name(const char *url);
+
+char* util_concat_path(const char *url_base, const char *path);
+cxmutstr util_concat_path_s(cxstring url_base, cxstring path);
+cxmutstr util_concat_path_ext(cxstring url_base, cxstring path, char separator);
+cxmutstr util_concat_sys_path(cxstring base, cxstring path);
+char* util_get_url(DavSession *sn, const char *href);
+void util_set_url(DavSession *sn, const char *href);
+
+/*
+ * returns true if path1 and path2 are equal or if path2 is a child of path1
+ */
+int util_path_isrelated(const char *path1, const char *path2);
+
+int util_path_isabsolut(const char *path);
+
+char* util_path_normalize(const char *path);
+char* util_create_relative_path(const char *abspath, const char *base);
+
+void util_capture_header(CURL *handle, CxMap* map);
+
+char* util_path_to_url(DavSession *sn, const char *path);
+char* util_parent_path(const char *path);
+char* util_sys_parent_path(const char *path);
+
+char* util_size_str(DavBool iscollection, uint64_t contentlength);
+char* util_size_str2(DavBool iscollection, uint64_t contentlength, uint64_t dimension, int precision);
+char* util_date_str(time_t tm);
+
+int util_getboolean(const char *v);
+int util_strtouint(const char *str, uint64_t *value);
+int util_strtoint(const char *str, int64_t *value);
+int util_szstrtouint(const char *str, uint64_t *value);
+
+int util_uint_mul(uint64_t a, uint64_t b, uint64_t *result);
+
+char* util_xml_get_text(const xmlNode *elm);
+
+char* util_base64decode(const char *in);
+char* util_base64decode_len(const char *in, int *outlen);
+char* util_base64encode(const char *in, size_t len);
+
+char* util_encrypt_str(DavSession *sn, const char *str, const char *key);
+char* util_encrypt_str_k(DavSession *sn, const char *str, DavKey *key);
+char* util_decrypt_str(DavSession *sn, const char *str, const char *key);
+char* util_decrypt_str_k(DavSession *sn, const char *str, DavKey *key);
+
+char* util_random_str();
+
+//sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub);
+
+cxmutstr util_readline(FILE *stream);
+char* util_password_input(char *prompt);
+
+int util_exec_command(char *command, CxBuffer *outbuf);
+
+char* util_hexstr(const unsigned char *data, size_t len);
+
+void util_remove_trailing_pathseparator(char *path);
+
+char* util_file_hash(const char *path);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UTILS_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "versioning.h"
+
+#include "methods.h"
+#include "utils.h"
+#include "session.h"
+
+static int basic_deltav_op(DavResource *res, char *method) {
+ DavSession *sn = res->session;
+ CURL *handle = sn->handle;
+ util_set_url(sn, dav_resource_get_href(res));
+
+ DavLock *lock = dav_get_lock(res->session, res->path);
+ char *locktoken = lock ? lock->token : NULL;
+
+ CURLcode ret = do_simple_request(sn, method, locktoken);
+ long status = 0;
+ curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+ if(!(ret == CURLE_OK && (status >= 200 && status < 300))) {
+ dav_session_set_error(sn, ret, status);
+ return 1;
+ }
+ return 0;
+}
+
+int dav_versioncontrol(DavResource *res) {
+ return basic_deltav_op(res, "VERSION-CONTROL");
+}
+
+int dav_checkout(DavResource *res) {
+ return basic_deltav_op(res, "CHECKOUT");
+}
+
+int dav_checkin(DavResource *res) {
+ return basic_deltav_op(res, "CHECKIN");
+}
+
+int dav_uncheckout(DavResource *res) {
+ return basic_deltav_op(res, "UNCHECKOUT");
+}
+
+DavResource* dav_versiontree(DavResource *res, char *properties) {
+ DavSession *sn = res->session;
+ util_set_url(sn, dav_resource_get_href(res));
+
+ CxList *proplist = NULL;
+ if(properties) {
+ proplist = parse_properties_string(sn->context, cx_str(properties));
+
+ // check if the list already contains a D:version-name property
+ int add_vname = 1;
+ CxIterator i = cxListIterator(proplist);
+ cx_foreach(DavProperty *, p, i) {
+ if(!strcmp(p->ns->name, "DAV:") && !strcmp(p->name, "version-name")) {
+ add_vname = 0;
+ break;
+ }
+ }
+ if(add_vname) {
+ // we need at least the D:version-name prop
+ DavProperty p;
+ p.ns = dav_get_namespace(sn->context, "D");
+ p.name = strdup("version-name");
+ p.value = NULL;
+ cxListInsert(proplist, 0, &p);
+ }
+ }
+
+
+
+ // create a version-tree request, which is almost the same as propfind
+ CxBuffer *rqbuf = create_propfind_request(sn, proplist, "version-tree", 1);
+ CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+
+ // do the request
+ CURLcode ret = do_report_request(sn, rqbuf, rpbuf);
+ long status = 0;
+ curl_easy_getinfo (sn->handle, CURLINFO_RESPONSE_CODE, &status);
+ int error = 0;
+ DavResource *versions = NULL;
+ if(ret == CURLE_OK && status == 207) {
+ sn->error = DAV_OK;
+
+ // parse multistatus response
+ PropfindParser *parser = create_propfind_parser(rpbuf, NULL);
+ if(parser) {
+ DavResource *list_end = NULL;
+
+ ResponseTag response;
+ int r;
+
+ // we don't want name decryption for version resources
+ int snflags = sn->flags;
+ sn->flags = 0;
+ while((r = get_propfind_response(parser, &response)) != 0) {
+ if(r == -1) {
+ res->session->error = DAV_ERROR;
+ error = 1;
+ break;
+ }
+ DavResource *v = response2resource(sn, &response, NULL);
+ // add version to list
+ if(!versions) {
+ versions = v;
+ } else {
+ list_end->next = v;
+ }
+ list_end = v;
+
+ cleanup_response(&response);
+ }
+ sn->flags = snflags;
+
+ destroy_propfind_parser(parser);
+ } else {
+ sn->error = DAV_ERROR;
+ error = 1;
+ }
+ } else {
+ dav_session_set_error(sn, ret, status);
+ error = 1;
+ }
+
+ // cleanup
+ if(proplist) {
+ CxIterator i = cxListIterator(proplist);
+ cx_foreach(DavProperty*, p, i) {
+ free(p->name);
+ }
+ cxListDestroy(proplist);
+ }
+
+ if(error && versions) {
+ DavResource *cur = versions;
+ while(cur) {
+ DavResource *next = cur->next;
+ dav_resource_free(cur);
+ cur = next;
+ }
+ versions = NULL;
+ }
+
+ return versions;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef VERSIONING_H
+#define VERSIONING_H
+
+#include "webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* VERSIONING_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libxml/tree.h>
+
+#include "utils.h"
+#include "webdav.h"
+#include "session.h"
+#include "methods.h"
+#include <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/linked_list.h>
+#include <cx/hash_map.h>
+#include <cx/compare.h>
+#include "davqlparser.h"
+#include "davqlexec.h"
+
+
+DavContext* dav_context_new(void) {
+ // initialize
+ DavContext *context = calloc(1, sizeof(DavContext));
+ if(!context) {
+ return NULL;
+ }
+ context->sessions = cxLinkedListCreate(cxDefaultAllocator, cx_cmp_ptr, CX_STORE_POINTERS);
+ cxDefineDestructor(context->sessions, dav_session_destructor);
+ context->http_proxy = calloc(1, sizeof(DavProxy));
+ if(!context->http_proxy) {
+ dav_context_destroy(context);
+ return NULL;
+ }
+ context->https_proxy = calloc(1, sizeof(DavProxy));
+ if(!context->https_proxy) {
+ dav_context_destroy(context);
+ return NULL;
+ }
+ context->namespaces = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+ if(!context->namespaces) {
+ dav_context_destroy(context);
+ return NULL;
+ }
+ context->namespaceinfo = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+ if(!context->namespaceinfo) {
+ dav_context_destroy(context);
+ }
+ context->keys = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+ if(!context->keys) {
+ dav_context_destroy(context);
+ return NULL;
+ }
+
+ // add DAV: namespace
+ if(dav_add_namespace(context, "D", "DAV:")) {
+ dav_context_destroy(context);
+ return NULL;
+ }
+
+
+ // add idav namespace
+ if(dav_add_namespace(context, "idav", DAV_NS)) {
+ dav_context_destroy(context);
+ return NULL;
+ }
+
+ // add idavprops namespace
+ if(dav_add_namespace(context, "idavprops", DAV_PROPS_NS)) {
+ dav_context_destroy(context);
+ return NULL;
+ }
+
+ return context;
+}
+
+void dav_context_destroy(DavContext *ctx) {
+ // destroy all sessions assoziated with this context
+ // ctx->sessions destructor must be dav_session_destructor
+ cxListDestroy(ctx->sessions);
+
+ if(ctx->http_proxy) {
+ free(ctx->http_proxy);
+ }
+ if(ctx->https_proxy) {
+ free(ctx->https_proxy);
+ }
+
+ if(ctx->namespaces) {
+ CxIterator i = cxMapIteratorValues(ctx->namespaces);
+ cx_foreach(DavNamespace*, ns, i) {
+ if(!ns) continue;
+ if(ns->prefix) {
+ free(ns->prefix);
+ }
+ if(ns->name) {
+ free(ns->name);
+ }
+ free(ns);
+ }
+ cxMapDestroy(ctx->namespaces);
+ }
+ if(ctx->namespaceinfo) {
+ // TODO: implement
+ }
+ if(ctx->keys) {
+ CxIterator i = cxMapIteratorValues(ctx->keys);
+ cx_foreach(DavKey*, key, i) {
+ if(!key) continue;
+ if(key->name) {
+ free(key->name);
+ }
+ if(key->data) {
+ free(key->data);
+ }
+ free(key);
+ }
+ cxMapDestroy(ctx->keys);
+ }
+
+ free(ctx);
+}
+
+#ifndef _WIN32
+
+void dav_context_set_mtsafe(DavContext *ctx, DavBool enable) {
+ if (enable) {
+ pthread_mutex_init(&ctx->mutex, NULL);
+ } else {
+ pthread_mutex_destroy(&ctx->mutex);
+ }
+ ctx->mtsafe = enable;
+}
+
+void dav_context_lock(DavContext *ctx) {
+ if (ctx->mtsafe) {
+ pthread_mutex_lock(&ctx->mutex);
+ }
+}
+
+void dav_context_unlock(DavContext *ctx) {
+ if (ctx->mtsafe) {
+ pthread_mutex_unlock(&ctx->mutex);
+ }
+}
+
+#else
+
+void dav_context_set_mtsafe(DavContext *ctx, DavBool enable) {
+ if (enable) {
+ ctx->mutex = CreateMutex(NULL, FALSE, NULL);
+ } else {
+ CloseHandle(ctx->mutex);
+ }
+ ctx->mtsafe = enable;
+}
+
+void dav_context_lock(DavContext *ctx) {
+ if (ctx->mtsafe) {
+ WaitForSingleObject(ctx->mutex, INFINITE);
+ }
+}
+
+void dav_context_unlock(DavContext *ctx) {
+ if (ctx->mtsafe) {
+ ReleaseMutex(ctx->mutex);
+ }
+}
+
+#endif
+
+void dav_context_add_key(DavContext *context, DavKey *key) {
+ dav_context_lock(context);
+ cxMapPut(context->keys, cx_hash_key_str(key->name), key);
+ dav_context_unlock(context);
+}
+
+DavKey* dav_context_get_key(DavContext *context, const char *name) {
+ DavKey *key = NULL;
+ dav_context_lock(context);
+ if(name) {
+ key = cxMapGet(context->keys, cx_hash_key_str(name));
+ }
+ dav_context_unlock(context);
+ return key;
+}
+
+int dav_add_namespace(DavContext *context, const char *prefix, const char *name) {
+ DavNamespace *namespace = malloc(sizeof(DavNamespace));
+ if(!namespace) {
+ return 1;
+ }
+
+ char *p = strdup(prefix);
+ if (!p) {
+ free(namespace);
+ return 1;
+ }
+ char *n = strdup(name);
+ if (!n) {
+ free(namespace);
+ free(p);
+ return 1;
+ }
+
+ dav_context_lock(context);
+
+ int err = 0;
+ if(p && n) {
+ namespace->prefix = p;
+ namespace->name = n;
+ err = cxMapPut(context->namespaces, cx_hash_key_str(prefix), namespace);
+ }
+
+ if(err) {
+ free(namespace);
+ if(p) free(p);
+ if(n) free(n);
+ }
+
+ dav_context_unlock(context);
+
+ return err;
+}
+
+DavNamespace* dav_get_namespace(DavContext *context, const char *prefix) {
+ dav_context_lock(context);
+ DavNamespace *ns = cxMapGet(context->namespaces, cx_hash_key_str(prefix));
+ dav_context_unlock(context);
+ return ns;
+}
+
+DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix) {
+ dav_context_lock(context);
+ DavNamespace *ns = cxMapGet(context->namespaces, cx_hash_key(prefix.ptr, prefix.length));
+ dav_context_unlock(context);
+ return ns;
+}
+
+int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt) {
+ dav_context_lock(context);
+
+ CxHashKey hkey = cx_hash_key_str(ns);
+ DavNSInfo *info = cxMapGet(context->namespaceinfo, hkey);
+ if(!info) {
+ info = calloc(1, sizeof(DavNSInfo));
+ info->encrypt = encrypt;
+ cxMapPut(context->namespaceinfo, hkey, info);
+ } else {
+ info->encrypt = encrypt;
+ }
+
+ dav_context_unlock(context);
+ return 0;
+}
+
+int dav_namespace_is_encrypted(DavContext *context, const char *ns) {
+ int ret = 0;
+ dav_context_lock(context);
+
+ DavNSInfo *info = cxMapGet(context->namespaceinfo, cx_hash_key_str(ns));
+ if(info) {
+ ret = info->encrypt;
+ }
+ dav_context_unlock(context);
+ return ret;
+}
+
+void dav_get_property_namespace_str(
+ DavContext *ctx,
+ char *prefixed_name,
+ char **ns,
+ char **name)
+{
+ // TODO: rewrite using dav_get_property_ns
+
+ char *pname = strchr(prefixed_name, ':');
+ char *pns = "DAV:";
+ if(pname) {
+ DavNamespace *davns = dav_get_namespace_s(
+ ctx,
+ cx_strn(prefixed_name, pname-prefixed_name));
+ if(davns) {
+ pns = davns->name;
+ pname++;
+ } else {
+ pns = NULL;
+ pname = NULL;
+ }
+ } else {
+ pname = prefixed_name;
+ }
+ *ns = pns;
+ *name = pname;
+}
+
+DavNamespace* dav_get_property_namespace(
+ DavContext *ctx,
+ char *prefixed_name,
+ char **name)
+{
+ char *pname = strchr(prefixed_name, ':');
+ if(pname) {
+ DavNamespace *ns = dav_get_namespace_s(
+ ctx,
+ cx_strn(prefixed_name, pname-prefixed_name));
+ if(ns) {
+ *name = pname +1;
+ return ns;
+ } else {
+ *name = NULL;
+ return NULL;
+ }
+ } else {
+ *name = prefixed_name;
+ return dav_get_namespace_s(ctx, cx_str("D"));
+ }
+}
+
+int dav_context_add_session(DavContext *context, DavSession *sn) {
+ dav_context_lock(context);
+ int ret = cxListAdd(context->sessions, sn);
+ dav_context_unlock(context);
+ return ret;
+}
+
+int dav_context_remove_session(DavContext *context, DavSession *sn) {
+ int ret = 0;
+ dav_context_lock(context);
+ CxList *sessions = context->sessions;
+ ssize_t i = cxListFind(sessions, sn);
+ if(i >= 0) {
+ cxListRemove(sessions, i);
+ } else {
+ ret = 1;
+ }
+ dav_context_unlock(context);
+ return ret;
+}
+
+
+// TODO: add sstr_t version of dav_get_property_ns
+
+void dav_set_effective_href(DavSession *sn, DavResource *resource) {
+ char *eff_url;
+ curl_easy_getinfo(sn->handle, CURLINFO_EFFECTIVE_URL, &eff_url);
+ if(eff_url) {
+ const char *href = util_url_path(eff_url);
+ if(strcmp(href, resource->href)) {
+ dav_session_free(sn, resource->href);
+ resource->href = dav_session_strdup(sn, href);
+ }
+ }
+}
+
+DavResource* dav_get(DavSession *sn, char *path, const char *properties) {
+ CURL *handle = sn->handle;
+ DavResource *resource = dav_resource_new(sn, path);
+ util_set_url(sn, dav_resource_get_href(resource));
+
+ CxList *proplist = NULL;
+ if(properties) {
+ proplist = parse_properties_string(sn->context, cx_str(properties));
+ }
+ CxBuffer *rqbuf = create_propfind_request(sn, proplist, "propfind", 0);
+ CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+
+ //fwrite(rqbuf->space, 1, rqbuf->size, stdout);
+ //printf("\n");
+
+ CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf);
+ long status = 0;
+ curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+ if(ret == CURLE_OK && status == 207) {
+ dav_set_effective_href(sn, resource);
+
+ //printf("response\n%s\n", rpbuf->space);
+ // TODO: use PropfindParser
+ resource = parse_propfind_response(sn, resource, rpbuf);
+ resource->exists = 1;
+ sn->error = DAV_OK;
+ } else {
+ dav_session_set_error(sn, ret, status);
+ dav_resource_free(resource);
+ resource = NULL;
+ }
+
+ cxBufferFree(rqbuf);
+ cxBufferFree(rpbuf);
+
+ if(proplist) {
+ CxIterator i = cxListIterator(proplist);
+ cx_foreach(DavProperty*, p, i) {
+ free(p->name);
+ }
+ cxListDestroy(proplist);
+ }
+
+ return resource;
+}
+
+
+int dav_propfind(DavSession *sn, DavResource *root, CxBuffer *rqbuf) {
+ // clean resource properties
+ DavResourceData *data = root->data;
+ cxMapClear(data->properties); // TODO: free existing content
+
+ CURL *handle = sn->handle;
+ util_set_url(sn, dav_resource_get_href(root));
+
+ CxBuffer *rpbuf = cxBufferCreate(NULL, 4096, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ DavResource *resource = root;
+ CURLcode ret = do_propfind_request(sn, rqbuf, rpbuf);
+ long status = 0;
+ long error = 0;
+ curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status);
+ if(ret == CURLE_OK && status == 207) {
+ //printf("response\n%s\n", rpbuf->space);
+ dav_set_effective_href(sn, resource);
+ // TODO: use PropfindParser
+ resource = parse_propfind_response(sn, resource, rpbuf);
+ sn->error = DAV_OK;
+ root->exists = 1;
+ } else {
+ dav_session_set_error(sn, ret, status);
+ error = 1;
+ }
+ cxBufferFree(rpbuf);
+ return error;
+}
+
+CxList* parse_properties_string(DavContext *context, cxstring str) {
+ CxList *proplist = cxLinkedListCreateSimple(sizeof(DavProperty));
+
+ CxStrtokCtx tok = cx_strtok(str, cx_str(","), INT_MAX);
+ cxstring s;
+ while(cx_strtok_next(&tok, &s)) {
+ cxstring nsname = cx_strchr(s, ':');
+ if(nsname.length > 0) {
+ cxstring nspre = cx_strsubsl(s, 0, nsname.ptr - s.ptr);
+ nsname.ptr++;
+ nsname.length--;
+
+ DavProperty dp;
+ cxstring pre = cx_strtrim(nspre);
+ dp.ns = dav_get_namespace_s(context, pre);
+ dp.name = cx_strdup(nsname).ptr;
+ dp.value = NULL;
+ if(dp.ns && dp.name) {
+ cxListAdd(proplist, &dp);
+ } else {
+ free(dp.name);
+ }
+ }
+ }
+
+ return proplist;
+}
+
+DavResource* dav_query(DavSession *sn, char *query, ...) {
+ DavQLStatement *stmt = dav_parse_statement(cx_str(query));
+ if(!stmt) {
+ sn->error = DAV_ERROR;
+ return NULL;
+ }
+ if(stmt->errorcode != 0) {
+ sn->error = DAV_QL_ERROR;
+ dav_free_statement(stmt);
+ return NULL;
+ }
+
+ va_list ap;
+ va_start(ap, query);
+ DavResult result = dav_statement_execv(sn, stmt, ap);
+ va_end(ap);
+
+ dav_free_statement(stmt);
+
+ if(result.status == -1) {
+ if(result.result) {
+ dav_resource_free(result.result);
+ result.result = NULL;
+ }
+ }
+
+ return result.result;
+}
+
+
+
+
+void dav_verbose_log(
+ DavSession *sn,
+ const char *method,
+ const char *url,
+ const char *request_body,
+ size_t request_bodylen,
+ int status,
+ const char *response_body,
+ size_t response_bodylen)
+{
+ fprintf(stderr, "# method: %s url: %s status: %d\n", method, url, status);
+ fprintf(stderr, "# request len: %d\n%.*s\n", (int)request_bodylen, (int)request_bodylen, request_body);
+ fprintf(stderr, "# response len: %d\n%.*s\n", (int)response_bodylen, (int)response_bodylen, response_body);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WEBDAV_H
+#define WEBDAV_H
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <cx/map.h>
+#include <cx/mempool.h>
+#include <cx/linked_list.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
+#include <curl/curl.h>
+#include <libxml/tree.h>
+
+#ifndef _WIN32
+#include <pthread.h>
+#else
+#include <Windows.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef char DavBool;
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+typedef struct DavContext DavContext;
+typedef struct DavProxy DavProxy;
+typedef struct DavSession DavSession;
+typedef struct DavResource DavResource;
+typedef struct DavResult DavResult;
+typedef struct DavNamespace DavNamespace;
+typedef struct DavProperty DavProperty;
+typedef struct DavPropName DavPropName;
+typedef struct DavKey DavKey;
+typedef struct DavNSInfo DavNSInfo;
+typedef struct DavXmlNode DavXmlNode;
+typedef struct DavXmlAttr DavXmlAttr;
+
+typedef struct DavInputStream DavInputStream;
+typedef struct DavOutputStream DavOutputStream;
+
+#ifndef _WIN32
+#define DAV_MUTEX pthread_mutex_t
+#else
+#define DAV_MUTEX HANDLE
+#endif
+
+typedef size_t(*dav_read_func)(void*, size_t, size_t, void*);
+typedef size_t(*dav_write_func)(const void*, size_t, size_t, void*);
+typedef int(*dav_seek_func)(const void *, long, int);
+
+typedef int(*dav_auth_func)(DavSession *, void *);
+typedef void(*dav_progress_func)(DavResource *, int64_t, int64_t, void *);
+
+
+typedef void(*dav_rqlog_func)(
+ DavSession *sn,
+ const char *method,
+ const char *url,
+ const char *request_body,
+ size_t request_bodylen,
+ int status,
+ const char *response_body,
+ size_t response_bodylen);
+
+enum DavError {
+ DAV_OK = 0,
+ DAV_ERROR,
+ DAV_NOT_FOUND,
+ DAV_UNAUTHORIZED,
+ DAV_FORBIDDEN,
+ DAV_METHOD_NOT_ALLOWED,
+ DAV_CONFLICT,
+ DAV_LOCKED,
+ DAV_UNSUPPORTED_PROTOCOL,
+ DAV_COULDNT_RESOLVE_PROXY,
+ DAV_COULDNT_RESOLVE_HOST,
+ DAV_COULDNT_CONNECT,
+ DAV_TIMEOUT,
+ DAV_SSL_ERROR,
+ DAV_QL_ERROR,
+ DAV_CONTENT_VERIFICATION_ERROR,
+ DAV_PRECONDITION_FAILED,
+ DAV_REQUEST_ENTITY_TOO_LARGE,
+ DAV_REQUEST_URL_TOO_LONG,
+ DAV_PROXY_AUTH_REQUIRED,
+ DAV_NET_AUTH_REQUIRED
+};
+
+typedef enum DavError DavError;
+
+enum DavXmlNodeType {
+ DAV_XML_NONE = 0,
+ DAV_XML_ELEMENT,
+ DAV_XML_TEXT
+};
+
+typedef enum DavXmlNodeType DavXmlNodeType;
+
+#define DAV_SESSION_ENCRYPT_CONTENT 0x0001
+#define DAV_SESSION_ENCRYPT_NAME 0x0002
+#define DAV_SESSION_ENCRYPT_PROPERTIES 0x0004
+#define DAV_SESSION_DECRYPT_CONTENT 0x0008
+#define DAV_SESSION_DECRYPT_NAME 0x0010
+#define DAV_SESSION_DECRYPT_PROPERTIES 0x0020
+#define DAV_SESSION_STORE_HASH 0x0040
+
+#define DAV_SESSION_CONTENT_ENCRYPTION 0x0009
+#define DAV_SESSION_FULL_ENCRYPTION 0x003f
+
+
+#define DAV_NS "http://davutils.org/"
+#define DAV_PROPS_NS "http://davutils.org/props/"
+
+struct DavNamespace {
+ char *prefix;
+ char *name;
+};
+
+struct DavResource {
+ DavSession *session;
+ DavResource *prev;
+ DavResource *next;
+ DavResource *parent;
+ DavResource *children;
+ char *name;
+ char *path;
+ char *href;
+ uint64_t contentlength;
+ char *contenttype;
+ time_t creationdate;
+ time_t lastmodified;
+ void *data;
+ int iscollection;
+ int exists;
+};
+
+struct DavSession {
+ DavContext *context;
+ CURL *handle;
+ char *base_url;
+ CxMempool *mp;
+ CxMap *pathcache;
+ DavKey *key;
+ void *locks;
+ uint32_t flags;
+ DavError error;
+ char *errorstr;
+
+ int(*auth_prompt)(DavSession *sn, void *userdata);
+ void *authprompt_userdata;
+
+ dav_rqlog_func logfunc;
+
+ void(*get_progress)(DavResource *res, int64_t total, int64_t now, void *userdata);
+ void(*put_progress)(DavResource *res, int64_t total, int64_t now, void *userdata);
+ void *progress_userdata;
+};
+
+struct DavContext {
+ CxMap *namespaces;
+ CxMap *namespaceinfo;
+ CxMap *keys;
+ CxList *sessions;
+ DavProxy *http_proxy;
+ DavProxy *https_proxy;
+ DAV_MUTEX mutex;
+ DavBool mtsafe;
+};
+
+struct DavProxy {
+ char *url;
+ char *username;
+ char *password;
+ char *no_proxy;
+};
+
+struct DavProperty {
+ DavNamespace *ns;
+ char *name;
+ DavXmlNode *value;
+};
+
+struct DavPropName {
+ char *ns;
+ char *name;
+};
+
+struct DavResult {
+ DavResource *result;
+ int status;
+};
+
+#define DAV_KEY_AES128 0
+#define DAV_KEY_AES256 1
+
+struct DavKey {
+ char *name;
+ int type;
+ void *data;
+ size_t length;
+};
+
+struct DavNSInfo {
+ char *prefix;
+ DavBool encrypt;
+};
+
+struct DavXmlNode {
+ DavXmlNodeType type;
+
+ char *namespace;
+ char *name;
+
+ DavXmlNode *prev;
+ DavXmlNode *next;
+ DavXmlNode *children;
+ DavXmlNode *parent;
+
+ DavXmlAttr *attributes;
+
+ char *content;
+ size_t contentlength;
+};
+
+struct DavXmlAttr {
+ char *name;
+ char *value;
+ DavXmlAttr *next;
+};
+
+DavContext* dav_context_new(void);
+void dav_context_destroy(DavContext *ctx);
+void dav_context_set_mtsafe(DavContext *ctx, DavBool enable);
+
+void dav_context_lock(DavContext *ctx);
+void dav_context_unlock(DavContext *ctx);
+
+void dav_context_add_key(DavContext *context, DavKey *key);
+DavKey* dav_context_get_key(DavContext *context, const char *name);
+
+int dav_add_namespace(DavContext *context, const char *prefix, const char *ns);
+DavNamespace* dav_get_namespace(DavContext *context, const char *prefix);
+DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix);
+
+int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt);
+int dav_namespace_is_encrypted(DavContext *context, const char *ns);
+
+int dav_context_add_session(DavContext *context, DavSession *sn);
+int dav_context_remove_session(DavContext *context, DavSession *sn);
+
+DavSession* dav_session_new(DavContext *context, char *base_url);
+DavSession* dav_session_new_auth(
+ DavContext *context,
+ char *base_url,
+ char *user,
+ char *password);
+DavSession* dav_session_clone(DavSession *sn);
+void dav_session_set_auth(DavSession *sn, const char *user, const char *password);
+void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password);
+void dav_session_set_baseurl(DavSession *sn, char *base_url);
+void dav_session_enable_encryption(DavSession *sn, DavKey *key, int flags);
+
+void dav_session_set_authcallback(DavSession *sn, dav_auth_func func, void *userdata);
+void dav_session_set_progresscallback(DavSession *sn, dav_progress_func get, dav_progress_func put, void *userdata);
+
+void dav_session_destroy(DavSession *sn);
+
+void dav_session_destructor(DavSession *sn);
+
+void* dav_session_malloc(DavSession *sn, size_t size);
+void* dav_session_calloc(DavSession *sn, size_t nelm, size_t size);
+void* dav_session_realloc(DavSession *sn, void *ptr, size_t size);
+void dav_session_free(DavSession *sn, void *ptr);
+char* dav_session_strdup(DavSession *sn, const char *str);
+
+void dav_set_effective_href(DavSession *sn, DavResource *resource);
+DavResource* dav_get(DavSession *sn, char *path, const char *properties);
+
+CxList* parse_properties_string(DavContext *context, cxstring str);
+
+DavResource* dav_query(DavSession *sn, char *query, ...);
+
+cxmutstr dav_property_key(const char *ns, const char *name);
+void dav_get_property_namespace_str(
+ DavContext *ctx,
+ char *prefixed_name,
+ char **ns,
+ char **name);
+DavNamespace* dav_get_property_namespace(
+ DavContext *ctx,
+ char *prefixed_name,
+ char **name);
+
+/* ------------------------ resource functions ------------------------ */
+
+DavResource* dav_resource_new(DavSession *sn, const char *path);
+DavResource* dav_resource_new_child(DavSession *sn, DavResource *parent, const char *name);
+DavResource* dav_resource_new_href(DavSession *sn, const char *href);
+
+void dav_resource_free(DavResource *res);
+void dav_resource_free_all(DavResource *res);
+
+char* dav_resource_get_href(DavResource *resource);
+
+DavResource* dav_create_child(DavResource *parent, char *name);
+int dav_delete(DavResource *res);
+int dav_create(DavResource *res);
+int dav_exists(DavResource *res);
+
+int dav_copy(DavResource *res, char *newpath);
+int dav_move(DavResource *res, char *newpath);
+int dav_copy_o(DavResource *res, char *newpath, DavBool override);
+int dav_move_o(DavResource *res, char *newpath, DavBool override);
+int dav_copyto(DavResource *res, char *url, DavBool override);
+int dav_moveto(DavResource *res, char *url, DavBool override);
+
+int dav_lock(DavResource *res);
+int dav_lock_t(DavResource *res, time_t timeout);
+int dav_unlock(DavResource *res);
+
+DavXmlNode* dav_get_property(DavResource *res, char *name);
+DavXmlNode* dav_get_property_ns(DavResource *res, const char *ns, const char *name);
+DavXmlNode* dav_get_encrypted_property_ns(DavResource *res, const char *ns, const char *name);
+char* dav_get_string_property(DavResource *res, char *name);
+char* dav_get_string_property_ns(DavResource *res, char *ns, char *name);
+int dav_set_string_property(DavResource *res, char *name, char *value);
+void dav_set_string_property_ns(DavResource *res, char *ns, char *name, char *value);
+void dav_set_property(DavResource *res, char *name, DavXmlNode *value);
+void dav_set_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value);
+void dav_remove_property(DavResource *res, char *name);
+void dav_remove_property_ns(DavResource *res, char *ns, char *name);
+void dav_set_encrypted_property_ns(DavResource *res, char *ns, char *name, DavXmlNode *value);
+void dav_set_encrypted_string_property_ns(DavResource *res, char *ns, char *name, char *value);
+void dav_remove_encrypted_property_ns(DavResource *res, char *ns, char *name);
+
+DavPropName* dav_get_property_names(DavResource *res, size_t *count);
+
+void dav_set_content(DavResource *res, void *stream, dav_read_func read_func, dav_seek_func seek_func);
+void dav_set_content_data(DavResource *res, char *content, size_t length);
+void dav_set_content_length(DavResource *res, size_t length);
+
+int dav_load(DavResource *res);
+int dav_load_prop(DavResource *res, DavPropName *properties, size_t numprop);
+int dav_store(DavResource *res);
+int dav_get_content(DavResource *res, void *stream, dav_write_func write_func);
+
+DavInputStream* dav_inputstream_open(DavResource *res);
+size_t dav_read(void *buf, size_t size, size_t nitems, DavInputStream *in);
+void dav_inputstream_close(DavInputStream *in);
+
+DavOutputStream* dav_outputstream_open(DavResource *res);
+size_t dav_write(const void *buf, size_t size, size_t nitems, DavOutputStream *out);
+int dav_outputstream_close(DavOutputStream *out);
+
+void dav_verbose_log(
+ DavSession *sn,
+ const char *method,
+ const char *url,
+ const char *request_body,
+ size_t request_bodylen,
+ int status,
+ const char *response_body,
+ size_t response_bodylen);
+
+// private
+int dav_propfind(DavSession *sn, DavResource *root, CxBuffer *rqbuf);
+
+
+/* --------------------------- DeltaV ---------------------------- */
+
+int dav_versioncontrol(DavResource *res);
+int dav_checkout(DavResource *res);
+int dav_checkin(DavResource *res);
+int dav_uncheckout(DavResource *res);
+DavResource* dav_versiontree(DavResource *res, char *properties);
+
+/* ------------------------ xml functions ------------------------ */
+char* dav_xml_getstring(DavXmlNode *node);
+DavBool dav_xml_isstring(DavXmlNode *node);
+DavXmlNode* dav_xml_nextelm(DavXmlNode *node);
+DavXmlNode* dav_text_node(DavSession *sn, const char *text);
+DavXmlNode* dav_text_element(DavSession *sn, const char *ns, const char *name, const char *text);
+
+DavXmlNode* dav_copy_node(DavXmlNode *node);
+
+void dav_free_xml_node_sn(DavSession *sn, DavXmlNode *node);
+void dav_free_xml_node(DavXmlNode *node);
+
+DavXmlNode* dav_xml_createnode(const char *ns, const char *name);
+DavXmlNode* dav_xml_createnode_with_text(const char *ns, const char *name, const char *text);
+DavXmlNode* dav_xml_createtextnode(const char *text);
+void dav_xml_add_child(DavXmlNode *node, DavXmlNode *child);
+void dav_xml_add_attr(DavXmlNode *node, const char *name, const char *value);
+char* dav_xml_get_attr(DavXmlNode *node, const char *name);
+
+DavXmlNode* dav_parse_xml(DavSession *sn, const char *str, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WEBDAV_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cx/utils.h>
+#include <cx/printf.h>
+
+#include "xml.h"
+
+static DavXmlNodeType convert_type(xmlElementType type) {
+ DavXmlNodeType ct;
+ switch(type) {
+ default: ct = DAV_XML_NONE; break;
+ case XML_ELEMENT_NODE: ct = DAV_XML_ELEMENT; break;
+ case XML_TEXT_NODE: ct = DAV_XML_TEXT;
+ }
+ return ct;
+}
+
+typedef struct {
+ xmlNode *node;
+ DavXmlNode *parent;
+} ConvXmlElm;
+
+DavXmlNode* dav_convert_xml(DavSession *sn, xmlNode *node) {
+ if(!node) {
+ return NULL;
+ }
+ DavXmlNodeType newnt = convert_type(node->type);
+ if(newnt == DAV_XML_NONE) {
+ return NULL;
+ }
+
+ const CxAllocator *a = sn->mp->allocator;
+
+ ConvXmlElm ce;
+ ce.node = node;
+ ce.parent = NULL;
+ CxList *stack = cxLinkedListCreate(cxDefaultAllocator, NULL, sizeof(ConvXmlElm));
+ if(!stack) {
+ return NULL;
+ }
+ cxListInsert(stack, 0, &ce);
+
+ DavXmlNode *ret = NULL;
+
+ while(cxListSize(stack) > 0) {
+ ConvXmlElm *c = cxListAt(stack, 0);
+ xmlNode *n = c->node;
+ DavXmlNode *c_parent = c->parent;
+ DavXmlNode *prev = NULL;
+ cxListRemove(stack, 0);
+ while(n) {
+ DavXmlNode *newxn = cxCalloc(a, 1, sizeof(DavXmlNode));
+ if(!ret) {
+ ret = newxn;
+ }
+ newxn->type = convert_type(n->type);
+ newxn->parent = c_parent;
+ if(c_parent && !c_parent->children) {
+ c_parent->children = newxn;
+ }
+ newxn->prev = prev;
+ if(prev) {
+ prev->next = newxn;
+ }
+
+ if(newxn->type == DAV_XML_ELEMENT) {
+ newxn->name = dav_session_strdup(sn, (char*)n->name);
+ if(n->ns && n->ns->href) {
+ newxn->namespace = dav_session_strdup(sn, (char*)n->ns->href);
+ }
+
+ xmlAttr *attr = n->properties;
+ DavXmlAttr *newattr = NULL;
+ DavXmlAttr *newattr_last = NULL;
+ while(attr) {
+ DavXmlAttr *na = cxCalloc(a, 1, sizeof(DavXmlAttr));
+ na->name = dav_session_strdup(sn, (char*)attr->name);
+ if(attr->children && attr->children->type == XML_TEXT_NODE) {
+ na->value = dav_session_strdup(sn, (char*)attr->children->content);
+ }
+ if(!newattr) {
+ newattr = na;
+ } else {
+ newattr_last->next = na;
+ }
+ newattr_last = na;
+
+ attr = attr->next;
+ }
+ newxn->attributes = newattr;
+
+ if(n->children) {
+ ConvXmlElm convc;
+ convc.node = n->children;
+ convc.parent = newxn;
+ cxListInsert(stack, 0, &convc);
+ }
+ } else if(newxn->type == DAV_XML_TEXT) {
+ cxmutstr content = cx_strdup_a(a, cx_str((char*)n->content));
+ newxn->content = content.ptr;
+ newxn->contentlength = content.length;
+ }
+
+ prev = newxn;
+ n = n->next;
+ }
+ }
+
+ return ret;
+}
+
+void dav_print_xml(DavXmlNode *node) {
+ if(node->type == DAV_XML_ELEMENT) {
+ printf("<%s", node->name);
+ DavXmlAttr *attr = node->attributes;
+ while(attr) {
+ printf(" %s=\"%s\"", attr->name, attr->value);
+ attr = attr->next;
+ }
+ putchar('>');
+
+ DavXmlNode *child = node->children;
+ if(child) {
+ dav_print_xml(child);
+ }
+
+ printf("</%s>", node->name);
+ } else {
+ fwrite(node->content, 1, node->contentlength, stdout);
+ fflush(stdout);
+ }
+ if(node->next) {
+ dav_print_xml(node->next);
+ }
+}
+
+void dav_print_node(void *stream, cx_write_func writef, CxMap *nsmap, DavXmlNode *node) {
+ while(node) {
+ if(node->type == DAV_XML_ELEMENT) {
+ char *tagend = node->children ? ">" : " />";
+ char *prefix = NULL;
+ char *prefix_fr = NULL;
+ if(node->namespace) {
+ prefix = cxMapGet(nsmap, cx_hash_key_str(node->namespace));
+ if(!prefix) {
+ cxmutstr newpre = cx_asprintf("x%zu", cxMapSize(nsmap)+1);
+ // TODO: fix
+ //cxMapPut(nsmap, node->namespace, newpre.ptr);
+ prefix = newpre.ptr;
+ prefix_fr = prefix;
+ cx_fprintf(
+ stream,
+ writef,
+ "<%s:%s xmlns:%s=\"%s\"",
+ prefix,
+ node->name,
+ prefix,
+ node->namespace);
+ } else {
+ cx_fprintf(stream, writef, "<%s:%s", prefix, node->name);
+ }
+ } else {
+ cx_fprintf(stream, writef, "<%s", node->name);
+ }
+
+ DavXmlAttr *attr = node->attributes;
+ while(attr) {
+ cx_fprintf(stream, writef, " %s=\"%s\"", attr->name, attr->value);
+ attr = attr->next;
+ }
+ writef(tagend, 1, strlen(tagend), stream); // end xml tag
+
+ if(node->children) {
+ dav_print_node(stream, writef, nsmap, node->children);
+ if(prefix) {
+ cx_fprintf(stream, writef, "</%s:%s>", prefix, node->name);
+ } else {
+ cx_fprintf(stream, writef, "</%s>", node->name);
+ }
+ }
+
+ if(prefix_fr) {
+ free(prefix_fr);
+ }
+ } else if(node->type == DAV_XML_TEXT) {
+ writef(node->content, 1, node->contentlength, stream);
+ }
+
+ node = node->next;
+ }
+}
+
+/* ------------------------- public API ------------------------- */
+
+char* dav_xml_getstring(DavXmlNode *node) {
+ if(node && node->type == DAV_XML_TEXT) {
+ return node->content;
+ } else {
+ return NULL;
+ }
+}
+
+DavBool dav_xml_isstring(DavXmlNode *node) {
+ if(node && node->type == DAV_XML_TEXT && !node->next) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+DavXmlNode* dav_xml_nextelm(DavXmlNode *node) {
+ node = node->next;
+ while(node) {
+ if(node->type == DAV_XML_ELEMENT) {
+ return node;
+ }
+ node = node->next;
+ }
+ return NULL;
+}
+
+DavXmlNode* dav_text_node(DavSession *sn, const char *text) {
+ const CxAllocator *a = sn->mp->allocator;
+ DavXmlNode *newxn = cxCalloc(a, 1, sizeof(DavXmlNode));
+ newxn->type = DAV_XML_TEXT;
+ cxmutstr content = cx_strdup_a(a, cx_str(text));
+ newxn->content = content.ptr;
+ newxn->contentlength = content.length;
+ return newxn;
+}
+
+DavXmlNode* dav_text_element(DavSession *sn, const char *ns, const char *name, const char *text) {
+ const CxAllocator *a = sn->mp->allocator;
+ DavXmlNode *newelm = cxCalloc(a, 1, sizeof(DavXmlNode));
+ newelm->type = DAV_XML_ELEMENT;
+ newelm->namespace = cx_strdup_a(a, cx_str(ns)).ptr;
+ newelm->name = cx_strdup_a(a, cx_str(name)).ptr;
+ newelm->children = dav_text_node(sn, text);
+ return newelm;
+}
+
+static void dav_free_xml_node_a(const CxAllocator *a, DavXmlNode *node) {
+ if(node->name) cxFree(a, node->name);
+ if(node->namespace) cxFree(a, node->namespace);
+ if(node->content) cxFree(a, node->content);
+ DavXmlAttr *attr = node->attributes;
+ while(attr) {
+ if(attr->name) cxFree(a, attr->name);
+ if(attr->value) cxFree(a, attr->value);
+ attr = attr->next;
+ }
+ DavXmlNode *children = node->children;
+ while(children) {
+ DavXmlNode *next_ch = children->next;
+ dav_free_xml_node_a(a, children);
+ children = next_ch;
+ }
+ cxFree(a, node);
+}
+
+void dav_free_xml_node_sn(DavSession *sn, DavXmlNode *node) {
+ dav_free_xml_node_a(sn->mp->allocator, node);
+}
+
+void dav_free_xml_node(DavXmlNode *node) {
+ dav_free_xml_node_a(cxDefaultAllocator, node);
+}
+
+DavXmlAttr* dav_copy_xml_attr(DavXmlAttr *attr) {
+ if(!attr) {
+ return NULL;
+ }
+ DavXmlAttr *newattr = NULL;
+ DavXmlAttr *prev = NULL;
+ while(attr) {
+ DavXmlAttr *n = calloc(1, sizeof(DavXmlAttr));
+ n->name = strdup(attr->name);
+ n->value = strdup(attr->value);
+ if(prev) {
+ prev->next = n;
+ } else {
+ newattr = n;
+ }
+ prev = n;
+ attr = attr->next;
+ }
+ return newattr;
+}
+
+DavXmlNode* dav_copy_node(DavXmlNode *node) {
+ DavXmlNode *ret = NULL;
+ DavXmlNode *prev = NULL;
+ while(node) {
+ DavXmlNode *copy = calloc(1, sizeof(DavXmlNode));
+ copy->type = node->type;
+ if(node->type == DAV_XML_ELEMENT) {
+ copy->namespace = strdup(node->namespace);
+ copy->name = strdup(node->name);
+ copy->children = dav_copy_node(node->children);
+ copy->attributes = dav_copy_xml_attr(node->attributes);
+ } else {
+ copy->contentlength = node->contentlength;
+ copy->content = malloc(node->contentlength+1);
+ memcpy(copy->content, node->content, node->contentlength);
+ copy->content[copy->contentlength] = 0;
+ }
+ if(!ret) {
+ ret = copy;
+ }
+ if(prev) {
+ prev->next = copy;
+ copy->prev = prev;
+ }
+ prev = copy;
+ node = node->next;
+ }
+ return ret;
+}
+
+
+DavXmlNode* dav_xml_createnode(const char *ns, const char *name) {
+ DavXmlNode *node = calloc(1, sizeof(DavXmlNode));
+ node->type = DAV_XML_ELEMENT;
+ node->namespace = strdup(ns);
+ node->name = strdup(name);
+ return node;
+}
+
+DavXmlNode* dav_xml_createnode_with_text(const char *ns, const char *name, const char *text) {
+ DavXmlNode *node = calloc(1, sizeof(DavXmlNode));
+ node->type = DAV_XML_ELEMENT;
+ node->namespace = strdup(ns);
+ node->name = strdup(name);
+
+ DavXmlNode *textnode = dav_xml_createtextnode(text);
+ node->children = textnode;
+
+ return node;
+}
+
+DavXmlNode* dav_xml_createtextnode(const char *text) {
+ DavXmlNode *node = calloc(1, sizeof(DavXmlNode));
+ node->type = DAV_XML_TEXT;
+ cxmutstr content = cx_strdup(cx_str((char*)text));
+ node->content = content.ptr;
+ node->contentlength = content.length;
+ return node;
+}
+
+void dav_xml_add_child(DavXmlNode *node, DavXmlNode *child) {
+ DavXmlNode *last_child = NULL;
+ DavXmlNode *c = node->children;
+ while(c) {
+ last_child = c;
+ c = c->next;
+ }
+ if(last_child) {
+ last_child->next = child;
+ child->prev = last_child;
+ } else {
+ node->children = child;
+ }
+}
+
+void dav_xml_add_attr(DavXmlNode *node, const char *name, const char *value) {
+ DavXmlAttr *attr = calloc(1, sizeof(DavXmlAttr));
+ attr->name = strdup(name);
+ attr->value = strdup(value);
+
+ if(node->attributes) {
+ DavXmlAttr *end = node->attributes;
+ DavXmlAttr* last = end;
+ while(end) {
+ last = end;
+ end = end->next;
+ }
+ last->next = attr;
+ } else {
+ node->attributes = attr;
+ }
+}
+
+char* dav_xml_get_attr(DavXmlNode *node, const char *name) {
+ DavXmlAttr *attr = node->attributes;
+ while(attr) {
+ if(!strcmp(attr->name, name)) {
+ return attr->value;
+ }
+
+ attr = attr->next;
+ }
+ return NULL;
+}
+
+DavXmlNode* dav_parse_xml(DavSession *sn, const char *str, size_t len) {
+ xmlDoc *doc = xmlReadMemory(str, len, NULL, NULL, 0);
+ if(!doc) {
+ return NULL;
+ }
+ xmlNode *xml_root = xmlDocGetRootElement(doc);
+ if(!xml_root) {
+ xmlFreeDoc(doc);
+ return NULL;
+ }
+ DavXmlNode *x = dav_convert_xml(sn, xml_root);
+ xmlFreeDoc(doc);
+ return x;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef DAV_XML_H
+#define DAV_XML_H
+
+#include "webdav.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DavXmlNode* dav_convert_xml(DavSession *sn, xmlNode *node);
+
+void dav_print_xml(DavXmlNode *node);
+
+void dav_print_node(void *stream, cx_write_func writef, CxMap *nsmap, DavXmlNode *node);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DAV_XML_H */
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2023 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+# this makefile is invoked from the build root directory
+
+BUILD_ROOT = ./
+include config.mk
+
+BUILD_DIRS = build/bin build/lib
+BUILD_DIRS += build/application build/ucx build/libidav
+BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT)
+
+all: $(BUILD_DIRS) ucx ui libidav application
+
+
+$(BUILD_DIRS):
+ mkdir -p $@
+
+ui: ucx FORCE
+ cd ui; $(MAKE) all
+
+ucx: FORCE
+ cd ucx; $(MAKE) all
+
+libidav: ucx FORCE
+ cd libidav; $(MAKE) all
+
+application: ui libidav FORCE
+ cd application; $(MAKE)
+
+FORCE:
+
--- /dev/null
+#
+# 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
--- /dev/null
+#
+# clang toolchain config
+#
+
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
--- /dev/null
+#!/bin/sh
+
+# create temporary directory
+TEMP_DIR=".tmp-`uname -n`"
+rm -Rf "$TEMP_DIR"
+if mkdir -p "$TEMP_DIR"; then
+ :
+else
+ echo "Cannot create tmp dir $TEMP_DIR"
+ echo "Abort"
+ exit 1
+fi
+touch "$TEMP_DIR/options"
+touch "$TEMP_DIR/features"
+
+# define standard variables
+# also define standard prefix (this is where we will search for config.site)
+prefix=/usr
+exec_prefix=
+bindir=
+sbindir=
+libdir=
+libexecdir=
+datarootdir=
+datadir=
+sysconfdir=
+sharedstatedir=
+localstatedir=
+runstatedir=
+includedir=
+infodir=
+localedir=
+mandir=
+
+# custom variables
+#foreach( $var in $vars )
+#if( $var.exec )
+${var.varName}=`${var.value}`
+#else
+${var.varName}="${var.value}"
+#end
+#end
+
+# features
+#foreach( $feature in $features )
+#if( ${feature.auto} )
+${feature.varName}=auto
+#end
+#end
+
+# clean abort
+abort_configure()
+{
+ rm -Rf "$TEMP_DIR"
+ exit 1
+}
+
+# help text
+printhelp()
+{
+ echo "Usage: $0 [OPTIONS]..."
+ cat << __EOF__
+Installation directories:
+ --prefix=PREFIX path prefix for architecture-independent files
+ [/usr]
+ --exec-prefix=EPREFIX path prefix for architecture-dependent files
+ [PREFIX]
+
+ --bindir=DIR user executables [EPREFIX/bin]
+ --sbindir=DIR system admin executables [EPREFIX/sbin]
+ --libexecdir=DIR program executables [EPREFIX/libexec]
+ --sysconfdir=DIR system configuration files [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR run-time variable data [LOCALSTATEDIR/run]
+ --libdir=DIR object code libraries [EPREFIX/lib]
+ --includedir=DIR C header files [PREFIX/include]
+ --datarootdir=DIR read-only arch.-independent data root [PREFIX/share]
+ --datadir=DIR read-only architecture-independent data [DATAROOTDIR]
+ --infodir=DIR info documentation [DATAROOTDIR/info]
+ --mandir=DIR man documentation [DATAROOTDIR/man]
+ --localedir=DIR locale-dependent data [DATAROOTDIR/locale]
+
+#if( $options.size() > 0 )
+Options:
+ --debug add extra compile flags for debug builds
+ --release add extra compile flags for release builds
+#foreach( $opt in $options )
+ --${opt.argument}=${opt.valuesString}
+#end
+
+#end
+#if( $features.size() > 0 )
+Optional Features:
+#foreach( $feature in $features )
+${feature.helpText}
+#end
+
+#end
+__EOF__
+}
+
+#
+# parse arguments
+#
+BUILD_TYPE="default"
+#set( $D = '$' )
+for ARG in "$@"
+do
+ case "$ARG" in
+ "--prefix="*) prefix=${D}{ARG#--prefix=} ;;
+ "--exec-prefix="*) exec_prefix=${D}{ARG#--exec-prefix=} ;;
+ "--bindir="*) bindir=${D}{ARG#----bindir=} ;;
+ "--sbindir="*) sbindir=${D}{ARG#--sbindir=} ;;
+ "--libdir="*) libdir=${D}{ARG#--libdir=} ;;
+ "--libexecdir="*) libexecdir=${D}{ARG#--libexecdir=} ;;
+ "--datarootdir="*) datarootdir=${D}{ARG#--datarootdir=} ;;
+ "--datadir="*) datadir=${D}{ARG#--datadir=} ;;
+ "--sysconfdir="*) sysconfdir=${D}{ARG#--sysconfdir=} ;;
+ "--sharedstatedir="*) sharedstatedir=${D}{ARG#--sharedstatedir=} ;;
+ "--localstatedir="*) localstatedir=${D}{ARG#--localstatedir=} ;;
+ "--includedir="*) includedir=${D}{ARG#--includedir=} ;;
+ "--infodir="*) infodir=${D}{ARG#--infodir=} ;;
+ "--mandir"*) mandir=${D}{ARG#--mandir} ;;
+ "--localedir"*) localedir=${D}{ARG#--localedir} ;;
+ "--help"*) printhelp; abort_configure ;;
+ "--debug") BUILD_TYPE="debug" ;;
+ "--release") BUILD_TYPE="release" ;;
+ #foreach( $opt in $options )
+ "--${opt.argument}="*) ${opt.varName}=${D}{ARG#--${opt.argument}=} ;;
+ #end
+ #foreach( $feature in $features )
+ "--enable-${feature.arg}") ${feature.varName}=on ;;
+ "--disable-${feature.arg}") unset ${feature.varName} ;;
+ #end
+ "-"*) echo "unknown option: $ARG"; abort_configure ;;
+ esac
+done
+
+## Begin unparsed content. **
+#[[
+
+# set defaults for dir variables
+: ${exec_prefix:="$prefix"}
+: ${bindir:='${exec_prefix}/bin'}
+: ${sbindir:='${exec_prefix}/sbin'}
+: ${libdir:='${exec_prefix}/lib'}
+: ${libexecdir:='${exec_prefix}/libexec'}
+: ${datarootdir:='${prefix}/share'}
+: ${datadir:='${datarootdir}'}
+: ${sysconfdir:='${prefix}/etc'}
+: ${sharedstatedir:='${prefix}/com'}
+: ${localstatedir:='${prefix}/var'}
+: ${runstatedir:='${localstatedir}/run'}
+: ${includedir:='${prefix}/include'}
+: ${infodir:='${datarootdir}/info'}
+: ${mandir:='${datarootdir}/man'}
+: ${localedir:='${datarootdir}/locale'}
+
+# check if a config.site exists and load it
+if [ -n "$CONFIG_SITE" ]; then
+ # CONFIG_SITE may contain space separated file names
+ for cs in $CONFIG_SITE; do
+ printf "loading defaults from $cs... "
+ . "$cs"
+ echo ok
+ done
+elif [ -f "$prefix/share/config.site" ]; then
+ printf "loading site defaults... "
+ . "$prefix/share/config.site"
+ echo ok
+elif [ -f "$prefix/etc/config.site" ]; then
+ printf "loading site defaults... "
+ . "$prefix/etc/config.site"
+ echo ok
+fi
+
+# Test for availability of pkg-config
+PKG_CONFIG=`command -v pkg-config`
+: ${PKG_CONFIG:="false"}
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+OS=`uname -s`
+OS_VERSION=`uname -r`
+printf "detect platform... "
+if [ "$OS" = "SunOS" ]; then
+ PLATFORM="solaris sunos unix svr4"
+elif [ "$OS" = "Linux" ]; then
+ PLATFORM="linux unix"
+elif [ "$OS" = "FreeBSD" ]; then
+ PLATFORM="freebsd bsd unix"
+elif [ "$OS" = "OpenBSD" ]; then
+ PLATFORM="openbsd bsd unix"
+elif [ "$OS" = "NetBSD" ]; then
+ PLATFORM="netbsd bsd unix"
+elif [ "$OS" = "Darwin" ]; then
+ PLATFORM="macos osx bsd unix"
+elif echo "$OS" | grep -i "MINGW" > /dev/null; then
+ PLATFORM="windows mingw"
+fi
+: ${PLATFORM:="unix"}
+
+PLATFORM_NAME=`echo "$PLATFORM" | cut -f1 -d' ' -`
+echo "$PLATFORM_NAME"
+
+isplatform()
+{
+ for p in $PLATFORM
+ do
+ if [ "$p" = "$1" ]; then
+ return 0
+ fi
+ done
+ return 1
+}
+notisplatform()
+{
+ for p in $PLATFORM
+ do
+ if [ "$p" = "$1" ]; then
+ return 1
+ fi
+ done
+ return 0
+}
+istoolchain()
+{
+ for t in $TOOLCHAIN
+ do
+ if [ "$t" = "$1" ]; then
+ return 0
+ fi
+ done
+ return 1
+}
+notistoolchain()
+{
+ for t in $TOOLCHAIN
+ do
+ if [ "$t" = "$1" ]; then
+ return 1
+ fi
+ done
+ return 0
+}
+]]#
+## End of unparsed content **
+
+# generate vars.mk
+cat > "$TEMP_DIR/vars.mk" << __EOF__
+prefix=$prefix
+exec_prefix=$exec_prefix
+bindir=$bindir
+sbindir=$sbindir
+libdir=$libdir
+libexecdir=$libexecdir
+datarootdir=$datarootdir
+datadir=$datadir
+sysconfdir=$sysconfdir
+sharedstatedir=$sharedstatedir
+localstatedir=$localstatedir
+runstatedir=$runstatedir
+includedir=$includedir
+infodir=$infodir
+mandir=$mandir
+localedir=$localedir
+#foreach( $var in $vars )
+${var.varName}=${D}${var.varName}
+#end
+__EOF__
+
+# toolchain detection utilities
+. make/toolchain.sh
+
+#
+# DEPENDENCIES
+#
+
+# check languages
+lang_c=
+lang_cpp=
+#foreach( $lang in $languages )
+if detect_${lang}_compiler ; then
+ lang_${lang}=1
+fi
+#end
+
+# create buffer for make variables required by dependencies
+echo > "$TEMP_DIR/make.mk"
+
+test_pkg_config()
+{
+ if "$PKG_CONFIG" --exists "$1" ; then :
+ else return 1 ; fi
+ if [ -z "$2" ] || "$PKG_CONFIG" --atleast-version="$2" "$1" ; then :
+ else return 1 ; fi
+ if [ -z "$3" ] || "$PKG_CONFIG" --exact-version="$3" "$1" ; then :
+ else return 1 ; fi
+ if [ -z "$4" ] || "$PKG_CONFIG" --max-version="$4" "$1" ; then :
+ else return 1 ; fi
+ return 0
+}
+
+print_check_msg()
+{
+ if [ -z "$1" ]; then
+ shift
+ printf "$@"
+ fi
+}
+
+#foreach( $dependency in $namedDependencies )
+dependency_error_${dependency.id}()
+{
+ print_check_msg "${D}dep_checked_${dependency.id}" "checking for ${dependency.name}... "
+ #foreach( $sub in $dependency.subdependencies )
+ # dependency $sub.fullName
+ while true
+ do
+ #if( $sub.platform )
+ if notisplatform "${sub.platform}"; then
+ break
+ fi
+ #end
+ #if( $sub.toolchain )
+ if notistoolchain "${sub.toolchain}"; then
+ break
+ fi
+ #end
+ #foreach( $np in $sub.notList )
+ if isplatform "${np}" || istoolchain "${np}"; then
+ break
+ fi
+ #end
+ #foreach( $lang in $sub.lang )
+ if [ -z "$lang_${lang}" ] ; then
+ break
+ fi
+ #end
+ #if( $sub.pkgconfig.size() > 0 )
+ if [ -z "$PKG_CONFIG" ]; then
+ break
+ fi
+ #end
+ #foreach( $test in $sub.tests )
+ if $test > /dev/null ; then
+ :
+ else
+ break
+ fi
+ #end
+ #foreach( $pkg in $sub.pkgconfig )
+ if test_pkg_config "$pkg.name" "$pkg.atleast" "$pkg.exact" "$pkg.max" ; then
+ TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags $pkg.name`"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs $pkg.name`"
+ else
+ break
+ fi
+ #end
+ #foreach( $flags in $sub.flags )
+ #if( $flags.exec )
+ if tmp_flags=`$flags.value` ; then
+ TEMP_$flags.varName="$TEMP_$flags.varName $tmp_flags"
+ else
+ break
+ fi
+ #else
+ TEMP_$flags.varName="$TEMP_$flags.varName $flags.value"
+ #end
+ #end
+ #if ( $sub.make.length() > 0 )
+ cat >> $TEMP_DIR/make.mk << __EOF__
+# Dependency: $dependency.name
+$sub.make
+__EOF__
+ #end
+ print_check_msg "${D}dep_checked_${dependency.id}" "yes\n"
+ dep_checked_${dependency.id}=1
+ return 1
+ done
+
+ #end
+ print_check_msg "${D}dep_checked_${dependency.id}" "no\n"
+ dep_checked_${dependency.id}=1
+ return 0
+}
+#end
+
+# start collecting dependency information
+echo > "$TEMP_DIR/flags.mk"
+
+DEPENDENCIES_FAILED=
+ERROR=0
+#if( $dependencies.size() > 0 )
+# unnamed dependencies
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
+#foreach( $dependency in $dependencies )
+while true
+do
+ #if( $dependency.platform )
+ if notisplatform "${dependency.platform}"; then
+ break
+ fi
+ #end
+ #if( $dependency.toolchain )
+ if notistoolchain "${dependency.toolchain}"; then
+ break
+ fi
+ #end
+ #foreach( $np in $dependency.notList )
+ if isplatform "${np}" || istoolchain "${np}"; then
+ break
+ fi
+ #end
+ while true
+ do
+ #foreach( $lang in $dependency.lang )
+ if [ -z "$lang_${lang}" ] ; then
+ ERROR=1
+ break
+ fi
+ #end
+ #if( $dependency.pkgconfig.size() > 0 )
+ if [ -z "$PKG_CONFIG" ]; then
+ ERROR=1
+ break
+ fi
+ #end
+ #foreach( $pkg in $dependency.pkgconfig )
+ print_check_msg "${D}dep_pkgconfig_checked_${pkg.id}" "checking for pkg-config package $pkg.name... "
+ if test_pkg_config "$pkg.name" "$pkg.atleast" "$pkg.exact" "$pkg.max" ; then
+ print_check_msg "${D}dep_pkgconfig_checked_${pkg.id}" "yes\n"
+ dep_pkgconfig_checked_${pkg.id}=1
+ TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags $pkg.name`"
+ TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs $pkg.name`"
+ else
+ print_check_msg "${D}dep_pkgconfig_checked_${pkg.id}" "no\n"
+ dep_pkgconfig_checked_${pkg.id}=1
+ ERROR=1
+ break
+ fi
+ #end
+
+ #foreach( $flags in $dependency.flags )
+ #if( $flags.exec )
+ $flags.value > /dev/null
+ if tmp_flags=`$flags.value` ; then
+ TEMP_$flags.varName="$TEMP_$flags.varName $tmp_flags"
+ else
+ ERROR=1
+ break
+ fi
+ #else
+ TEMP_$flags.varName="$TEMP_$flags.varName $flags.value"
+ #end
+ #end
+ #if ( $dependency.make.length() > 0 )
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+$dependency.make
+__EOF__
+ #end
+ break
+ done
+ break
+done
+#end
+
+# add general dependency flags to flags.mk
+echo "# general flags" >> "$TEMP_DIR/flags.mk"
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+ echo "CFLAGS += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+ echo "CXXFLAGS += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_LDFLAGS}" ]; then
+ echo "LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+#end
+
+#
+# OPTION VALUES
+#
+#foreach( $opt in $options )
+#foreach( $val in $opt.values )
+${val.func}()
+{
+ VERR=0
+ #foreach( $dep in $val.dependencies )
+ if dependency_error_$dep ; then
+ VERR=1
+ fi
+ #end
+ if [ $VERR -ne 0 ]; then
+ return 1
+ fi
+ #foreach( $def in $val.defines )
+ TEMP_CFLAGS="$TEMP_CFLAGS ${def.toFlags()}"
+ TEMP_CXXFLAGS="$TEMP_CXXFLAGS ${def.toFlags()}"
+ #end
+ #if( $val.hasMake() )
+ cat >> "$TEMP_DIR/make.mk" << __EOF__
+$val.make
+__EOF__
+ #end
+ return 0
+}
+#end
+#end
+
+#
+# TARGETS
+#
+
+#foreach( $target in $targets )
+echo >> "$TEMP_DIR/flags.mk"
+#if ( $target.name )
+echo "configuring target: $target.name"
+echo "# flags for target $target.name" >> "$TEMP_DIR/flags.mk"
+#else
+echo "configuring global target"
+echo "# flags for unnamed target" >> "$TEMP_DIR/flags.mk"
+#end
+TEMP_CFLAGS=
+TEMP_CXXFLAGS=
+TEMP_LDFLAGS=
+
+#foreach( $dependency in $target.dependencies )
+if dependency_error_$dependency; then
+ DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+ ERROR=1
+fi
+#end
+
+# Features
+#foreach( $feature in $target.features )
+if [ -n "${D}${feature.varName}" ]; then
+#foreach( $dependency in $feature.dependencies )
+ # check dependency
+ if dependency_error_$dependency ; then
+ # "auto" features can fail and are just disabled in this case
+ if [ "${D}${feature.varName}" = "auto" ]; then
+ DISABLE_${feature.varName}=1
+ else
+ DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+ ERROR=1
+ fi
+ fi
+#end
+ if [ -n "$DISABLE_${feature.varName}" ]; then
+ unset ${feature.varName}
+ fi
+fi
+#end
+
+#foreach( $opt in $target.options )
+# Option: --${opt.argument}
+if [ -z "${D}${opt.varName}" ]; then
+ echo "auto-detecting option '${opt.argument}'"
+ SAVED_ERROR="$ERROR"
+ SAVED_DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED"
+ ERROR=1
+ while true
+ do
+ #foreach( $optdef in $opt.defaults )
+ #if( $optdef.platform )
+ if isplatform "$optdef.platform"; then
+ #end
+ if $optdef.func ; then
+ echo " ${opt.argument}: ${optdef.valueName}" >> "$TEMP_DIR/options"
+ ERROR=0
+ break
+ fi
+ #if( $optdef.platform )
+ fi
+ #end
+ #end
+ break
+ done
+ if [ $ERROR -ne 0 ]; then
+ SAVED_ERROR=1
+ SAVED_DEPENDENCIES_FAILED="option '${opt.argument}' $SAVED_DEPENDENCIES_FAILED"
+ fi
+ ERROR="$SAVED_ERROR"
+ DEPENDENCIES_FAILED="$SAVED_DEPENDENCIES_FAILED"
+else
+ echo "checking option ${opt.argument} = ${D}${opt.varName}"
+ if false; then
+ false
+ #foreach( $optval in $opt.values )
+ elif [ "${D}${opt.varName}" = "${optval.value}" ]; then
+ echo " ${opt.argument}: ${D}${opt.varName}" >> $TEMP_DIR/options
+ if $optval.func ; then
+ :
+ else
+ ERROR=1
+ DEPENDENCIES_FAILED="option '${opt.argument}' $DEPENDENCIES_FAILED"
+ fi
+ #end
+ fi
+fi
+#end
+
+if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then
+ echo "${target.cFlags} += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then
+ echo "${target.cxxFlags} += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+if [ "$BUILD_TYPE" = "debug" ]; then
+ if [ -n "$lang_c" ]; then
+ echo '${target.cFlags} += ${DEBUG_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+ fi
+ if [ -n "$lang_cpp" ]; then
+ echo '${target.cxxFlags} += ${DEBUG_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+ fi
+fi
+if [ "$BUILD_TYPE" = "release" ]; then
+ if [ -n "$lang_c" ]; then
+ echo '${target.cFlags} += ${RELEASE_CC_FLAGS}' >> "$TEMP_DIR/flags.mk"
+ fi
+ if [ -n "$lang_cpp" ]; then
+ echo '${target.cxxFlags} += ${RELEASE_CXX_FLAGS}' >> "$TEMP_DIR/flags.mk"
+ fi
+fi
+if [ -n "${TEMP_LDFLAGS}" ]; then
+ echo "${target.ldFlags} += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk"
+fi
+
+#end
+
+# final result
+if [ $ERROR -ne 0 ]; then
+ echo
+ echo "Error: Unresolved dependencies"
+ echo "$DEPENDENCIES_FAILED"
+ abort_configure
+fi
+
+echo "configure finished"
+echo
+echo "Build Config:"
+echo " PREFIX: $prefix"
+echo " TOOLCHAIN: $TOOLCHAIN_NAME"
+#if ( $options.size() > 0 )
+echo "Options:"
+cat "$TEMP_DIR/options"
+#end
+#if ( $features.size() > 0 )
+echo "Features:"
+#foreach( $feature in $features )
+if [ -n "${D}${feature.varName}" ]; then
+echo " $feature.name: on"
+else
+echo " $feature.name: off"
+fi
+#end
+#end
+echo
+
+# generate the config.mk file
+cat > "$TEMP_DIR/config.mk" << __EOF__
+#
+# config.mk generated by configure
+#
+
+__EOF__
+write_toolchain_defaults "$TEMP_DIR/toolchain.mk"
+cat "$TEMP_DIR/vars.mk" "$TEMP_DIR/toolchain.mk" "$TEMP_DIR/flags.mk" "$TEMP_DIR/make.mk" > config.mk
+rm -Rf "$TEMP_DIR"
--- /dev/null
+#
+# gcc toolchain config
+#
+
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+CC = gcc
+LD = gcc
+AR = ar
+RM = rm
+MSBUILD = MSBuild.exe
+
+CFLAGS = -std=gnu99 -c -O2 -m64
+COFLAGS = -o
+LDFLAGS =
+LOFLAGS = -o
+ARFLAGS = -r
+RMFLAGS = -f
+
+OBJ_EXT = o
+LIB_EXT = a
+APP_EXT = .exe
+
+PACKAGE_SCRIPT = package_windows.sh
\ No newline at end of file
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+CC = gcc
+LD = gcc
+AR = ar
+RM = rm
+
+CFLAGS += -std=gnu99 -g -I/usr/include/libxml2
+LDFLAGS += -lxml2 -lz -lpthread -licucore -lm
+ARFLAGS = -r
+RMFLAGS = -f
+
+OBJ_EXT = o
+LIB_EXT = a
+APP_EXT =
+
+PACKAGE_SCRIPT = package_osx.sh
--- /dev/null
+#!/bin/sh
+
+# create .app
+rm -Rf build/mk12.app
+cp -R resource/template.app build/mk12.app
+
+mkdir -p build/mk12.app/Contents/MacOS/
+
+cp build/bin/mk12 build/mk12.app/Contents/MacOS/
+
+cp -R resource/locales build/mk12.app/Contents/Resources/
+
--- /dev/null
+#!/bin/sh
+
--- /dev/null
+#!/bin/sh
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://unixwork.de/uwproj">
+ <dependency>
+ <lang>c</lang>
+ </dependency>
+
+ <dependency name="curl" platform="macos">
+ <cflags exec="true">curl-config --cflags</cflags>
+ <ldflags exec="true">curl-config --ldflags</ldflags>
+ </dependency>
+ <dependency name="curl">
+ <pkgconfig>libcurl</pkgconfig>
+ </dependency>
+ <dependency name="curl">
+ <cflags exec="true">curl-config --cflags</cflags>
+ <ldflags exec="true">curl-config --libs</ldflags>
+ </dependency>
+
+ <dependency name="libxml2" platform="windows">
+ <cflags exec="true">xml2-config --cflags</cflags>
+ <ldflags exec="true">xml2-config --libs</ldflags>
+ </dependency>
+ <dependency name="libxml2" platform="macos">
+ <cflags exec="true">xml2-config --cflags</cflags>
+ <ldflags exec="true">xml2-config --libs</ldflags>
+ </dependency>
+ <dependency name="libxml2">
+ <pkgconfig>libxml-2.0</pkgconfig>
+ </dependency>
+ <dependency name="libxml2">
+ <cflags exec="true">xml2-config --cflags</cflags>
+ <ldflags exec="true">xml2-config --libs</ldflags>
+ </dependency>
+
+ <dependency name="openssl" platform="windows">
+ <ldflags>-lssl -lcrypto</ldflags>
+ </dependency>
+ <dependency name="openssl" platform="macos">
+ <ldflags>-framework CoreFoundation</ldflags>
+ </dependency>
+ <dependency name="openssl" platform="bsd" not="macos">
+ <ldflags>-lssl -lcrypto</ldflags>
+ </dependency>
+ <dependency name="openssl">
+ <pkgconfig>openssl</pkgconfig>
+ </dependency>
+
+ <dependency name="libadwaita">
+ <pkgconfig>libadwaita-1</pkgconfig>
+ <cflags>-DUI_GTK4 -DUI_LIBADWAITA</cflags>
+ <ldflags>-lpthread</ldflags>
+ </dependency>
+ <dependency name="gtk4">
+ <pkgconfig>gtk4</pkgconfig>
+ <cflags>-DUI_GTK3</cflags>
+ <ldflags>-lpthread</ldflags>
+ </dependency>
+ <dependency name="gtk3">
+ <pkgconfig>gtk+-3.0</pkgconfig>
+ <cflags>-DUI_GTK3</cflags>
+ <ldflags>-lpthread</ldflags>
+ </dependency>
+ <dependency name="gtk2">
+ <test>pkg-config --atleast-version=2.20 gtk+-2.0</test>
+ <pkgconfig>gtk+-2.0</pkgconfig>
+ <cflags>-DUI_GTK2</cflags>
+ <ldflags>-lpthread</ldflags>
+ </dependency>
+ <dependency name="gtk2legacy">
+ <pkgconfig>gtk+-2.0</pkgconfig>
+ <cflags>-DUI_GTK2 -DUI_GTK2LEGACY</cflags>
+ <ldflags>-lpthread</ldflags>
+ </dependency>
+ <dependency name="winui" platform="windows">
+ <cflags>-DUI_WINUI</cflags>
+ </dependency>
+
+ <!--
+ <dependency name="qt4">
+ <test>which qmake-qt4</test>
+ <cflags exec="true">qmake-qt4 -o - /dev/null | grep DEFINES\ </cflags>
+ <cflags exec="true">qmake-qt4 -o - /dev/null | grep INCPATH\ </cflags>
+ <ldflags exec="true">qmake-qt4 -o - /dev/null | grep LIBS\ </ldflags>
+ </dependency>
+
+ <dependency name="qt5">
+ <test>which qmake-qt5</test>
+ <cflags exec="true">qmake-qt5 -o - /dev/null | grep DEFINES\ </cflags>
+ <cflags exec="true">qmake-qt5 -o - /dev/null | grep INCPATH\ </cflags>
+ <ldflags exec="true">qmake-qt5 -o - /dev/null | grep LIBS\ </ldflags>
+ </dependency>
+ -->
+ <dependency name="cocoa" platform="macos">
+ <cflags>-DUI_COCOA</cflags>
+ <ldflags>-lobjc -framework Cocoa</ldflags>
+ </dependency>
+
+ <dependency name="motif" platform="bsd">
+ <cflags>-DUI_MOTIF -I/usr/local/include/X11</cflags>
+ <ldflags>-lXm -lXt -lX11 -lpthread</ldflags>
+ </dependency>
+
+ <dependency name="motif">
+ <cflags>-DUI_MOTIF</cflags>
+ <ldflags>-lXm -lXt -lX11 -lpthread</ldflags>
+ </dependency>
+
+ <dependency platform="macos">
+ <make>OBJ_EXT = .o</make>
+ <make>LIB_EXT = .a</make>
+ <make>PACKAGE_SCRIPT = package_osx.sh</make>
+ </dependency>
+ <dependency platform="unix" not="macos">
+ <make>OBJ_EXT = .o</make>
+ <make>LIB_EXT = .a</make>
+ <make>PACKAGE_SCRIPT = package_unix.sh</make>
+ </dependency>
+
+ <dependency platform="bsd">
+ <cflags>-I/usr/local/include</cflags>
+ <ldflags>-L/usr/local/lib</ldflags>
+ </dependency>
+
+ <target name="dav">
+ <dependencies>curl,libxml2,openssl</dependencies>
+ </target>
+
+ <target name="tk">
+ <option arg="toolkit">
+ <value str="libadwaita">
+ <dependencies>libadwaita</dependencies>
+ <make>TOOLKIT = gtk</make>
+ <make>GTKOBJ = draw_cairo.o</make>
+ </value>
+ <value str="gtk4">
+ <dependencies>gtk4</dependencies>
+ <make>TOOLKIT = gtk</make>
+ <make>GTKOBJ = draw_cairo.o</make>
+ </value>
+ <value str="gtk3">
+ <dependencies>gtk3</dependencies>
+ <make>TOOLKIT = gtk</make>
+ <make>GTKOBJ = draw_cairo.o</make>
+ </value>
+ <value str="gtk2">
+ <dependencies>gtk2</dependencies>
+ <make>TOOLKIT = gtk</make>
+ <make>GTKOBJ = draw_cairo.o</make>
+ </value>
+ <value str="gtk2legacy">
+ <dependencies>gtk2legacy</dependencies>
+ <make>TOOLKIT = gtk</make>
+ <make>GTKOBJ = draw_gdk.o</make>
+ </value>
+ <value str="qt5">
+ <dependencies>qt5</dependencies>
+ <make>TOOLKIT = qt</make>
+ <make>LD = $(CXX)</make>
+ </value>
+ <value str="qt4">
+ <dependencies>qt4</dependencies>
+ <make>TOOLKIT = qt</make>
+ <make>LD = $(CXX)</make>
+ </value>
+ <value str="motif">
+ <dependencies>motif</dependencies>
+ <make>TOOLKIT = motif</make>
+ </value>
+ <default value="winui" platform="windows" />
+ <default value="cocoa" platform="macos" />
+ <default value="gtk4" />
+ <default value="gtk3" />
+ <default value="qt5" />
+ <default value="gtk2" />
+ <default value="qt4" />
+ <default value="motif" />
+ </option>
+ </target>
+</project>
+
--- /dev/null
+#
+# suncc toolchain
+#
+
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
+
+SHLIB_CFLAGS = -Kpic
+SHLIB_LDFLAGS = -G
+
--- /dev/null
+#!/bin/sh
+#
+# toolchain detection
+#
+
+if isplatform "bsd" && notisplatform "openbsd"; then
+ C_COMPILERS="clang gcc cc"
+ CPP_COMPILERS="clang++ g++ CC"
+else
+ C_COMPILERS="gcc clang suncc cc"
+ CPP_COMPILERS="g++ clang++ sunCC CC"
+fi
+unset TOOLCHAIN
+unset TOOLCHAIN_NAME
+unset TOOLCHAIN_CC
+unset TOOLCHAIN_CXX
+
+check_c_compiler()
+{
+ cat > "$TEMP_DIR/test.c" << __EOF__
+/* test file */
+#include <stdio.h>
+int main(int argc, char **argv) {
+#if defined(_MSC_VER)
+ printf("msc\n");
+#elif defined(__clang__)
+ printf("clang gnuc\n");
+#elif defined(__GNUC__)
+ printf("gcc gnuc\n");
+#elif defined(__sun)
+ printf("suncc\n");
+#else
+ printf("unknown\n");
+#endif
+ return 0;
+}
+__EOF__
+ rm -f "$TEMP_DIR/checkcc"
+ $1 -o "$TEMP_DIR/checkcc" $CFLAGS $LDFLAGS "$TEMP_DIR/test.c" 2> /dev/null
+}
+
+check_cpp_compiler()
+{
+ cat > "$TEMP_DIR/test.cpp" << __EOF__
+/* test file */
+#include <iostream>
+int main(int argc, char **argv) {
+#if defined(_MSC_VER)
+ std::cout << "msc" << std::endl;
+#elif defined(__clang__)
+ std::cout << "clang gnuc" << std::endl;
+#elif defined(__GNUC__)
+ std::cout << "gcc gnuc" << std::endl;
+#elif defined(__sun)
+ std::cout << "suncc" << std::endl;
+#else
+ std::cout << "cc" << std::endl;
+#endif
+ return 0;
+}
+__EOF__
+ rm -f "$TEMP_DIR/checkcc"
+ $1 -o "$TEMP_DIR/checkcc" $CXXFLAGS $LDFLAGS "$TEMP_DIR/test.cpp" 2> /dev/null
+}
+
+create_libtest_source()
+{
+ # $1: filename
+ # $2: optional include
+ cat > "$TEMP_DIR/$1" << __EOF__
+/* libtest file */
+int main(int argc, char **argv) {
+ return 0;
+}
+__EOF__
+ if [ -n "$2" ]; then
+ echo "#include <$2>" >> "$TEMP_DIR/$1"
+ fi
+}
+
+check_c_lib()
+{
+ # $1: libname
+ # $2: optional include
+ if [ -z "$TOOLCHAIN_CC" ]; then
+ return 1
+ fi
+ create_libtest_source "test.c" "$2"
+ rm -f "$TEMP_DIR/checklib"
+ $TOOLCHAIN_CC -o "$TEMP_DIR/checklib" $CFLAGS $LDFLAGS "-l$1" "$TEMP_DIR/test.c" 2> /dev/null
+}
+
+check_cpp_lib()
+{
+ # $1: libname
+ # $2: optional include
+ if [ -z "$TOOLCHAIN_CXX" ]; then
+ return 1
+ fi
+ create_libtest_source "test.cpp" "$2"
+ rm -f "$TEMP_DIR/checklib"
+ $TOOLCHAIN_CXX -o "$TEMP_DIR/checklib" $CXXFLAGS $LDFLAGS "-l$1" "$TEMP_DIR/test.cpp" 2> /dev/null
+}
+
+check_lib()
+{
+ # $1: libname
+ # $2: optional include
+ if [ -n "$TOOLCHAIN_CC" ]; then
+ check_c_lib "$1" "$2"
+ elif [ -n "$TOOLCHAIN_CXX" ]; then
+ check_cpp_lib "$1" "$2"
+ fi
+}
+
+detect_c_compiler()
+{
+ if [ -n "$TOOLCHAIN_CC" ]; then
+ return 0
+ fi
+ printf "detect C compiler... "
+ if [ -n "$CC" ]; then
+ if check_c_compiler "$CC"; then
+ TOOLCHAIN_CC=$CC
+ TOOLCHAIN=`"$TEMP_DIR/checkcc"`
+ TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -`
+ echo "$CC"
+ return 0
+ else
+ echo "$CC is not a working C compiler"
+ return 1
+ fi
+ else
+ for COMP in $C_COMPILERS
+ do
+ if check_c_compiler "$COMP"; then
+ TOOLCHAIN_CC=$COMP
+ TOOLCHAIN=`"$TEMP_DIR/checkcc"`
+ TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -`
+ echo "$COMP"
+ return 0
+ fi
+ done
+ echo "not found"
+ return 1
+ fi
+}
+
+detect_cpp_compiler()
+{
+ if [ -n "$TOOLCHAIN_CXX" ]; then
+ return 0
+ fi
+ printf "detect C++ compiler... "
+
+ if [ -n "$CXX" ]; then
+ if check_cpp_compiler "$CXX"; then
+ TOOLCHAIN_CXX=$CXX
+ TOOLCHAIN=`"$TEMP_DIR/checkcc"`
+ TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -`
+ echo "$CXX"
+ return 0
+ else
+ echo "$CXX is not a working C++ compiler"
+ return 1
+ fi
+ else
+ for COMP in $CPP_COMPILERS
+ do
+ if check_cpp_compiler "$COMP"; then
+ TOOLCHAIN_CXX=$COMP
+ TOOLCHAIN=`"$TEMP_DIR/checkcc"`
+ TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -`
+ echo "$COMP"
+ return 0
+ fi
+ done
+ echo "${TOOLCHAIN_CXX:-"not found"}"
+ return 1
+ fi
+}
+
+write_toolchain_defaults()
+{
+ echo "# toolchain" >> "$1"
+ if [ -n "$TOOLCHAIN_CC" ]; then
+ echo "CC = ${TOOLCHAIN_CC}" >> "$1"
+ fi
+ if [ -n "$TOOLCHAIN_CXX" ]; then
+ echo "CXX = ${TOOLCHAIN_CXX}" >> "$1"
+ fi
+ echo >> "$1"
+ if [ -f "make/${TOOLCHAIN_NAME}.mk" ]; then
+ cat "make/${TOOLCHAIN_NAME}.mk" >> "$1"
+ elif [ -f "make/cc.mk" ]; then
+ cat "make/cc.mk" >> "$1"
+ else
+ echo "!!! WARNING !!! Default toolchain flags not found. Configuration might be incomplete."
+ fi
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns="http://unixwork.de/uwproj"
+ targetNamespace="http://unixwork.de/uwproj"
+ elementFormDefault="qualified"
+ version="0.2"
+>
+ <xs:element name="project" type="ProjectType"/>
+
+ <xs:complexType name="ProjectType">
+ <xs:annotation>
+ <xs:documentation>
+ The root element of an uwproj project.
+ Consists of an optional <code>config</code> element
+ and an arbitrary number of <code>dependency</code>
+ and <code>target</code> elements.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:element name="config" type="ConfigType" minOccurs="0"/>
+ <xs:element name="dependency" type="DependencyType" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:element name="target" type="TargetType" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="ConfigType">
+ <xs:annotation>
+ <xs:documentation>
+ The configuration section.
+ Consists of an arbitrary number of <code>var</code> elements.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:element name="var" type="ConfigVarType" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="ConfigVarType">
+ <xs:annotation>
+ <xs:documentation>
+ The definition of a configuration variable.
+ <p>
+ Configuration variables are supposed to be used in the configure script and are also
+ written to the resulting config file (in contrast to make variables, which are only
+ written to the config file).
+ The <code>name</code> attribute is mandatory, the value is defined by the text body of the element.
+ The optional Boolean <code>exec</code> attribute (false by default) controls, whether the entire
+ definition is automatically executed under command substitution.
+ </p>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute name="name" type="xs:string" use="required"/>
+ <xs:attribute name="exec" type="xs:boolean" default="false"/>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+
+ <xs:complexType name="PkgConfigType">
+ <xs:annotation>
+ <xs:documentation>
+ Instructs configure to invoke <code>pkg-config</code>, if present on the system, to determine
+ compiler and linker flags. The text body of this element defines the package name to search.
+ To constrain the allowed versions, use the attributes <code>atleast, exact, max</code>.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute name="atleast" type="xs:string"/>
+ <xs:attribute name="exact" type="xs:string"/>
+ <xs:attribute name="max" type="xs:string"/>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+
+ <xs:simpleType name="LangType">
+ <xs:annotation>
+ <xs:documentation>
+ Requests a compiler for the specified language. Allowed values are
+ c, cpp.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="c"/>
+ <xs:enumeration value="cpp"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:complexType name="DependencyType">
+ <xs:annotation>
+ <xs:documentation>
+ Declares a dependency.
+ <p>
+ If the optional <code>name</code> attribute is omitted, the dependency is global
+ and must be satisfied, otherwise configuration shall fail.
+ A <em>named dependency</em> can be referenced by a target (or is implicitly referenced
+ by the default target, if no targets are specified).
+ Multiple declarations for the same named dependency may exist, in which case each declaration
+ is checked one after another, until one block is satisfied. The result of the first satisfied
+ dependency declaration is supposed to be applied to the config file.
+ </p>
+ <p>
+ The optional <code>platform</code> attribute may specify a <em>single</em> platform identifier and
+ the optional <code>toolchain</code> attribute may specify a <em>single</em> toolchain.
+ The optional <code>not</code> attribute may specify a comma-separated list of platform and/or
+ toolchain identifiers.
+ The configure script shall skip this dependency declaration if the detected platform and toolchain
+ is not matching the filter specification of these attributes.
+ </p>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="lang" type="LangType"/>
+ <xs:element name="cflags" type="FlagsType"/>
+ <xs:element name="cxxflags" type="FlagsType"/>
+ <xs:element name="ldflags" type="FlagsType"/>
+ <xs:element name="pkgconfig" type="PkgConfigType"/>
+ <xs:element name="test" type="xs:string">
+ <xs:annotation>
+ <xs:documentation>
+ Specifies a custom command that shall be executed to test whether this dependency is satisfied.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="make" type="MakeVarType"/>
+ </xs:choice>
+ <xs:attribute name="name" type="xs:string"/>
+ <xs:attribute name="platform" type="xs:string"/>
+ <xs:attribute name="toolchain" type="xs:string"/>
+ <xs:attribute name="not" type="xs:string"/>
+ </xs:complexType>
+
+ <xs:complexType name="FlagsType">
+ <xs:annotation>
+ <xs:documentation>
+ Instructs configure to append the contents of the element's body to the respective flags variable.
+ If the optional <code>exec</code> flag is set to <code>true</code>, the contents are supposed to be
+ executed under command substitution <em>at configuration time</em> before they are applied.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute name="exec" type="xs:boolean" default="false"/>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+
+ <xs:complexType name="TargetType">
+ <xs:annotation>
+ <xs:documentation>
+ Declares a build target that is supposed to be configured.
+ <p>
+ If no build target is declared explicitly, an implicit default
+ target is generated, which has the <code>alldependencies</code>
+ flag set.
+ </p>
+ <p>
+ The optional <code>name</code> attribute is also used to generate a prefix
+ for the compiler and linker flags variables.
+ Furthermore, a target may consist of an arbitrary number of <code>feature</code>,
+ <code>option</code>, and <code>define</code> elements.
+ Named dependencies can be listed (separated by comma) in the <code>dependencies</code>
+ element. If this target shall use <em>all</em> available named dependencies, the empty
+ element <code>alldependencies</code> can be used as a shortcut.
+ </p>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="feature" type="FeatureType"/>
+ <xs:element name="option" type="OptionType"/>
+ <xs:element name="define" type="DefineType"/>
+ <xs:element name="dependencies" type="DependenciesType"/>
+ <xs:element name="alldependencies">
+ <xs:complexType/>
+ </xs:element>
+ </xs:choice>
+ <xs:attribute name="name" type="xs:string"/>
+ </xs:complexType>
+
+ <xs:complexType name="FeatureType">
+ <xs:annotation>
+ <xs:documentation>
+ Declares an optional feature, that can be enabled during configuration, if all
+ <code>dependencies</code> are satisfied.
+ If a feature is enabled, all <code>define</code> and <code>make</code> definitions are
+ supposed to be applied to the config file.
+ In case the optional <code>default</code> attribute is set to true, the feature is enabled by default
+ and is supposed to be automatically disabled (without error) when the dependencies are not satisfied.
+ The name that is supposed to be used for the --enable and --disable arguments can be optionally
+ specified with the <code>arg</code> attribute. Otherwise, the <code>name</code> is used by default.
+ Optionally, a description for the help text of the resulting configure script can be specified by
+ adding a <code>desc</code> element.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:group ref="TargetDataGroup"/>
+ </xs:choice>
+ <xs:attribute name="name" type="xs:string" use="required"/>
+ <xs:attribute name="arg" type="xs:string"/>
+ <xs:attribute name="default" type="xs:boolean" default="false"/>
+ <xs:element name="desc" type="xs:string"/>
+ </xs:complexType>
+
+ <xs:complexType name="OptionType">
+ <xs:annotation>
+ <xs:documentation>
+ Declares a configuration option.
+ The option argument name is specified with the <code>arg</code> attribute.
+ Then, the children of this element specify possible <code>values</code> by defining the conditions
+ (in terms of dependencies) and effects (in terms of defines and make variables) of each value.
+ Finally, a set of <code>default</code>s is specified which supposed to automagically select the most
+ appropriate value for a specific platform under the available dependencies (in case the option is not
+ explicitly specified by using the command line argument).
+ </xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:element name="value" type="OptionValueType" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:element name="default" type="OptionDefaultType" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="arg" type="xs:string" use="required"/>
+ </xs:complexType>
+
+ <xs:complexType name="OptionValueType">
+ <xs:annotation>
+ <xs:documentation>
+ Declares a possible value for the option (in the <code>str</code> attribute) and
+ the conditions (<code>dependencies</code>) and effects, the value has.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:group ref="TargetDataGroup"/>
+ </xs:choice>
+ <xs:attribute name="str" type="xs:string" use="required"/>
+ </xs:complexType>
+
+ <xs:complexType name="OptionDefaultType">
+ <xs:annotation>
+ <xs:documentation>
+ Specifies a default value for this option. Multiple default values can be specified, in which case
+ they are checked one after another for availability. With the optional <code>platform</code> attribute,
+ the default value can be constrained to a <em>single</em> specific platform and is supposed to be
+ skipped by configure, when this platform is not detected.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:attribute name="value" type="xs:string" use="required"/>
+ <xs:attribute name="platform" type="xs:string"/>
+ </xs:complexType>
+
+ <xs:group name="TargetDataGroup">
+ <xs:choice>
+ <xs:element name="define" type="DefineType" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:element name="dependencies" type="DependenciesType" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:element name="make" type="MakeVarType" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:complexType name="DefineType">
+ <xs:annotation>
+ <xs:documentation>
+ Specifies C/C++ pre-processor definitions that are supposed to
+ be appended to the compiler flags, if supported.
+ (Note: for example, Fortran also supports C/C++ style pre-processor definitions under
+ certain circumstances)
+ </xs:documentation>
+ </xs:annotation>
+ <xs:attribute name="name" type="xs:string" use="required"/>
+ <xs:attribute name="value" type="xs:string"/>
+ </xs:complexType>
+
+ <xs:simpleType name="DependenciesType">
+ <xs:annotation>
+ <xs:documentation>A comma-separated list of named dependencies.</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <xs:simpleType name="MakeVarType">
+ <xs:annotation>
+ <xs:documentation>
+ The text contents in the body of this element are supposed to be appended literally
+ to the config file without prior processing.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+</xs:schema>
--- /dev/null
+\r
+Microsoft Visual Studio Solution File, Format Version 12.00\r
+# Visual Studio Version 17\r
+VisualStudioVersion = 17.5.33530.505\r
+MinimumVisualStudioVersion = 10.0.40219.1\r
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ucx", "ucx\ucx.vcxproj", "{27DA0164-3475-43E2-A1A4-A5D07D305749}"\r
+EndProject\r
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libidav", "libidav\libidav.vcxproj", "{C29C0378-6548-48E8-9426-31922515212A}"\r
+EndProject\r
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winui", "..\..\ui\winui\winui.vcxproj", "{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}"\r
+EndProject\r
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "idav", "idav\idav.vcxproj", "{F975D652-779E-4945-BFC2-8C649C6F9938}"\r
+EndProject\r
+Global\r
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution\r
+ Debug|ARM64 = Debug|ARM64\r
+ Debug|x64 = Debug|x64\r
+ Debug|x86 = Debug|x86\r
+ Release|ARM64 = Release|ARM64\r
+ Release|x64 = Release|x64\r
+ Release|x86 = Release|x86\r
+ EndGlobalSection\r
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution\r
+ {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|ARM64.ActiveCfg = Debug|x64\r
+ {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|ARM64.Build.0 = Debug|x64\r
+ {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x64.ActiveCfg = Debug|x64\r
+ {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x64.Build.0 = Debug|x64\r
+ {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x86.ActiveCfg = Debug|Win32\r
+ {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x86.Build.0 = Debug|Win32\r
+ {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|ARM64.ActiveCfg = Release|x64\r
+ {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|ARM64.Build.0 = Release|x64\r
+ {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x64.ActiveCfg = Release|x64\r
+ {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x64.Build.0 = Release|x64\r
+ {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x86.ActiveCfg = Release|Win32\r
+ {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x86.Build.0 = Release|Win32\r
+ {C29C0378-6548-48E8-9426-31922515212A}.Debug|ARM64.ActiveCfg = Debug|x64\r
+ {C29C0378-6548-48E8-9426-31922515212A}.Debug|ARM64.Build.0 = Debug|x64\r
+ {C29C0378-6548-48E8-9426-31922515212A}.Debug|x64.ActiveCfg = Debug|x64\r
+ {C29C0378-6548-48E8-9426-31922515212A}.Debug|x64.Build.0 = Debug|x64\r
+ {C29C0378-6548-48E8-9426-31922515212A}.Debug|x86.ActiveCfg = Debug|Win32\r
+ {C29C0378-6548-48E8-9426-31922515212A}.Debug|x86.Build.0 = Debug|Win32\r
+ {C29C0378-6548-48E8-9426-31922515212A}.Release|ARM64.ActiveCfg = Release|x64\r
+ {C29C0378-6548-48E8-9426-31922515212A}.Release|ARM64.Build.0 = Release|x64\r
+ {C29C0378-6548-48E8-9426-31922515212A}.Release|x64.ActiveCfg = Release|x64\r
+ {C29C0378-6548-48E8-9426-31922515212A}.Release|x64.Build.0 = Release|x64\r
+ {C29C0378-6548-48E8-9426-31922515212A}.Release|x86.ActiveCfg = Release|Win32\r
+ {C29C0378-6548-48E8-9426-31922515212A}.Release|x86.Build.0 = Release|Win32\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.Build.0 = Debug|ARM64\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.Deploy.0 = Debug|ARM64\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.ActiveCfg = Debug|x64\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.Build.0 = Debug|x64\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.Deploy.0 = Debug|x64\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.ActiveCfg = Debug|Win32\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.Build.0 = Debug|Win32\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.Deploy.0 = Debug|Win32\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.ActiveCfg = Release|ARM64\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.Build.0 = Release|ARM64\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.Deploy.0 = Release|ARM64\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.ActiveCfg = Release|x64\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.Build.0 = Release|x64\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.Deploy.0 = Release|x64\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.ActiveCfg = Release|Win32\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.Build.0 = Release|Win32\r
+ {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.Deploy.0 = Release|Win32\r
+ {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|ARM64.ActiveCfg = Debug|x64\r
+ {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|ARM64.Build.0 = Debug|x64\r
+ {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x64.ActiveCfg = Debug|x64\r
+ {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x64.Build.0 = Debug|x64\r
+ {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x86.ActiveCfg = Debug|Win32\r
+ {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x86.Build.0 = Debug|Win32\r
+ {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|ARM64.ActiveCfg = Release|x64\r
+ {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|ARM64.Build.0 = Release|x64\r
+ {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x64.ActiveCfg = Release|x64\r
+ {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x64.Build.0 = Release|x64\r
+ {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x86.ActiveCfg = Release|Win32\r
+ {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x86.Build.0 = Release|Win32\r
+ EndGlobalSection\r
+ GlobalSection(SolutionProperties) = preSolution\r
+ HideSolutionNode = FALSE\r
+ EndGlobalSection\r
+ GlobalSection(ExtensibilityGlobals) = postSolution\r
+ SolutionGuid = {141CA624-F556-4BE7-9218-8D6EEAB95C95}\r
+ EndGlobalSection\r
+EndGlobal\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">\r
+ <assemblyIdentity version="1.0.0.0" name="test.app"/>\r
+\r
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">\r
+ <application>\r
+ <!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8. \r
+ For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 \r
+ \r
+ It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->\r
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />\r
+ </application>\r
+ </compatibility>\r
+ \r
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">\r
+ <windowsSettings>\r
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>\r
+ </windowsSettings>\r
+ </application>\r
+</assembly>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+ <Import Project="..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props')" />\r
+ <Import Project="..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props')" />\r
+ <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props')" />\r
+ <ItemGroup Label="ProjectConfigurations">\r
+ <ProjectConfiguration Include="Debug|Win32">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>Win32</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|Win32">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>Win32</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Debug|x64">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>x64</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|x64">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>x64</Platform>\r
+ </ProjectConfiguration>\r
+ </ItemGroup>\r
+ <PropertyGroup Label="Globals">\r
+ <VCProjectVersion>16.0</VCProjectVersion>\r
+ <Keyword>Win32Proj</Keyword>\r
+ <ProjectGuid>{F975D652-779E-4945-BFC2-8C649C6F9938}</ProjectGuid>\r
+ <RootNamespace>testapp</RootNamespace>\r
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r
+ <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>\r
+ <WindowsPackageType>None</WindowsPackageType>\r
+ <AppxPackage>false</AppxPackage>\r
+ <ProjectName>idav</ProjectName>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>true</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>false</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <WholeProgramOptimization>true</WholeProgramOptimization>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>true</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>false</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <WholeProgramOptimization>true</WholeProgramOptimization>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+ <ImportGroup Label="ExtensionSettings">\r
+ </ImportGroup>\r
+ <ImportGroup Label="Shared">\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <PropertyGroup Label="UserMacros" />\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+ <IntDir>..\..\..\build\vs\idav\$(Platform)\$(Configuration)\</IntDir>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+ <IntDir>..\..\..\build\vs\idav\$(Platform)\$(Configuration)\</IntDir>\r
+ </PropertyGroup>\r
+ <PropertyGroup Label="Vcpkg">\r
+ <VcpkgEnableManifest>true</VcpkgEnableManifest>\r
+ </PropertyGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <FunctionLevelLinking>true</FunctionLevelLinking>\r
+ <IntrinsicFunctions>true</IntrinsicFunctions>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+ <OptimizeReferences>true</OptimizeReferences>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <SDLCheck>false</SDLCheck>\r
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ <AdditionalIncludeDirectories>..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include;..\..\..\ui\;..\..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+ <LanguageStandard_C>stdc17</LanguageStandard_C>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Windows</SubSystem>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <FunctionLevelLinking>true</FunctionLevelLinking>\r
+ <IntrinsicFunctions>true</IntrinsicFunctions>\r
+ <SDLCheck>false</SDLCheck>\r
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ <LanguageStandard_C>stdc17</LanguageStandard_C>\r
+ <AdditionalIncludeDirectories>..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include;..\..\..\ui\;..\..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Windows</SubSystem>\r
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+ <OptimizeReferences>true</OptimizeReferences>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemGroup>\r
+ <None Include="packages.config" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ProjectReference Include="..\..\..\ui\winui\winui.vcxproj">\r
+ <Project>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</Project>\r
+ </ProjectReference>\r
+ <ProjectReference Include="..\libidav\libidav.vcxproj">\r
+ <Project>{c29c0378-6548-48e8-9426-31922515212a}</Project>\r
+ </ProjectReference>\r
+ <ProjectReference Include="..\ucx\ucx.vcxproj">\r
+ <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>\r
+ </ProjectReference>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Manifest Include="app.manifest" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClCompile Include="..\..\..\application\application.c" />\r
+ <ClCompile Include="..\..\..\application\config.c" />\r
+ <ClCompile Include="..\..\..\application\davcontroller.c" />\r
+ <ClCompile Include="..\..\..\application\download.c" />\r
+ <ClCompile Include="..\..\..\application\upload.c" />\r
+ <ClCompile Include="..\..\..\application\main.c" />\r
+ <ClCompile Include="..\..\..\application\settings.c" />\r
+ <ClCompile Include="..\..\..\application\system.c" />\r
+ <ClCompile Include="..\..\..\application\window.c" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClInclude Include="..\..\..\application\application.h" />\r
+ <ClInclude Include="..\..\..\application\config.h" />\r
+ <ClInclude Include="..\..\..\application\davcontroller.h" />\r
+ <ClInclude Include="..\..\..\application\download.h" />\r
+ <ClInclude Include="..\..\..\application\upload.h" />\r
+ <ClInclude Include="..\..\..\application\settings.h" />\r
+ <ClInclude Include="..\..\..\application\system.h" />\r
+ <ClInclude Include="..\..\..\application\window.h" />\r
+ </ItemGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+ <ImportGroup Label="ExtensionTargets">\r
+ <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets')" />\r
+ <Import Project="..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets')" />\r
+ <Import Project="..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets')" />\r
+ <Import Project="..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />\r
+ </ImportGroup>\r
+ <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">\r
+ <PropertyGroup>\r
+ <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>\r
+ </PropertyGroup>\r
+ <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props'))" />\r
+ <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />\r
+ <Error Condition="!Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props'))" />\r
+ <Error Condition="!Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets'))" />\r
+ <Error Condition="!Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props'))" />\r
+ <Error Condition="!Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets'))" />\r
+ <Error Condition="!Exists('..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />\r
+ </Target>\r
+</Project>\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+ <ItemGroup>\r
+ <Filter Include="Ressourcendateien">\r
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\r
+ </Filter>\r
+ <Filter Include="src">\r
+ <UniqueIdentifier>{46525c70-7cfc-45fe-ae78-54123ad9bd7e}</UniqueIdentifier>\r
+ </Filter>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Manifest Include="app.manifest" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <None Include="packages.config" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClCompile Include="..\..\..\application\application.c">\r
+ <Filter>src</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\application\main.c">\r
+ <Filter>src</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\application\window.c">\r
+ <Filter>src</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\application\config.c">\r
+ <Filter>src</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\application\system.c">\r
+ <Filter>src</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\application\davcontroller.c">\r
+ <Filter>src</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\application\download.c">\r
+ <Filter>src</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\application\upload.c">\r
+ <Filter>src</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\application\settings.c">\r
+ <Filter>src</Filter>\r
+ </ClCompile>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClInclude Include="..\..\..\application\application.h">\r
+ <Filter>src</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\application\window.h">\r
+ <Filter>src</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\application\config.h">\r
+ <Filter>src</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\application\system.h">\r
+ <Filter>src</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\application\davcontroller.h">\r
+ <Filter>src</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\application\download.h">\r
+ <Filter>src</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\application\upload.h">\r
+ <Filter>src</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\application\settings.h">\r
+ <Filter>src</Filter>\r
+ </ClInclude>\r
+ </ItemGroup>\r
+</Project>\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<packages>\r
+ <package id="Microsoft.Windows.CppWinRT" version="2.0.230225.1" targetFramework="native" />\r
+ <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.230824.2" targetFramework="native" />\r
+ <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.1" targetFramework="native" />\r
+ <package id="Microsoft.WindowsAppSDK" version="1.3.230331000" targetFramework="native" />\r
+</packages>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+ <ItemGroup Label="ProjectConfigurations">\r
+ <ProjectConfiguration Include="Debug|Win32">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>Win32</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|Win32">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>Win32</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Debug|x64">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>x64</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|x64">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>x64</Platform>\r
+ </ProjectConfiguration>\r
+ </ItemGroup>\r
+ <PropertyGroup Label="Globals">\r
+ <VCProjectVersion>16.0</VCProjectVersion>\r
+ <Keyword>Win32Proj</Keyword>\r
+ <ProjectGuid>{c29c0378-6548-48e8-9426-31922515212a}</ProjectGuid>\r
+ <RootNamespace>libidav</RootNamespace>\r
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>true</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>false</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <WholeProgramOptimization>true</WholeProgramOptimization>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
+ <ConfigurationType>StaticLibrary</ConfigurationType>\r
+ <UseDebugLibraries>true</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
+ <ConfigurationType>StaticLibrary</ConfigurationType>\r
+ <UseDebugLibraries>false</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <WholeProgramOptimization>true</WholeProgramOptimization>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+ <ImportGroup Label="ExtensionSettings">\r
+ </ImportGroup>\r
+ <ImportGroup Label="Shared">\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <PropertyGroup Label="UserMacros" />\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <TargetExt>.dll</TargetExt>\r
+ <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+ <IntDir>..\..\..\build\vs\libidav\$(Platform)\$(Configuration)\</IntDir>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+ <IntDir>..\..\..\build\vs\libidav\$(Platform)\$(Configuration)\</IntDir>\r
+ </PropertyGroup>\r
+ <PropertyGroup Label="Vcpkg">\r
+ <VcpkgEnableManifest>true</VcpkgEnableManifest>\r
+ </PropertyGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <FunctionLevelLinking>true</FunctionLevelLinking>\r
+ <IntrinsicFunctions>true</IntrinsicFunctions>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+ <OptimizeReferences>true</OptimizeReferences>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <SDLCheck>false</SDLCheck>\r
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ <LanguageStandard_C>stdc11</LanguageStandard_C>\r
+ <AdditionalIncludeDirectories>..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include</AdditionalIncludeDirectories>\r
+ <AdditionalOptions>\r
+ </AdditionalOptions>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ <AdditionalLibraryDirectories>..\vcpkg_installed\x64-windows\x64-windows\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>\r
+ <AdditionalDependencies>charset.lib;iconv.lib;libcurl.lib;libxml2.lib;lzma.lib;zlib.lib;bcrypt.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+ </Link>\r
+ <PostBuildEvent>\r
+ <Command>xcopy /y $(SolutionDir)vcpkg_installed\x64-windows\x64-windows\bin\*.dll $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</Command>\r
+ </PostBuildEvent>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <FunctionLevelLinking>true</FunctionLevelLinking>\r
+ <IntrinsicFunctions>true</IntrinsicFunctions>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ <LanguageStandard_C>stdc17</LanguageStandard_C>\r
+ <AdditionalIncludeDirectories>..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include</AdditionalIncludeDirectories>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+ <OptimizeReferences>true</OptimizeReferences>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemGroup>\r
+ <ClCompile Include="..\..\..\libidav\config.c" />\r
+ <ClCompile Include="..\..\..\libidav\crypto.c" />\r
+ <ClCompile Include="..\..\..\libidav\davqlexec.c" />\r
+ <ClCompile Include="..\..\..\libidav\davqlparser.c" />\r
+ <ClCompile Include="..\..\..\libidav\methods.c" />\r
+ <ClCompile Include="..\..\..\libidav\pwdstore.c" />\r
+ <ClCompile Include="..\..\..\libidav\resource.c" />\r
+ <ClCompile Include="..\..\..\libidav\session.c" />\r
+ <ClCompile Include="..\..\..\libidav\utils.c" />\r
+ <ClCompile Include="..\..\..\libidav\versioning.c" />\r
+ <ClCompile Include="..\..\..\libidav\webdav.c" />\r
+ <ClCompile Include="..\..\..\libidav\xml.c" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClInclude Include="..\..\..\libidav\config.h" />\r
+ <ClInclude Include="..\..\..\libidav\crypto.h" />\r
+ <ClInclude Include="..\..\..\libidav\davqlexec.h" />\r
+ <ClInclude Include="..\..\..\libidav\davqlparser.h" />\r
+ <ClInclude Include="..\..\..\libidav\methods.h" />\r
+ <ClInclude Include="..\..\..\libidav\pwdstore.h" />\r
+ <ClInclude Include="..\..\..\libidav\resource.h" />\r
+ <ClInclude Include="..\..\..\libidav\session.h" />\r
+ <ClInclude Include="..\..\..\libidav\utils.h" />\r
+ <ClInclude Include="..\..\..\libidav\versioning.h" />\r
+ <ClInclude Include="..\..\..\libidav\webdav.h" />\r
+ <ClInclude Include="..\..\..\libidav\xml.h" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ProjectReference Include="..\ucx\ucx.vcxproj">\r
+ <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>\r
+ </ProjectReference>\r
+ </ItemGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+ <ImportGroup Label="ExtensionTargets">\r
+ </ImportGroup>\r
+</Project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+ <ItemGroup>\r
+ <Filter Include="Quelldateien">\r
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\r
+ <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\r
+ </Filter>\r
+ <Filter Include="Headerdateien">\r
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\r
+ <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>\r
+ </Filter>\r
+ <Filter Include="Ressourcendateien">\r
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\r
+ </Filter>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClCompile Include="..\..\..\libidav\crypto.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\libidav\davqlexec.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\libidav\davqlparser.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\libidav\methods.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\libidav\resource.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\libidav\session.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\libidav\utils.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\libidav\versioning.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\libidav\webdav.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\libidav\xml.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\libidav\config.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\libidav\pwdstore.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClInclude Include="..\..\..\libidav\crypto.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\libidav\davqlexec.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\libidav\davqlparser.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\libidav\methods.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\libidav\resource.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\libidav\session.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\libidav\utils.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\libidav\versioning.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\libidav\webdav.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\libidav\xml.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\libidav\config.h">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\libidav\pwdstore.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ </ItemGroup>\r
+</Project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">\r
+ <assemblyIdentity version="1.0.0.0" name="test.app"/>\r
+\r
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">\r
+ <application>\r
+ <!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8. \r
+ For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 \r
+ \r
+ It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->\r
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />\r
+ </application>\r
+ </compatibility>\r
+ \r
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">\r
+ <windowsSettings>\r
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>\r
+ </windowsSettings>\r
+ </application>\r
+</assembly>
\ No newline at end of file
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include <Windows.h>\r
+\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+#include <stdbool.h>\r
+\r
+#include <ui/ui.h>\r
+\r
+typedef struct WindowData {\r
+ UiInteger* check;\r
+ UiInteger* toggle;\r
+ UiInteger* radio;\r
+ UiString* text;\r
+ UiString* password;\r
+ UiList* list;\r
+ UiString* t1;\r
+ UiString* t2;\r
+ UiString* t3;\r
+ UiString* path;\r
+ UiList* list2;\r
+ UiList* list3;\r
+ UiDouble* progress;\r
+ UiInteger* spinner;\r
+} WindowData;\r
+\r
+static UiIcon* folder_icon;\r
+\r
+void action1(UiEvent* event, void* data) {\r
+ char* action = data;\r
+ \r
+ WindowData* wdata = event->window;\r
+ int64_t is_checked = wdata->check->get(wdata->check);\r
+ int64_t radio = wdata->radio->get(wdata->radio);\r
+\r
+ printf("data: %s %d\n", data, is_checked);\r
+\r
+ double d = wdata->progress->get(wdata->progress);\r
+ wdata->progress->set(wdata->progress, d + 1);\r
+\r
+ int spinner_active = wdata->spinner->get(wdata->spinner);\r
+ wdata->spinner->set(wdata->spinner, !spinner_active);\r
+}\r
+\r
+void action_set_checkbox(UiEvent* event, void* data) {\r
+ char* action = data;\r
+\r
+ WindowData* wdata = event->window;\r
+ wdata->check->set(wdata->check, 1);\r
+}\r
+\r
+void action_onchange(UiEvent* event, void* data) {\r
+ printf("onchange: %d\n", event->intval);\r
+}\r
+\r
+void action_switch(UiEvent* event, void* data) {\r
+ printf("onchange: %d\n", event->intval);\r
+}\r
+\r
+void action_toolbar_button(UiEvent* event, void *data) {\r
+ printf("toolbar action\n");\r
+}\r
+\r
+\r
+void action_listselection_changed(UiEvent* event, void* data) {\r
+ printf("selection changed\n");\r
+ UiListSelection* sel = event->eventdata;\r
+ for (int i = 0; i < sel->count; i++) {\r
+ int row = sel->rows[i];\r
+ printf("row: %d\n", row);\r
+ }\r
+}\r
+\r
+void action_onactivate(UiEvent* event, void* Data) {\r
+ printf("activate\n");\r
+ UiListSelection* sel = event->eventdata;\r
+ for (int i = 0; i < sel->count; i++) {\r
+ int row = sel->rows[i];\r
+ printf("row: %d\n", row);\r
+ }\r
+}\r
+\r
+typedef struct TableData {\r
+ char* col1;\r
+ char* col2;\r
+ char* col3;\r
+} TableData;\r
+\r
+void* table_getvalue(void* data, int i) {\r
+ TableData* t = data;\r
+ switch (i) {\r
+ case 0: return folder_icon;\r
+ case 1: return t->col1;\r
+ case 2: return t->col2;\r
+ case 3: return t->col3;\r
+ }\r
+ return NULL;\r
+}\r
+\r
+void action_add(UiEvent* event, void* data) {\r
+ WindowData* wdata = event->window;\r
+ char* t1 = wdata->t1->get(wdata->t1);\r
+ char* t2 = wdata->t2->get(wdata->t2);\r
+ char* t3 = wdata->t3->get(wdata->t3);\r
+\r
+ TableData* tdat = malloc(sizeof(TableData));\r
+ tdat->col1 = _strdup(t1);\r
+ tdat->col2 = _strdup(t2);\r
+ tdat->col3 = _strdup(t3);\r
+ ui_list_append(wdata->list2, tdat);\r
+ wdata->list2->update(wdata->list2, 0);\r
+\r
+}\r
+\r
+void action_breadcrumb(UiEvent* event, void* data) {\r
+ int i = event->intval;\r
+ char* c = event->eventdata;\r
+ printf("index: %d\n", i);\r
+}\r
+\r
+void dragstart(UiEvent* event, void* data) {\r
+ UiListDnd* ldnd = event->eventdata;\r
+ ui_selection_settext(ldnd->dnd, "Hello World!", -1);\r
+}\r
+\r
+void dragcomplete(UiEvent* event, void* data) {\r
+\r
+}\r
+\r
+void dragover(UiEvent* event, void* data) {\r
+\r
+}\r
+\r
+void drop(UiEvent* event, void* data) {\r
+\r
+}\r
+\r
+void application_startup(UiEvent* event, void* data) {\r
+ UiObject* obj = ui_window("Test", NULL);\r
+ WindowData* wdata = ui_malloc(obj->ctx, sizeof(WindowData));\r
+ obj->window = wdata;\r
+ wdata->check = ui_int_new(obj->ctx, "check");\r
+ wdata->toggle = ui_int_new(obj->ctx, "toggle");\r
+ wdata->radio = ui_int_new(obj->ctx, "radio");\r
+ wdata->text = ui_string_new(obj->ctx, "text");\r
+ wdata->password = ui_string_new(obj->ctx, "password");\r
+ wdata->list = ui_list_new(obj->ctx, "list");\r
+ wdata->list2 = ui_list_new(obj->ctx, "list2");\r
+ wdata->list3 = ui_list_new(obj->ctx, "list3");\r
+ wdata->t1 = ui_string_new(obj->ctx, "t1");\r
+ wdata->t2 = ui_string_new(obj->ctx, "t2");\r
+ wdata->t3 = ui_string_new(obj->ctx, "t3");\r
+ wdata->path = ui_string_new(obj->ctx, "path");\r
+ wdata->progress = ui_double_new(obj->ctx, "progress");\r
+ wdata->spinner = ui_int_new(obj->ctx, "spinner");\r
+\r
+ ui_list_append(wdata->list, "Hello");\r
+ ui_list_append(wdata->list, "World");\r
+ ui_list_append(wdata->list, "Item3");\r
+ ui_list_append(wdata->list, "Item4");\r
+ ui_list_append(wdata->list, "Item5");\r
+ ui_list_append(wdata->list, "Item6");\r
+\r
+ ui_list_append(wdata->list3, "usr");\r
+ ui_list_append(wdata->list3, "share");\r
+ ui_list_append(wdata->list3, "test");\r
+ ui_list_append(wdata->list3, "dir");\r
+\r
+ //folder_icon = ui_icon("Folder", 32);\r
+ folder_icon = ui_foldericon(16);\r
+\r
+ TableData* td1 = malloc(sizeof(TableData));\r
+ TableData* td2 = malloc(sizeof(TableData));\r
+ TableData* td3 = malloc(sizeof(TableData));\r
+ TableData* td4 = malloc(sizeof(TableData));\r
+ TableData* td5 = malloc(sizeof(TableData));\r
+ TableData* td6 = malloc(sizeof(TableData));\r
+ td1->col1 = "a1";\r
+ td1->col2 = "b1";\r
+ td1->col3 = "c1";\r
+ td2->col1 = "a2";\r
+ td2->col2 = "b2";\r
+ td2->col3 = "b3";\r
+ td3->col1 = "a3";\r
+ td3->col2 = "b3";\r
+ td3->col3 = "c3";\r
+ td4->col1 = "a3";\r
+ td4->col2 = "b3";\r
+ td4->col3 = "c3";\r
+ td5->col1 = "a3";\r
+ td5->col2 = "b3";\r
+ td5->col3 = "c3";\r
+ td6->col1 = "a3";\r
+ td6->col2 = "b3";\r
+ td6->col3 = "c3";\r
+\r
+ ui_list_append(wdata->list2, td1);\r
+ ui_list_append(wdata->list2, td2);\r
+ ui_list_append(wdata->list2, td3);\r
+ ui_list_append(wdata->list2, td4);\r
+ ui_list_append(wdata->list2, td5);\r
+ ui_list_append(wdata->list2, td6);\r
+\r
+ ui_scrolledwindow0(obj) {\r
+ ui_grid(obj, .margin = 10, .columnspacing = 5, .rowspacing = 20) {\r
+ ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");\r
+ ui_button(obj, .label = "Button2", .onclick = action1, .onclickdata = "action2");\r
+ ui_button(obj, .label = "Button3", .onclick = action1, .onclickdata = "action3", .hexpand = true);\r
+ ui_newline(obj);\r
+\r
+ ui_button(obj, .label = "Button4", .onclick = action1, .onclickdata = "action4");\r
+ ui_button(obj, .label = "Button5", .onclick = action1, .onclickdata = "action5", .colspan = 2);\r
+ ui_newline(obj);\r
+\r
+ ui_button(obj, .label = "Very Long Button Label Text ____________ Test", .onclick = action_set_checkbox);\r
+ ui_newline(obj);\r
+\r
+ ui_checkbox(obj, .label = "Option 1", .value = wdata->check, .onchange = action_onchange);\r
+ ui_togglebutton(obj, .label = "Option 2", .value = wdata->toggle);\r
+ ui_newline(obj);\r
+\r
+ ui_label(obj, .label = "Progress");\r
+ ui_progressspinner(obj, .value = wdata->spinner);\r
+ ui_newline(obj);\r
+ \r
+ ui_hbox(obj, .colspan = 3) {\r
+ ui_radiobutton(obj, .label = "Radio 1", .value = wdata->radio);\r
+ ui_radiobutton(obj, .label = "Radio 2", .value = wdata->radio);\r
+ ui_radiobutton(obj, .label = "Radio 3", .value = wdata->radio);\r
+ }\r
+ ui_newline(obj);\r
+ ui_radiobutton(obj, .label = "Radio 4", .value = wdata->radio);\r
+ ui_switch(obj, .label = "test", .onchange = action_switch);\r
+ ui_newline(obj);\r
+\r
+ //ui_breadcrumbbar(obj, .list = wdata->list3, .onactivate=action_breadcrumb);\r
+ ui_textfield(obj, .varname = "newtext");\r
+ ui_path_textfield(obj, .colspan = 2, .value=wdata->path, .onactivate = action_breadcrumb);\r
+ ui_newline(obj);\r
+ wdata->path->set(wdata->path, "/usr/path/test");\r
+ \r
+ ui_textfield(obj, .value = wdata->text);\r
+ ui_passwordfield(obj, .value = wdata->password);\r
+ ui_newline(obj);\r
+\r
+ ui_frame(obj, .label = "Test", .colspan = 3) {\r
+ ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");\r
+ }\r
+ ui_newline(obj);\r
+ \r
+ ui_expander(obj, .label = "Expand", .colspan = 3, .margin = 10, .spacing = 5, .isexpanded = false) {\r
+ ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");\r
+ ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");\r
+ ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1");\r
+ }\r
+ ui_newline(obj);\r
+\r
+ ui_combobox(obj, .list = wdata->list, .onselection= action_listselection_changed, .onactivate= action_onactivate);\r
+ ui_newline(obj);\r
+\r
+ ui_tabview(obj, .colspan = 3, .vexpand = true, .hexpand = true, .tabview = UI_TABVIEW_NAVIGATION_SIDE) {\r
+ ui_tab(obj, "Tab 1") {\r
+ ui_button(obj, .label = "Tab 1 Button");\r
+ }\r
+ ui_tab(obj, "Tab 2") {\r
+ ui_button(obj, .label = "Tab 2 Button");\r
+ }\r
+ ui_tab(obj, "Tab 3") {\r
+\r
+ }\r
+ }\r
+ ui_newline(obj);\r
+\r
+ ui_label(obj, .label = "Test Label");\r
+ ui_progressbar(obj, .value = wdata->progress, .colspan = 2);\r
+ ui_newline(obj);\r
+\r
+ ui_newline(obj);\r
+ ui_textfield(obj, .value = wdata->t1);\r
+ ui_textfield(obj, .value = wdata->t2);\r
+ ui_textfield(obj, .value = wdata->t3);\r
+ ui_newline(obj);\r
+ ui_button(obj, .label = "Add", .onclick = action_add);\r
+ ui_newline(obj);\r
+\r
+\r
+ ui_newline(obj);\r
+\r
+ UiModel* model = ui_model(obj->ctx, UI_ICON_TEXT, "Col 1", UI_STRING, "Col 2", UI_STRING, "Col 3", -1);\r
+ model->getvalue = table_getvalue;\r
+ ui_table(obj, .colspan = 3, .model = model, .list = wdata->list2, .onactivate = action_onactivate,\r
+ .onselection = action_listselection_changed,\r
+ .ondragstart = dragstart, .ondragcomplete = dragcomplete, .ondrop = drop);\r
+ ui_model_free(obj->ctx, model);\r
+ }\r
+ } \r
+\r
+ ui_show(obj);\r
+}\r
+\r
+\r
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow)\r
+{ \r
+ ui_init("app1", 0, NULL);\r
+ ui_onstartup(application_startup, NULL);\r
+ \r
+ ui_menu("File") {\r
+ ui_menuitem(.label = "Item 1");\r
+ ui_menuitem(.label = "Item 2");\r
+ ui_menuseparator();\r
+ ui_menu("File Sub") {\r
+ ui_menuitem(.label = "Sub Item");\r
+ }\r
+\r
+ ui_menuitem(.label = "Exit");\r
+ }\r
+\r
+ ui_toolbar_item("Test", .label = "Home", .icon = "Home", .onclick = action_toolbar_button);\r
+ ui_toolbar_toggleitem("Toggle", .label = "Toggle", .onchange = action_toolbar_button);\r
+ ui_toolbar_toggleitem("Toggle2", .label = "Toggle2", .onchange = action_toolbar_button);\r
+ ui_toolbar_toggleitem("Toggle3", .label = "Toggle3", .onchange = action_toolbar_button);\r
+\r
+ ui_toolbar_menu("Menu", .label = "Menu") {\r
+ \r
+ ui_menuitem(.label = "x", NULL, NULL);\r
+ ui_menuitem(.label = "x", NULL, NULL);\r
+ ui_menuitem(.label = "x", NULL, NULL);\r
+ ui_menuitem(.label = "x", NULL, NULL);\r
+ ui_menuitem(.label = "x", NULL, NULL);\r
+ ui_menu("TB Sub") {\r
+ ui_menuitem("TB subitem", NULL, NULL);\r
+ }\r
+ }\r
+\r
+ ui_toolbar_menu(NULL, .label = "Menu") {\r
+ ui_menuitem("Secondary Test", NULL, NULL);\r
+ ui_menu("Secondary Sub") {\r
+ ui_menuitem("Secondary subitem", NULL, NULL);\r
+ }\r
+ }\r
+\r
+ ui_toolbar_add_default("Test", UI_TOOLBAR_LEFT);\r
+ ui_toolbar_add_default("Toggle", UI_TOOLBAR_LEFT);\r
+ ui_toolbar_add_default("Toggle2", UI_TOOLBAR_CENTER);\r
+ ui_toolbar_add_default("Toggle3", UI_TOOLBAR_CENTER);\r
+ ui_toolbar_add_default("Menu", UI_TOOLBAR_RIGHT);\r
+\r
+ ui_main();\r
+\r
+ return (EXIT_SUCCESS);\r
+}\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<packages>\r
+ <package id="Microsoft.Windows.CppWinRT" version="2.0.230225.1" targetFramework="native" />\r
+ <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.230824.2" targetFramework="native" />\r
+ <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.1" targetFramework="native" />\r
+ <package id="Microsoft.WindowsAppSDK" version="1.3.230331000" targetFramework="native" />\r
+</packages>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+ <Import Project="..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props')" />\r
+ <Import Project="..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props')" />\r
+ <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props')" />\r
+ <ItemGroup Label="ProjectConfigurations">\r
+ <ProjectConfiguration Include="Debug|Win32">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>Win32</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|Win32">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>Win32</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Debug|x64">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>x64</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|x64">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>x64</Platform>\r
+ </ProjectConfiguration>\r
+ \r
+ </ItemGroup>\r
+ <PropertyGroup Label="Globals">\r
+ <VCProjectVersion>16.0</VCProjectVersion>\r
+ <Keyword>Win32Proj</Keyword>\r
+ <ProjectGuid>{3541f08b-e6cc-4c23-a0d3-51983aab33c6}</ProjectGuid>\r
+ <RootNamespace>testapp</RootNamespace>\r
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r
+ <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>\r
+ <WindowsPackageType>None</WindowsPackageType>\r
+ <AppxPackage>false</AppxPackage>\r
+ <ProjectName>idav</ProjectName>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>true</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>false</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <WholeProgramOptimization>true</WholeProgramOptimization>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>true</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>false</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <WholeProgramOptimization>true</WholeProgramOptimization>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+ <ImportGroup Label="ExtensionSettings">\r
+ </ImportGroup>\r
+ <ImportGroup Label="Shared">\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <PropertyGroup Label="UserMacros" />\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+ <IntDir>..\..\..\build\vs\testapp\$(Platform)\$(Configuration)\</IntDir>\r
+ </PropertyGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <FunctionLevelLinking>true</FunctionLevelLinking>\r
+ <IntrinsicFunctions>true</IntrinsicFunctions>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+ <OptimizeReferences>true</OptimizeReferences>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ <AdditionalIncludeDirectories>C:\Users\Olaf\Projekte\toolkit\ui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Windows</SubSystem>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <FunctionLevelLinking>true</FunctionLevelLinking>\r
+ <IntrinsicFunctions>true</IntrinsicFunctions>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+ <OptimizeReferences>true</OptimizeReferences>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemGroup>\r
+ <ClCompile Include="main.c" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <None Include="packages.config" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ProjectReference Include="..\..\..\ui\winui\winui.vcxproj">\r
+ <Project>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</Project>\r
+ </ProjectReference>\r
+ <ProjectReference Include="..\ucx\ucx.vcxproj">\r
+ <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>\r
+ </ProjectReference>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Manifest Include="app.manifest" />\r
+ </ItemGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+ <ImportGroup Label="ExtensionTargets">\r
+ <Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets')" />\r
+ <Import Project="..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets')" />\r
+ <Import Project="..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets')" />\r
+ <Import Project="..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />\r
+ </ImportGroup>\r
+ <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">\r
+ <PropertyGroup>\r
+ <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>\r
+ </PropertyGroup>\r
+ <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.props'))" />\r
+ <Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.230225.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />\r
+ <Error Condition="!Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.props'))" />\r
+ <Error Condition="!Exists('..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.1\build\Microsoft.Windows.SDK.BuildTools.targets'))" />\r
+ <Error Condition="!Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.props'))" />\r
+ <Error Condition="!Exists('..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.WindowsAppSDK.1.3.230331000\build\native\Microsoft.WindowsAppSDK.targets'))" />\r
+ <Error Condition="!Exists('..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.ImplementationLibrary.1.0.230824.2\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />\r
+ </Target>\r
+</Project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+ <ItemGroup>\r
+ <Filter Include="Quelldateien">\r
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\r
+ <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\r
+ </Filter>\r
+ <Filter Include="Headerdateien">\r
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\r
+ <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>\r
+ </Filter>\r
+ <Filter Include="Ressourcendateien">\r
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\r
+ </Filter>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClCompile Include="main.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <None Include="packages.config" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Manifest Include="app.manifest" />\r
+ </ItemGroup>\r
+</Project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+ <ItemGroup Label="ProjectConfigurations">\r
+ <ProjectConfiguration Include="Debug|Win32">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>Win32</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|Win32">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>Win32</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Debug|x64">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>x64</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|x64">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>x64</Platform>\r
+ </ProjectConfiguration>\r
+ </ItemGroup>\r
+ <PropertyGroup Label="Globals">\r
+ <VCProjectVersion>16.0</VCProjectVersion>\r
+ <Keyword>Win32Proj</Keyword>\r
+ <ProjectGuid>{27da0164-3475-43e2-a1a4-a5d07d305749}</ProjectGuid>\r
+ <RootNamespace>ucx</RootNamespace>\r
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>true</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>false</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <WholeProgramOptimization>true</WholeProgramOptimization>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
+ <ConfigurationType>StaticLibrary</ConfigurationType>\r
+ <UseDebugLibraries>true</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
+ <ConfigurationType>StaticLibrary</ConfigurationType>\r
+ <UseDebugLibraries>false</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <WholeProgramOptimization>true</WholeProgramOptimization>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+ <ImportGroup Label="ExtensionSettings">\r
+ </ImportGroup>\r
+ <ImportGroup Label="Shared">\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <PropertyGroup Label="UserMacros" />\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+ <IntDir>..\..\..\build\vs\ucx\$(Platform)\$(Configuration)\</IntDir>\r
+ </PropertyGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <FunctionLevelLinking>true</FunctionLevelLinking>\r
+ <IntrinsicFunctions>true</IntrinsicFunctions>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+ <OptimizeReferences>true</OptimizeReferences>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <SDLCheck>false</SDLCheck>\r
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ <LanguageStandard_C>stdc17</LanguageStandard_C>\r
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <FunctionLevelLinking>true</FunctionLevelLinking>\r
+ <IntrinsicFunctions>true</IntrinsicFunctions>\r
+ <SDLCheck>false</SDLCheck>\r
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ <LanguageStandard_C>stdc17</LanguageStandard_C>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+ <OptimizeReferences>true</OptimizeReferences>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemGroup>\r
+ <ClCompile Include="..\..\..\ucx\allocator.c" />\r
+ <ClCompile Include="..\..\..\ucx\array_list.c" />\r
+ <ClCompile Include="..\..\..\ucx\buffer.c" />\r
+ <ClCompile Include="..\..\..\ucx\compare.c" />\r
+ <ClCompile Include="..\..\..\ucx\hash_key.c" />\r
+ <ClCompile Include="..\..\..\ucx\hash_map.c" />\r
+ <ClCompile Include="..\..\..\ucx\linked_list.c" />\r
+ <ClCompile Include="..\..\..\ucx\list.c" />\r
+ <ClCompile Include="..\..\..\ucx\map.c" />\r
+ <ClCompile Include="..\..\..\ucx\mempool.c" />\r
+ <ClCompile Include="..\..\..\ucx\printf.c" />\r
+ <ClCompile Include="..\..\..\ucx\string.c" />\r
+ <ClCompile Include="..\..\..\ucx\utils.c" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClInclude Include="..\..\..\ucx\cx\allocator.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\array_list.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\buffer.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\collection.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\common.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\compare.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\hash_key.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\hash_map.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\iterator.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\linked_list.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\list.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\map.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\mempool.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\printf.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\string.h" />\r
+ <ClInclude Include="..\..\..\ucx\cx\utils.h" />\r
+ </ItemGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+ <ImportGroup Label="ExtensionTargets">\r
+ </ImportGroup>\r
+</Project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+ <ItemGroup>\r
+ <Filter Include="Quelldateien">\r
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\r
+ <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\r
+ </Filter>\r
+ <Filter Include="Headerdateien">\r
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\r
+ <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>\r
+ </Filter>\r
+ <Filter Include="Ressourcendateien">\r
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\r
+ </Filter>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClCompile Include="..\..\..\ucx\allocator.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ucx\array_list.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ucx\buffer.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ucx\compare.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ucx\hash_key.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ucx\hash_map.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ucx\linked_list.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ucx\list.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ucx\map.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ucx\printf.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ucx\string.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ucx\utils.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ucx\mempool.c">\r
+ <Filter>Quelldateien</Filter>\r
+ </ClCompile>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClInclude Include="..\..\..\ucx\cx\allocator.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\array_list.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\buffer.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\collection.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\common.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\compare.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\hash_key.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\hash_map.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\iterator.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\linked_list.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\list.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\map.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\mempool.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\printf.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\string.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ucx\cx\utils.h">\r
+ <Filter>Headerdateien</Filter>\r
+ </ClInclude>\r
+ </ItemGroup>\r
+</Project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+ <ItemGroup Label="ProjectConfigurations">\r
+ <ProjectConfiguration Include="Debug|Win32">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>Win32</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|Win32">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>Win32</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Debug|x64">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>x64</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|x64">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>x64</Platform>\r
+ </ProjectConfiguration>\r
+ </ItemGroup>\r
+ <PropertyGroup Label="Globals">\r
+ <VCProjectVersion>17.0</VCProjectVersion>\r
+ <Keyword>Win32Proj</Keyword>\r
+ <ProjectGuid>{8b88698e-c185-4383-99fe-0c34d6deed2e}</ProjectGuid>\r
+ <RootNamespace>uicommon</RootNamespace>\r
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>true</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>false</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <WholeProgramOptimization>true</WholeProgramOptimization>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
+ <ConfigurationType>StaticLibrary</ConfigurationType>\r
+ <UseDebugLibraries>true</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
+ <ConfigurationType>Application</ConfigurationType>\r
+ <UseDebugLibraries>false</UseDebugLibraries>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <WholeProgramOptimization>true</WholeProgramOptimization>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+ <ImportGroup Label="ExtensionSettings">\r
+ </ImportGroup>\r
+ <ImportGroup Label="Shared">\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <PropertyGroup Label="UserMacros" />\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+ <IntDir>..\..\..\build\vs\uicommon\$(Platform)\$(Configuration)\</IntDir>\r
+ </PropertyGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <FunctionLevelLinking>true</FunctionLevelLinking>\r
+ <IntrinsicFunctions>true</IntrinsicFunctions>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+ <OptimizeReferences>true</OptimizeReferences>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <SDLCheck>false</SDLCheck>\r
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;UI_WINUI;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ <LanguageStandard_C>stdc17</LanguageStandard_C>\r
+ <AdditionalIncludeDirectories>$(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <ClCompile>\r
+ <WarningLevel>Level3</WarningLevel>\r
+ <FunctionLevelLinking>true</FunctionLevelLinking>\r
+ <IntrinsicFunctions>true</IntrinsicFunctions>\r
+ <SDLCheck>true</SDLCheck>\r
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <ConformanceMode>true</ConformanceMode>\r
+ </ClCompile>\r
+ <Link>\r
+ <SubSystem>Console</SubSystem>\r
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+ <OptimizeReferences>true</OptimizeReferences>\r
+ <GenerateDebugInformation>true</GenerateDebugInformation>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemGroup>\r
+ <ClCompile Include="..\..\..\ui\common\context.c" />\r
+ <ClCompile Include="..\..\..\ui\common\document.c" />\r
+ <ClCompile Include="..\..\..\ui\common\menu.c" />\r
+ <ClCompile Include="..\..\..\ui\common\object.c" />\r
+ <ClCompile Include="..\..\..\ui\common\properties.c" />\r
+ <ClCompile Include="..\..\..\ui\common\toolbar.c" />\r
+ <ClCompile Include="..\..\..\ui\common\types.c" />\r
+ <ClCompile Include="..\..\..\ui\common\ucx_properties.c" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClInclude Include="..\..\..\ui\common\context.h" />\r
+ <ClInclude Include="..\..\..\ui\common\document.h" />\r
+ <ClInclude Include="..\..\..\ui\common\menu.h" />\r
+ <ClInclude Include="..\..\..\ui\common\object.h" />\r
+ <ClInclude Include="..\..\..\ui\common\properties.h" />\r
+ <ClInclude Include="..\..\..\ui\common\toolbar.h" />\r
+ <ClInclude Include="..\..\..\ui\common\types.h" />\r
+ <ClInclude Include="..\..\..\ui\common\ucx_properties.h" />\r
+ </ItemGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+ <ImportGroup Label="ExtensionTargets">\r
+ </ImportGroup>\r
+</Project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+ <ItemGroup>\r
+ <Filter Include="Ressourcendateien">\r
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\r
+ </Filter>\r
+ <Filter Include="Ressourcendateien\Source">\r
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\r
+ <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\r
+ </Filter>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClCompile Include="..\..\..\ui\common\context.c">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ui\common\document.c">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ui\common\menu.c">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ui\common\object.c">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ui\common\properties.c">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ui\common\toolbar.c">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ui\common\types.c">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\..\..\ui\common\ucx_properties.c">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClCompile>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClInclude Include="..\..\..\ui\common\context.h">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ui\common\document.h">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ui\common\menu.h">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ui\common\object.h">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ui\common\properties.h">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ui\common\toolbar.h">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ui\common\types.h">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\..\..\ui\common\ucx_properties.h">\r
+ <Filter>Ressourcendateien\Source</Filter>\r
+ </ClInclude>\r
+ </ItemGroup>\r
+</Project>
\ No newline at end of file
--- /dev/null
+{
+ "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
+ "name": "libidav",
+ "dependencies": [
+ "libxml2",
+ "curl",
+ "pcre"
+ ],
+ "builtin-baseline": "2c401863dd54a640aeb26ed736c55489c079323b"
+}
--- /dev/null
+hello = HALLO WELT!
--- /dev/null
+hello = HELLO WORLD!
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>BuildMachineOSBuild</key>
+ <string>10K549</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>de_DE</string>
+ <key>CFBundleExecutable</key>
+ <string>mk12</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.yourcompany.toolkit</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>toolkit</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>DTCompiler</key>
+ <string></string>
+ <key>DTPlatformBuild</key>
+ <string>10M2518</string>
+ <key>DTPlatformVersion</key>
+ <string>PG</string>
+ <key>DTSDKBuild</key>
+ <string>10M2518</string>
+ <key>DTSDKName</key>
+ <string>macosx10.6</string>
+ <key>DTXcode</key>
+ <string>0400</string>
+ <key>DTXcodeBuild</key>
+ <string>10M2518</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.7</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>CFBundleDisplayName</key>
+ <string></string>
+ <key>CFBundleGetInfoString</key>
+ <string></string>
+ <key>LSApplicationCategoryType</key>
+ <string></string>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>LSItemContentTypes</key>
+ <array>
+ <string>public.data</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string></string>
+ <key>CFBundleTypeName</key>
+ <string>DocumentType</string>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+<!--
+ <key>NSDocumentClass</key>
+ <string>Document</string>
+-->
+ </dict>
+ </array>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
--- /dev/null
+APPL????
\ No newline at end of file
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2013 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+BUILD_ROOT = ../
+include ../config.mk
+
+# list of source files
+SRC = allocator.c
+SRC += array_list.c
+SRC += mempool.c
+SRC += buffer.c
+SRC += compare.c
+SRC += hash_key.c
+SRC += hash_map.c
+SRC += linked_list.c
+SRC += list.c
+SRC += map.c
+SRC += printf.c
+SRC += string.c
+SRC += utils.c
+SRC += tree.c
+SRC += iterator.c
+
+OBJ = $(SRC:%.c=../build/ucx/%$(OBJ_EXT))
+
+UCX_LIB = ../build/lib/libucx$(LIB_EXT)
+
+all: ../build/ucx $(UCX_LIB)
+
+$(UCX_LIB): $(OBJ)
+ $(AR) $(ARFLAGS) $(UCX_LIB) $(OBJ)
+
+../build/ucx:
+ mkdir -p ../build/ucx
+
+../build/ucx/%$(OBJ_EXT): %.c
+ $(CC) $(CFLAGS) -o $@ -c $<
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/allocator.h"
+
+__attribute__((__malloc__, __alloc_size__(2)))
+static void *cx_malloc_stdlib(
+ __attribute__((__unused__)) void *d,
+ size_t n
+) {
+ return malloc(n);
+}
+
+__attribute__((__warn_unused_result__, __alloc_size__(3)))
+static void *cx_realloc_stdlib(
+ __attribute__((__unused__)) void *d,
+ void *mem,
+ size_t n
+) {
+ return realloc(mem, n);
+}
+
+__attribute__((__malloc__, __alloc_size__(2, 3)))
+static void *cx_calloc_stdlib(
+ __attribute__((__unused__)) void *d,
+ size_t nelem,
+ size_t n
+) {
+ return calloc(nelem, n);
+}
+
+__attribute__((__nonnull__))
+static void cx_free_stdlib(
+ __attribute__((__unused__)) void *d,
+ void *mem
+) {
+ free(mem);
+}
+
+static cx_allocator_class cx_default_allocator_class = {
+ cx_malloc_stdlib,
+ cx_realloc_stdlib,
+ cx_calloc_stdlib,
+ cx_free_stdlib
+};
+
+struct cx_allocator_s cx_default_allocator = {
+ &cx_default_allocator_class,
+ NULL
+};
+CxAllocator *cxDefaultAllocator = &cx_default_allocator;
+
+
+int cx_reallocate(
+ void **mem,
+ size_t n
+) {
+ void *nmem = realloc(*mem, n);
+ if (nmem == NULL) {
+ return 1;
+ } else {
+ *mem = nmem;
+ return 0;
+ }
+}
+
+// IMPLEMENTATION OF HIGH LEVEL API
+
+void *cxMalloc(
+ const CxAllocator *allocator,
+ size_t n
+) {
+ return allocator->cl->malloc(allocator->data, n);
+}
+
+void *cxRealloc(
+ const CxAllocator *allocator,
+ void *mem,
+ size_t n
+) {
+ return allocator->cl->realloc(allocator->data, mem, n);
+}
+
+int cxReallocate(
+ const CxAllocator *allocator,
+ void **mem,
+ size_t n
+) {
+ void *nmem = allocator->cl->realloc(allocator->data, *mem, n);
+ if (nmem == NULL) {
+ return 1;
+ } else {
+ *mem = nmem;
+ return 0;
+ }
+}
+
+void *cxCalloc(
+ const CxAllocator *allocator,
+ size_t nelem,
+ size_t n
+) {
+ return allocator->cl->calloc(allocator->data, nelem, n);
+}
+
+void cxFree(
+ const CxAllocator *allocator,
+ void *mem
+) {
+ allocator->cl->free(allocator->data, mem);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/array_list.h"
+#include "cx/compare.h"
+#include <assert.h>
+#include <string.h>
+
+// Default array reallocator
+
+static void *cx_array_default_realloc(
+ void *array,
+ size_t capacity,
+ size_t elem_size,
+ __attribute__((__unused__)) struct cx_array_reallocator_s *alloc
+) {
+ return realloc(array, capacity * elem_size);
+}
+
+struct cx_array_reallocator_s cx_array_default_reallocator_impl = {
+ cx_array_default_realloc, NULL, NULL, 0, 0
+};
+
+struct cx_array_reallocator_s *cx_array_default_reallocator = &cx_array_default_reallocator_impl;
+
+// LOW LEVEL ARRAY LIST FUNCTIONS
+
+enum cx_array_result cx_array_copy(
+ void **target,
+ size_t *size,
+ size_t *capacity,
+ size_t index,
+ const void *src,
+ size_t elem_size,
+ size_t elem_count,
+ struct cx_array_reallocator_s *reallocator
+) {
+ // assert pointers
+ assert(target != NULL);
+ assert(size != NULL);
+ assert(src != NULL);
+
+ // determine capacity
+ size_t cap = capacity == NULL ? *size : *capacity;
+
+ // check if resize is required
+ size_t minsize = index + elem_count;
+ size_t newsize = *size < minsize ? minsize : *size;
+ bool needrealloc = newsize > cap;
+
+ // reallocate if possible
+ if (needrealloc) {
+ // a reallocator and a capacity variable must be available
+ if (reallocator == NULL || capacity == NULL) {
+ return CX_ARRAY_REALLOC_NOT_SUPPORTED;
+ }
+
+ // check, if we need to repair the src pointer
+ uintptr_t targetaddr = (uintptr_t) *target;
+ uintptr_t srcaddr = (uintptr_t) src;
+ bool repairsrc = targetaddr <= srcaddr
+ && srcaddr < targetaddr + cap * elem_size;
+
+ // calculate new capacity (next number divisible by 16)
+ cap = newsize - (newsize % 16) + 16;
+ assert(cap > newsize);
+
+ // perform reallocation
+ void *newmem = reallocator->realloc(
+ *target, cap, elem_size, reallocator
+ );
+ if (newmem == NULL) {
+ return CX_ARRAY_REALLOC_FAILED;
+ }
+
+ // repair src pointer, if necessary
+ if (repairsrc) {
+ src = ((char *) newmem) + (srcaddr - targetaddr);
+ }
+
+ // store new pointer and capacity
+ *target = newmem;
+ *capacity = cap;
+ }
+
+ // determine target pointer
+ char *start = *target;
+ start += index * elem_size;
+
+ // copy elements and set new size
+ memmove(start, src, elem_count * elem_size);
+ *size = newsize;
+
+ // return successfully
+ return CX_ARRAY_SUCCESS;
+}
+
+enum cx_array_result cx_array_insert_sorted(
+ void **target,
+ size_t *size,
+ size_t *capacity,
+ cx_compare_func cmp_func,
+ const void *sorted_data,
+ size_t elem_size,
+ size_t elem_count,
+ struct cx_array_reallocator_s *reallocator
+) {
+ // assert pointers
+ assert(target != NULL);
+ assert(size != NULL);
+ assert(capacity != NULL);
+ assert(cmp_func != NULL);
+ assert(sorted_data != NULL);
+ assert(reallocator != NULL);
+
+ // corner case
+ if (elem_count == 0) return 0;
+
+ // store some counts
+ size_t old_size = *size;
+ size_t needed_capacity = old_size + elem_count;
+
+ // if we need more than we have, try a reallocation
+ if (needed_capacity > *capacity) {
+ size_t new_capacity = needed_capacity - (needed_capacity % 16) + 16;
+ void *new_mem = reallocator->realloc(
+ *target, new_capacity, elem_size, reallocator
+ );
+ if (new_mem == NULL) {
+ // give it up right away, there is no contract
+ // that requires us to insert as much as we can
+ return CX_ARRAY_REALLOC_FAILED;
+ }
+ *target = new_mem;
+ *capacity = new_capacity;
+ }
+
+ // now we have guaranteed that we can insert everything
+ size_t new_size = old_size + elem_count;
+ *size = new_size;
+
+ // declare the source and destination indices/pointers
+ size_t si = 0, di = 0;
+ const char *src = sorted_data;
+ char *dest = *target;
+
+ // find the first insertion point
+ di = cx_array_binary_search_sup(dest, old_size, elem_size, src, cmp_func);
+ dest += di * elem_size;
+
+ // move the remaining elements in the array completely to the right
+ // we will call it the "buffer" for parked elements
+ size_t buf_size = old_size - di;
+ size_t bi = new_size - buf_size;
+ char *bptr = ((char *) *target) + bi * elem_size;
+ memmove(bptr, dest, buf_size * elem_size);
+
+ // while there are both source and buffered elements left,
+ // copy them interleaving
+ while (si < elem_count && bi < new_size) {
+ // determine how many source elements can be inserted
+ size_t copy_len, bytes_copied;
+ copy_len = cx_array_binary_search_sup(
+ src,
+ elem_count - si,
+ elem_size,
+ bptr,
+ cmp_func
+ );
+
+ // copy the source elements
+ bytes_copied = copy_len * elem_size;
+ memcpy(dest, src, bytes_copied);
+ dest += bytes_copied;
+ src += bytes_copied;
+ si += copy_len;
+
+ // when all source elements are in place, we are done
+ if (si >= elem_count) break;
+
+ // determine how many buffered elements need to be restored
+ copy_len = cx_array_binary_search_sup(
+ bptr,
+ new_size - bi,
+ elem_size,
+ src,
+ cmp_func
+ );
+
+ // restore the buffered elements
+ bytes_copied = copy_len * elem_size;
+ memmove(dest, bptr, bytes_copied);
+ dest += bytes_copied;
+ bptr += bytes_copied;
+ bi += copy_len;
+ }
+
+ // still source elements left? simply append them
+ if (si < elem_count) {
+ memcpy(dest, src, elem_size * (elem_count - si));
+ }
+
+ // still buffer elements left?
+ // don't worry, we already moved them to the correct place
+
+ return CX_ARRAY_SUCCESS;
+}
+
+size_t cx_array_binary_search_inf(
+ const void *arr,
+ size_t size,
+ size_t elem_size,
+ const void *elem,
+ cx_compare_func cmp_func
+) {
+ // special case: empty array
+ if (size == 0) return 0;
+
+ // declare a variable that will contain the compare results
+ int result;
+
+ // cast the array pointer to something we can use offsets with
+ const char *array = arr;
+
+ // check the first array element
+ result = cmp_func(elem, array);
+ if (result < 0) {
+ return size;
+ } else if (result == 0) {
+ return 0;
+ }
+
+ // check the last array element
+ result = cmp_func(elem, array + elem_size * (size - 1));
+ if (result >= 0) {
+ return size - 1;
+ }
+
+ // the element is now guaranteed to be somewhere in the list
+ // so start the binary search
+ size_t left_index = 1;
+ size_t right_index = size - 1;
+ size_t pivot_index;
+
+ while (left_index <= right_index) {
+ pivot_index = left_index + (right_index - left_index) / 2;
+ const char *arr_elem = array + pivot_index * elem_size;
+ result = cmp_func(elem, arr_elem);
+ if (result == 0) {
+ // found it!
+ return pivot_index;
+ } else if (result < 0) {
+ // element is smaller than pivot, continue search left
+ right_index = pivot_index - 1;
+ } else {
+ // element is larger than pivot, continue search right
+ left_index = pivot_index + 1;
+ }
+ }
+
+ // report the largest upper bound
+ return result < 0 ? (pivot_index - 1) : pivot_index;
+}
+
+#ifndef CX_ARRAY_SWAP_SBO_SIZE
+#define CX_ARRAY_SWAP_SBO_SIZE 128
+#endif
+unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE;
+
+void cx_array_swap(
+ void *arr,
+ size_t elem_size,
+ size_t idx1,
+ size_t idx2
+) {
+ assert(arr != NULL);
+
+ // short circuit
+ if (idx1 == idx2) return;
+
+ char sbo_mem[CX_ARRAY_SWAP_SBO_SIZE];
+ void *tmp;
+
+ // decide if we can use the local buffer
+ if (elem_size > CX_ARRAY_SWAP_SBO_SIZE) {
+ tmp = malloc(elem_size);
+ // we don't want to enforce error handling
+ if (tmp == NULL) abort();
+ } else {
+ tmp = sbo_mem;
+ }
+
+ // calculate memory locations
+ char *left = arr, *right = arr;
+ left += idx1 * elem_size;
+ right += idx2 * elem_size;
+
+ // three-way swap
+ memcpy(tmp, left, elem_size);
+ memcpy(left, right, elem_size);
+ memcpy(right, tmp, elem_size);
+
+ // free dynamic memory, if it was needed
+ if (tmp != sbo_mem) {
+ free(tmp);
+ }
+}
+
+// HIGH LEVEL ARRAY LIST FUNCTIONS
+
+typedef struct {
+ struct cx_list_s base;
+ void *data;
+ size_t capacity;
+ struct cx_array_reallocator_s reallocator;
+} cx_array_list;
+
+static void *cx_arl_realloc(
+ void *array,
+ size_t capacity,
+ size_t elem_size,
+ struct cx_array_reallocator_s *alloc
+) {
+ // retrieve the pointer to the list allocator
+ const CxAllocator *al = alloc->ptr1;
+
+ // use the list allocator to reallocate the memory
+ return cxRealloc(al, array, capacity * elem_size);
+}
+
+static void cx_arl_destructor(struct cx_list_s *list) {
+ cx_array_list *arl = (cx_array_list *) list;
+
+ char *ptr = arl->data;
+
+ if (list->collection.simple_destructor) {
+ for (size_t i = 0; i < list->collection.size; i++) {
+ cx_invoke_simple_destructor(list, ptr);
+ ptr += list->collection.elem_size;
+ }
+ }
+ if (list->collection.advanced_destructor) {
+ for (size_t i = 0; i < list->collection.size; i++) {
+ cx_invoke_advanced_destructor(list, ptr);
+ ptr += list->collection.elem_size;
+ }
+ }
+
+ cxFree(list->collection.allocator, arl->data);
+ cxFree(list->collection.allocator, list);
+}
+
+static size_t cx_arl_insert_array(
+ struct cx_list_s *list,
+ size_t index,
+ const void *array,
+ size_t n
+) {
+ // out of bounds and special case check
+ if (index > list->collection.size || n == 0) return 0;
+
+ // get a correctly typed pointer to the list
+ cx_array_list *arl = (cx_array_list *) list;
+
+ // do we need to move some elements?
+ if (index < list->collection.size) {
+ const char *first_to_move = (const char *) arl->data;
+ first_to_move += index * list->collection.elem_size;
+ size_t elems_to_move = list->collection.size - index;
+ size_t start_of_moved = index + n;
+
+ if (CX_ARRAY_SUCCESS != cx_array_copy(
+ &arl->data,
+ &list->collection.size,
+ &arl->capacity,
+ start_of_moved,
+ first_to_move,
+ list->collection.elem_size,
+ elems_to_move,
+ &arl->reallocator
+ )) {
+ // if moving existing elems is unsuccessful, abort
+ return 0;
+ }
+ }
+
+ // note that if we had to move the elements, the following operation
+ // is guaranteed to succeed, because we have the memory already allocated
+ // therefore, it is impossible to leave this function with an invalid array
+
+ // place the new elements
+ if (CX_ARRAY_SUCCESS == cx_array_copy(
+ &arl->data,
+ &list->collection.size,
+ &arl->capacity,
+ index,
+ array,
+ list->collection.elem_size,
+ n,
+ &arl->reallocator
+ )) {
+ return n;
+ } else {
+ // array list implementation is "all or nothing"
+ return 0;
+ }
+}
+
+static size_t cx_arl_insert_sorted(
+ struct cx_list_s *list,
+ const void *sorted_data,
+ size_t n
+) {
+ // get a correctly typed pointer to the list
+ cx_array_list *arl = (cx_array_list *) list;
+
+ if (CX_ARRAY_SUCCESS == cx_array_insert_sorted(
+ &arl->data,
+ &list->collection.size,
+ &arl->capacity,
+ list->collection.cmpfunc,
+ sorted_data,
+ list->collection.elem_size,
+ n,
+ &arl->reallocator
+ )) {
+ return n;
+ } else {
+ // array list implementation is "all or nothing"
+ return 0;
+ }
+}
+
+static int cx_arl_insert_element(
+ struct cx_list_s *list,
+ size_t index,
+ const void *element
+) {
+ return 1 != cx_arl_insert_array(list, index, element, 1);
+}
+
+static int cx_arl_insert_iter(
+ struct cx_iterator_s *iter,
+ const void *elem,
+ int prepend
+) {
+ struct cx_list_s *list = iter->src_handle.m;
+ if (iter->index < list->collection.size) {
+ int result = cx_arl_insert_element(
+ list,
+ iter->index + 1 - prepend,
+ elem
+ );
+ if (result == 0) {
+ iter->elem_count++;
+ if (prepend != 0) {
+ iter->index++;
+ iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size;
+ }
+ }
+ return result;
+ } else {
+ int result = cx_arl_insert_element(list, list->collection.size, elem);
+ if (result == 0) {
+ iter->elem_count++;
+ iter->index = list->collection.size;
+ }
+ return result;
+ }
+}
+
+static int cx_arl_remove(
+ struct cx_list_s *list,
+ size_t index
+) {
+ cx_array_list *arl = (cx_array_list *) list;
+
+ // out-of-bounds check
+ if (index >= list->collection.size) {
+ return 1;
+ }
+
+ // content destruction
+ cx_invoke_destructor(list, ((char *) arl->data) + index * list->collection.elem_size);
+
+ // short-circuit removal of last element
+ if (index == list->collection.size - 1) {
+ list->collection.size--;
+ return 0;
+ }
+
+ // just move the elements starting at index to the left
+ int result = cx_array_copy(
+ &arl->data,
+ &list->collection.size,
+ &arl->capacity,
+ index,
+ ((char *) arl->data) + (index + 1) * list->collection.elem_size,
+ list->collection.elem_size,
+ list->collection.size - index - 1,
+ &arl->reallocator
+ );
+
+ // cx_array_copy cannot fail, array cannot grow
+ assert(result == 0);
+
+ // decrease the size
+ list->collection.size--;
+
+ return 0;
+}
+
+static void cx_arl_clear(struct cx_list_s *list) {
+ if (list->collection.size == 0) return;
+
+ cx_array_list *arl = (cx_array_list *) list;
+ char *ptr = arl->data;
+
+ if (list->collection.simple_destructor) {
+ for (size_t i = 0; i < list->collection.size; i++) {
+ cx_invoke_simple_destructor(list, ptr);
+ ptr += list->collection.elem_size;
+ }
+ }
+ if (list->collection.advanced_destructor) {
+ for (size_t i = 0; i < list->collection.size; i++) {
+ cx_invoke_advanced_destructor(list, ptr);
+ ptr += list->collection.elem_size;
+ }
+ }
+
+ memset(arl->data, 0, list->collection.size * list->collection.elem_size);
+ list->collection.size = 0;
+}
+
+static int cx_arl_swap(
+ struct cx_list_s *list,
+ size_t i,
+ size_t j
+) {
+ if (i >= list->collection.size || j >= list->collection.size) return 1;
+ cx_array_list *arl = (cx_array_list *) list;
+ cx_array_swap(arl->data, list->collection.elem_size, i, j);
+ return 0;
+}
+
+static void *cx_arl_at(
+ const struct cx_list_s *list,
+ size_t index
+) {
+ if (index < list->collection.size) {
+ const cx_array_list *arl = (const cx_array_list *) list;
+ char *space = arl->data;
+ return space + index * list->collection.elem_size;
+ } else {
+ return NULL;
+ }
+}
+
+static ssize_t cx_arl_find_remove(
+ struct cx_list_s *list,
+ const void *elem,
+ bool remove
+) {
+ assert(list->collection.cmpfunc != NULL);
+ assert(list->collection.size < SIZE_MAX / 2);
+ char *cur = ((const cx_array_list *) list)->data;
+
+ for (ssize_t i = 0; i < (ssize_t) list->collection.size; i++) {
+ if (0 == list->collection.cmpfunc(elem, cur)) {
+ if (remove) {
+ if (0 == cx_arl_remove(list, i)) {
+ return i;
+ } else {
+ return -1;
+ }
+ } else {
+ return i;
+ }
+ }
+ cur += list->collection.elem_size;
+ }
+
+ return -1;
+}
+
+static void cx_arl_sort(struct cx_list_s *list) {
+ assert(list->collection.cmpfunc != NULL);
+ qsort(((cx_array_list *) list)->data,
+ list->collection.size,
+ list->collection.elem_size,
+ list->collection.cmpfunc
+ );
+}
+
+static int cx_arl_compare(
+ const struct cx_list_s *list,
+ const struct cx_list_s *other
+) {
+ assert(list->collection.cmpfunc != NULL);
+ if (list->collection.size == other->collection.size) {
+ const char *left = ((const cx_array_list *) list)->data;
+ const char *right = ((const cx_array_list *) other)->data;
+ for (size_t i = 0; i < list->collection.size; i++) {
+ int d = list->collection.cmpfunc(left, right);
+ if (d != 0) {
+ return d;
+ }
+ left += list->collection.elem_size;
+ right += other->collection.elem_size;
+ }
+ return 0;
+ } else {
+ return list->collection.size < other->collection.size ? -1 : 1;
+ }
+}
+
+static void cx_arl_reverse(struct cx_list_s *list) {
+ if (list->collection.size < 2) return;
+ void *data = ((const cx_array_list *) list)->data;
+ size_t half = list->collection.size / 2;
+ for (size_t i = 0; i < half; i++) {
+ cx_array_swap(data, list->collection.elem_size, i, list->collection.size - 1 - i);
+ }
+}
+
+static bool cx_arl_iter_valid(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ const struct cx_list_s *list = iter->src_handle.c;
+ return iter->index < list->collection.size;
+}
+
+static void *cx_arl_iter_current(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ return iter->elem_handle;
+}
+
+static void cx_arl_iter_next(void *it) {
+ struct cx_iterator_s *iter = it;
+ if (iter->base.remove) {
+ iter->base.remove = false;
+ cx_arl_remove(iter->src_handle.m, iter->index);
+ } else {
+ iter->index++;
+ iter->elem_handle =
+ ((char *) iter->elem_handle)
+ + ((const struct cx_list_s *) iter->src_handle.c)->collection.elem_size;
+ }
+}
+
+static void cx_arl_iter_prev(void *it) {
+ struct cx_iterator_s *iter = it;
+ const cx_array_list *list = iter->src_handle.c;
+ if (iter->base.remove) {
+ iter->base.remove = false;
+ cx_arl_remove(iter->src_handle.m, iter->index);
+ }
+ iter->index--;
+ if (iter->index < list->base.collection.size) {
+ iter->elem_handle = ((char *) list->data)
+ + iter->index * list->base.collection.elem_size;
+ }
+}
+
+
+static struct cx_iterator_s cx_arl_iterator(
+ const struct cx_list_s *list,
+ size_t index,
+ bool backwards
+) {
+ struct cx_iterator_s iter;
+
+ iter.index = index;
+ iter.src_handle.c = list;
+ iter.elem_handle = cx_arl_at(list, index);
+ iter.elem_size = list->collection.elem_size;
+ iter.elem_count = list->collection.size;
+ iter.base.valid = cx_arl_iter_valid;
+ iter.base.current = cx_arl_iter_current;
+ iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next;
+ iter.base.remove = false;
+ iter.base.mutating = false;
+
+ return iter;
+}
+
+static cx_list_class cx_array_list_class = {
+ cx_arl_destructor,
+ cx_arl_insert_element,
+ cx_arl_insert_array,
+ cx_arl_insert_sorted,
+ cx_arl_insert_iter,
+ cx_arl_remove,
+ cx_arl_clear,
+ cx_arl_swap,
+ cx_arl_at,
+ cx_arl_find_remove,
+ cx_arl_sort,
+ cx_arl_compare,
+ cx_arl_reverse,
+ cx_arl_iterator,
+};
+
+CxList *cxArrayListCreate(
+ const CxAllocator *allocator,
+ cx_compare_func comparator,
+ size_t elem_size,
+ size_t initial_capacity
+) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+
+ cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list));
+ if (list == NULL) return NULL;
+
+ list->base.cl = &cx_array_list_class;
+ list->base.collection.allocator = allocator;
+ list->capacity = initial_capacity;
+
+ if (elem_size > 0) {
+ list->base.collection.elem_size = elem_size;
+ list->base.collection.cmpfunc = comparator;
+ } else {
+ elem_size = sizeof(void *);
+ list->base.collection.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator;
+ cxListStorePointers((CxList *) list);
+ }
+
+ // allocate the array after the real elem_size is known
+ list->data = cxCalloc(allocator, initial_capacity, elem_size);
+ if (list->data == NULL) {
+ cxFree(allocator, list);
+ return NULL;
+ }
+
+ // configure the reallocator
+ list->reallocator.realloc = cx_arl_realloc;
+ list->reallocator.ptr1 = (void *) allocator;
+
+ return (CxList *) list;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/buffer.h"
+#include "cx/utils.h"
+
+#include <stdio.h>
+#include <string.h>
+
+int cxBufferInit(
+ CxBuffer *buffer,
+ void *space,
+ size_t capacity,
+ const CxAllocator *allocator,
+ int flags
+) {
+ if (allocator == NULL) allocator = cxDefaultAllocator;
+ buffer->allocator = allocator;
+ buffer->flags = flags;
+ if (!space) {
+ buffer->bytes = cxMalloc(allocator, capacity);
+ if (buffer->bytes == NULL) {
+ return 1;
+ }
+ buffer->flags |= CX_BUFFER_FREE_CONTENTS;
+ } else {
+ buffer->bytes = space;
+ }
+ buffer->capacity = capacity;
+ buffer->size = 0;
+ buffer->pos = 0;
+
+ buffer->flush_func = NULL;
+ buffer->flush_target = NULL;
+ buffer->flush_blkmax = 0;
+ buffer->flush_blksize = 4096;
+ buffer->flush_threshold = SIZE_MAX;
+
+ return 0;
+}
+
+void cxBufferDestroy(CxBuffer *buffer) {
+ if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
+ cxFree(buffer->allocator, buffer->bytes);
+ }
+}
+
+CxBuffer *cxBufferCreate(
+ void *space,
+ size_t capacity,
+ const CxAllocator *allocator,
+ int flags
+) {
+ CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer));
+ if (buf == NULL) return NULL;
+ if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) {
+ return buf;
+ } else {
+ cxFree(allocator, buf);
+ return NULL;
+ }
+}
+
+void cxBufferFree(CxBuffer *buffer) {
+ if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
+ cxFree(buffer->allocator, buffer->bytes);
+ }
+ cxFree(buffer->allocator, buffer);
+}
+
+int cxBufferSeek(
+ CxBuffer *buffer,
+ off_t offset,
+ int whence
+) {
+ size_t npos;
+ switch (whence) {
+ case SEEK_CUR:
+ npos = buffer->pos;
+ break;
+ case SEEK_END:
+ npos = buffer->size;
+ break;
+ case SEEK_SET:
+ npos = 0;
+ break;
+ default:
+ return -1;
+ }
+
+ size_t opos = npos;
+ npos += offset;
+
+ if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) {
+ return -1;
+ }
+
+ if (npos >= buffer->size) {
+ return -1;
+ } else {
+ buffer->pos = npos;
+ return 0;
+ }
+
+}
+
+void cxBufferClear(CxBuffer *buffer) {
+ memset(buffer->bytes, 0, buffer->size);
+ buffer->size = 0;
+ buffer->pos = 0;
+}
+
+void cxBufferReset(CxBuffer *buffer) {
+ buffer->size = 0;
+ buffer->pos = 0;
+}
+
+int cxBufferEof(const CxBuffer *buffer) {
+ return buffer->pos >= buffer->size;
+}
+
+int cxBufferMinimumCapacity(
+ CxBuffer *buffer,
+ size_t newcap
+) {
+ if (newcap <= buffer->capacity) {
+ return 0;
+ }
+
+ if (cxReallocate(buffer->allocator,
+ (void **) &buffer->bytes, newcap) == 0) {
+ buffer->capacity = newcap;
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+/**
+ * Helps flushing data to the flush target of a buffer.
+ *
+ * @param buffer the buffer containing the config
+ * @param space the data to flush
+ * @param size the element size
+ * @param nitems the number of items
+ * @return the number of items flushed
+ */
+static size_t cx_buffer_write_flush_helper(
+ CxBuffer *buffer,
+ const unsigned char *space,
+ size_t size,
+ size_t nitems
+) {
+ size_t pos = 0;
+ size_t remaining = nitems;
+ size_t max_items = buffer->flush_blksize / size;
+ while (remaining > 0) {
+ size_t items = remaining > max_items ? max_items : remaining;
+ size_t flushed = buffer->flush_func(
+ space + pos,
+ size, items,
+ buffer->flush_target);
+ if (flushed > 0) {
+ pos += (flushed * size);
+ remaining -= flushed;
+ } else {
+ // if no bytes can be flushed out anymore, we give up
+ break;
+ }
+ }
+ return nitems - remaining;
+}
+
+size_t cxBufferWrite(
+ const void *ptr,
+ size_t size,
+ size_t nitems,
+ CxBuffer *buffer
+) {
+ // optimize for easy case
+ if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) {
+ memcpy(buffer->bytes + buffer->pos, ptr, nitems);
+ buffer->pos += nitems;
+ if (buffer->pos > buffer->size) {
+ buffer->size = buffer->pos;
+ }
+ return nitems;
+ }
+
+ size_t len;
+ size_t nitems_out = nitems;
+ if (cx_szmul(size, nitems, &len)) {
+ return 0;
+ }
+ size_t required = buffer->pos + len;
+ if (buffer->pos > required) {
+ return 0;
+ }
+
+ bool perform_flush = false;
+ if (required > buffer->capacity) {
+ if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND && required) {
+ if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) {
+ perform_flush = true;
+ } else {
+ if (cxBufferMinimumCapacity(buffer, required)) {
+ return 0;
+ }
+ }
+ } else {
+ if (buffer->flush_blkmax > 0) {
+ perform_flush = true;
+ } else {
+ // truncate data to be written, if we can neither extend nor flush
+ len = buffer->capacity - buffer->pos;
+ if (size > 1) {
+ len -= len % size;
+ }
+ nitems_out = len / size;
+ }
+ }
+ }
+
+ if (len == 0) {
+ return len;
+ }
+
+ if (perform_flush) {
+ size_t flush_max;
+ if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) {
+ return 0;
+ }
+ size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL
+ ? buffer->pos
+ : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos);
+ if (flush_pos == buffer->pos) {
+ // entire buffer has been flushed, we can reset
+ buffer->size = buffer->pos = 0;
+
+ size_t items_flush; // how many items can also be directly flushed
+ size_t items_keep; // how many items have to be written to the buffer
+
+ items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size;
+ if (items_flush > 0) {
+ items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size);
+ // in case we could not flush everything, keep the rest
+ }
+ items_keep = nitems - items_flush;
+ if (items_keep > 0) {
+ // try again with the remaining stuff
+ const unsigned char *new_ptr = ptr;
+ new_ptr += items_flush * size;
+ // report the directly flushed items as written plus the remaining stuff
+ return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer);
+ } else {
+ // all items have been flushed - report them as written
+ return nitems;
+ }
+ } else if (flush_pos == 0) {
+ // nothing could be flushed at all, we immediately give up without writing any data
+ return 0;
+ } else {
+ // we were partially successful, we shift left and try again
+ cxBufferShiftLeft(buffer, flush_pos);
+ return cxBufferWrite(ptr, size, nitems, buffer);
+ }
+ } else {
+ memcpy(buffer->bytes + buffer->pos, ptr, len);
+ buffer->pos += len;
+ if (buffer->pos > buffer->size) {
+ buffer->size = buffer->pos;
+ }
+ return nitems_out;
+ }
+
+}
+
+int cxBufferPut(
+ CxBuffer *buffer,
+ int c
+) {
+ c &= 0xFF;
+ unsigned char const ch = c;
+ if (cxBufferWrite(&ch, 1, 1, buffer) == 1) {
+ return c;
+ } else {
+ return EOF;
+ }
+}
+
+size_t cxBufferPutString(
+ CxBuffer *buffer,
+ const char *str
+) {
+ return cxBufferWrite(str, 1, strlen(str), buffer);
+}
+
+size_t cxBufferRead(
+ void *ptr,
+ size_t size,
+ size_t nitems,
+ CxBuffer *buffer
+) {
+ size_t len;
+ if (cx_szmul(size, nitems, &len)) {
+ return 0;
+ }
+ if (buffer->pos + len > buffer->size) {
+ len = buffer->size - buffer->pos;
+ if (size > 1) len -= len % size;
+ }
+
+ if (len <= 0) {
+ return len;
+ }
+
+ memcpy(ptr, buffer->bytes + buffer->pos, len);
+ buffer->pos += len;
+
+ return len / size;
+}
+
+int cxBufferGet(CxBuffer *buffer) {
+ if (cxBufferEof(buffer)) {
+ return EOF;
+ } else {
+ int c = buffer->bytes[buffer->pos];
+ buffer->pos++;
+ return c;
+ }
+}
+
+int cxBufferShiftLeft(
+ CxBuffer *buffer,
+ size_t shift
+) {
+ if (shift >= buffer->size) {
+ buffer->pos = buffer->size = 0;
+ } else {
+ memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift);
+ buffer->size -= shift;
+
+ if (buffer->pos >= shift) {
+ buffer->pos -= shift;
+ } else {
+ buffer->pos = 0;
+ }
+ }
+ return 0;
+}
+
+int cxBufferShiftRight(
+ CxBuffer *buffer,
+ size_t shift
+) {
+ size_t req_capacity = buffer->size + shift;
+ size_t movebytes;
+
+ // auto extend buffer, if required and enabled
+ if (buffer->capacity < req_capacity) {
+ if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND) {
+ if (cxBufferMinimumCapacity(buffer, req_capacity)) {
+ return 1;
+ }
+ movebytes = buffer->size;
+ } else {
+ movebytes = buffer->capacity - shift;
+ }
+ } else {
+ movebytes = buffer->size;
+ }
+
+ memmove(buffer->bytes + shift, buffer->bytes, movebytes);
+ buffer->size = shift + movebytes;
+
+ buffer->pos += shift;
+ if (buffer->pos > buffer->size) {
+ buffer->pos = buffer->size;
+ }
+
+ return 0;
+}
+
+int cxBufferShift(
+ CxBuffer *buffer,
+ off_t shift
+) {
+ if (shift < 0) {
+ return cxBufferShiftLeft(buffer, (size_t) (-shift));
+ } else if (shift > 0) {
+ return cxBufferShiftRight(buffer, (size_t) shift);
+ } else {
+ return 0;
+ }
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/compare.h"
+
+#include <math.h>
+
+int cx_cmp_int(const void *i1, const void *i2) {
+ int a = *((const int *) i1);
+ int b = *((const int *) i2);
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_longint(const void *i1, const void *i2) {
+ long int a = *((const long int *) i1);
+ long int b = *((const long int *) i2);
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_longlong(const void *i1, const void *i2) {
+ long long a = *((const long long *) i1);
+ long long b = *((const long long *) i2);
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_int16(const void *i1, const void *i2) {
+ int16_t a = *((const int16_t *) i1);
+ int16_t b = *((const int16_t *) i2);
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_int32(const void *i1, const void *i2) {
+ int32_t a = *((const int32_t *) i1);
+ int32_t b = *((const int32_t *) i2);
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_int64(const void *i1, const void *i2) {
+ int64_t a = *((const int64_t *) i1);
+ int64_t b = *((const int64_t *) i2);
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_uint(const void *i1, const void *i2) {
+ unsigned int a = *((const unsigned int *) i1);
+ unsigned int b = *((const unsigned int *) i2);
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_ulongint(const void *i1, const void *i2) {
+ unsigned long int a = *((const unsigned long int *) i1);
+ unsigned long int b = *((const unsigned long int *) i2);
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_ulonglong(const void *i1, const void *i2) {
+ unsigned long long a = *((const unsigned long long *) i1);
+ unsigned long long b = *((const unsigned long long *) i2);
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_uint16(const void *i1, const void *i2) {
+ uint16_t a = *((const uint16_t *) i1);
+ uint16_t b = *((const uint16_t *) i2);
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_uint32(const void *i1, const void *i2) {
+ uint32_t a = *((const uint32_t *) i1);
+ uint32_t b = *((const uint32_t *) i2);
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_uint64(const void *i1, const void *i2) {
+ uint64_t a = *((const uint64_t *) i1);
+ uint64_t b = *((const uint64_t *) i2);
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_float(const void *f1, const void *f2) {
+ float a = *((const float *) f1);
+ float b = *((const float *) f2);
+ if (fabsf(a - b) < 1e-6f) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_double(
+ const void *d1,
+ const void *d2
+) {
+ double a = *((const double *) d1);
+ double b = *((const double *) d2);
+ if (fabs(a - b) < 1e-14) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_intptr(
+ const void *ptr1,
+ const void *ptr2
+) {
+ intptr_t p1 = *(const intptr_t *) ptr1;
+ intptr_t p2 = *(const intptr_t *) ptr2;
+ if (p1 == p2) {
+ return 0;
+ } else {
+ return p1 < p2 ? -1 : 1;
+ }
+}
+
+int cx_cmp_uintptr(
+ const void *ptr1,
+ const void *ptr2
+) {
+ uintptr_t p1 = *(const uintptr_t *) ptr1;
+ uintptr_t p2 = *(const uintptr_t *) ptr2;
+ if (p1 == p2) {
+ return 0;
+ } else {
+ return p1 < p2 ? -1 : 1;
+ }
+}
+
+int cx_cmp_ptr(
+ const void *ptr1,
+ const void *ptr2
+) {
+ uintptr_t p1 = (uintptr_t) ptr1;
+ uintptr_t p2 = (uintptr_t) ptr2;
+ if (p1 == p2) {
+ return 0;
+ } else {
+ return p1 < p2 ? -1 : 1;
+ }
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file allocator.h
+ * Interface for custom allocators.
+ */
+
+#ifndef UCX_ALLOCATOR_H
+#define UCX_ALLOCATOR_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The class definition for an allocator.
+ */
+typedef struct {
+ /**
+ * The allocator's malloc() implementation.
+ */
+ void *(*malloc)(
+ void *data,
+ size_t n
+ );
+
+ /**
+ * The allocator's realloc() implementation.
+ */
+ __attribute__((__warn_unused_result__))
+ void *(*realloc)(
+ void *data,
+ void *mem,
+ size_t n
+ );
+
+ /**
+ * The allocator's calloc() implementation.
+ */
+ void *(*calloc)(
+ void *data,
+ size_t nelem,
+ size_t n
+ );
+
+ /**
+ * The allocator's free() implementation.
+ */
+ __attribute__((__nonnull__))
+ void (*free)(
+ void *data,
+ void *mem
+ );
+} cx_allocator_class;
+
+/**
+ * Structure holding the data for an allocator.
+ */
+struct cx_allocator_s {
+ /**
+ * A pointer to the instance of the allocator class.
+ */
+ cx_allocator_class *cl;
+ /**
+ * A pointer to the data this allocator uses.
+ */
+ void *data;
+};
+
+/**
+ * High-Level type alias for the allocator type.
+ */
+typedef struct cx_allocator_s CxAllocator;
+
+/**
+ * A default allocator using standard library malloc() etc.
+ */
+extern CxAllocator *cxDefaultAllocator;
+
+/**
+ * Function pointer type for destructor functions.
+ *
+ * A destructor function deallocates possible contents and MAY free the memory
+ * pointed to by \p memory. Read the documentation of the respective function
+ * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
+ * particular implementation.
+ *
+ * @param memory a pointer to the object to destruct
+ */
+__attribute__((__nonnull__))
+typedef void (*cx_destructor_func)(void *memory);
+
+/**
+ * Function pointer type for destructor functions.
+ *
+ * A destructor function deallocates possible contents and MAY free the memory
+ * pointed to by \p memory. Read the documentation of the respective function
+ * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
+ * particular implementation.
+ *
+ * @param data an optional pointer to custom data
+ * @param memory a pointer to the object to destruct
+ */
+__attribute__((__nonnull__(2)))
+typedef void (*cx_destructor_func2)(
+ void *data,
+ void *memory
+);
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ *
+ * \par Error handling
+ * \c errno will be set by realloc() on failure.
+ *
+ * @param mem pointer to the pointer to allocated block
+ * @param n the new size in bytes
+ * @return zero on success, non-zero on failure
+ */
+__attribute__((__nonnull__))
+int cx_reallocate(
+ void **mem,
+ size_t n
+);
+
+/**
+ * Allocate \p n bytes of memory.
+ *
+ * @param allocator the allocator
+ * @param n the number of bytes
+ * @return a pointer to the allocated memory
+ */
+__attribute__((__malloc__))
+__attribute__((__alloc_size__(2)))
+void *cxMalloc(
+ const CxAllocator *allocator,
+ size_t n
+);
+
+/**
+ * Re-allocate the previously allocated block in \p mem, making the new block \p n bytes long.
+ * This function may return the same pointer that was passed to it, if moving the memory
+ * was not necessary.
+ *
+ * \note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @param allocator the allocator
+ * @param mem pointer to the previously allocated block
+ * @param n the new size in bytes
+ * @return a pointer to the re-allocated memory
+ */
+__attribute__((__warn_unused_result__))
+__attribute__((__alloc_size__(3)))
+void *cxRealloc(
+ const CxAllocator *allocator,
+ void *mem,
+ size_t n
+);
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ * This function acts like cxRealloc() using the pointer pointed to by \p mem.
+ *
+ * \note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * \par Error handling
+ * \c errno will be set, if the underlying realloc function does so.
+ *
+ * @param allocator the allocator
+ * @param mem pointer to the pointer to allocated block
+ * @param n the new size in bytes
+ * @return zero on success, non-zero on failure
+ */
+__attribute__((__nonnull__))
+int cxReallocate(
+ const CxAllocator *allocator,
+ void **mem,
+ size_t n
+);
+
+/**
+ * Allocate \p nelem elements of \p n bytes each, all initialized to zero.
+ *
+ * @param allocator the allocator
+ * @param nelem the number of elements
+ * @param n the size of each element in bytes
+ * @return a pointer to the allocated memory
+ */
+__attribute__((__malloc__))
+__attribute__((__alloc_size__(2, 3)))
+void *cxCalloc(
+ const CxAllocator *allocator,
+ size_t nelem,
+ size_t n
+);
+
+/**
+ * Free a block allocated by this allocator.
+ *
+ * \note Freeing a block of a different allocator is undefined.
+ *
+ * @param allocator the allocator
+ * @param mem a pointer to the block to free
+ */
+__attribute__((__nonnull__))
+void cxFree(
+ const CxAllocator *allocator,
+ void *mem
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_ALLOCATOR_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file array_list.h
+ * \brief Array list implementation.
+ * \details Also provides several low-level functions for custom array list implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+
+#ifndef UCX_ARRAY_LIST_H
+#define UCX_ARRAY_LIST_H
+
+#include "list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The maximum item size in an array list that fits into stack buffer when swapped.
+ */
+extern unsigned cx_array_swap_sbo_size;
+
+/**
+ * Declares variables for an array that can be used with the convenience macros.
+ *
+ * @see cx_array_simple_add()
+ * @see cx_array_simple_copy()
+ * @see cx_array_initialize()
+ * @see cx_array_simple_add_sorted()
+ * @see cx_array_simple_insert_sorted()
+ */
+#define CX_ARRAY_DECLARE(type, name) \
+ type * name; \
+ size_t name##_size; \
+ size_t name##_capacity
+
+/**
+ * Initializes an array declared with CX_ARRAY_DECLARE().
+ *
+ * The memory for the array is allocated with stdlib malloc().
+ * @param array the array
+ * @param capacity the initial capacity
+ */
+#define cx_array_initialize(array, capacity) \
+ array##_capacity = capacity; \
+ array##_size = 0; \
+ array = malloc(sizeof(array[0]) * capacity)
+
+/**
+ * Defines a reallocation mechanism for arrays.
+ */
+struct cx_array_reallocator_s {
+ /**
+ * Reallocates space for the given array.
+ *
+ * Implementations are not required to free the original array.
+ * This allows reallocation of static memory by allocating heap memory
+ * and copying the array contents. The information in the custom fields of
+ * the referenced allocator can be used to track the state of the memory
+ * or to transport other additional data.
+ *
+ * @param array the array to reallocate
+ * @param capacity the new capacity (number of elements)
+ * @param elem_size the size of each element
+ * @param alloc a reference to this allocator
+ * @return a pointer to the reallocated memory or \c NULL on failure
+ */
+ void *(*realloc)(
+ void *array,
+ size_t capacity,
+ size_t elem_size,
+ struct cx_array_reallocator_s *alloc
+ );
+
+ /**
+ * Custom data pointer.
+ */
+ void *ptr1;
+ /**
+ * Custom data pointer.
+ */
+ void *ptr2;
+ /**
+ * Custom data integer.
+ */
+ size_t int1;
+ /**
+ * Custom data integer.
+ */
+ size_t int2;
+};
+
+/**
+ * A default stdlib-based array reallocator.
+ */
+extern struct cx_array_reallocator_s *cx_array_default_reallocator;
+
+/**
+ * Return codes for array functions.
+ */
+enum cx_array_result {
+ CX_ARRAY_SUCCESS,
+ CX_ARRAY_REALLOC_NOT_SUPPORTED,
+ CX_ARRAY_REALLOC_FAILED,
+};
+
+/**
+ * Copies elements from one array to another.
+ *
+ * The elements are copied to the \p target array at the specified \p index,
+ * overwriting possible elements. The \p index does not need to be in range of
+ * the current array \p size. If the new index plus the number of elements added
+ * would extend the array's size, and \p capacity is not \c NULL, the remaining
+ * capacity is used.
+ *
+ * If the capacity is insufficient to hold the new data, a reallocation
+ * attempt is made, unless the \p reallocator is set to \c NULL, in which case
+ * this function ultimately returns a failure.
+ *
+ * @param target a pointer to the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity -
+ * \c NULL if only the size shall be used to bound the array (reallocations
+ * will NOT be supported in that case)
+ * @param index the index where the copied elements shall be placed
+ * @param src the source array
+ * @param elem_size the size of one element
+ * @param elem_count the number of elements to copy
+ * @param reallocator the array reallocator to use, or \c NULL
+ * if reallocation shall not happen
+ * @return zero on success, non-zero error code on failure
+ */
+__attribute__((__nonnull__(1, 2, 5)))
+enum cx_array_result cx_array_copy(
+ void **target,
+ size_t *size,
+ size_t *capacity,
+ size_t index,
+ const void *src,
+ size_t elem_size,
+ size_t elem_count,
+ struct cx_array_reallocator_s *reallocator
+);
+
+/**
+ * Convenience macro that uses cx_array_copy() with a default layout and the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer to the array)
+ * @param index the index where the copied elements shall be placed
+ * @param src the source array
+ * @param count the number of elements to copy
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_simple_copy(array, index, src, count) \
+ cx_array_copy((void**)&(array), &(array##_size), &(array##_capacity), \
+ index, src, sizeof((array)[0]), count, cx_array_default_reallocator)
+
+/**
+ * Adds an element to an array with the possibility of allocating more space.
+ *
+ * The element \p elem is added to the end of the \p target array which containing
+ * \p size elements, already. The \p capacity must not be \c NULL and point a
+ * variable holding the current maximum number of elements the array can hold.
+ *
+ * If the capacity is insufficient to hold the new element, and the optional
+ * \p reallocator is not \c NULL, an attempt increase the \p capacity is made
+ * and the new capacity is written back.
+ *
+ * @param target a pointer to the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity - must not be \c NULL
+ * @param elem_size the size of one element
+ * @param elem a pointer to the element to add
+ * @param reallocator the array reallocator to use, or \c NULL if reallocation shall not happen
+ * @return zero on success, non-zero error code on failure
+ */
+#define cx_array_add(target, size, capacity, elem_size, elem, reallocator) \
+ cx_array_copy((void**)(target), size, capacity, *(size), elem, elem_size, 1, reallocator)
+
+/**
+ * Convenience macro that uses cx_array_add() with a default layout and
+ * the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer to the array)
+ * @param elem the element to add (NOT a pointer, address is automatically taken)
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_simple_add(array, elem) \
+ cx_array_simple_copy(array, array##_size, &(elem), 1)
+
+
+/**
+ * Inserts a sorted array into another sorted array.
+ *
+ * If either the target or the source array is not already sorted with respect
+ * to the specified \p cmp_func, the behavior is undefined.
+ *
+ * If the capacity is insufficient to hold the new data, a reallocation
+ * attempt is made.
+ *
+ * @param target a pointer to the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity
+ * @param cmp_func the compare function for the elements
+ * @param src the source array
+ * @param elem_size the size of one element
+ * @param elem_count the number of elements to insert
+ * @param reallocator the array reallocator to use
+ * @return zero on success, non-zero error code on failure
+ */
+__attribute__((__nonnull__))
+enum cx_array_result cx_array_insert_sorted(
+ void **target,
+ size_t *size,
+ size_t *capacity,
+ cx_compare_func cmp_func,
+ const void *src,
+ size_t elem_size,
+ size_t elem_count,
+ struct cx_array_reallocator_s *reallocator
+);
+
+/**
+ * Inserts an element into a sorted array.
+ *
+ * If the target array is not already sorted with respect
+ * to the specified \p cmp_func, the behavior is undefined.
+ *
+ * If the capacity is insufficient to hold the new data, a reallocation
+ * attempt is made.
+ *
+ * @param target a pointer to the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity
+ * @param elem_size the size of one element
+ * @param elem a pointer to the element to add
+ * @param reallocator the array reallocator to use
+ * @return zero on success, non-zero error code on failure
+ */
+#define cx_array_add_sorted(target, size, capacity, elem_size, elem, cmp_func, reallocator) \
+ cx_array_insert_sorted((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator)
+
+/**
+ * Convenience macro for cx_array_add_sorted() with a default
+ * layout and the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer to the array)
+ * @param elem the element to add (NOT a pointer, address is automatically taken)
+ * @param cmp_func the compare function for the elements
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_simple_add_sorted(array, elem, cmp_func) \
+ cx_array_add_sorted(&array, &(array##_size), &(array##_capacity), \
+ sizeof((array)[0]), &(elem), cmp_func, cx_array_default_reallocator)
+
+/**
+ * Convenience macro for cx_array_insert_sorted() with a default
+ * layout and the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer to the array)
+ * @param src pointer to the source array
+ * @param n number of elements in the source array
+ * @param cmp_func the compare function for the elements
+ * @see CX_ARRAY_DECLARE()
+ */
+#define cx_array_simple_insert_sorted(array, src, n, cmp_func) \
+ cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \
+ cmp_func, src, sizeof((array)[0]), n, cx_array_default_reallocator)
+
+
+/**
+ * Searches the largest lower bound in a sorted array.
+ *
+ * In other words, this function returns the index of the largest element
+ * in \p arr that is less or equal to \p elem with respect to \p cmp_func.
+ * When no such element exists, \p size is returned.
+ *
+ * If \p elem is contained in the array, this is identical to
+ * #cx_array_binary_search().
+ *
+ * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the largest lower bound, or \p size
+ */
+__attribute__((__nonnull__))
+size_t cx_array_binary_search_inf(
+ const void *arr,
+ size_t size,
+ size_t elem_size,
+ const void *elem,
+ cx_compare_func cmp_func
+);
+
+/**
+ * Searches an item in a sorted array.
+ *
+ * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the element in the array, or \p size if the element
+ * cannot be found
+ */
+__attribute__((__nonnull__))
+static inline size_t cx_array_binary_search(
+ const void *arr,
+ size_t size,
+ size_t elem_size,
+ const void *elem,
+ cx_compare_func cmp_func
+) {
+ size_t index = cx_array_binary_search_inf(
+ arr, size, elem_size, elem, cmp_func
+ );
+ if (index < size && cmp_func(((const char *) arr) + index * elem_size, elem) == 0) {
+ return index;
+ } else {
+ return size;
+ }
+}
+
+/**
+ * Searches the smallest upper bound in a sorted array.
+ *
+ * In other words, this function returns the index of the smallest element
+ * in \p arr that is greater or equal to \p elem with respect to \p cmp_func.
+ * When no such element exists, \p size is returned.
+ *
+ * If \p elem is contained in the array, this is identical to
+ * #cx_array_binary_search().
+ *
+ * If the array is not sorted with respect to the \p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the smallest upper bound, or \p size
+ */
+__attribute__((__nonnull__))
+static inline size_t cx_array_binary_search_sup(
+ const void *arr,
+ size_t size,
+ size_t elem_size,
+ const void *elem,
+ cx_compare_func cmp_func
+) {
+ size_t inf = cx_array_binary_search_inf(arr, size, elem_size, elem, cmp_func);
+ if (inf == size) {
+ // no infimum means, first element is supremum
+ return 0;
+ } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) {
+ return inf;
+ } else {
+ return inf + 1;
+ }
+}
+
+/**
+ * Swaps two array elements.
+ *
+ * @param arr the array
+ * @param elem_size the element size
+ * @param idx1 index of first element
+ * @param idx2 index of second element
+ */
+__attribute__((__nonnull__))
+void cx_array_swap(
+ void *arr,
+ size_t elem_size,
+ size_t idx1,
+ size_t idx2
+);
+
+/**
+ * Allocates an array list for storing elements with \p elem_size bytes each.
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr(), if none is given.
+ *
+ * @param allocator the allocator for allocating the list memory
+ * (if \c NULL the cxDefaultAllocator will be used)
+ * @param comparator the comparator for the elements
+ * (if \c NULL, and the list is not storing pointers, sort and find
+ * functions will not work)
+ * @param elem_size the size of each element in bytes
+ * @param initial_capacity the initial number of elements the array can store
+ * @return the created list
+ */
+CxList *cxArrayListCreate(
+ const CxAllocator *allocator,
+ cx_compare_func comparator,
+ size_t elem_size,
+ size_t initial_capacity
+);
+
+/**
+ * Allocates an array list for storing elements with \p elem_size bytes each.
+ *
+ * The list will use the cxDefaultAllocator and \em NO compare function.
+ * If you want to call functions that need a compare function, you have to
+ * set it immediately after creation or use cxArrayListCreate().
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr().
+ *
+ * @param elem_size the size of each element in bytes
+ * @param initial_capacity the initial number of elements the array can store
+ * @return the created list
+ */
+#define cxArrayListCreateSimple(elem_size, initial_capacity) \
+ cxArrayListCreate(NULL, NULL, elem_size, initial_capacity)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_ARRAY_LIST_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file buffer.h
+ *
+ * \brief Advanced buffer implementation.
+ *
+ * Instances of CxBuffer can be used to read from or to write to like one
+ * would do with a stream.
+ *
+ * Some features for convenient use of the buffer
+ * can be enabled. See the documentation of the macro constants for more
+ * information.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_BUFFER_H
+#define UCX_BUFFER_H
+
+#include "common.h"
+#include "allocator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * No buffer features enabled (all flags cleared).
+ */
+#define CX_BUFFER_DEFAULT 0x00
+
+/**
+ * If this flag is enabled, the buffer will automatically free its contents when destroyed.
+ */
+#define CX_BUFFER_FREE_CONTENTS 0x01
+
+/**
+ * If this flag is enabled, the buffer will automatically extends its capacity.
+ */
+#define CX_BUFFER_AUTO_EXTEND 0x02
+
+/** Structure for the UCX buffer data. */
+typedef struct {
+ /** A pointer to the buffer contents. */
+ union {
+ /**
+ * Data is interpreted as text.
+ */
+ char *space;
+ /**
+ * Data is interpreted as binary.
+ */
+ unsigned char *bytes;
+ };
+ /** The allocator to use for automatic memory management. */
+ const CxAllocator *allocator;
+ /** Current position of the buffer. */
+ size_t pos;
+ /** Current capacity (i.e. maximum size) of the buffer. */
+ size_t capacity;
+ /** Current size of the buffer content. */
+ size_t size;
+ /**
+ * The buffer may not extend beyond this threshold before starting to flush.
+ * Default is \c SIZE_MAX (flushing disabled when auto extension is enabled).
+ */
+ size_t flush_threshold;
+ /**
+ * The block size for the elements to flush.
+ * Default is 4096 bytes.
+ */
+ size_t flush_blksize;
+ /**
+ * The maximum number of blocks to flush in one cycle.
+ * Zero disables flushing entirely (this is the default).
+ * Set this to \c SIZE_MAX to flush the entire buffer.
+ *
+ * @attention if the maximum number of blocks multiplied with the block size
+ * is smaller than the expected contents written to this buffer within one write
+ * operation, multiple flush cycles are performed after that write.
+ * That means the total number of blocks flushed after one write to this buffer may
+ * be larger than \c flush_blkmax.
+ */
+ size_t flush_blkmax;
+
+ /**
+ * The write function used for flushing.
+ * If NULL, the flushed content gets discarded.
+ */
+ cx_write_func flush_func;
+
+ /**
+ * The target for \c flush_func.
+ */
+ void *flush_target;
+
+ /**
+ * Flag register for buffer features.
+ * @see #CX_BUFFER_DEFAULT
+ * @see #CX_BUFFER_FREE_CONTENTS
+ * @see #CX_BUFFER_AUTO_EXTEND
+ */
+ int flags;
+} cx_buffer_s;
+
+/**
+ * UCX buffer.
+ */
+typedef cx_buffer_s CxBuffer;
+
+/**
+ * Initializes a fresh buffer.
+ *
+ * \note You may provide \c NULL as argument for \p space.
+ * Then this function will allocate the space and enforce
+ * the #CX_BUFFER_FREE_CONTENTS flag.
+ *
+ * @param buffer the buffer to initialize
+ * @param space pointer to the memory area, or \c NULL to allocate
+ * new memory
+ * @param capacity the capacity of the buffer
+ * @param allocator the allocator this buffer shall use for automatic
+ * memory management. If \c NULL, the default heap allocator will be used.
+ * @param flags buffer features (see cx_buffer_s.flags)
+ * @return zero on success, non-zero if a required allocation failed
+ */
+__attribute__((__nonnull__(1)))
+int cxBufferInit(
+ CxBuffer *buffer,
+ void *space,
+ size_t capacity,
+ const CxAllocator *allocator,
+ int flags
+);
+
+/**
+ * Allocates and initializes a fresh buffer.
+ *
+ * \note You may provide \c NULL as argument for \p space.
+ * Then this function will allocate the space and enforce
+ * the #CX_BUFFER_FREE_CONTENTS flag.
+ *
+ * @param space pointer to the memory area, or \c NULL to allocate
+ * new memory
+ * @param capacity the capacity of the buffer
+ * @param allocator the allocator to use for allocating the structure and the automatic
+ * memory management within the buffer. If \c NULL, the default heap allocator will be used.
+ * @param flags buffer features (see cx_buffer_s.flags)
+ * @return a pointer to the buffer on success, \c NULL if a required allocation failed
+ */
+CxBuffer *cxBufferCreate(
+ void *space,
+ size_t capacity,
+ const CxAllocator *allocator,
+ int flags
+);
+
+/**
+ * Destroys the buffer contents.
+ *
+ * Has no effect if the #CX_BUFFER_FREE_CONTENTS feature is not enabled.
+ * If you want to free the memory of the entire buffer, use cxBufferFree().
+ *
+ * @param buffer the buffer which contents shall be destroyed
+ * @see cxBufferInit()
+ */
+__attribute__((__nonnull__))
+void cxBufferDestroy(CxBuffer *buffer);
+
+/**
+ * Deallocates the buffer.
+ *
+ * If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys
+ * the contents. If you \em only want to destroy the contents, use cxBufferDestroy().
+ *
+ * @param buffer the buffer to deallocate
+ * @see cxBufferCreate()
+ */
+__attribute__((__nonnull__))
+void cxBufferFree(CxBuffer *buffer);
+
+/**
+ * Shifts the contents of the buffer by the given offset.
+ *
+ * If the offset is positive, the contents are shifted to the right.
+ * If auto extension is enabled, the buffer grows, if necessary.
+ * In case the auto extension fails, this function returns a non-zero value and
+ * no contents are changed.
+ * If auto extension is disabled, the contents that do not fit into the buffer
+ * are discarded.
+ *
+ * If the offset is negative, the contents are shifted to the left where the
+ * first \p shift bytes are discarded.
+ * The new size of the buffer is the old size minus the absolute shift value.
+ * If this value is larger than the buffer size, the buffer is emptied (but
+ * not cleared, see the security note below).
+ *
+ * The buffer position gets shifted alongside with the content but is kept
+ * within the boundaries of the buffer.
+ *
+ * \note For situations where \c off_t is not large enough, there are specialized cxBufferShiftLeft() and
+ * cxBufferShiftRight() functions using a \c size_t as parameter type.
+ *
+ * \attention
+ * Security Note: The shifting operation does \em not erase the previously occupied memory cells.
+ * But you can easily do that manually, e.g. by calling
+ * <code>memset(buffer->bytes, 0, shift)</code> for a right shift or
+ * <code>memset(buffer->bytes + buffer->size, 0, buffer->capacity - buffer->size)</code>
+ * for a left shift.
+ *
+ * @param buffer the buffer
+ * @param shift the shift offset (negative means left shift)
+ * @return 0 on success, non-zero if a required auto-extension fails
+ */
+__attribute__((__nonnull__))
+int cxBufferShift(
+ CxBuffer *buffer,
+ off_t shift
+);
+
+/**
+ * Shifts the buffer to the right.
+ * See cxBufferShift() for details.
+ *
+ * @param buffer the buffer
+ * @param shift the shift offset
+ * @return 0 on success, non-zero if a required auto-extension fails
+ * @see cxBufferShift()
+ */
+__attribute__((__nonnull__))
+int cxBufferShiftRight(
+ CxBuffer *buffer,
+ size_t shift
+);
+
+/**
+ * Shifts the buffer to the left.
+ * See cxBufferShift() for details.
+ *
+ * \note Since a left shift cannot fail due to memory allocation problems, this
+ * function always returns zero.
+ *
+ * @param buffer the buffer
+ * @param shift the positive shift offset
+ * @return always zero
+ * @see cxBufferShift()
+ */
+__attribute__((__nonnull__))
+int cxBufferShiftLeft(
+ CxBuffer *buffer,
+ size_t shift
+);
+
+
+/**
+ * Moves the position of the buffer.
+ *
+ * The new position is relative to the \p whence argument.
+ *
+ * \li \c SEEK_SET marks the start of the buffer.
+ * \li \c SEEK_CUR marks the current position.
+ * \li \c SEEK_END marks the end of the buffer.
+ *
+ * With an offset of zero, this function sets the buffer position to zero
+ * (\c SEEK_SET), the buffer size (\c SEEK_END) or leaves the buffer position
+ * unchanged (\c SEEK_CUR).
+ *
+ * @param buffer the buffer
+ * @param offset position offset relative to \p whence
+ * @param whence one of \c SEEK_SET, \c SEEK_CUR or \c SEEK_END
+ * @return 0 on success, non-zero if the position is invalid
+ *
+ */
+__attribute__((__nonnull__))
+int cxBufferSeek(
+ CxBuffer *buffer,
+ off_t offset,
+ int whence
+);
+
+/**
+ * Clears the buffer by resetting the position and deleting the data.
+ *
+ * The data is deleted by zeroing it with a call to memset().
+ * If you do not need that, you can use the faster cxBufferReset().
+ *
+ * @param buffer the buffer to be cleared
+ * @see cxBufferReset()
+ */
+__attribute__((__nonnull__))
+void cxBufferClear(CxBuffer *buffer);
+
+/**
+ * Resets the buffer by resetting the position and size to zero.
+ *
+ * The data in the buffer is not deleted. If you need a safe
+ * reset of the buffer, use cxBufferClear().
+ *
+ * @param buffer the buffer to be cleared
+ * @see cxBufferClear()
+ */
+__attribute__((__nonnull__))
+void cxBufferReset(CxBuffer *buffer);
+
+/**
+ * Tests, if the buffer position has exceeded the buffer size.
+ *
+ * @param buffer the buffer to test
+ * @return non-zero, if the current buffer position has exceeded the last
+ * byte of the buffer's contents.
+ */
+__attribute__((__nonnull__))
+int cxBufferEof(const CxBuffer *buffer);
+
+
+/**
+ * Ensures that the buffer has a minimum capacity.
+ *
+ * If the current capacity is not sufficient, the buffer will be extended.
+ *
+ * @param buffer the buffer
+ * @param capacity the minimum required capacity for this buffer
+ * @return 0 on success or a non-zero value on failure
+ */
+__attribute__((__nonnull__))
+int cxBufferMinimumCapacity(
+ CxBuffer *buffer,
+ size_t capacity
+);
+
+/**
+ * Writes data to a CxBuffer.
+ *
+ * If flushing is enabled and the buffer needs to flush, the data is flushed to
+ * the target until the target signals that it cannot take more data by
+ * returning zero via the respective write function. In that case, the remaining
+ * data in this buffer is shifted to the beginning of this buffer so that the
+ * newly available space can be used to append as much data as possible. This
+ * function only stops writing more elements, when the flush target and this
+ * buffer are both incapable of taking more data or all data has been written.
+ * The number returned by this function is the total number of elements that
+ * could be written during the process. It does not necessarily mean that those
+ * elements are still in this buffer, because some of them could have also be
+ * flushed already.
+ *
+ * If automatic flushing is not enabled, the position of the buffer is increased
+ * by the number of bytes written.
+ *
+ * \note The signature is compatible with the fwrite() family of functions.
+ *
+ * @param ptr a pointer to the memory area containing the bytes to be written
+ * @param size the length of one element
+ * @param nitems the element count
+ * @param buffer the CxBuffer to write to
+ * @return the total count of elements written
+ */
+__attribute__((__nonnull__))
+size_t cxBufferWrite(
+ const void *ptr,
+ size_t size,
+ size_t nitems,
+ CxBuffer *buffer
+);
+
+/**
+ * Reads data from a CxBuffer.
+ *
+ * The position of the buffer is increased by the number of bytes read.
+ *
+ * \note The signature is compatible with the fread() family of functions.
+ *
+ * @param ptr a pointer to the memory area where to store the read data
+ * @param size the length of one element
+ * @param nitems the element count
+ * @param buffer the CxBuffer to read from
+ * @return the total number of elements read
+ */
+__attribute__((__nonnull__))
+size_t cxBufferRead(
+ void *ptr,
+ size_t size,
+ size_t nitems,
+ CxBuffer *buffer
+);
+
+/**
+ * Writes a character to a buffer.
+ *
+ * The least significant byte of the argument is written to the buffer. If the
+ * end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled,
+ * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature is
+ * disabled or buffer extension fails, \c EOF is returned.
+ *
+ * On successful write, the position of the buffer is increased.
+ *
+ * @param buffer the buffer to write to
+ * @param c the character to write
+ * @return the byte that has bean written or \c EOF when the end of the stream is
+ * reached and automatic extension is not enabled or not possible
+ */
+__attribute__((__nonnull__))
+int cxBufferPut(
+ CxBuffer *buffer,
+ int c
+);
+
+/**
+ * Writes a string to a buffer.
+ *
+ * @param buffer the buffer
+ * @param str the zero-terminated string
+ * @return the number of bytes written
+ */
+__attribute__((__nonnull__))
+size_t cxBufferPutString(
+ CxBuffer *buffer,
+ const char *str
+);
+
+/**
+ * Gets a character from a buffer.
+ *
+ * The current position of the buffer is increased after a successful read.
+ *
+ * @param buffer the buffer to read from
+ * @return the character or \c EOF, if the end of the buffer is reached
+ */
+__attribute__((__nonnull__))
+int cxBufferGet(CxBuffer *buffer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // UCX_BUFFER_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file collection.h
+ * \brief Common definitions for various collection implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_COLLECTION_H
+#define UCX_COLLECTION_H
+
+#include "allocator.h"
+#include "iterator.h"
+#include "compare.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Special constant used for creating collections that are storing pointers.
+ */
+#define CX_STORE_POINTERS 0
+
+/**
+ * Base attributes of a collection.
+ */
+struct cx_collection_s {
+ /**
+ * The allocator to use.
+ */
+ const CxAllocator *allocator;
+ /**
+ * The comparator function for the elements.
+ */
+ cx_compare_func cmpfunc;
+ /**
+ * The size of each element.
+ */
+ size_t elem_size;
+ /**
+ * The number of currently stored elements.
+ */
+ size_t size;
+ /**
+ * An optional simple destructor for the collection's elements.
+ *
+ * @attention Read the documentation of the particular collection implementation
+ * whether this destructor shall only destroy the contents or also free the memory.
+ */
+ cx_destructor_func simple_destructor;
+ /**
+ * An optional advanced destructor for the collection's elements.
+ *
+ * @attention Read the documentation of the particular collection implementation
+ * whether this destructor shall only destroy the contents or also free the memory.
+ */
+ cx_destructor_func2 advanced_destructor;
+ /**
+ * The pointer to additional data that is passed to the advanced destructor.
+ */
+ void *destructor_data;
+ /**
+ * Indicates if this list is supposed to store pointers
+ * instead of copies of the actual objects.
+ */
+ bool store_pointer;
+};
+
+/**
+ * Use this macro to declare common members for a collection structure.
+ */
+#define CX_COLLECTION_BASE struct cx_collection_s collection
+
+/**
+ * Sets a simple destructor function for this collection.
+ *
+ * @param c the collection
+ * @param destr the destructor function
+ */
+#define cxDefineDestructor(c, destr) \
+ (c)->collection.simple_destructor = (cx_destructor_func) destr
+
+/**
+ * Sets a simple destructor function for this collection.
+ *
+ * @param c the collection
+ * @param destr the destructor function
+ */
+#define cxDefineAdvancedDestructor(c, destr, data) \
+ (c)->collection.advanced_destructor = (cx_destructor_func2) destr; \
+ (c)->collection.destructor_data = data
+
+/**
+ * Invokes the simple destructor function for a specific element.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c the collection
+ * @param e the element
+ */
+#define cx_invoke_simple_destructor(c, e) \
+ (c)->collection.simple_destructor((c)->collection.store_pointer ? (*((void **) (e))) : (e))
+
+/**
+ * Invokes the advanced destructor function for a specific element.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c the collection
+ * @param e the element
+ */
+#define cx_invoke_advanced_destructor(c, e) \
+ (c)->collection.advanced_destructor((c)->collection.destructor_data, \
+ (c)->collection.store_pointer ? (*((void **) (e))) : (e))
+
+
+/**
+ * Invokes all available destructor functions for a specific element.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c the collection
+ * @param e the element
+ */
+#define cx_invoke_destructor(c, e) \
+ if ((c)->collection.simple_destructor) cx_invoke_simple_destructor(c,e); \
+ if ((c)->collection.advanced_destructor) cx_invoke_advanced_destructor(c,e)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_COLLECTION_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file common.h
+ *
+ * \brief Common definitions and feature checks.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ *
+ * \mainpage UAP Common Extensions
+ * Library with common and useful functions, macros and data structures.
+ * <p>
+ * Latest available source:<br>
+ * <a href="https://sourceforge.net/projects/ucx/files/">https://sourceforge.net/projects/ucx/files/</a>
+ * </p>
+ *
+ * <p>
+ * Repositories:<br>
+ * <a href="https://sourceforge.net/p/ucx/code">https://sourceforge.net/p/ucx/code</a>
+ * - or -
+ * <a href="https://develop.uap-core.de/hg/ucx">https://develop.uap-core.de/hg/ucx</a>
+ * </p>
+ *
+ * <h2>LICENCE</h2>
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UCX_COMMON_H
+#define UCX_COMMON_H
+
+/** Major UCX version as integer constant. */
+#define UCX_VERSION_MAJOR 3
+
+/** Minor UCX version as integer constant. */
+#define UCX_VERSION_MINOR 1
+
+/** Version constant which ensures to increase monotonically. */
+#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
+
+// Common Includes
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifndef UCX_TEST_H
+/**
+ * Function pointer compatible with fwrite-like functions.
+ */
+typedef size_t (*cx_write_func)(
+ const void *,
+ size_t,
+ size_t,
+ void *
+);
+#endif // UCX_TEST_H
+
+/**
+ * Function pointer compatible with fread-like functions.
+ */
+typedef size_t (*cx_read_func)(
+ void *,
+ size_t,
+ size_t,
+ void *
+);
+
+
+// Compiler specific stuff
+
+#ifndef __GNUC__
+/**
+ * Removes GNU C attributes where they are not supported.
+ */
+#define __attribute__(x)
+#endif
+
+#ifdef _MSC_VER
+
+// fix missing ssize_t definition
+#include <BaseTsd.h>
+typedef SSIZE_T ssize_t;
+
+// fix missing _Thread_local support
+#define _Thread_local __declspec(thread)
+
+#endif
+
+#endif // UCX_COMMON_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file common.h
+ *
+ * \brief Common definitions and feature checks.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ *
+ * \mainpage UAP Common Extensions
+ * Library with common and useful functions, macros and data structures.
+ * <p>
+ * Latest available source:<br>
+ * <a href="https://sourceforge.net/projects/ucx/files/">https://sourceforge.net/projects/ucx/files/</a>
+ * </p>
+ *
+ * <p>
+ * Repositories:<br>
+ * <a href="https://sourceforge.net/p/ucx/code">https://sourceforge.net/p/ucx/code</a>
+ * - or -
+ * <a href="https://develop.uap-core.de/hg/ucx">https://develop.uap-core.de/hg/ucx</a>
+ * </p>
+ *
+ * <h2>LICENCE</h2>
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UCX_COMMON_H
+#define UCX_COMMON_H
+
+/** Major UCX version as integer constant. */
+#define UCX_VERSION_MAJOR 3
+
+/** Minor UCX version as integer constant. */
+#define UCX_VERSION_MINOR 0
+
+/** Version constant which ensures to increase monotonically. */
+#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
+
+#define __attribute__(...)
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Function pointer compatible with fwrite-like functions.
+ */
+typedef size_t (*cx_write_func)(
+ void const *,
+ size_t,
+ size_t,
+ void *
+);
+
+/**
+ * Function pointer compatible with fread-like functions.
+ */
+typedef size_t (*cx_read_func)(
+ void *,
+ size_t,
+ size_t,
+ void *
+);
+
+#ifdef _WIN32
+
+#ifdef __MINGW32__
+#include <sys/types.h>
+#endif // __MINGW32__
+
+#else // !_WIN32
+
+#include <sys/types.h>
+
+#endif // _WIN32
+
+#ifndef __GNUC__
+/**
+ * Removes GNU C attributes where they are not supported.
+ */
+#define __attribute__(x)
+#endif
+
+#endif // UCX_COMMON_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file compare.h
+ * \brief A collection of simple compare functions.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_COMPARE_H
+#define UCX_COMPARE_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef CX_COMPARE_FUNC_DEFINED
+#define CX_COMPARE_FUNC_DEFINED
+/**
+ * A comparator function comparing two collection elements.
+ */
+typedef int(*cx_compare_func)(
+ const void *left,
+ const void *right
+);
+#endif // CX_COMPARE_FUNC_DEFINED
+
+/**
+ * Compares two integers of type int.
+ *
+ * @param i1 pointer to integer one
+ * @param i2 pointer to integer two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_int(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type long int.
+ *
+ * @param i1 pointer to long integer one
+ * @param i2 pointer to long integer two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_longint(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type long long.
+ *
+ * @param i1 pointer to long long one
+ * @param i2 pointer to long long two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_longlong(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type int16_t.
+ *
+ * @param i1 pointer to int16_t one
+ * @param i2 pointer to int16_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_int16(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type int32_t.
+ *
+ * @param i1 pointer to int32_t one
+ * @param i2 pointer to int32_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_int32(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type int64_t.
+ *
+ * @param i1 pointer to int64_t one
+ * @param i2 pointer to int64_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_int64(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type unsigned int.
+ *
+ * @param i1 pointer to unsigned integer one
+ * @param i2 pointer to unsigned integer two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_uint(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type unsigned long int.
+ *
+ * @param i1 pointer to unsigned long integer one
+ * @param i2 pointer to unsigned long integer two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_ulongint(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type unsigned long long.
+ *
+ * @param i1 pointer to unsigned long long one
+ * @param i2 pointer to unsigned long long two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_ulonglong(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type uint16_t.
+ *
+ * @param i1 pointer to uint16_t one
+ * @param i2 pointer to uint16_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_uint16(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type uint32_t.
+ *
+ * @param i1 pointer to uint32_t one
+ * @param i2 pointer to uint32_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_uint32(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type uint64_t.
+ *
+ * @param i1 pointer to uint64_t one
+ * @param i2 pointer to uint64_t two
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int cx_cmp_uint64(const void *i1, const void *i2);
+
+/**
+ * Compares two real numbers of type float with precision 1e-6f.
+ *
+ * @param f1 pointer to float one
+ * @param f2 pointer to float two
+ * @return -1, if *f1 is less than *f2, 0 if both are equal,
+ * 1 if *f1 is greater than *f2
+ */
+
+int cx_cmp_float(const void *f1, const void *f2);
+
+/**
+ * Compares two real numbers of type double with precision 1e-14.
+ *
+ * @param d1 pointer to double one
+ * @param d2 pointer to double two
+ * @return -1, if *d1 is less than *d2, 0 if both are equal,
+ * 1 if *d1 is greater than *d2
+ */
+int cx_cmp_double(
+ const void *d1,
+ const void *d2
+);
+
+/**
+ * Compares the integer representation of two pointers.
+ *
+ * @param ptr1 pointer to pointer one (const intptr_t*)
+ * @param ptr2 pointer to pointer two (const intptr_t*)
+ * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
+ * 1 if *ptr1 is greater than *ptr2
+ */
+int cx_cmp_intptr(
+ const void *ptr1,
+ const void *ptr2
+);
+
+/**
+ * Compares the unsigned integer representation of two pointers.
+ *
+ * @param ptr1 pointer to pointer one (const uintptr_t*)
+ * @param ptr2 pointer to pointer two (const uintptr_t*)
+ * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
+ * 1 if *ptr1 is greater than *ptr2
+ */
+int cx_cmp_uintptr(
+ const void *ptr1,
+ const void *ptr2
+);
+
+/**
+ * Compares the pointers specified in the arguments without de-referencing.
+ *
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @return -1 if ptr1 is less than ptr2, 0 if both are equal,
+ * 1 if ptr1 is greater than ptr2
+ */
+int cx_cmp_ptr(
+ const void *ptr1,
+ const void *ptr2
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_COMPARE_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file hash_key.h
+ * \brief Interface for map implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+
+#ifndef UCX_HASH_KEY_H
+#define UCX_HASH_KEY_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Internal structure for a key within a hash map. */
+struct cx_hash_key_s {
+ /** The key data. */
+ const void *data;
+ /**
+ * The key data length.
+ */
+ size_t len;
+ /** The hash value of the key data. */
+ unsigned hash;
+};
+
+/**
+ * Type for a hash key.
+ */
+typedef struct cx_hash_key_s CxHashKey;
+
+/**
+ * Computes a murmur2 32 bit hash.
+ *
+ * You need to initialize \c data and \c len in the key struct.
+ * The hash is then directly written to that struct.
+ *
+ * \note If \c data is \c NULL, the hash is defined as 1574210520.
+ *
+ * @param key the key, the hash shall be computed for
+ */
+void cx_hash_murmur(CxHashKey *key);
+
+/**
+ * Computes a hash key from a string.
+ *
+ * The string needs to be zero-terminated.
+ *
+ * @param str the string
+ * @return the hash key
+ */
+__attribute__((__warn_unused_result__))
+CxHashKey cx_hash_key_str(const char *str);
+
+/**
+ * Computes a hash key from a byte array.
+ *
+ * @param bytes the array
+ * @param len the length
+ * @return the hash key
+ */
+__attribute__((__warn_unused_result__))
+CxHashKey cx_hash_key_bytes(
+ const unsigned char *bytes,
+ size_t len
+);
+
+/**
+ * Computes a hash key for an arbitrary object.
+ *
+ * The computation uses the in-memory representation that might not be
+ * the same on different platforms. Therefore, this hash should not be
+ * used for data exchange with different machines.
+ *
+ * @param obj a pointer to an arbitrary object
+ * @param len the length of object in memory
+ * @return the hash key
+ */
+__attribute__((__warn_unused_result__))
+CxHashKey cx_hash_key(
+ const void *obj,
+ size_t len
+);
+
+/**
+ * Computes a hash key from a UCX string.
+ *
+ * @param str the string
+ * @return the hash key
+ */
+#define cx_hash_key_cxstr(str) cx_hash_key((void*)(str).ptr, (str).length)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_HASH_KEY_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file hash_map.h
+ * \brief Hash map implementation.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_HASH_MAP_H
+#define UCX_HASH_MAP_H
+
+#include "map.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Internal structure for an element of a hash map. */
+struct cx_hash_map_element_s;
+
+/**
+ * Internal structure for a hash map.
+ */
+struct cx_hash_map_s {
+ /**
+ * Base structure for maps.
+ */
+ struct cx_map_s base;
+ /**
+ * The buckets of this map, each containing a linked list of elements.
+ */
+ struct cx_hash_map_element_s **buckets;
+ /**
+ * The number of buckets.
+ */
+ size_t bucket_count;
+};
+
+
+/**
+ * Creates a new hash map with the specified number of buckets.
+ *
+ * If \p buckets is zero, an implementation defined default will be used.
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created map will be created as if
+ * cxMapStorePointers() was called immediately after creation.
+ *
+ * @note Iterators provided by this hash map implementation provide the remove operation.
+ * The index value of an iterator is incremented when the iterator advanced without removal.
+ * In other words, when the iterator is finished, \c index==size .
+ *
+ * @param allocator the allocator to use
+ * @param itemsize the size of one element
+ * @param buckets the initial number of buckets in this hash map
+ * @return a pointer to the new hash map
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMap *cxHashMapCreate(
+ const CxAllocator *allocator,
+ size_t itemsize,
+ size_t buckets
+);
+
+/**
+ * Creates a new hash map with a default number of buckets.
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created map will be created as if
+ * cxMapStorePointers() was called immediately after creation.
+ *
+ * @note Iterators provided by this hash map implementation provide the remove operation.
+ * The index value of an iterator is incremented when the iterator advanced without removal.
+ * In other words, when the iterator is finished, \c index==size .
+ *
+ * @param itemsize the size of one element
+ * @return a pointer to the new hash map
+ */
+#define cxHashMapCreateSimple(itemsize) \
+ cxHashMapCreate(cxDefaultAllocator, itemsize, 0)
+
+/**
+ * Increases the number of buckets, if necessary.
+ *
+ * The load threshold is \c 0.75*buckets. If the element count exceeds the load
+ * threshold, the map will be rehashed. Otherwise, no action is performed and
+ * this function simply returns 0.
+ *
+ * The rehashing process ensures, that the number of buckets is at least
+ * 2.5 times the element count. So there is enough room for additional
+ * elements without the need of another soon rehashing.
+ *
+ * You can use this function after filling a map to increase access performance.
+ *
+ * @note If the specified map is not a hash map, the behavior is undefined.
+ *
+ * @param map the map to rehash
+ * @return zero on success, non-zero if a memory allocation error occurred
+ */
+__attribute__((__nonnull__))
+int cxMapRehash(CxMap *map);
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_HASH_MAP_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file iterator.h
+ * \brief Interface for iterator implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_ITERATOR_H
+#define UCX_ITERATOR_H
+
+#include "common.h"
+
+struct cx_iterator_base_s {
+ /**
+ * True iff the iterator points to valid data.
+ */
+ __attribute__ ((__nonnull__))
+ bool (*valid)(const void *);
+
+ /**
+ * Returns a pointer to the current element.
+ *
+ * When valid returns false, the behavior of this function is undefined.
+ */
+ __attribute__ ((__nonnull__))
+ void *(*current)(const void *);
+
+ /**
+ * Original implementation in case the function needs to be wrapped.
+ */
+ __attribute__ ((__nonnull__))
+ void *(*current_impl)(const void *);
+
+ /**
+ * Advances the iterator.
+ *
+ * When valid returns false, the behavior of this function is undefined.
+ */
+ __attribute__ ((__nonnull__))
+ void (*next)(void *);
+ /**
+ * Indicates whether this iterator may remove elements.
+ */
+ bool mutating;
+ /**
+ * Internal flag for removing the current element when advancing.
+ */
+ bool remove;
+};
+
+/**
+ * Declares base attributes for an iterator.
+ * Must be the first member of an iterator structure.
+ */
+#define CX_ITERATOR_BASE struct cx_iterator_base_s base
+
+/**
+ * Internal iterator struct - use CxIterator.
+ */
+struct cx_iterator_s {
+ CX_ITERATOR_BASE;
+
+ /**
+ * Handle for the current element.
+ */
+ void *elem_handle;
+
+ /**
+ * Handle for the source collection, if any.
+ */
+ union {
+ /**
+ * Access for mutating iterators.
+ */
+ void *m;
+ /**
+ * Access for normal iterators.
+ */
+ const void *c;
+ } src_handle;
+
+ /**
+ * Field for storing a key-value pair.
+ * May be used by iterators that iterate over k/v-collections.
+ */
+ struct {
+ /**
+ * A pointer to the key.
+ */
+ const void *key;
+ /**
+ * A pointer to the value.
+ */
+ void *value;
+ } kv_data;
+
+ /**
+ * Field for storing a slot number.
+ * May be used by iterators that iterate over multi-bucket collections.
+ */
+ size_t slot;
+
+ /**
+ * If the iterator is position-aware, contains the index of the element in the underlying collection.
+ * Otherwise, this field is usually uninitialized.
+ */
+ size_t index;
+
+ /**
+ * The size of an individual element.
+ */
+ size_t elem_size;
+
+ /**
+ * May contain the total number of elements, if known.
+ * Shall be set to \c SIZE_MAX when the total number is unknown during iteration.
+ */
+ size_t elem_count;
+};
+
+/**
+ * Iterator type.
+ *
+ * An iterator points to a certain element in a (possibly unbounded) chain of elements.
+ * Iterators that are based on collections (which have a defined "first" element), are supposed
+ * to be "position-aware", which means that they keep track of the current index within the collection.
+ *
+ * @note Objects that are pointed to by an iterator are always mutable through that iterator. However,
+ * any concurrent mutation of the collection other than by this iterator makes this iterator invalid
+ * and it must not be used anymore.
+ */
+typedef struct cx_iterator_s CxIterator;
+
+/**
+ * Checks if the iterator points to valid data.
+ *
+ * This is especially false for past-the-end iterators.
+ *
+ * @param iter the iterator
+ * @return true iff the iterator points to valid data
+ */
+#define cxIteratorValid(iter) (iter).base.valid(&(iter))
+
+/**
+ * Returns a pointer to the current element.
+ *
+ * The behavior is undefined if this iterator is invalid.
+ *
+ * @param iter the iterator
+ * @return a pointer to the current element
+ */
+#define cxIteratorCurrent(iter) (iter).base.current(&iter)
+
+/**
+ * Advances the iterator to the next element.
+ *
+ * @param iter the iterator
+ */
+#define cxIteratorNext(iter) (iter).base.next(&iter)
+
+/**
+ * Flags the current element for removal, if this iterator is mutating.
+ *
+ * @param iter the iterator
+ */
+#define cxIteratorFlagRemoval(iter) (iter).base.remove |= (iter).base.mutating
+
+/**
+ * Obtains a reference to an arbitrary iterator.
+ *
+ * This is useful for APIs that expect some iterator as an argument.
+ *
+ * @param iter the iterator
+ */
+#define cxIteratorRef(iter) &((iter).base)
+
+/**
+ * Loops over an iterator.
+ * @param type the type of the elements
+ * @param elem the name of the iteration variable
+ * @param iter the iterator
+ */
+#define cx_foreach(type, elem, iter) \
+for (type elem; cxIteratorValid(iter) && (elem = (type)cxIteratorCurrent(iter)) != NULL ; cxIteratorNext(iter))
+
+
+/**
+ * Creates an iterator for the specified plain array.
+ *
+ * The \p array can be \c NULL in which case the iterator will be immediately
+ * initialized such that #cxIteratorValid() returns \c false.
+ *
+ *
+ * @param array a pointer to the array (can be \c NULL)
+ * @param elem_size the size of one array element
+ * @param elem_count the number of elements in the array
+ * @return an iterator for the specified array
+ */
+__attribute__((__warn_unused_result__))
+CxIterator cxIterator(
+ const void *array,
+ size_t elem_size,
+ size_t elem_count
+);
+
+/**
+ * Creates a mutating iterator for the specified plain array.
+ *
+ * While the iterator is in use, the array may only be altered by removing
+ * elements through #cxIteratorFlagRemoval(). Every other change to the array
+ * will bring this iterator to an undefined state.
+ *
+ * When \p remove_keeps_order is set to \c false, removing an element will only
+ * move the last element to the position of the removed element, instead of
+ * moving all subsequent elements by one. Usually, when the order of elements is
+ * not important, this parameter should be set to \c false.
+ *
+ * The \p array can be \c NULL in which case the iterator will be immediately
+ * initialized such that #cxIteratorValid() returns \c false.
+ *
+ *
+ * @param array a pointer to the array (can be \c NULL)
+ * @param elem_size the size of one array element
+ * @param elem_count the number of elements in the array
+ * @param remove_keeps_order \c true if the order of elements must be preserved
+ * when removing an element
+ * @return an iterator for the specified array
+ */
+__attribute__((__warn_unused_result__))
+CxIterator cxMutIterator(
+ void *array,
+ size_t elem_size,
+ size_t elem_count,
+ bool remove_keeps_order
+);
+
+#endif // UCX_ITERATOR_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file linked_list.h
+ * \brief Linked list implementation.
+ * \details Also provides several low-level functions for custom linked list implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_LINKED_LIST_H
+#define UCX_LINKED_LIST_H
+
+#include "common.h"
+#include "list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The maximum item size that uses SBO swap instead of relinking.
+ */
+extern unsigned cx_linked_list_swap_sbo_size;
+
+/**
+ * Allocates a linked list for storing elements with \p elem_size bytes each.
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr(), if none is given.
+ *
+ * @param allocator the allocator for allocating the list nodes
+ * (if \c NULL the cxDefaultAllocator will be used)
+ * @param comparator the comparator for the elements
+ * (if \c NULL, and the list is not storing pointers, sort and find
+ * functions will not work)
+ * @param elem_size the size of each element in bytes
+ * @return the created list
+ */
+CxList *cxLinkedListCreate(
+ const CxAllocator *allocator,
+ cx_compare_func comparator,
+ size_t elem_size
+);
+
+/**
+ * Allocates a linked list for storing elements with \p elem_size bytes each.
+ *
+ * The list will use cxDefaultAllocator and no comparator function. If you want
+ * to call functions that need a comparator, you must either set one immediately
+ * after list creation or use cxLinkedListCreate().
+ *
+ * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation and the compare
+ * function will be automatically set to cx_cmp_ptr().
+ *
+ * @param elem_size the size of each element in bytes
+ * @return the created list
+ */
+#define cxLinkedListCreateSimple(elem_size) \
+ cxLinkedListCreate(NULL, NULL, elem_size)
+
+/**
+ * Finds the node at a certain index.
+ *
+ * This function can be used to start at an arbitrary position within the list.
+ * If the search index is large than the start index, \p loc_advance must denote
+ * the location of some sort of \c next pointer (i.e. a pointer to the next node).
+ * But it is also possible that the search index is smaller than the start index
+ * (e.g. in cases where traversing a list backwards is faster) in which case
+ * \p loc_advance must denote the location of some sort of \c prev pointer
+ * (i.e. a pointer to the previous node).
+ *
+ * @param start a pointer to the start node
+ * @param start_index the start index
+ * @param loc_advance the location of the pointer to advance
+ * @param index the search index
+ * @return the node found at the specified index
+ */
+__attribute__((__nonnull__))
+void *cx_linked_list_at(
+ const void *start,
+ size_t start_index,
+ ptrdiff_t loc_advance,
+ size_t index
+);
+
+/**
+ * Finds the index of an element within a linked list.
+ *
+ * @param start a pointer to the start node
+ * @param loc_advance the location of the pointer to advance
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func a compare function to compare \p elem against the node data
+ * @param elem a pointer to the element to find
+ * @return the index of the element or a negative value if it could not be found
+ */
+__attribute__((__nonnull__))
+ssize_t cx_linked_list_find(
+ const void *start,
+ ptrdiff_t loc_advance,
+ ptrdiff_t loc_data,
+ cx_compare_func cmp_func,
+ const void *elem
+);
+
+/**
+ * Finds the node containing an element within a linked list.
+ *
+ * @param result a pointer to the memory where the node pointer (or \c NULL if the element
+ * could not be found) shall be stored to
+ * @param start a pointer to the start node
+ * @param loc_advance the location of the pointer to advance
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func a compare function to compare \p elem against the node data
+ * @param elem a pointer to the element to find
+ * @return the index of the element or a negative value if it could not be found
+ */
+__attribute__((__nonnull__))
+ssize_t cx_linked_list_find_node(
+ void **result,
+ const void *start,
+ ptrdiff_t loc_advance,
+ ptrdiff_t loc_data,
+ cx_compare_func cmp_func,
+ const void *elem
+);
+
+/**
+ * Finds the first node in a linked list.
+ *
+ * The function starts with the pointer denoted by \p node and traverses the list
+ * along a prev pointer whose location within the node struct is
+ * denoted by \p loc_prev.
+ *
+ * @param node a pointer to a node in the list
+ * @param loc_prev the location of the \c prev pointer
+ * @return a pointer to the first node
+ */
+__attribute__((__nonnull__))
+void *cx_linked_list_first(
+ const void *node,
+ ptrdiff_t loc_prev
+);
+
+/**
+ * Finds the last node in a linked list.
+ *
+ * The function starts with the pointer denoted by \p node and traverses the list
+ * along a next pointer whose location within the node struct is
+ * denoted by \p loc_next.
+ *
+ * @param node a pointer to a node in the list
+ * @param loc_next the location of the \c next pointer
+ * @return a pointer to the last node
+ */
+__attribute__((__nonnull__))
+void *cx_linked_list_last(
+ const void *node,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Finds the predecessor of a node in case it is not linked.
+ *
+ * \remark If \p node is not contained in the list starting with \p begin, the behavior is undefined.
+ *
+ * @param begin the node where to start the search
+ * @param loc_next the location of the \c next pointer
+ * @param node the successor of the node to find
+ * @return the node or \c NULL if \p node has no predecessor
+ */
+__attribute__((__nonnull__))
+void *cx_linked_list_prev(
+ const void *begin,
+ ptrdiff_t loc_next,
+ const void *node
+);
+
+/**
+ * Adds a new node to a linked list.
+ * The node must not be part of any list already.
+ *
+ * \remark One of the pointers \p begin or \p end may be \c NULL, but not both.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param new_node a pointer to the node that shall be appended
+ */
+__attribute__((__nonnull__(5)))
+void cx_linked_list_add(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *new_node
+);
+
+/**
+ * Prepends a new node to a linked list.
+ * The node must not be part of any list already.
+ *
+ * \remark One of the pointers \p begin or \p end may be \c NULL, but not both.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param new_node a pointer to the node that shall be prepended
+ */
+__attribute__((__nonnull__(5)))
+void cx_linked_list_prepend(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *new_node
+);
+
+/**
+ * Links two nodes.
+ *
+ * @param left the new predecessor of \p right
+ * @param right the new successor of \p left
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ */
+__attribute__((__nonnull__))
+void cx_linked_list_link(
+ void *left,
+ void *right,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Unlinks two nodes.
+ *
+ * If right is not the successor of left, the behavior is undefined.
+ *
+ * @param left the predecessor of \p right
+ * @param right the successor of \p left
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ */
+__attribute__((__nonnull__))
+void cx_linked_list_unlink(
+ void *left,
+ void *right,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Inserts a new node after a given node of a linked list.
+ * The new node must not be part of any list already.
+ *
+ * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or
+ * the \p end pointer to determine the start of the list. Then the new node will be prepended to the list.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param node the node after which to insert (\c NULL if you want to prepend the node to the list)
+ * @param new_node a pointer to the node that shall be inserted
+ */
+__attribute__((__nonnull__(6)))
+void cx_linked_list_insert(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *node,
+ void *new_node
+);
+
+/**
+ * Inserts a chain of nodes after a given node of a linked list.
+ * The chain must not be part of any list already.
+ *
+ * If you do not explicitly specify the end of the chain, it will be determined by traversing
+ * the \c next pointer.
+ *
+ * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or
+ * the \p end pointer to determine the start of the list. If only the \p end pointer is specified, you also need
+ * to provide a valid \p loc_prev location.
+ * Then the chain will be prepended to the list.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param node the node after which to insert (\c NULL to prepend the chain to the list)
+ * @param insert_begin a pointer to the first node of the chain that shall be inserted
+ * @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined)
+ */
+__attribute__((__nonnull__(6)))
+void cx_linked_list_insert_chain(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *node,
+ void *insert_begin,
+ void *insert_end
+);
+
+/**
+ * Inserts a node into a sorted linked list.
+ * The new node must not be part of any list already.
+ *
+ * If the list starting with the node pointed to by \p begin is not sorted
+ * already, the behavior is undefined.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param new_node a pointer to the node that shall be inserted
+ * @param cmp_func a compare function that will receive the node pointers
+ */
+__attribute__((__nonnull__(1, 5, 6)))
+void cx_linked_list_insert_sorted(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *new_node,
+ cx_compare_func cmp_func
+);
+
+/**
+ * Inserts a chain of nodes into a sorted linked list.
+ * The chain must not be part of any list already.
+ *
+ * If either the list starting with the node pointed to by \p begin or the list
+ * starting with \p insert_begin is not sorted, the behavior is undefined.
+ *
+ * \attention In contrast to cx_linked_list_insert_chain(), the source chain
+ * will be broken and inserted into the target list so that the resulting list
+ * will be sorted according to \p cmp_func. That means, each node in the source
+ * chain may be re-linked with nodes from the target list.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param insert_begin a pointer to the first node of the chain that shall be inserted
+ * @param cmp_func a compare function that will receive the node pointers
+ */
+__attribute__((__nonnull__(1, 5, 6)))
+void cx_linked_list_insert_sorted_chain(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *insert_begin,
+ cx_compare_func cmp_func
+);
+
+/**
+ * Removes a node from the linked list.
+ *
+ * If the node to remove is the begin (resp. end) node of the list and if \p begin (resp. \p end)
+ * addresses are provided, the pointers are adjusted accordingly.
+ *
+ * The following combinations of arguments are valid (more arguments are optional):
+ * \li \p loc_next and \p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance)
+ * \li \p loc_next and \p begin (ancestor node is determined by list traversal, overall O(n) performance)
+ *
+ * \remark The \c next and \c prev pointers of the removed node are not cleared by this function and may still be used
+ * to traverse to a former adjacent node in the list.
+ *
+ * @param begin a pointer to the begin node pointer (optional)
+ * @param end a pointer to the end node pointer (optional)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param node the node to remove
+ */
+__attribute__((__nonnull__(5)))
+void cx_linked_list_remove(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *node
+);
+
+
+/**
+ * Determines the size of a linked list starting with \p node.
+ * @param node the first node
+ * @param loc_next the location of the \c next pointer within the node struct
+ * @return the size of the list or zero if \p node is \c NULL
+ */
+size_t cx_linked_list_size(
+ const void *node,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Sorts a linked list based on a comparison function.
+ *
+ * This function can work with linked lists of the following structure:
+ * \code
+ * typedef struct node node;
+ * struct node {
+ * node* prev;
+ * node* next;
+ * my_payload data;
+ * }
+ * \endcode
+ *
+ * @note This is a recursive function with at most logarithmic recursion depth.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @param end a pointer to the end node pointer (optional)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if not present)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func the compare function defining the sort order
+ */
+__attribute__((__nonnull__(1, 6)))
+void cx_linked_list_sort(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ ptrdiff_t loc_data,
+ cx_compare_func cmp_func
+);
+
+
+/**
+ * Compares two lists element wise.
+ *
+ * \note Both list must have the same structure.
+ *
+ * @param begin_left the begin of the left list (\c NULL denotes an empty list)
+ * @param begin_right the begin of the right list (\c NULL denotes an empty list)
+ * @param loc_advance the location of the pointer to advance
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func the function to compare the elements
+ * @return the first non-zero result of invoking \p cmp_func or: negative if the left list is smaller than the
+ * right list, positive if the left list is larger than the right list, zero if both lists are equal.
+ */
+__attribute__((__nonnull__(5)))
+int cx_linked_list_compare(
+ const void *begin_left,
+ const void *begin_right,
+ ptrdiff_t loc_advance,
+ ptrdiff_t loc_data,
+ cx_compare_func cmp_func
+);
+
+/**
+ * Reverses the order of the nodes in a linked list.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @param end a pointer to the end node pointer (optional)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ */
+__attribute__((__nonnull__(1)))
+void cx_linked_list_reverse(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_LINKED_LIST_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file list.h
+ * \brief Interface for list implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_LIST_H
+#define UCX_LIST_H
+
+#include "common.h"
+#include "collection.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * List class type.
+ */
+typedef struct cx_list_class_s cx_list_class;
+
+/**
+ * Structure for holding the base data of a list.
+ */
+struct cx_list_s {
+ CX_COLLECTION_BASE;
+ /**
+ * The list class definition.
+ */
+ const cx_list_class *cl;
+ /**
+ * The actual implementation in case the list class is delegating.
+ */
+ const cx_list_class *climpl;
+};
+
+/**
+ * The class definition for arbitrary lists.
+ */
+struct cx_list_class_s {
+ /**
+ * Destructor function.
+ *
+ * Implementations SHALL invoke the content destructor functions if provided
+ * and SHALL deallocate the list memory.
+ */
+ void (*destructor)(struct cx_list_s *list);
+
+ /**
+ * Member function for inserting a single element.
+ * Implementors SHOULD see to performant implementations for corner cases.
+ */
+ int (*insert_element)(
+ struct cx_list_s *list,
+ size_t index,
+ const void *data
+ );
+
+ /**
+ * Member function for inserting multiple elements.
+ * Implementors SHOULD see to performant implementations for corner cases.
+ * @see cx_list_default_insert_array()
+ */
+ size_t (*insert_array)(
+ struct cx_list_s *list,
+ size_t index,
+ const void *data,
+ size_t n
+ );
+
+ /**
+ * Member function for inserting sorted elements into a sorted list.
+ *
+ * @see cx_list_default_insert_sorted()
+ */
+ size_t (*insert_sorted)(
+ struct cx_list_s *list,
+ const void *sorted_data,
+ size_t n
+ );
+
+ /**
+ * Member function for inserting an element relative to an iterator position.
+ */
+ int (*insert_iter)(
+ struct cx_iterator_s *iter,
+ const void *elem,
+ int prepend
+ );
+
+ /**
+ * Member function for removing an element.
+ */
+ int (*remove)(
+ struct cx_list_s *list,
+ size_t index
+ );
+
+ /**
+ * Member function for removing all elements.
+ */
+ void (*clear)(struct cx_list_s *list);
+
+ /**
+ * Member function for swapping two elements.
+ * @see cx_list_default_swap()
+ */
+ int (*swap)(
+ struct cx_list_s *list,
+ size_t i,
+ size_t j
+ );
+
+ /**
+ * Member function for element lookup.
+ */
+ void *(*at)(
+ const struct cx_list_s *list,
+ size_t index
+ );
+
+ /**
+ * Member function for finding and optionally removing an element.
+ */
+ ssize_t (*find_remove)(
+ struct cx_list_s *list,
+ const void *elem,
+ bool remove
+ );
+
+ /**
+ * Member function for sorting the list in-place.
+ * @see cx_list_default_sort()
+ */
+ void (*sort)(struct cx_list_s *list);
+
+ /**
+ * Optional member function for comparing this list
+ * to another list of the same type.
+ * If set to \c NULL, comparison won't be optimized.
+ */
+ int (*compare)(
+ const struct cx_list_s *list,
+ const struct cx_list_s *other
+ );
+
+ /**
+ * Member function for reversing the order of the items.
+ */
+ void (*reverse)(struct cx_list_s *list);
+
+ /**
+ * Member function for returning an iterator pointing to the specified index.
+ */
+ struct cx_iterator_s (*iterator)(
+ const struct cx_list_s *list,
+ size_t index,
+ bool backward
+ );
+};
+
+/**
+ * Default implementation of an array insert.
+ *
+ * This function uses the element insert function for each element of the array.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list
+ * @param index the index where to insert the data
+ * @param data a pointer to the array of data to insert
+ * @param n the number of elements to insert
+ * @return the number of elements actually inserted
+ */
+__attribute__((__nonnull__))
+size_t cx_list_default_insert_array(
+ struct cx_list_s *list,
+ size_t index,
+ const void *data,
+ size_t n
+);
+
+/**
+ * Default implementation of a sorted insert.
+ *
+ * This function uses the array insert function to insert consecutive groups
+ * of sorted data.
+ *
+ * The source data \em must already be sorted wrt. the list's compare function.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list
+ * @param sorted_data a pointer to the array of pre-sorted data to insert
+ * @param n the number of elements to insert
+ * @return the number of elements actually inserted
+ */
+__attribute__((__nonnull__))
+size_t cx_list_default_insert_sorted(
+ struct cx_list_s *list,
+ const void *sorted_data,
+ size_t n
+);
+
+/**
+ * Default unoptimized sort implementation.
+ *
+ * This function will copy all data to an array, sort the array with standard
+ * qsort, and then copy the data back to the list memory.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list that shall be sorted
+ */
+__attribute__((__nonnull__))
+void cx_list_default_sort(struct cx_list_s *list);
+
+/**
+ * Default unoptimized swap implementation.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list in which to swap
+ * @param i index of one element
+ * @param j index of the other element
+ * @return zero on success, non-zero when indices are out of bounds or memory
+ * allocation for the temporary buffer fails
+ */
+__attribute__((__nonnull__))
+int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j);
+
+/**
+ * Common type for all list implementations.
+ */
+typedef struct cx_list_s CxList;
+
+/**
+ * Advises the list to store copies of the objects (default mode of operation).
+ *
+ * Retrieving objects from this list will yield pointers to the copies stored
+ * within this list.
+ *
+ * @param list the list
+ * @see cxListStorePointers()
+ */
+__attribute__((__nonnull__))
+void cxListStoreObjects(CxList *list);
+
+/**
+ * Advises the list to only store pointers to the objects.
+ *
+ * Retrieving objects from this list will yield the original pointers stored.
+ *
+ * @note This function forcibly sets the element size to the size of a pointer.
+ * Invoking this function on a non-empty list that already stores copies of
+ * objects is undefined.
+ *
+ * @param list the list
+ * @see cxListStoreObjects()
+ */
+__attribute__((__nonnull__))
+void cxListStorePointers(CxList *list);
+
+/**
+ * Returns true, if this list is storing pointers instead of the actual data.
+ *
+ * @param list
+ * @return true, if this list is storing pointers
+ * @see cxListStorePointers()
+ */
+__attribute__((__nonnull__))
+static inline bool cxListIsStoringPointers(const CxList *list) {
+ return list->collection.store_pointer;
+}
+
+/**
+ * Returns the number of elements currently stored in the list.
+ *
+ * @param list the list
+ * @return the number of currently stored elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListSize(const CxList *list) {
+ return list->collection.size;
+}
+
+/**
+ * Adds an item to the end of the list.
+ *
+ * @param list the list
+ * @param elem a pointer to the element to add
+ * @return zero on success, non-zero on memory allocation failure
+ * @see cxListAddArray()
+ */
+__attribute__((__nonnull__))
+static inline int cxListAdd(
+ CxList *list,
+ const void *elem
+) {
+ return list->cl->insert_element(list, list->collection.size, elem);
+}
+
+/**
+ * Adds multiple items to the end of the list.
+ *
+ * This method is more efficient than invoking cxListAdd() multiple times.
+ *
+ * If there is not enough memory to add all elements, the returned value is
+ * less than \p n.
+ *
+ * If this list is storing pointers instead of objects \p array is expected to
+ * be an array of pointers.
+ *
+ * @param list the list
+ * @param array a pointer to the elements to add
+ * @param n the number of elements to add
+ * @return the number of added elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListAddArray(
+ CxList *list,
+ const void *array,
+ size_t n
+) {
+ return list->cl->insert_array(list, list->collection.size, array, n);
+}
+
+/**
+ * Inserts an item at the specified index.
+ *
+ * If \p index equals the list \c size, this is effectively cxListAdd().
+ *
+ * @param list the list
+ * @param index the index the element shall have
+ * @param elem a pointer to the element to add
+ * @return zero on success, non-zero on memory allocation failure
+ * or when the index is out of bounds
+ * @see cxListInsertAfter()
+ * @see cxListInsertBefore()
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsert(
+ CxList *list,
+ size_t index,
+ const void *elem
+) {
+ return list->cl->insert_element(list, index, elem);
+}
+
+/**
+ * Inserts an item into a sorted list.
+ *
+ * @param list the list
+ * @param elem a pointer to the element to add
+ * @return zero on success, non-zero on memory allocation failure
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsertSorted(
+ CxList *list,
+ const void *elem
+) {
+ const void *data = list->collection.store_pointer ? &elem : elem;
+ return list->cl->insert_sorted(list, data, 1) == 0;
+}
+
+/**
+ * Inserts multiple items to the list at the specified index.
+ * If \p index equals the list size, this is effectively cxListAddArray().
+ *
+ * This method is usually more efficient than invoking cxListInsert()
+ * multiple times.
+ *
+ * If there is not enough memory to add all elements, the returned value is
+ * less than \p n.
+ *
+ * If this list is storing pointers instead of objects \p array is expected to
+ * be an array of pointers.
+ *
+ * @param list the list
+ * @param index the index where to add the elements
+ * @param array a pointer to the elements to add
+ * @param n the number of elements to add
+ * @return the number of added elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListInsertArray(
+ CxList *list,
+ size_t index,
+ const void *array,
+ size_t n
+) {
+ return list->cl->insert_array(list, index, array, n);
+}
+
+/**
+ * Inserts a sorted array into a sorted list.
+ *
+ * This method is usually more efficient than inserting each element separately,
+ * because consecutive chunks of sorted data are inserted in one pass.
+ *
+ * If there is not enough memory to add all elements, the returned value is
+ * less than \p n.
+ *
+ * If this list is storing pointers instead of objects \p array is expected to
+ * be an array of pointers.
+ *
+ * @param list the list
+ * @param array a pointer to the elements to add
+ * @param n the number of elements to add
+ * @return the number of added elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListInsertSortedArray(
+ CxList *list,
+ const void *array,
+ size_t n
+) {
+ return list->cl->insert_sorted(list, array, n);
+}
+
+/**
+ * Inserts an element after the current location of the specified iterator.
+ *
+ * The used iterator remains operational, but all other active iterators should
+ * be considered invalidated.
+ *
+ * If \p iter is not a list iterator, the behavior is undefined.
+ * If \p iter is a past-the-end iterator, the new element gets appended to the list.
+ *
+ * @param iter an iterator
+ * @param elem the element to insert
+ * @return zero on success, non-zero on memory allocation failure
+ * @see cxListInsert()
+ * @see cxListInsertBefore()
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsertAfter(
+ CxIterator *iter,
+ const void *elem
+) {
+ return ((struct cx_list_s *) iter->src_handle.m)->cl->insert_iter(iter, elem, 0);
+}
+
+/**
+ * Inserts an element before the current location of the specified iterator.
+ *
+ * The used iterator remains operational, but all other active iterators should
+ * be considered invalidated.
+ *
+ * If \p iter is not a list iterator, the behavior is undefined.
+ * If \p iter is a past-the-end iterator, the new element gets appended to the list.
+ *
+ * @param iter an iterator
+ * @param elem the element to insert
+ * @return zero on success, non-zero on memory allocation failure
+ * @see cxListInsert()
+ * @see cxListInsertAfter()
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsertBefore(
+ CxIterator *iter,
+ const void *elem
+) {
+ return ((struct cx_list_s *) iter->src_handle.m)->cl->insert_iter(iter, elem, 1);
+}
+
+/**
+ * Removes the element at the specified index.
+ *
+ * If an element destructor function is specified, it is called before
+ * removing the element.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @return zero on success, non-zero if the index is out of bounds
+ */
+__attribute__((__nonnull__))
+static inline int cxListRemove(
+ CxList *list,
+ size_t index
+) {
+ return list->cl->remove(list, index);
+}
+
+/**
+ * Removes all elements from this list.
+ *
+ * If an element destructor function is specified, it is called for each
+ * element before removing them.
+ *
+ * @param list the list
+ */
+__attribute__((__nonnull__))
+static inline void cxListClear(CxList *list) {
+ list->cl->clear(list);
+}
+
+/**
+ * Swaps two items in the list.
+ *
+ * Implementations should only allocate temporary memory for the swap, if
+ * it is necessary.
+ *
+ * @param list the list
+ * @param i the index of the first element
+ * @param j the index of the second element
+ * @return zero on success, non-zero if one of the indices is out of bounds
+ */
+__attribute__((__nonnull__))
+static inline int cxListSwap(
+ CxList *list,
+ size_t i,
+ size_t j
+) {
+ return list->cl->swap(list, i, j);
+}
+
+/**
+ * Returns a pointer to the element at the specified index.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @return a pointer to the element or \c NULL if the index is out of bounds
+ */
+__attribute__((__nonnull__))
+static inline void *cxListAt(
+ CxList *list,
+ size_t index
+) {
+ return list->cl->at(list, index);
+}
+
+/**
+ * Returns an iterator pointing to the item at the specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListIteratorAt(
+ const CxList *list,
+ size_t index
+) {
+ return list->cl->iterator(list, index, false);
+}
+
+/**
+ * Returns a backwards iterator pointing to the item at the specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListBackwardsIteratorAt(
+ const CxList *list,
+ size_t index
+) {
+ return list->cl->iterator(list, index, true);
+}
+
+/**
+ * Returns a mutating iterator pointing to the item at the specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxIterator cxListMutIteratorAt(
+ CxList *list,
+ size_t index
+);
+
+/**
+ * Returns a mutating backwards iterator pointing to the item at the
+ * specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxIterator cxListMutBackwardsIteratorAt(
+ CxList *list,
+ size_t index
+);
+
+/**
+ * Returns an iterator pointing to the first item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListIterator(const CxList *list) {
+ return list->cl->iterator(list, 0, false);
+}
+
+/**
+ * Returns a mutating iterator pointing to the first item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListMutIterator(CxList *list) {
+ return cxListMutIteratorAt(list, 0);
+}
+
+
+/**
+ * Returns a backwards iterator pointing to the last item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListBackwardsIterator(const CxList *list) {
+ return list->cl->iterator(list, list->collection.size - 1, true);
+}
+
+/**
+ * Returns a mutating backwards iterator pointing to the last item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListMutBackwardsIterator(CxList *list) {
+ return cxListMutBackwardsIteratorAt(list, list->collection.size - 1);
+}
+
+/**
+ * Returns the index of the first element that equals \p elem.
+ *
+ * Determining equality is performed by the list's comparator function.
+ *
+ * @param list the list
+ * @param elem the element to find
+ * @return the index of the element or a negative
+ * value when the element is not found
+ */
+__attribute__((__nonnull__))
+static inline ssize_t cxListFind(
+ const CxList *list,
+ const void *elem
+) {
+ return list->cl->find_remove((CxList*)list, elem, false);
+}
+
+/**
+ * Removes and returns the index of the first element that equals \p elem.
+ *
+ * Determining equality is performed by the list's comparator function.
+ *
+ * @param list the list
+ * @param elem the element to find and remove
+ * @return the index of the now removed element or a negative
+ * value when the element is not found or could not be removed
+ */
+__attribute__((__nonnull__))
+static inline ssize_t cxListFindRemove(
+ CxList *list,
+ const void *elem
+) {
+ return list->cl->find_remove(list, elem, true);
+}
+
+/**
+ * Sorts the list in-place.
+ *
+ * \remark The underlying sort algorithm is implementation defined.
+ *
+ * @param list the list
+ */
+__attribute__((__nonnull__))
+static inline void cxListSort(CxList *list) {
+ list->cl->sort(list);
+}
+
+/**
+ * Reverses the order of the items.
+ *
+ * @param list the list
+ */
+__attribute__((__nonnull__))
+static inline void cxListReverse(CxList *list) {
+ list->cl->reverse(list);
+}
+
+/**
+ * Compares a list to another list of the same type.
+ *
+ * First, the list sizes are compared.
+ * If they match, the lists are compared element-wise.
+ *
+ * @param list the list
+ * @param other the list to compare to
+ * @return zero, if both lists are equal element wise,
+ * negative if the first list is smaller, positive if the first list is larger
+ */
+__attribute__((__nonnull__))
+int cxListCompare(
+ const CxList *list,
+ const CxList *other
+);
+
+/**
+ * Deallocates the memory of the specified list structure.
+ *
+ * Also calls content a destructor function, depending on the configuration
+ * in CxList.content_destructor_type.
+ *
+ * This function itself is a destructor function for the CxList.
+ *
+ * @param list the list which shall be destroyed
+ */
+__attribute__((__nonnull__))
+void cxListDestroy(CxList *list);
+
+/**
+ * A shared instance of an empty list.
+ *
+ * Writing to that list is undefined.
+ */
+extern CxList * const cxEmptyList;
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_LIST_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file map.h
+ * \brief Interface for map implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_MAP_H
+#define UCX_MAP_H
+
+#include "common.h"
+#include "collection.h"
+#include "string.h"
+#include "hash_key.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Type for the UCX map. */
+typedef struct cx_map_s CxMap;
+
+/** Type for a map entry. */
+typedef struct cx_map_entry_s CxMapEntry;
+
+/** Type for map class definitions. */
+typedef struct cx_map_class_s cx_map_class;
+
+/** Structure for the UCX map. */
+struct cx_map_s {
+ /**
+ * Base attributes.
+ */
+ CX_COLLECTION_BASE;
+ /** The map class definition. */
+ cx_map_class *cl;
+};
+
+/**
+ * The type of iterator for a map.
+ */
+enum cx_map_iterator_type {
+ /**
+ * Iterates over key/value pairs.
+ */
+ CX_MAP_ITERATOR_PAIRS,
+ /**
+ * Iterates over keys only.
+ */
+ CX_MAP_ITERATOR_KEYS,
+ /**
+ * Iterates over values only.
+ */
+ CX_MAP_ITERATOR_VALUES
+};
+
+/**
+ * The class definition for arbitrary maps.
+ */
+struct cx_map_class_s {
+ /**
+ * Deallocates the entire memory.
+ */
+ __attribute__((__nonnull__))
+ void (*destructor)(struct cx_map_s *map);
+
+ /**
+ * Removes all elements.
+ */
+ __attribute__((__nonnull__))
+ void (*clear)(struct cx_map_s *map);
+
+ /**
+ * Add or overwrite an element.
+ */
+ __attribute__((__nonnull__))
+ int (*put)(
+ CxMap *map,
+ CxHashKey key,
+ void *value
+ );
+
+ /**
+ * Returns an element.
+ */
+ __attribute__((__nonnull__, __warn_unused_result__))
+ void *(*get)(
+ const CxMap *map,
+ CxHashKey key
+ );
+
+ /**
+ * Removes an element.
+ */
+ __attribute__((__nonnull__))
+ void *(*remove)(
+ CxMap *map,
+ CxHashKey key,
+ bool destroy
+ );
+
+ /**
+ * Creates an iterator for this map.
+ */
+ __attribute__((__nonnull__, __warn_unused_result__))
+ CxIterator (*iterator)(const CxMap *map, enum cx_map_iterator_type type);
+};
+
+/**
+ * A map entry.
+ */
+struct cx_map_entry_s {
+ /**
+ * A pointer to the key.
+ */
+ const CxHashKey *key;
+ /**
+ * A pointer to the value.
+ */
+ void *value;
+};
+
+/**
+ * A shared instance of an empty map.
+ *
+ * Writing to that map is undefined.
+ */
+extern CxMap *const cxEmptyMap;
+
+/**
+ * Advises the map to store copies of the objects (default mode of operation).
+ *
+ * Retrieving objects from this map will yield pointers to the copies stored
+ * within this list.
+ *
+ * @param map the map
+ * @see cxMapStorePointers()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapStoreObjects(CxMap *map) {
+ map->collection.store_pointer = false;
+}
+
+/**
+ * Advises the map to only store pointers to the objects.
+ *
+ * Retrieving objects from this list will yield the original pointers stored.
+ *
+ * @note This function forcibly sets the element size to the size of a pointer.
+ * Invoking this function on a non-empty map that already stores copies of
+ * objects is undefined.
+ *
+ * @param map the map
+ * @see cxMapStoreObjects()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapStorePointers(CxMap *map) {
+ map->collection.store_pointer = true;
+ map->collection.elem_size = sizeof(void *);
+}
+
+/**
+ * Returns true, if this map is storing pointers instead of the actual data.
+ *
+ * @param map
+ * @return true, if this map is storing pointers
+ * @see cxMapStorePointers()
+ */
+__attribute__((__nonnull__))
+static inline bool cxMapIsStoringPointers(const CxMap *map) {
+ return map->collection.store_pointer;
+}
+
+/**
+ * Deallocates the memory of the specified map.
+ *
+ * @param map the map to be destroyed
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDestroy(CxMap *map) {
+ map->cl->destructor(map);
+}
+
+
+/**
+ * Clears a map by removing all elements.
+ *
+ * @param map the map to be cleared
+ */
+__attribute__((__nonnull__))
+static inline void cxMapClear(CxMap *map) {
+ map->cl->clear(map);
+}
+
+/**
+ * Returns the number of elements in this map.
+ *
+ * @param map the map
+ * @return the number of stored elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxMapSize(const CxMap *map) {
+ return map->collection.size;
+}
+
+
+// TODO: set-like map operations (union, intersect, difference)
+
+/**
+ * Creates a value iterator for a map.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored values
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxMapIteratorValues(const CxMap *map) {
+ return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);
+}
+
+/**
+ * Creates a key iterator for a map.
+ *
+ * The elements of the iterator are keys of type CxHashKey.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored keys
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxMapIteratorKeys(const CxMap *map) {
+ return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);
+}
+
+/**
+ * Creates an iterator for a map.
+ *
+ * The elements of the iterator are key/value pairs of type CxMapEntry.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored entries
+ * @see cxMapIteratorKeys()
+ * @see cxMapIteratorValues()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxMapIterator(const CxMap *map) {
+ return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);
+}
+
+
+/**
+ * Creates a mutating iterator over the values of a map.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored values
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxIterator cxMapMutIteratorValues(CxMap *map);
+
+/**
+ * Creates a mutating iterator over the keys of a map.
+ *
+ * The elements of the iterator are keys of type CxHashKey.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored keys
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxIterator cxMapMutIteratorKeys(CxMap *map);
+
+/**
+ * Creates a mutating iterator for a map.
+ *
+ * The elements of the iterator are key/value pairs of type CxMapEntry.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored entries
+ * @see cxMapMutIteratorKeys()
+ * @see cxMapMutIteratorValues()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxIterator cxMapMutIterator(CxMap *map);
+
+#ifdef __cplusplus
+} // end the extern "C" block here, because we want to start overloading
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxMapPut(
+ CxMap *map,
+ CxHashKey const &key,
+ void *value
+) {
+ return map->cl->put(map, key, value);
+}
+
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxMapPut(
+ CxMap *map,
+ cxstring const &key,
+ void *value
+) {
+ return map->cl->put(map, cx_hash_key_cxstr(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxMapPut(
+ CxMap *map,
+ cxmutstr const &key,
+ void *value
+) {
+ return map->cl->put(map, cx_hash_key_cxstr(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxMapPut(
+ CxMap *map,
+ const char *key,
+ void *value
+) {
+ return map->cl->put(map, cx_hash_key_str(key), value);
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+ const CxMap *map,
+ CxHashKey const &key
+) {
+ return map->cl->get(map, key);
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+ const CxMap *map,
+ cxstring const &key
+) {
+ return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+ const CxMap *map,
+ cxmutstr const &key
+) {
+ return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+ const CxMap *map,
+ const char *key
+) {
+ return map->cl->get(map, cx_hash_key_str(key));
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+ CxMap *map,
+ CxHashKey const &key
+) {
+ (void) map->cl->remove(map, key, true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+ CxMap *map,
+ cxstring const &key
+) {
+ (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+ CxMap *map,
+ cxmutstr const &key
+) {
+ (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+ CxMap *map,
+ const char *key
+) {
+ (void) map->cl->remove(map, cx_hash_key_str(key), true);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+ CxMap *map,
+ CxHashKey const &key
+) {
+ (void) map->cl->remove(map, key, false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+ CxMap *map,
+ cxstring const &key
+) {
+ (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+ CxMap *map,
+ cxmutstr const &key
+) {
+ (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+ CxMap *map,
+ const char *key
+) {
+ (void) map->cl->remove(map, cx_hash_key_str(key), false);
+}
+
+
+
+#else // __cplusplus
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put(
+ CxMap *map,
+ CxHashKey key,
+ void *value
+) {
+ return map->cl->put(map, key, value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put_cxstr(
+ CxMap *map,
+ cxstring key,
+ void *value
+) {
+ return map->cl->put(map, cx_hash_key_cxstr(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put_mustr(
+ CxMap *map,
+ cxmutstr key,
+ void *value
+) {
+ return map->cl->put(map, cx_hash_key_cxstr(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put_str(
+ CxMap *map,
+ const char *key,
+ void *value
+) {
+ return map->cl->put(map, cx_hash_key_str(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+#define cxMapPut(map, key, value) _Generic((key), \
+ CxHashKey: cx_map_put, \
+ cxstring: cx_map_put_cxstr, \
+ cxmutstr: cx_map_put_mustr, \
+ char*: cx_map_put_str, \
+ const char*: cx_map_put_str) \
+ (map, key, value)
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get(
+ const CxMap *map,
+ CxHashKey key
+) {
+ return map->cl->get(map, key);
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get_cxstr(
+ const CxMap *map,
+ cxstring key
+) {
+ return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get_mustr(
+ const CxMap *map,
+ cxmutstr key
+) {
+ return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get_str(
+ const CxMap *map,
+ const char *key
+) {
+ return map->cl->get(map, cx_hash_key_str(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+#define cxMapGet(map, key) _Generic((key), \
+ CxHashKey: cx_map_get, \
+ cxstring: cx_map_get_cxstr, \
+ cxmutstr: cx_map_get_mustr, \
+ char*: cx_map_get_str, \
+ const char*: cx_map_get_str) \
+ (map, key)
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove(
+ CxMap *map,
+ CxHashKey key
+) {
+ (void) map->cl->remove(map, key, true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove_cxstr(
+ CxMap *map,
+ cxstring key
+) {
+ (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove_mustr(
+ CxMap *map,
+ cxmutstr key
+) {
+ (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove_str(
+ CxMap *map,
+ const char *key
+) {
+ (void) map->cl->remove(map, cx_hash_key_str(key), true);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+#define cxMapRemove(map, key) _Generic((key), \
+ CxHashKey: cx_map_remove, \
+ cxstring: cx_map_remove_cxstr, \
+ cxmutstr: cx_map_remove_mustr, \
+ char*: cx_map_remove_str, \
+ const char*: cx_map_remove_str) \
+ (map, key)
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach(
+ CxMap *map,
+ CxHashKey key
+) {
+ (void) map->cl->remove(map, key, false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach_cxstr(
+ CxMap *map,
+ cxstring key
+) {
+ (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach_mustr(
+ CxMap *map,
+ cxmutstr key
+) {
+ (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach_str(
+ CxMap *map,
+ const char *key
+) {
+ (void) map->cl->remove(map, cx_hash_key_str(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+#define cxMapDetach(map, key) _Generic((key), \
+ CxHashKey: cx_map_detach, \
+ cxstring: cx_map_detach_cxstr, \
+ cxmutstr: cx_map_detach_mustr, \
+ char*: cx_map_detach_str, \
+ const char*: cx_map_detach_str) \
+ (map, key)
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get(
+ CxMap *map,
+ CxHashKey key
+) {
+ return map->cl->remove(map, key, !map->collection.store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get_cxstr(
+ CxMap *map,
+ cxstring key
+) {
+ return map->cl->remove(map, cx_hash_key_cxstr(key), !map->collection.store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get_mustr(
+ CxMap *map,
+ cxmutstr key
+) {
+ return map->cl->remove(map, cx_hash_key_cxstr(key), !map->collection.store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get_str(
+ CxMap *map,
+ const char *key
+) {
+ return map->cl->remove(map, cx_hash_key_str(key), !map->collection.store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function can be used when the map is storing pointers,
+ * in order to retrieve the pointer from the map without invoking
+ * any destructor function. Sometimes you do not want the pointer
+ * to be returned - in that case (instead of suppressing the "unused
+ * result" warning) you can use cxMapDetach().
+ *
+ * If this map is not storing pointers, this function behaves like
+ * cxMapRemove() and returns \c NULL.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ * @see cxMapStorePointers()
+ * @see cxMapDetach()
+ */
+#define cxMapRemoveAndGet(map, key) _Generic((key), \
+ CxHashKey: cx_map_remove_and_get, \
+ cxstring: cx_map_remove_and_get_cxstr, \
+ cxmutstr: cx_map_remove_and_get_mustr, \
+ char*: cx_map_remove_and_get_str, \
+ const char*: cx_map_remove_and_get_str) \
+ (map, key)
+
+#endif // __cplusplus
+
+#endif // UCX_MAP_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file mempool.h
+ * \brief Interface for memory pool implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_MEMPOOL_H
+#define UCX_MEMPOOL_H
+
+#include "common.h"
+#include "allocator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Internal structure for pooled memory. */
+struct cx_mempool_memory_s;
+
+/**
+ * The basic structure of a memory pool.
+ * Should be the first member of an actual memory pool implementation.
+ */
+struct cx_mempool_s {
+ /** The provided allocator. */
+ const CxAllocator *allocator;
+
+ /**
+ * A destructor that shall be automatically registered for newly allocated memory.
+ * This destructor MUST NOT free the memory.
+ */
+ cx_destructor_func auto_destr;
+
+ /** Array of pooled memory. */
+ struct cx_mempool_memory_s **data;
+
+ /** Number of pooled memory items. */
+ size_t size;
+
+ /** Memory pool capacity. */
+ size_t capacity;
+};
+
+/**
+ * Common type for all memory pool implementations.
+ */
+typedef struct cx_mempool_s CxMempool;
+
+/**
+ * Creates an array-based memory pool with a shared destructor function.
+ *
+ * This destructor MUST NOT free the memory.
+ *
+ * @param capacity the initial capacity of the pool
+ * @param destr the destructor function to use for allocated memory
+ * @return the created memory pool or \c NULL if allocation failed
+ */
+__attribute__((__warn_unused_result__))
+CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr);
+
+/**
+ * Creates a basic array-based memory pool.
+ *
+ * @param capacity the initial capacity of the pool
+ * @return the created memory pool or \c NULL if allocation failed
+ */
+__attribute__((__warn_unused_result__))
+static inline CxMempool *cxBasicMempoolCreate(size_t capacity) {
+ return cxMempoolCreate(capacity, NULL);
+}
+
+/**
+ * Destroys a memory pool and frees the managed memory.
+ *
+ * @param pool the memory pool to destroy
+ */
+__attribute__((__nonnull__))
+void cxMempoolDestroy(CxMempool *pool);
+
+/**
+ * Sets the destructor function for a specific allocated memory object.
+ *
+ * If the memory is not managed by a UCX memory pool, the behavior is undefined.
+ * The destructor MUST NOT free the memory.
+ *
+ * @param memory the object allocated in the pool
+ * @param fnc the destructor function
+ */
+__attribute__((__nonnull__))
+void cxMempoolSetDestructor(
+ void *memory,
+ cx_destructor_func fnc
+);
+
+/**
+ * Registers foreign memory with this pool.
+ *
+ * The destructor, in contrast to memory allocated by the pool, MUST free the memory.
+ *
+ * A small portion of memory will be allocated to register the information in the pool.
+ * If that allocation fails, this function will return non-zero.
+ *
+ * @param pool the pool
+ * @param memory the object allocated in the pool
+ * @param destr the destructor function
+ * @return zero on success, non-zero on failure
+ */
+__attribute__((__nonnull__))
+int cxMempoolRegister(
+ CxMempool *pool,
+ void *memory,
+ cx_destructor_func destr
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_MEMPOOL_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file printf.h
+ * \brief Wrapper for write functions with a printf-like interface.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_PRINTF_H
+#define UCX_PRINTF_H
+
+#include "common.h"
+#include "string.h"
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * The maximum string length that fits into stack memory.
+ */
+extern unsigned const cx_printf_sbo_size;
+
+/**
+ * A \c fprintf like function which writes the output to a stream by
+ * using a write_func.
+ *
+ * @param stream the stream the data is written to
+ * @param wfc the write function
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return the total number of bytes written
+ */
+__attribute__((__nonnull__(1, 2, 3), __format__(printf, 3, 4)))
+int cx_fprintf(
+ void *stream,
+ cx_write_func wfc,
+ const char *fmt,
+ ...
+);
+
+/**
+ * A \c vfprintf like function which writes the output to a stream by
+ * using a write_func.
+ *
+ * @param stream the stream the data is written to
+ * @param wfc the write function
+ * @param fmt format string
+ * @param ap argument list
+ * @return the total number of bytes written
+ * @see cx_fprintf()
+ */
+__attribute__((__nonnull__))
+int cx_vfprintf(
+ void *stream,
+ cx_write_func wfc,
+ const char *fmt,
+ va_list ap
+);
+
+/**
+ * A \c asprintf like function which allocates space for a string
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the CxAllocator used for allocating the string
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return the formatted string
+ * @see cx_strfree_a()
+ */
+__attribute__((__nonnull__(1, 2), __format__(printf, 2, 3)))
+cxmutstr cx_asprintf_a(
+ const CxAllocator *allocator,
+ const char *fmt,
+ ...
+);
+
+/**
+ * A \c asprintf like function which allocates space for a string
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return the formatted string
+ * @see cx_strfree()
+ */
+#define cx_asprintf(fmt, ...) \
+ cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__)
+
+/**
+* A \c vasprintf like function which allocates space for a string
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the CxAllocator used for allocating the string
+ * @param fmt format string
+ * @param ap argument list
+ * @return the formatted string
+ * @see cx_asprintf_a()
+ */
+__attribute__((__nonnull__))
+cxmutstr cx_vasprintf_a(
+ const CxAllocator *allocator,
+ const char *fmt,
+ va_list ap
+);
+
+/**
+* A \c vasprintf like function which allocates space for a string
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param fmt format string
+ * @param ap argument list
+ * @return the formatted string
+ * @see cx_asprintf()
+ */
+#define cx_vasprintf(fmt, ap) cx_vasprintf_a(cxDefaultAllocator, fmt, ap)
+
+/**
+ * A \c printf like function which writes the output to a CxBuffer.
+ *
+ * @param buffer a pointer to the buffer the data is written to
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the total number of bytes written
+ * @see ucx_fprintf()
+ */
+#define cx_bprintf(buffer, fmt, ...) cx_fprintf((CxBuffer*)buffer, \
+ (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__)
+
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+#define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__)
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \attention The original buffer MUST have been allocated with the same allocator!
+ *
+ * @param alloc the allocator to use
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__(1, 2, 3, 4), __format__(printf, 4, 5)))
+int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... );
+
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+#define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap)
+
+/**
+ * An \c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \attention The original buffer MUST have been allocated with the same allocator!
+ *
+ * @param alloc the allocator to use
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__))
+int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap);
+
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+#define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__)
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param alloc the allocator to use
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__(1, 2, 4, 5), __format__(printf, 5, 6)))
+int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... );
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+#define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap)
+
+/**
+ * An \c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in \p len when necessary.
+ *
+ * The location of the resulting string will \em always be stored to \p str. When the buffer
+ * was sufficiently large, \p buf itself will be stored to the location of \p str.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * \remark When a new string needed to be allocated, the contents of \p buf will be
+ * poisoned after the call, because this function tries to produce the string in \p buf, first.
+ *
+ * @param alloc the allocator to use
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer to the location
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string
+ */
+__attribute__((__nonnull__))
+int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap);
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_PRINTF_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file string.h
+ * \brief Strings that know their length.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_STRING_H
+#define UCX_STRING_H
+
+#include "common.h"
+#include "allocator.h"
+
+/**
+ * The maximum length of the "needle" in cx_strstr() that can use SBO.
+ */
+extern unsigned const cx_strstr_sbo_size;
+
+/**
+ * The UCX string structure.
+ */
+struct cx_mutstr_s {
+ /**
+ * A pointer to the string.
+ * \note The string is not necessarily \c NULL terminated.
+ * Always use the length.
+ */
+ char *ptr;
+ /** The length of the string */
+ size_t length;
+};
+
+/**
+ * A mutable string.
+ */
+typedef struct cx_mutstr_s cxmutstr;
+
+/**
+ * The UCX string structure for immutable (constant) strings.
+ */
+struct cx_string_s {
+ /**
+ * A pointer to the immutable string.
+ * \note The string is not necessarily \c NULL terminated.
+ * Always use the length.
+ */
+ const char *ptr;
+ /** The length of the string */
+ size_t length;
+};
+
+/**
+ * An immutable string.
+ */
+typedef struct cx_string_s cxstring;
+
+/**
+ * Context for string tokenizing.
+ */
+struct cx_strtok_ctx_s {
+ /**
+ * The string to tokenize.
+ */
+ cxstring str;
+ /**
+ * The primary delimiter.
+ */
+ cxstring delim;
+ /**
+ * Optional array of more delimiters.
+ */
+ const cxstring *delim_more;
+ /**
+ * Length of the array containing more delimiters.
+ */
+ size_t delim_more_count;
+ /**
+ * Position of the currently active token in the source string.
+ */
+ size_t pos;
+ /**
+ * Position of next delimiter in the source string.
+ *
+ * If the tokenizer has not yet returned a token, the content of this field
+ * is undefined. If the tokenizer reached the end of the string, this field
+ * contains the length of the source string.
+ */
+ size_t delim_pos;
+ /**
+ * The position of the next token in the source string.
+ */
+ size_t next_pos;
+ /**
+ * The number of already found tokens.
+ */
+ size_t found;
+ /**
+ * The maximum number of tokens that shall be returned.
+ */
+ size_t limit;
+};
+
+/**
+ * A string tokenizing context.
+ */
+typedef struct cx_strtok_ctx_s CxStrtokCtx;
+
+#ifdef __cplusplus
+extern "C" {
+
+/**
+ * A literal initializer for an UCX string structure.
+ *
+ * @param literal the string literal
+ */
+#define CX_STR(literal) cxstring{literal, sizeof(literal) - 1}
+
+#else // __cplusplus
+
+/**
+ * A literal initializer for an UCX string structure.
+ *
+ * The argument MUST be a string (const char*) \em literal.
+ *
+ * @param literal the string literal
+ */
+#define CX_STR(literal) (cxstring){literal, sizeof(literal) - 1}
+
+#endif
+
+
+/**
+ * Wraps a mutable string that must be zero-terminated.
+ *
+ * The length is implicitly inferred by using a call to \c strlen().
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a constant string, use cx_str().
+ *
+ * @param cstring the string to wrap, must be zero-terminated
+ * @return the wrapped string
+ *
+ * @see cx_mutstrn()
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_mutstr(char *cstring);
+
+/**
+ * Wraps a string that does not need to be zero-terminated.
+ *
+ * The argument may be \c NULL if the length is zero.
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a constant string, use cx_strn().
+ *
+ * @param cstring the string to wrap (or \c NULL, only if the length is zero)
+ * @param length the length of the string
+ * @return the wrapped string
+ *
+ * @see cx_mutstr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_mutstrn(
+ char *cstring,
+ size_t length
+);
+
+/**
+ * Wraps a string that must be zero-terminated.
+ *
+ * The length is implicitly inferred by using a call to \c strlen().
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a non-constant string, use cx_mutstr().
+ *
+ * @param cstring the string to wrap, must be zero-terminated
+ * @return the wrapped string
+ *
+ * @see cx_strn()
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxstring cx_str(const char *cstring);
+
+
+/**
+ * Wraps a string that does not need to be zero-terminated.
+ *
+ * The argument may be \c NULL if the length is zero.
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a non-constant string, use cx_mutstrn().
+ *
+ * @param cstring the string to wrap (or \c NULL, only if the length is zero)
+ * @param length the length of the string
+ * @return the wrapped string
+ *
+ * @see cx_str()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strn(
+ const char *cstring,
+ size_t length
+);
+
+/**
+* Casts a mutable string to an immutable string.
+*
+* \note This is not seriously a cast. Instead you get a copy
+* of the struct with the desired pointer type. Both structs still
+* point to the same location, though!
+*
+* @param str the mutable string to cast
+* @return an immutable copy of the string pointer
+*/
+__attribute__((__warn_unused_result__))
+cxstring cx_strcast(cxmutstr str);
+
+/**
+ * Passes the pointer in this string to \c free().
+ *
+ * The pointer in the struct is set to \c NULL and the length is set to zero.
+ *
+ * \note There is no implementation for cxstring, because it is unlikely that
+ * you ever have a <code>const char*</code> you are really supposed to free.
+ * If you encounter such situation, you should double-check your code.
+ *
+ * @param str the string to free
+ */
+__attribute__((__nonnull__))
+void cx_strfree(cxmutstr *str);
+
+/**
+ * Passes the pointer in this string to the allocators free function.
+ *
+ * The pointer in the struct is set to \c NULL and the length is set to zero.
+ *
+ * \note There is no implementation for cxstring, because it is unlikely that
+ * you ever have a <code>const char*</code> you are really supposed to free.
+ * If you encounter such situation, you should double-check your code.
+ *
+ * @param alloc the allocator
+ * @param str the string to free
+ */
+__attribute__((__nonnull__))
+void cx_strfree_a(
+ const CxAllocator *alloc,
+ cxmutstr *str
+);
+
+/**
+ * Returns the accumulated length of all specified strings.
+ *
+ * \attention if the count argument is larger than the number of the
+ * specified strings, the behavior is undefined.
+ *
+ * @param count the total number of specified strings
+ * @param ... all strings
+ * @return the accumulated length of all strings
+ */
+__attribute__((__warn_unused_result__))
+size_t cx_strlen(
+ size_t count,
+ ...
+);
+
+/**
+ * Concatenates strings.
+ *
+ * The resulting string will be allocated by the specified allocator.
+ * So developers \em must pass the return value to cx_strfree_a() eventually.
+ *
+ * If \p str already contains a string, the memory will be reallocated and
+ * the other strings are appended. Otherwise, new memory is allocated.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * It is also guaranteed that the returned string is zero-terminated.
+ *
+ * @param alloc the allocator to use
+ * @param str the string the other strings shall be concatenated to
+ * @param count the number of the other following strings to concatenate
+ * @param ... all other strings
+ * @return the concatenated string
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_strcat_ma(
+ const CxAllocator *alloc,
+ cxmutstr str,
+ size_t count,
+ ...
+);
+
+/**
+ * Concatenates strings and returns a new string.
+ *
+ * The resulting string will be allocated by the specified allocator.
+ * So developers \em must pass the return value to cx_strfree_a() eventually.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * It is also guaranteed that the returned string is zero-terminated.
+ *
+ * @param alloc the allocator to use
+ * @param count the number of the other following strings to concatenate
+ * @param ... all other strings
+ * @return the concatenated string
+ */
+#define cx_strcat_a(alloc, count, ...) \
+cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
+
+/**
+ * Concatenates strings and returns a new string.
+ *
+ * The resulting string will be allocated by standard \c malloc().
+ * So developers \em must pass the return value to cx_strfree() eventually.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * It is also guaranteed that the returned string is zero-terminated.
+ *
+ * @param count the number of the other following strings to concatenate
+ * @param ... all other strings
+ * @return the concatenated string
+ */
+#define cx_strcat(count, ...) \
+cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
+
+/**
+ * Concatenates strings.
+ *
+ * The resulting string will be allocated by standard \c malloc().
+ * So developers \em must pass the return value to cx_strfree() eventually.
+ *
+ * If \p str already contains a string, the memory will be reallocated and
+ * the other strings are appended. Otherwise, new memory is allocated.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * It is also guaranteed that the returned string is zero-terminated.
+ *
+ * @param str the string the other strings shall be concatenated to
+ * @param count the number of the other following strings to concatenate
+ * @param ... all other strings
+ * @return the concatenated string
+ */
+#define cx_strcat_m(str, count, ...) \
+cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__)
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * Use cx_strdup() to get a copy.
+ *
+ * @param string input string
+ * @param start start location of the substring
+ * @return a substring of \p string starting at \p start
+ *
+ * @see cx_strsubsl()
+ * @see cx_strsubs_m()
+ * @see cx_strsubsl_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strsubs(
+ cxstring string,
+ size_t start
+);
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * The returned string will be limited to \p length bytes or the number
+ * of bytes available in \p string, whichever is smaller.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * Use cx_strdup() to get a copy.
+ *
+ * @param string input string
+ * @param start start location of the substring
+ * @param length the maximum length of the returned string
+ * @return a substring of \p string starting at \p start
+ *
+ * @see cx_strsubs()
+ * @see cx_strsubs_m()
+ * @see cx_strsubsl_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strsubsl(
+ cxstring string,
+ size_t start,
+ size_t length
+);
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * Use cx_strdup() to get a copy.
+ *
+ * @param string input string
+ * @param start start location of the substring
+ * @return a substring of \p string starting at \p start
+ *
+ * @see cx_strsubsl_m()
+ * @see cx_strsubs()
+ * @see cx_strsubsl()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strsubs_m(
+ cxmutstr string,
+ size_t start
+);
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * The returned string will be limited to \p length bytes or the number
+ * of bytes available in \p string, whichever is smaller.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * Use cx_strdup() to get a copy.
+ *
+ * @param string input string
+ * @param start start location of the substring
+ * @param length the maximum length of the returned string
+ * @return a substring of \p string starting at \p start
+ *
+ * @see cx_strsubs_m()
+ * @see cx_strsubs()
+ * @see cx_strsubsl()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strsubsl_m(
+ cxmutstr string,
+ size_t start,
+ size_t length
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @param string the string where to locate the character
+ * @param chr the character to locate
+ * @return a substring starting at the first location of \p chr
+ *
+ * @see cx_strchr_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strchr(
+ cxstring string,
+ int chr
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @param string the string where to locate the character
+ * @param chr the character to locate
+ * @return a substring starting at the first location of \p chr
+ *
+ * @see cx_strchr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strchr_m(
+ cxmutstr string,
+ int chr
+);
+
+/**
+ * Returns a substring starting at the location of the last occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @param string the string where to locate the character
+ * @param chr the character to locate
+ * @return a substring starting at the last location of \p chr
+ *
+ * @see cx_strrchr_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strrchr(
+ cxstring string,
+ int chr
+);
+
+/**
+ * Returns a substring starting at the location of the last occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @param string the string where to locate the character
+ * @param chr the character to locate
+ * @return a substring starting at the last location of \p chr
+ *
+ * @see cx_strrchr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strrchr_m(
+ cxmutstr string,
+ int chr
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ *
+ * If \p haystack does not contain \p needle, an empty string is returned.
+ *
+ * If \p needle is an empty string, the complete \p haystack is
+ * returned.
+ *
+ * @param haystack the string to be scanned
+ * @param needle string containing the sequence of characters to match
+ * @return a substring starting at the first occurrence of
+ * \p needle, or an empty string, if the sequence is not
+ * contained
+ * @see cx_strstr_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strstr(
+ cxstring haystack,
+ cxstring needle
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ *
+ * If \p haystack does not contain \p needle, an empty string is returned.
+ *
+ * If \p needle is an empty string, the complete \p haystack is
+ * returned.
+ *
+ * @param haystack the string to be scanned
+ * @param needle string containing the sequence of characters to match
+ * @return a substring starting at the first occurrence of
+ * \p needle, or an empty string, if the sequence is not
+ * contained
+ * @see cx_strstr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strstr_m(
+ cxmutstr haystack,
+ cxstring needle
+);
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * @param string the string to split
+ * @param delim the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pre-allocated array of at least \p limit length
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit(
+ cxstring string,
+ cxstring delim,
+ size_t limit,
+ cxstring *output
+);
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * The array pointed to by \p output will be allocated by \p allocator.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * \attention If allocation fails, the \c NULL pointer will be written to
+ * \p output and the number returned will be zero.
+ *
+ * @param allocator the allocator to use for allocating the resulting array
+ * @param string the string to split
+ * @param delim the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pointer where the address of the allocated array shall be
+ * written to
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit_a(
+ const CxAllocator *allocator,
+ cxstring string,
+ cxstring delim,
+ size_t limit,
+ cxstring **output
+);
+
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * @param string the string to split
+ * @param delim the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pre-allocated array of at least \p limit length
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit_m(
+ cxmutstr string,
+ cxstring delim,
+ size_t limit,
+ cxmutstr *output
+);
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * The array pointed to by \p output will be allocated by \p allocator.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * \attention If allocation fails, the \c NULL pointer will be written to
+ * \p output and the number returned will be zero.
+ *
+ * @param allocator the allocator to use for allocating the resulting array
+ * @param string the string to split
+ * @param delim the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pointer where the address of the allocated array shall be
+ * written to
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit_ma(
+ const CxAllocator *allocator,
+ cxmutstr string,
+ cxstring delim,
+ size_t limit,
+ cxmutstr **output
+);
+
+/**
+ * Compares two strings.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
+ * than \p s2, zero if both strings equal
+ */
+__attribute__((__warn_unused_result__))
+int cx_strcmp(
+ cxstring s1,
+ cxstring s2
+);
+
+/**
+ * Compares two strings ignoring case.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
+ * than \p s2, zero if both strings equal ignoring case
+ */
+__attribute__((__warn_unused_result__))
+int cx_strcasecmp(
+ cxstring s1,
+ cxstring s2
+);
+
+/**
+ * Compares two strings.
+ *
+ * This function has a compatible signature for the use as a cx_compare_func.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
+ * than \p s2, zero if both strings equal
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+int cx_strcmp_p(
+ const void *s1,
+ const void *s2
+);
+
+/**
+ * Compares two strings ignoring case.
+ *
+ * This function has a compatible signature for the use as a cx_compare_func.
+ *
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
+ * than \p s2, zero if both strings equal ignoring case
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+int cx_strcasecmp_p(
+ const void *s1,
+ const void *s2
+);
+
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by \p allocator.
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the allocator to use
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup()
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_strdup_a(
+ const CxAllocator *allocator,
+ cxstring string
+);
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by standard
+ * \c malloc(). So developers \em must pass the return value to cx_strfree().
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup_a()
+ */
+#define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string)
+
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by \p allocator.
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the allocator to use
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup_m()
+ */
+#define cx_strdup_ma(allocator, string) cx_strdup_a(allocator, cx_strcast(string))
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by standard
+ * \c malloc(). So developers \em must pass the return value to cx_strfree().
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup_ma()
+ */
+#define cx_strdup_m(string) cx_strdup_a(cxDefaultAllocator, cx_strcast(string))
+
+/**
+ * Omits leading and trailing spaces.
+ *
+ * \note the returned string references the same memory, thus you
+ * must \em not free the returned memory.
+ *
+ * @param string the string that shall be trimmed
+ * @return the trimmed string
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strtrim(cxstring string);
+
+/**
+ * Omits leading and trailing spaces.
+ *
+ * \note the returned string references the same memory, thus you
+ * must \em not free the returned memory.
+ *
+ * @param string the string that shall be trimmed
+ * @return the trimmed string
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strtrim_m(cxmutstr string);
+
+/**
+ * Checks, if a string has a specific prefix.
+ *
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return \c true, if and only if the string has the specified prefix,
+ * \c false otherwise
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strprefix(
+ cxstring string,
+ cxstring prefix
+);
+
+/**
+ * Checks, if a string has a specific suffix.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return \c true, if and only if the string has the specified suffix,
+ * \c false otherwise
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strsuffix(
+ cxstring string,
+ cxstring suffix
+);
+
+/**
+ * Checks, if a string has a specific prefix, ignoring the case.
+ *
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return \c true, if and only if the string has the specified prefix,
+ * \c false otherwise
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strcaseprefix(
+ cxstring string,
+ cxstring prefix
+);
+
+/**
+ * Checks, if a string has a specific suffix, ignoring the case.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return \c true, if and only if the string has the specified suffix,
+ * \c false otherwise
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strcasesuffix(
+ cxstring string,
+ cxstring suffix
+);
+
+/**
+ * Converts the string to lower case.
+ *
+ * The change is made in-place. If you want a copy, use cx_strdup(), first.
+ *
+ * @param string the string to modify
+ * @see cx_strdup()
+ */
+void cx_strlower(cxmutstr string);
+
+/**
+ * Converts the string to upper case.
+ *
+ * The change is made in-place. If you want a copy, use cx_strdup(), first.
+ *
+ * @param string the string to modify
+ * @see cx_strdup()
+ */
+void cx_strupper(cxmutstr string);
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most \p replmax occurrences.
+ *
+ * The returned string will be allocated by \p allocator and is guaranteed
+ * to be zero-terminated.
+ *
+ * If allocation fails, or the input string is empty,
+ * the returned string will be empty.
+ *
+ * @param allocator the allocator to use
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_strreplacen_a(
+ const CxAllocator *allocator,
+ cxstring str,
+ cxstring pattern,
+ cxstring replacement,
+ size_t replmax
+);
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most \p replmax occurrences.
+ *
+ * The returned string will be allocated by \c malloc() and is guaranteed
+ * to be zero-terminated.
+ *
+ * If allocation fails, or the input string is empty,
+ * the returned string will be empty.
+ *
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+#define cx_strreplacen(str, pattern, replacement, replmax) \
+cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, replmax)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ *
+ * The returned string will be allocated by \p allocator and is guaranteed
+ * to be zero-terminated.
+ *
+ * If allocation fails, or the input string is empty,
+ * the returned string will be empty.
+ *
+ * @param allocator the allocator to use
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @return the resulting string after applying the replacements
+ */
+#define cx_strreplace_a(allocator, str, pattern, replacement) \
+cx_strreplacen_a(allocator, str, pattern, replacement, SIZE_MAX)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most \p replmax occurrences.
+ *
+ * The returned string will be allocated by \c malloc() and is guaranteed
+ * to be zero-terminated.
+ *
+ * If allocation fails, or the input string is empty,
+ * the returned string will be empty.
+ *
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @return the resulting string after applying the replacements
+ */
+#define cx_strreplace(str, pattern, replacement) \
+cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, SIZE_MAX)
+
+/**
+ * Creates a string tokenization context.
+ *
+ * @param str the string to tokenize
+ * @param delim the delimiter (must not be empty)
+ * @param limit the maximum number of tokens that shall be returned
+ * @return a new string tokenization context
+ */
+__attribute__((__warn_unused_result__))
+CxStrtokCtx cx_strtok(
+ cxstring str,
+ cxstring delim,
+ size_t limit
+);
+
+/**
+* Creates a string tokenization context for a mutable string.
+*
+* @param str the string to tokenize
+* @param delim the delimiter (must not be empty)
+* @param limit the maximum number of tokens that shall be returned
+* @return a new string tokenization context
+*/
+__attribute__((__warn_unused_result__))
+CxStrtokCtx cx_strtok_m(
+ cxmutstr str,
+ cxstring delim,
+ size_t limit
+);
+
+/**
+ * Returns the next token.
+ *
+ * The token will point to the source string.
+ *
+ * @param ctx the tokenization context
+ * @param token a pointer to memory where the next token shall be stored
+ * @return true if successful, false if the limit or the end of the string
+ * has been reached
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+bool cx_strtok_next(
+ CxStrtokCtx *ctx,
+ cxstring *token
+);
+
+/**
+ * Returns the next token of a mutable string.
+ *
+ * The token will point to the source string.
+ * If the context was not initialized over a mutable string, modifying
+ * the data of the returned token is undefined behavior.
+ *
+ * @param ctx the tokenization context
+ * @param token a pointer to memory where the next token shall be stored
+ * @return true if successful, false if the limit or the end of the string
+ * has been reached
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+bool cx_strtok_next_m(
+ CxStrtokCtx *ctx,
+ cxmutstr *token
+);
+
+/**
+ * Defines an array of more delimiters for the specified tokenization context.
+ *
+ * @param ctx the tokenization context
+ * @param delim array of more delimiters
+ * @param count number of elements in the array
+ */
+__attribute__((__nonnull__))
+void cx_strtok_delim(
+ CxStrtokCtx *ctx,
+ const cxstring *delim,
+ size_t count
+);
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_STRING_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file: test.h
+ *
+ * UCX Test Framework.
+ *
+ * Usage of this test framework:
+ *
+ * **** IN HEADER FILE: ****
+ *
+ * <pre>
+ * CX_TEST(function_name);
+ * CX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
+ * </pre>
+ *
+ * **** IN SOURCE FILE: ****
+ * <pre>
+ * CX_TEST_SUBROUTINE(subroutine_name, paramlist) {
+ * // tests with CX_TEST_ASSERT()
+ * }
+ *
+ * CX_TEST(function_name) {
+ * // memory allocation and other stuff here
+ * #CX_TEST_DO {
+ * // tests with CX_TEST_ASSERT() and/or
+ * // calls with CX_TEST_CALL_SUBROUTINE() here
+ * }
+ * // cleanup of memory here
+ * }
+ * </pre>
+ *
+ * @attention Do not call own functions within a test, that use
+ * CX_TEST_ASSERT() macros and are not defined by using CX_TEST_SUBROUTINE().
+ *
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ *
+ */
+
+#ifndef UCX_TEST_H
+#define UCX_TEST_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <setjmp.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __FUNCTION__
+/**
+ * Alias for the <code>__func__</code> preprocessor macro.
+ * Some compilers use <code>__func__</code> and others use __FUNCTION__.
+ * We use __FUNCTION__ so we define it for those compilers which use
+ * <code>__func__</code>.
+ */
+#define __FUNCTION__ __func__
+#endif
+
+//
+#if !defined(__clang__) && __GNUC__ > 3
+#pragma GCC diagnostic ignored "-Wclobbered"
+#endif
+
+#ifndef UCX_COMMON_H
+/**
+ * Function pointer compatible with fwrite-like functions.
+ */
+typedef size_t (*cx_write_func)(
+ const void *,
+ size_t,
+ size_t,
+ void *
+);
+#endif // UCX_COMMON_H
+
+/** Type for the CxTestSuite. */
+typedef struct CxTestSuite CxTestSuite;
+
+/** Pointer to a test function. */
+typedef void(*CxTest)(CxTestSuite *, void *, cx_write_func);
+
+/** Type for the internal list of test cases. */
+typedef struct CxTestSet CxTestSet;
+
+/** Structure for the internal list of test cases. */
+struct CxTestSet {
+
+ /** Test case. */
+ CxTest test;
+
+ /** Pointer to the next list element. */
+ CxTestSet *next;
+};
+
+/**
+ * A test suite containing multiple test cases.
+ */
+struct CxTestSuite {
+
+ /** The number of successful tests after the suite has been run. */
+ unsigned int success;
+
+ /** The number of failed tests after the suite has been run. */
+ unsigned int failure;
+
+ /** The optional name of this test suite. */
+ const char *name;
+
+ /**
+ * Internal list of test cases.
+ * Use cx_test_register() to add tests to this list.
+ */
+ CxTestSet *tests;
+};
+
+/**
+ * Creates a new test suite.
+ * @param name optional name of the suite
+ * @return a new test suite
+ */
+static inline CxTestSuite* cx_test_suite_new(const char *name) {
+ CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite));
+ if (suite != NULL) {
+ suite->name = name;
+ suite->success = 0;
+ suite->failure = 0;
+ suite->tests = NULL;
+ }
+
+ return suite;
+}
+
+/**
+ * Destroys a test suite.
+ * @param suite the test suite to destroy
+ */
+static inline void cx_test_suite_free(CxTestSuite* suite) {
+ CxTestSet *l = suite->tests;
+ while (l != NULL) {
+ CxTestSet *e = l;
+ l = l->next;
+ free(e);
+ }
+ free(suite);
+}
+
+/**
+ * Registers a test function with the specified test suite.
+ *
+ * @param suite the suite, the test function shall be added to
+ * @param test the test function to register
+ * @return zero on success or non-zero on failure
+ */
+static inline int cx_test_register(CxTestSuite* suite, CxTest test) {
+ CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet));
+ if (t) {
+ t->test = test;
+ t->next = NULL;
+ if (suite->tests == NULL) {
+ suite->tests = t;
+ } else {
+ CxTestSet *last = suite->tests;
+ while (last->next) {
+ last = last->next;
+ }
+ last->next = t;
+ }
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/**
+ * Runs a test suite and writes the test log to the specified stream.
+ * @param suite the test suite to run
+ * @param out_target the target buffer or file to write the output to
+ * @param out_writer the write function writing to \p out_target
+ */
+static inline void cx_test_run(CxTestSuite *suite,
+ void *out_target, cx_write_func out_writer) {
+ if (suite->name == NULL) {
+ out_writer("*** Test Suite ***\n", 1, 19, out_target);
+ } else {
+ out_writer("*** Test Suite : ", 1, 17, out_target);
+ out_writer(suite->name, 1, strlen(suite->name), out_target);
+ out_writer(" ***\n", 1, 5, out_target);
+ }
+ suite->success = 0;
+ suite->failure = 0;
+ for (CxTestSet *elem = suite->tests; elem; elem = elem->next) {
+ elem->test(suite, out_target, out_writer);
+ }
+ out_writer("\nAll test completed.\n", 1, 21, out_target);
+ char total[80];
+ int len = snprintf(
+ total, 80,
+ " Total: %u\n Success: %u\n Failure: %u\n\n",
+ suite->success + suite->failure, suite->success, suite->failure
+ );
+ out_writer(total, 1, len, out_target);
+}
+
+/**
+ * Runs a test suite and writes the test log to the specified FILE stream.
+ * @param suite the test suite to run
+ * @param file the target file to write the output to
+ */
+#define cx_test_run_f(suite, file) cx_test_run(suite, (void*)file, (cx_write_func)fwrite)
+
+/**
+ * Runs a test suite and writes the test log to stdout.
+ * @param suite the test suite to run
+ */
+#define cx_test_run_stdout(suite) cx_test_run_f(suite, stdout)
+
+/**
+ * Macro for a #CxTest function header.
+ *
+ * Use this macro to declare and/or define a #CxTest function.
+ *
+ * @param name the name of the test function
+ */
+#define CX_TEST(name) void name(CxTestSuite* _suite_,void *_output_, cx_write_func _writefnc_)
+
+/**
+ * Defines the scope of a test.
+ * @attention Any CX_TEST_ASSERT() calls must be performed in scope of
+ * #CX_TEST_DO.
+ */
+#define CX_TEST_DO _writefnc_("Running ", 1, 8, _output_);\
+ _writefnc_(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\
+ _writefnc_("... ", 1, 4, _output_);\
+ jmp_buf _env_;\
+ for (unsigned int _cx_test_loop_ = 0 ;\
+ _cx_test_loop_ == 0 && !setjmp(_env_);\
+ _writefnc_("success.\n", 1, 9, _output_),\
+ _suite_->success++, _cx_test_loop_++)
+
+/**
+ * Checks a test assertion.
+ * If the assertion is correct, the test carries on. If the assertion is not
+ * correct, the specified message (terminated by a dot and a line break) is
+ * written to the test suites output stream.
+ * @param condition the condition to check
+ * @param message the message that shall be printed out on failure
+ */
+#define CX_TEST_ASSERTM(condition,message) if (!(condition)) { \
+ const char *_assert_msg_ = message; \
+ _writefnc_(_assert_msg_, 1, strlen(_assert_msg_), _output_); \
+ _writefnc_(".\n", 1, 2, _output_); \
+ _suite_->failure++; \
+ longjmp(_env_, 1);\
+ } (void) 0
+
+/**
+ * Checks a test assertion.
+ * If the assertion is correct, the test carries on. If the assertion is not
+ * correct, the specified message (terminated by a dot and a line break) is
+ * written to the test suites output stream.
+ * @param condition the condition to check
+ */
+#define CX_TEST_ASSERT(condition) CX_TEST_ASSERTM(condition, #condition " failed")
+
+/**
+ * Macro for a test subroutine function header.
+ *
+ * Use this to declare and/or define a subroutine that can be called by using
+ * CX_TEST_CALL_SUBROUTINE().
+ *
+ * @param name the name of the subroutine
+ * @param ... the parameter list
+ *
+ * @see CX_TEST_CALL_SUBROUTINE()
+ */
+#define CX_TEST_SUBROUTINE(name,...) void name(CxTestSuite* _suite_,\
+ void *_output_, cx_write_func _writefnc_, jmp_buf _env_, __VA_ARGS__)
+
+/**
+ * Macro for calling a test subroutine.
+ *
+ * Subroutines declared with CX_TEST_SUBROUTINE() can be called by using this
+ * macro.
+ *
+ * @remark You may <b>only</b> call subroutines within a #CX_TEST_DO block.
+ *
+ * @param name the name of the subroutine
+ * @param ... the argument list
+ *
+ * @see CX_TEST_SUBROUTINE()
+ */
+#define CX_TEST_CALL_SUBROUTINE(name,...) \
+ name(_suite_,_output_,_writefnc_,_env_,__VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_TEST_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file tree.h
+ * \brief Interface for tree implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_TREE_H
+#define UCX_TREE_H
+
+#include "common.h"
+
+#include "collection.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * A depth-first tree iterator.
+ *
+ * This iterator is not position-aware in a strict sense, as it does not assume
+ * a particular order of elements in the tree. However, the iterator keeps track
+ * of the number of nodes it has passed in a counter variable.
+ * Each node, regardless of the number of passes, is counted only once.
+ *
+ * @note Objects that are pointed to by an iterator are mutable through that
+ * iterator. However, if the
+ * underlying data structure is mutated by other means than this iterator (e.g.
+ * elements added or removed), the iterator becomes invalid (regardless of what
+ * cxIteratorValid() returns).
+ *
+ * @see CxIterator
+ */
+typedef struct cx_tree_iterator_s {
+ /**
+ * Base members.
+ */
+ CX_ITERATOR_BASE;
+ /**
+ * Indicates whether the subtree below the current node shall be skipped.
+ */
+ bool skip;
+ /**
+ * Set to true, when the iterator shall visit a node again
+ * when all it's children have been processed.
+ */
+ bool visit_on_exit;
+ /**
+ * True, if this iterator is currently leaving the node.
+ */
+ bool exiting;
+ /**
+ * Offset in the node struct for the children linked list.
+ */
+ ptrdiff_t loc_children;
+ /**
+ * Offset in the node struct for the next pointer.
+ */
+ ptrdiff_t loc_next;
+ /**
+ * The total number of distinct nodes that have been passed so far.
+ */
+ size_t counter;
+ /**
+ * The currently observed node.
+ *
+ * This is the same what cxIteratorCurrent() would return.
+ */
+ void *node;
+ /**
+ * Stores a copy of the next pointer of the visited node.
+ * Allows freeing a node on exit without corrupting the iteration.
+ */
+ void *node_next;
+ /**
+ * Internal stack.
+ * Will be automatically freed once the iterator becomes invalid.
+ *
+ * If you want to discard the iterator before, you need to manually
+ * call cxTreeIteratorDispose().
+ */
+ void **stack;
+ /**
+ * Internal capacity of the stack.
+ */
+ size_t stack_capacity;
+ union {
+ /**
+ * Internal stack size.
+ */
+ size_t stack_size;
+ /**
+ * The current depth in the tree.
+ */
+ size_t depth;
+ };
+} CxTreeIterator;
+
+/**
+ * An element in a visitor queue.
+ */
+struct cx_tree_visitor_queue_s {
+ /**
+ * The tree node to visit.
+ */
+ void *node;
+ /**
+ * The depth of the node.
+ */
+ size_t depth;
+ /**
+ * The next element in the queue or \c NULL.
+ */
+ struct cx_tree_visitor_queue_s *next;
+};
+
+/**
+ * A breadth-first tree iterator.
+ *
+ * This iterator needs to maintain a visitor queue that will be automatically
+ * freed once the iterator becomes invalid.
+ * If you want to discard the iterator before, you MUST manually call
+ * cxTreeVisitorDispose().
+ *
+ * This iterator is not position-aware in a strict sense, as it does not assume
+ * a particular order of elements in the tree. However, the iterator keeps track
+ * of the number of nodes it has passed in a counter variable.
+ * Each node, regardless of the number of passes, is counted only once.
+ *
+ * @note Objects that are pointed to by an iterator are mutable through that
+ * iterator. However, if the
+ * underlying data structure is mutated by other means than this iterator (e.g.
+ * elements added or removed), the iterator becomes invalid (regardless of what
+ * cxIteratorValid() returns).
+ *
+ * @see CxIterator
+ */
+typedef struct cx_tree_visitor_s {
+ /**
+ * Base members.
+ */
+ CX_ITERATOR_BASE;
+ /**
+ * Indicates whether the subtree below the current node shall be skipped.
+ */
+ bool skip;
+ /**
+ * Offset in the node struct for the children linked list.
+ */
+ ptrdiff_t loc_children;
+ /**
+ * Offset in the node struct for the next pointer.
+ */
+ ptrdiff_t loc_next;
+ /**
+ * The total number of distinct nodes that have been passed so far.
+ */
+ size_t counter;
+ /**
+ * The currently observed node.
+ *
+ * This is the same what cxIteratorCurrent() would return.
+ */
+ void *node;
+ /**
+ * The current depth in the tree.
+ */
+ size_t depth;
+ /**
+ * The next element in the visitor queue.
+ */
+ struct cx_tree_visitor_queue_s *queue_next;
+ /**
+ * The last element in the visitor queue.
+ */
+ struct cx_tree_visitor_queue_s *queue_last;
+} CxTreeVisitor;
+
+/**
+ * Releases internal memory of the given tree iterator.
+ * @param iter the iterator
+ */
+ __attribute__((__nonnull__))
+static inline void cxTreeIteratorDispose(CxTreeIterator *iter) {
+ free(iter->stack);
+ iter->stack = NULL;
+}
+
+/**
+ * Releases internal memory of the given tree visitor.
+ * @param visitor the visitor
+ */
+__attribute__((__nonnull__))
+static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) {
+ struct cx_tree_visitor_queue_s *q = visitor->queue_next;
+ while (q != NULL) {
+ struct cx_tree_visitor_queue_s *next = q->next;
+ free(q);
+ q = next;
+ }
+}
+
+/**
+ * Advises the iterator to skip the subtree below the current node and
+ * also continues the current loop.
+ *
+ * @param iterator the iterator
+ */
+#define cxTreeIteratorContinue(iterator) (iterator).skip = true; continue
+
+/**
+ * Advises the visitor to skip the subtree below the current node and
+ * also continues the current loop.
+ *
+ * @param visitor the visitor
+ */
+#define cxTreeVisitorContinue(visitor) cxTreeIteratorContinue(visitor)
+
+/**
+ * Links a node to a (new) parent.
+ *
+ * If the node has already a parent, it is unlinked, first.
+ * If the parent has children already, the node is \em appended to the list
+ * of all currently existing children.
+ *
+ * @param parent the parent node
+ * @param node the node that shall be linked
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @see cx_tree_unlink()
+ */
+__attribute__((__nonnull__))
+void cx_tree_link(
+ void *restrict parent,
+ void *restrict node,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Unlinks a node from its parent.
+ *
+ * If the node has no parent, this function does nothing.
+ *
+ * @param node the node that shall be unlinked from its parent
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @see cx_tree_link()
+ */
+__attribute__((__nonnull__))
+void cx_tree_unlink(
+ void *node,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Function pointer for a search function.
+ *
+ * A function of this kind shall check if the specified \p node
+ * contains the given \p data or if one of the children might contain
+ * the data.
+ *
+ * The function should use the returned integer to indicate how close the
+ * match is, where a negative number means that it does not match at all.
+ *
+ * For example if a tree stores file path information, a node that is
+ * describing a parent directory of a filename that is searched, shall
+ * return a positive number to indicate that a child node might contain the
+ * searched item. On the other hand, if the node denotes a path that is not a
+ * prefix of the searched filename, the function would return -1 to indicate
+ * that the search does not need to be continued in that branch.
+ *
+ * @param node the node that is currently investigated
+ * @param data the data that is searched for
+ *
+ * @return 0 if the node contains the data,
+ * positive if one of the children might contain the data,
+ * negative if neither the node, nor the children contains the data
+ */
+typedef int (*cx_tree_search_data_func)(const void *node, const void *data);
+
+
+/**
+ * Function pointer for a search function.
+ *
+ * A function of this kind shall check if the specified \p node
+ * contains the same \p data as \p new_node or if one of the children might
+ * contain the data.
+ *
+ * The function should use the returned integer to indicate how close the
+ * match is, where a negative number means that it does not match at all.
+ *
+ * For example if a tree stores file path information, a node that is
+ * describing a parent directory of a filename that is searched, shall
+ * return a positive number to indicate that a child node might contain the
+ * searched item. On the other hand, if the node denotes a path that is not a
+ * prefix of the searched filename, the function would return -1 to indicate
+ * that the search does not need to be continued in that branch.
+ *
+ * @param node the node that is currently investigated
+ * @param new_node a new node with the information which is searched
+ *
+ * @return 0 if \p node contains the same data as \p new_node,
+ * positive if one of the children might contain the data,
+ * negative if neither the node, nor the children contains the data
+ */
+typedef int (*cx_tree_search_func)(const void *node, const void *new_node);
+
+/**
+ * Searches for data in a tree.
+ *
+ * When the data cannot be found exactly, the search function might return a
+ * closest result which might be a good starting point for adding a new node
+ * to the tree (see also #cx_tree_add()).
+ *
+ * Depending on the tree structure it is not necessarily guaranteed that the
+ * "closest" match is uniquely defined. This function will search for a node
+ * with the best match according to the \p sfunc (meaning: the return value of
+ * \p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
+ * node matching the criteria is returned.
+ *
+ * @param root the root node
+ * @param data the data to search for
+ * @param sfunc the search function
+ * @param result where the result shall be stored
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return zero if the node was found exactly, positive if a node was found that
+ * could contain the node (but doesn't right now), negative if the tree does not
+ * contain any node that might be related to the searched data
+ */
+__attribute__((__nonnull__))
+int cx_tree_search_data(
+ const void *root,
+ const void *data,
+ cx_tree_search_data_func sfunc,
+ void **result,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Searches for a node in a tree.
+ *
+ * When no node with the same data can be found, the search function might
+ * return a closest result which might be a good starting point for adding the
+ * new node to the tree (see also #cx_tree_add()).
+ *
+ * Depending on the tree structure it is not necessarily guaranteed that the
+ * "closest" match is uniquely defined. This function will search for a node
+ * with the best match according to the \p sfunc (meaning: the return value of
+ * \p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
+ * node matching the criteria is returned.
+ *
+ * @param root the root node
+ * @param node the node to search for
+ * @param sfunc the search function
+ * @param result where the result shall be stored
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return zero if the node was found exactly, positive if a node was found that
+ * could contain the node (but doesn't right now), negative if the tree does not
+ * contain any node that might be related to the searched data
+ */
+__attribute__((__nonnull__))
+int cx_tree_search(
+ const void *root,
+ const void *node,
+ cx_tree_search_func sfunc,
+ void **result,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Creates a depth-first iterator for a tree with the specified root node.
+ *
+ * @note A tree iterator needs to maintain a stack of visited nodes, which is
+ * allocated using stdlib malloc().
+ * When the iterator becomes invalid, this memory is automatically released.
+ * However, if you wish to cancel the iteration before the iterator becomes
+ * invalid by itself, you MUST call cxTreeIteratorDispose() manually to release
+ * the memory.
+ *
+ * @remark The returned iterator does not support cxIteratorFlagRemoval().
+ *
+ * @param root the root node
+ * @param visit_on_exit set to true, when the iterator shall visit a node again
+ * after processing all children
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree iterator
+ * @see cxTreeIteratorDispose()
+ */
+CxTreeIterator cx_tree_iterator(
+ void *root,
+ bool visit_on_exit,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Creates a breadth-first iterator for a tree with the specified root node.
+ *
+ * @note A tree visitor needs to maintain a queue of to be visited nodes, which
+ * is allocated using stdlib malloc().
+ * When the visitor becomes invalid, this memory is automatically released.
+ * However, if you wish to cancel the iteration before the visitor becomes
+ * invalid by itself, you MUST call cxTreeVisitorDispose() manually to release
+ * the memory.
+ *
+ * @remark The returned iterator does not support cxIteratorFlagRemoval().
+ *
+ * @param root the root node
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree visitor
+ * @see cxTreeVisitorDispose()
+ */
+CxTreeVisitor cx_tree_visitor(
+ void *root,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Describes a function that creates a tree node from the specified data.
+ * The first argument points to the data the node shall contain and
+ * the second argument may be used for additional data (e.g. an allocator).
+ * Functions of this type shall either return a new pointer to a newly
+ * created node or \c NULL when allocation fails.
+ *
+ * \note the function may leave the node pointers in the struct uninitialized.
+ * The caller is responsible to set them according to the intended use case.
+ */
+typedef void *(*cx_tree_node_create_func)(const void *, void *);
+
+/**
+ * The local search depth for a new subtree when adding multiple elements.
+ * The default value is 3.
+ * This variable is used by #cx_tree_add_array() and #cx_tree_add_iter() to
+ * implement optimized insertion of multiple elements into a tree.
+ */
+extern unsigned int cx_tree_add_look_around_depth;
+
+/**
+ * Adds multiple elements efficiently to a tree.
+ *
+ * Once an element cannot be added to the tree, this function returns, leaving
+ * the iterator in a valid state pointing to the element that could not be
+ * added.
+ * Also, the pointer of the created node will be stored to \p failed.
+ * The integer returned by this function denotes the number of elements obtained
+ * from the \p iter that have been successfully processed.
+ * When all elements could be processed, a \c NULL pointer will be written to
+ * \p failed.
+ *
+ * The advantage of this function compared to multiple invocations of
+ * #cx_tree_add() is that the search for the insert locations is not always
+ * started from the root node.
+ * Instead, the function checks #cx_tree_add_look_around_depth many parent nodes
+ * of the current insert location before starting from the root node again.
+ * When the variable is set to zero, only the last found location is checked
+ * again.
+ *
+ * Refer to the documentation of #cx_tree_add() for more details.
+ *
+ * @param iter a pointer to an arbitrary iterator
+ * @param num the maximum number of elements to obtain from the iterator
+ * @param sfunc a search function
+ * @param cfunc a node creation function
+ * @param cdata optional additional data
+ * @param root the root node of the tree
+ * @param failed location where the pointer to a failed node shall be stored
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the number of nodes created and added
+ * @see cx_tree_add()
+ */
+__attribute__((__nonnull__(1, 3, 4, 6, 7)))
+size_t cx_tree_add_iter(
+ struct cx_iterator_base_s *iter,
+ size_t num,
+ cx_tree_search_func sfunc,
+ cx_tree_node_create_func cfunc,
+ void *cdata,
+ void **failed,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Adds multiple elements efficiently to a tree.
+ *
+ * Once an element cannot be added to the tree, this function returns, storing
+ * the pointer of the created node to \p failed.
+ * The integer returned by this function denotes the number of elements from
+ * the \p src array that have been successfully processed.
+ * When all elements could be processed, a \c NULL pointer will be written to
+ * \p failed.
+ *
+ * The advantage of this function compared to multiple invocations of
+ * #cx_tree_add() is that the search for the insert locations is not always
+ * started from the root node.
+ * Instead, the function checks #cx_tree_add_look_around_depth many parent nodes
+ * of the current insert location before starting from the root node again.
+ * When the variable is set to zero, only the last found location is checked
+ * again.
+ *
+ * Refer to the documentation of #cx_tree_add() for more details.
+ *
+ * @param src a pointer to the source data array
+ * @param num the number of elements in the \p src array
+ * @param elem_size the size of each element in the \p src array
+ * @param sfunc a search function
+ * @param cfunc a node creation function
+ * @param cdata optional additional data
+ * @param failed location where the pointer to a failed node shall be stored
+ * @param root the root node of the tree
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the number of array elements successfully processed
+ * @see cx_tree_add()
+ */
+__attribute__((__nonnull__(1, 4, 5, 7, 8)))
+size_t cx_tree_add_array(
+ const void *src,
+ size_t num,
+ size_t elem_size,
+ cx_tree_search_func sfunc,
+ cx_tree_node_create_func cfunc,
+ void *cdata,
+ void **failed,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Adds data to a tree.
+ *
+ * An adequate location where to add the new tree node is searched with the
+ * specified \p sfunc.
+ *
+ * When a location is found, the \p cfunc will be invoked with \p cdata.
+ *
+ * The node returned by \p cfunc will be linked into the tree.
+ * When \p sfunc returned a positive integer, the new node will be linked as a
+ * child. The other children (now siblings of the new node) are then checked
+ * with \p sfunc, whether they could be children of the new node and re-linked
+ * accordingly.
+ *
+ * When \p sfunc returned zero and the found node has a parent, the new
+ * node will be added as sibling - otherwise, the new node will be added
+ * as a child.
+ *
+ * When \p sfunc returned a negative value, the new node will not be added to
+ * the tree and this function returns a non-zero value.
+ * The caller should check if \p cnode contains a node pointer and deal with the
+ * node that could not be added.
+ *
+ * This function also returns a non-zero value when \p cfunc tries to allocate
+ * a new node but fails to do so. In that case, the pointer stored to \p cnode
+ * will be \c NULL.
+ *
+ * Multiple elements can be added more efficiently with
+ * #cx_tree_add_array() or #cx_tree_add_iter().
+ *
+ * @param src a pointer to the data
+ * @param sfunc a search function
+ * @param cfunc a node creation function
+ * @param cdata optional additional data
+ * @param cnode the location where a pointer to the new node is stored
+ * @param root the root node of the tree
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return zero when a new node was created and added to the tree,
+ * non-zero otherwise
+ */
+__attribute__((__nonnull__(1, 2, 3, 5, 6)))
+int cx_tree_add(
+ const void *src,
+ cx_tree_search_func sfunc,
+ cx_tree_node_create_func cfunc,
+ void *cdata,
+ void **cnode,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+
+/**
+ * Tree class type.
+ */
+typedef struct cx_tree_class_s cx_tree_class;
+
+/**
+ * Base structure that can be used for tree nodes in a #CxTree.
+ */
+struct cx_tree_node_base_s {
+ /**
+ * Pointer to the parent.
+ */
+ struct cx_tree_node_base_s *parent;
+ /**
+ * Pointer to the first child.
+ */
+ struct cx_tree_node_base_s *children;
+ /**
+ * Pointer to the last child.
+ */
+ struct cx_tree_node_base_s *last_child;
+ /**
+ * Pointer to the previous sibling.
+ */
+ struct cx_tree_node_base_s *prev;
+ /**
+ * Pointer to the next sibling.
+ */
+ struct cx_tree_node_base_s *next;
+};
+
+/**
+ * Structure for holding the base data of a tree.
+ */
+struct cx_tree_s {
+ /**
+ * The tree class definition.
+ */
+ const cx_tree_class *cl;
+
+ /**
+ * Allocator to allocate new nodes.
+ */
+ const CxAllocator *allocator;
+
+ /**
+ * A pointer to the root node.
+ *
+ * Will be \c NULL when \c size is 0.
+ */
+ void *root;
+
+ /**
+ * A function to create new nodes.
+ *
+ * Invocations to this function will receive a pointer to this tree
+ * structure as second argument.
+ *
+ * Nodes MAY use #cx_tree_node_base_s as base layout, but do not need to.
+ */
+ cx_tree_node_create_func node_create;
+
+ /**
+ * An optional simple destructor for the tree nodes.
+ */
+ cx_destructor_func simple_destructor;
+
+ /**
+ * An optional advanced destructor for the tree nodes.
+ */
+ cx_destructor_func2 advanced_destructor;
+
+ /**
+ * The pointer to additional data that is passed to the advanced destructor.
+ */
+ void *destructor_data;
+
+ /**
+ * A function to compare two nodes.
+ */
+ cx_tree_search_func search;
+
+ /**
+ * A function to compare a node with data.
+ */
+ cx_tree_search_data_func search_data;
+
+ /**
+ * The number of currently stored elements.
+ */
+ size_t size;
+
+ /**
+ * Offset in the node struct for the parent pointer.
+ */
+ ptrdiff_t loc_parent;
+
+ /**
+ * Offset in the node struct for the children linked list.
+ */
+ ptrdiff_t loc_children;
+
+ /**
+ * Optional offset in the node struct for the pointer to the last child
+ * in the linked list (negative if there is no such pointer).
+ */
+ ptrdiff_t loc_last_child;
+
+ /**
+ * Offset in the node struct for the previous sibling pointer.
+ */
+ ptrdiff_t loc_prev;
+
+ /**
+ * Offset in the node struct for the next sibling pointer.
+ */
+ ptrdiff_t loc_next;
+};
+
+/**
+ * Macro to roll out the #cx_tree_node_base_s structure with a custom
+ * node type.
+ */
+#define CX_TREE_NODE_BASE(type) \
+ type *parent; \
+ type *children;\
+ type *last_child;\
+ type *prev;\
+ type *next
+
+/**
+ * Macro for specifying the layout of a base node tree.
+ */
+#define cx_tree_node_base_layout \
+ offsetof(struct cx_tree_node_base_s, parent),\
+ offsetof(struct cx_tree_node_base_s, children),\
+ offsetof(struct cx_tree_node_base_s, last_child),\
+ offsetof(struct cx_tree_node_base_s, prev), \
+ offsetof(struct cx_tree_node_base_s, next)
+
+/**
+ * Macro for obtaining the node pointer layout for a specific tree.
+ */
+#define cx_tree_node_layout(tree) \
+ (tree)->loc_parent,\
+ (tree)->loc_children,\
+ (tree)->loc_last_child,\
+ (tree)->loc_prev, \
+ (tree)->loc_next
+
+/**
+ * The class definition for arbitrary trees.
+ */
+struct cx_tree_class_s {
+ /**
+ * Destructor function.
+ *
+ * Implementations SHALL invoke the node destructor functions if provided
+ * and SHALL deallocate the tree memory.
+ */
+ void (*destructor)(struct cx_tree_s *);
+
+ /**
+ * Member function for inserting a single element.
+ *
+ * Implementations SHALL NOT simply invoke \p insert_many as this comes
+ * with too much overhead.
+ */
+ int (*insert_element)(
+ struct cx_tree_s *tree,
+ const void *data
+ );
+
+ /**
+ * Member function for inserting multiple elements.
+ *
+ * Implementations SHALL avoid to perform a full search in the tree for
+ * every element even though the source data MAY be unsorted.
+ */
+ size_t (*insert_many)(
+ struct cx_tree_s *tree,
+ struct cx_iterator_base_s *iter,
+ size_t n
+ );
+
+ /**
+ * Member function for finding a node.
+ */
+ void *(*find)(
+ struct cx_tree_s *tree,
+ const void *subtree,
+ const void *data
+ );
+
+ /**
+ * Member function for creating an iterator for the tree.
+ */
+ CxTreeIterator (*iterator)(
+ struct cx_tree_s *tree,
+ bool visit_on_exit
+ );
+
+ /**
+ * Member function for creating a visitor for the tree.
+ */
+ CxTreeVisitor (*visitor)(struct cx_tree_s *tree);
+};
+
+/**
+ * Common type for all tree implementations.
+ */
+typedef struct cx_tree_s CxTree;
+
+/**
+ * Creates a new tree structure based on the specified layout.
+ *
+ * The specified \p allocator will be used for creating the tree struct
+ * and SHALL be used by \p create_func to allocate memory for the nodes.
+ *
+ * \note This function will also register an advanced destructor which
+ * will free the nodes with the allocator's free() method.
+ *
+ * @param allocator the allocator that shall be used
+ * @param create_func a function that creates new nodes
+ * @param search_func a function that compares two nodes
+ * @param search_data_func a function that compares a node with data
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree
+ * @see cxTreeCreateSimple()
+ * @see cxTreeCreateWrapped()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxTree *cxTreeCreate(
+ const CxAllocator *allocator,
+ cx_tree_node_create_func create_func,
+ cx_tree_search_func search_func,
+ cx_tree_search_data_func search_data_func,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Creates a new tree structure based on a default layout.
+ *
+ * Nodes created by \p create_func MUST contain #cx_tree_node_base_s as first
+ * member (or at least respect the default offsets specified in the tree
+ * struct) and they MUST be allocated with the specified allocator.
+ *
+ * \note This function will also register an advanced destructor which
+ * will free the nodes with the allocator's free() method.
+ *
+ * @param allocator the allocator that shall be used
+ * @param create_func a function that creates new nodes
+ * @param search_func a function that compares two nodes
+ * @param search_data_func a function that compares a node with data
+ * @return the new tree
+ * @see cxTreeCreate()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxTree *cxTreeCreateSimple(
+ const CxAllocator *allocator,
+ cx_tree_node_create_func create_func,
+ cx_tree_search_func search_func,
+ cx_tree_search_data_func search_data_func
+) {
+ return cxTreeCreate(
+ allocator,
+ create_func,
+ search_func,
+ search_data_func,
+ cx_tree_node_base_layout
+ );
+}
+
+/**
+ * Creates a new tree structure based on an existing tree.
+ *
+ * The specified \p allocator will be used for creating the tree struct.
+ *
+ * \attention This function will create an incompletely defined tree structure
+ * where neither the create function, the search function, nor a destructor
+ * will be set. If you wish to use any of this functionality for the wrapped
+ * tree, you need to specify those functions afterwards.
+ *
+ * @param root the root node of the tree that shall be wrapped
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree
+ * @see cxTreeCreate()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxTree *cxTreeCreateWrapped(
+ const CxAllocator *allocator,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Destroys the tree structure.
+ *
+ * \attention This function will only invoke the destructor functions
+ * on the nodes, if specified.
+ * It will NOT additionally free the nodes with the tree's allocator, because
+ * that would cause a double-free in most scenarios.
+ *
+ * @param tree the tree to destroy
+ */
+__attribute__((__nonnull__))
+static inline void cxTreeDestroy(CxTree *tree) {
+ tree->cl->destructor(tree);
+}
+
+/**
+ * Inserts data into the tree.
+ *
+ * \remark For this function to work, the tree needs specified search and
+ * create functions, which might not be available for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the data to insert
+ * @return zero on success, non-zero on failure
+ */
+__attribute__((__nonnull__))
+static inline int cxTreeInsert(
+ CxTree *tree,
+ const void *data
+) {
+ return tree->cl->insert_element(tree, data);
+}
+
+/**
+ * Inserts elements provided by an iterator efficiently into the tree.
+ *
+ * \remark For this function to work, the tree needs specified search and
+ * create functions, which might not be available for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param iter the iterator providing the elements
+ * @param n the maximum number of elements to insert
+ * @return the number of elements that could be successfully inserted
+ */
+__attribute__((__nonnull__))
+static inline size_t cxTreeInsertIter(
+ CxTree *tree,
+ struct cx_iterator_base_s *iter,
+ size_t n
+) {
+ return tree->cl->insert_many(tree, iter, n);
+}
+
+/**
+ * Inserts an array of data efficiently into the tree.
+ *
+ * \remark For this function to work, the tree needs specified search and
+ * create functions, which might not be available for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the array of data to insert
+ * @param elem_size the size of each element in the array
+ * @param n the number of elements in the array
+ * @return the number of elements that could be successfully inserted
+ */
+__attribute__((__nonnull__))
+static inline size_t cxTreeInsertArray(
+ CxTree *tree,
+ const void *data,
+ size_t elem_size,
+ size_t n
+) {
+ if (n == 0) return 0;
+ if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0;
+ CxIterator iter = cxIterator(data, elem_size, n);
+ return cxTreeInsertIter(tree, cxIteratorRef(iter), n);
+}
+
+/**
+ * Searches the data in the specified tree.
+ *
+ * \remark For this function to work, the tree needs a specified \c search_data
+ * function, which might not be available wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the data to search for
+ * @return the first matching node, or \c NULL when the data cannot be found
+ */
+__attribute__((__nonnull__))
+static inline void *cxTreeFind(
+ CxTree *tree,
+ const void *data
+) {
+ return tree->cl->find(tree, tree->root, data);
+}
+
+/**
+ * Searches the data in the specified subtree.
+ *
+ * \note When \p subtree_root is not part of the \p tree, the behavior is
+ * undefined.
+ *
+ * \remark For this function to work, the tree needs a specified \c search_data
+ * function, which might not be the case for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the data to search for
+ * @param subtree_root the node where to start
+ * @return the first matching node, or \c NULL when the data cannot be found
+ */
+__attribute__((__nonnull__))
+static inline void *cxTreeFindInSubtree(
+ CxTree *tree,
+ const void *data,
+ void *subtree_root
+) {
+ return tree->cl->find(tree, subtree_root, data);
+}
+
+/**
+ * Determines the size of the specified subtree.
+ *
+ * @param tree the tree
+ * @param subtree_root the root node of the subtree
+ * @return the number of nodes in the specified subtree
+ */
+__attribute__((__nonnull__))
+size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root);
+
+/**
+ * Determines the depth of the specified subtree.
+ *
+ * @param tree the tree
+ * @param subtree_root the root node of the subtree
+ * @return the tree depth including the \p subtree_root
+ */
+__attribute__((__nonnull__))
+size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root);
+
+/**
+ * Determines the depth of the entire tree.
+ *
+ * @param tree the tree
+ * @return the tree depth, counting the root as one
+ */
+__attribute__((__nonnull__))
+size_t cxTreeDepth(CxTree *tree);
+
+/**
+ * Creates a depth-first iterator for the specified tree.
+ *
+ * @param tree the tree to iterate
+ * @param visit_on_exit true, if the iterator shall visit a node again when
+ * leaving the sub-tree
+ * @return a tree iterator (depth-first)
+ * @see cxTreeVisitor()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxTreeIterator cxTreeIterator(
+ CxTree *tree,
+ bool visit_on_exit
+) {
+ return tree->cl->iterator(tree, visit_on_exit);
+}
+
+/**
+ * Creates a breadth-first iterator for the specified tree.
+ *
+ * @param tree the tree to iterate
+ * @return a tree visitor (a.k.a. breadth-first iterator)
+ * @see cxTreeIterator()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxTreeVisitor cxTreeVisitor(CxTree *tree) {
+ return tree->cl->visitor(tree);
+}
+
+/**
+ * Adds a new node to the tree.
+ *
+ * \attention The node may be externally created, but MUST obey the same rules
+ * as if it was created by the tree itself with #cxTreeAddChild() (e.g. use
+ * the same allocator).
+ *
+ * @param tree the tree
+ * @param parent the parent of the node to add
+ * @param child the node to add
+ */
+__attribute__((__nonnull__))
+static inline void cxTreeAddChildNode(
+ CxTree *tree,
+ void *parent,
+ void *child) {
+ cx_tree_link(parent, child, cx_tree_node_layout(tree));
+ tree->size++;
+}
+
+/**
+ * Creates a new node and adds it to the tree.
+ *
+ * With this function you can decide where exactly the new node shall be added.
+ * If you specified an appropriate search function, you may want to consider
+ * leaving this task to the tree by using #cxTreeInsert().
+ *
+ * Be aware that adding nodes at arbitrary locations in the tree might cause
+ * wrong or undesired results when subsequently invoking #cxTreeInsert() and
+ * the invariant imposed by the search function does not hold any longer.
+ *
+ * @param tree the tree
+ * @param parent the parent node of the new node
+ * @param data the data that will be submitted to the create function
+ * @return zero when the new node was created, non-zero on allocation failure
+ * @see cxTreeInsert()
+ */
+__attribute__((__nonnull__))
+int cxTreeAddChild(
+ CxTree *tree,
+ void *parent,
+ const void *data
+);
+
+/**
+ * A function that is invoked when a node needs to be re-linked to a new parent.
+ *
+ * When a node is re-linked, sometimes the contents need to be updated.
+ * This callback is invoked by #cxTreeRemove() so that those updates can be
+ * applied when re-linking the children of the removed node.
+ *
+ * @param node the affected node
+ * @param old_parent the old parent of the node
+ * @param new_parent the new parent of the node
+ */
+typedef void (*cx_tree_relink_func)(
+ void *node,
+ const void *old_parent,
+ const void *new_parent
+);
+
+/**
+ * Removes a node and re-links its children to its former parent.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * \note The destructor function, if any, will \em not be invoked. That means
+ * you will need to free the removed node by yourself, eventually.
+ *
+ * @param tree the tree
+ * @param node the node to remove (must not be the root node)
+ * @param relink_func optional callback to update the content of each re-linked
+ * node
+ * @return zero on success, non-zero if \p node is the root node of the tree
+ */
+__attribute__((__nonnull__(1,2)))
+int cxTreeRemove(
+ CxTree *tree,
+ void *node,
+ cx_tree_relink_func relink_func
+);
+
+/**
+ * Removes a node and it's subtree from the tree.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * \note The destructor function, if any, will \em not be invoked. That means
+ * you will need to free the removed subtree by yourself, eventually.
+ *
+ * @param tree the tree
+ * @param node the node to remove
+ */
+__attribute__((__nonnull__))
+void cxTreeRemoveSubtree(CxTree *tree, void *node);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_TREE_H
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/hash_key.h"
+#include <string.h>
+
+void cx_hash_murmur(CxHashKey *key) {
+ const unsigned char *data = key->data;
+ if (data == NULL) {
+ // extension: special value for NULL
+ key->hash = 1574210520u;
+ return;
+ }
+ size_t len = key->len;
+
+ unsigned m = 0x5bd1e995;
+ unsigned r = 24;
+ unsigned h = 25 ^ len;
+ unsigned i = 0;
+ while (len >= 4) {
+ unsigned k = data[i + 0] & 0xFF;
+ k |= (data[i + 1] & 0xFF) << 8;
+ k |= (data[i + 2] & 0xFF) << 16;
+ k |= (data[i + 3] & 0xFF) << 24;
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h *= m;
+ h ^= k;
+
+ i += 4;
+ len -= 4;
+ }
+
+ switch (len) {
+ case 3:
+ h ^= (data[i + 2] & 0xFF) << 16;
+ __attribute__((__fallthrough__));
+ case 2:
+ h ^= (data[i + 1] & 0xFF) << 8;
+ __attribute__((__fallthrough__));
+ case 1:
+ h ^= (data[i + 0] & 0xFF);
+ h *= m;
+ __attribute__((__fallthrough__));
+ default: // do nothing
+ ;
+ }
+
+ h ^= h >> 13;
+ h *= m;
+ h ^= h >> 15;
+
+ key->hash = h;
+}
+
+CxHashKey cx_hash_key_str(const char *str) {
+ CxHashKey key;
+ key.data = str;
+ key.len = str == NULL ? 0 : strlen(str);
+ cx_hash_murmur(&key);
+ return key;
+}
+
+CxHashKey cx_hash_key_bytes(
+ const unsigned char *bytes,
+ size_t len
+) {
+ CxHashKey key;
+ key.data = bytes;
+ key.len = len;
+ cx_hash_murmur(&key);
+ return key;
+}
+
+CxHashKey cx_hash_key(
+ const void *obj,
+ size_t len
+) {
+ CxHashKey key;
+ key.data = obj;
+ key.len = len;
+ cx_hash_murmur(&key);
+ return key;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/hash_map.h"
+#include "cx/utils.h"
+
+#include <string.h>
+#include <assert.h>
+
+struct cx_hash_map_element_s {
+ /** A pointer to the next element in the current bucket. */
+ struct cx_hash_map_element_s *next;
+
+ /** The corresponding key. */
+ CxHashKey key;
+
+ /** The value data. */
+ char data[];
+};
+
+static void cx_hash_map_clear(struct cx_map_s *map) {
+ struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+ cx_for_n(i, hash_map->bucket_count) {
+ struct cx_hash_map_element_s *elem = hash_map->buckets[i];
+ if (elem != NULL) {
+ do {
+ struct cx_hash_map_element_s *next = elem->next;
+ // invoke the destructor
+ cx_invoke_destructor(map, elem->data);
+ // free the key data
+ cxFree(map->collection.allocator, (void *) elem->key.data);
+ // free the node
+ cxFree(map->collection.allocator, elem);
+ // proceed
+ elem = next;
+ } while (elem != NULL);
+
+ // do not leave a dangling pointer
+ hash_map->buckets[i] = NULL;
+ }
+ }
+ map->collection.size = 0;
+}
+
+static void cx_hash_map_destructor(struct cx_map_s *map) {
+ struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+
+ // free the buckets
+ cx_hash_map_clear(map);
+ cxFree(map->collection.allocator, hash_map->buckets);
+
+ // free the map structure
+ cxFree(map->collection.allocator, map);
+}
+
+static int cx_hash_map_put(
+ CxMap *map,
+ CxHashKey key,
+ void *value
+) {
+ struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+ const CxAllocator *allocator = map->collection.allocator;
+
+ unsigned hash = key.hash;
+ if (hash == 0) {
+ cx_hash_murmur(&key);
+ hash = key.hash;
+ }
+
+ size_t slot = hash % hash_map->bucket_count;
+ struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
+ struct cx_hash_map_element_s *prev = NULL;
+
+ while (elm != NULL && elm->key.hash < hash) {
+ prev = elm;
+ elm = elm->next;
+ }
+
+ if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len &&
+ memcmp(elm->key.data, key.data, key.len) == 0) {
+ // overwrite existing element
+ if (map->collection.store_pointer) {
+ memcpy(elm->data, &value, sizeof(void *));
+ } else {
+ memcpy(elm->data, value, map->collection.elem_size);
+ }
+ } else {
+ // allocate new element
+ struct cx_hash_map_element_s *e = cxMalloc(
+ allocator,
+ sizeof(struct cx_hash_map_element_s) + map->collection.elem_size
+ );
+ if (e == NULL) {
+ return -1;
+ }
+
+ // write the value
+ if (map->collection.store_pointer) {
+ memcpy(e->data, &value, sizeof(void *));
+ } else {
+ memcpy(e->data, value, map->collection.elem_size);
+ }
+
+ // copy the key
+ void *kd = cxMalloc(allocator, key.len);
+ if (kd == NULL) {
+ return -1;
+ }
+ memcpy(kd, key.data, key.len);
+ e->key.data = kd;
+ e->key.len = key.len;
+ e->key.hash = hash;
+
+ // insert the element into the linked list
+ if (prev == NULL) {
+ hash_map->buckets[slot] = e;
+ } else {
+ prev->next = e;
+ }
+ e->next = elm;
+
+ // increase the size
+ map->collection.size++;
+ }
+
+ return 0;
+}
+
+static void cx_hash_map_unlink(
+ struct cx_hash_map_s *hash_map,
+ size_t slot,
+ struct cx_hash_map_element_s *prev,
+ struct cx_hash_map_element_s *elm
+) {
+ // unlink
+ if (prev == NULL) {
+ hash_map->buckets[slot] = elm->next;
+ } else {
+ prev->next = elm->next;
+ }
+ // free element
+ cxFree(hash_map->base.collection.allocator, (void *) elm->key.data);
+ cxFree(hash_map->base.collection.allocator, elm);
+ // decrease size
+ hash_map->base.collection.size--;
+}
+
+/**
+ * Helper function to avoid code duplication.
+ *
+ * @param map the map
+ * @param key the key to look up
+ * @param remove flag indicating whether the looked up entry shall be removed
+ * @param destroy flag indicating whether the destructor shall be invoked
+ * @return a pointer to the value corresponding to the key or \c NULL
+ */
+static void *cx_hash_map_get_remove(
+ CxMap *map,
+ CxHashKey key,
+ bool remove,
+ bool destroy
+) {
+ struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+
+ unsigned hash = key.hash;
+ if (hash == 0) {
+ cx_hash_murmur(&key);
+ hash = key.hash;
+ }
+
+ size_t slot = hash % hash_map->bucket_count;
+ struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
+ struct cx_hash_map_element_s *prev = NULL;
+ while (elm && elm->key.hash <= hash) {
+ if (elm->key.hash == hash && elm->key.len == key.len) {
+ if (memcmp(elm->key.data, key.data, key.len) == 0) {
+ void *data = NULL;
+ if (destroy) {
+ cx_invoke_destructor(map, elm->data);
+ } else {
+ if (map->collection.store_pointer) {
+ data = *(void **) elm->data;
+ } else {
+ data = elm->data;
+ }
+ }
+ if (remove) {
+ cx_hash_map_unlink(hash_map, slot, prev, elm);
+ }
+ return data;
+ }
+ }
+ prev = elm;
+ elm = prev->next;
+ }
+
+ return NULL;
+}
+
+static void *cx_hash_map_get(
+ const CxMap *map,
+ CxHashKey key
+) {
+ // we can safely cast, because we know the map stays untouched
+ return cx_hash_map_get_remove((CxMap *) map, key, false, false);
+}
+
+static void *cx_hash_map_remove(
+ CxMap *map,
+ CxHashKey key,
+ bool destroy
+) {
+ return cx_hash_map_get_remove(map, key, true, destroy);
+}
+
+static void *cx_hash_map_iter_current_entry(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ // struct has to have a compatible signature
+ return (struct cx_map_entry_s *) &(iter->kv_data);
+}
+
+static void *cx_hash_map_iter_current_key(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ struct cx_hash_map_element_s *elm = iter->elem_handle;
+ return &elm->key;
+}
+
+static void *cx_hash_map_iter_current_value(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ const struct cx_hash_map_s *map = iter->src_handle.c;
+ struct cx_hash_map_element_s *elm = iter->elem_handle;
+ if (map->base.collection.store_pointer) {
+ return *(void **) elm->data;
+ } else {
+ return elm->data;
+ }
+}
+
+static bool cx_hash_map_iter_valid(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ return iter->elem_handle != NULL;
+}
+
+static void cx_hash_map_iter_next(void *it) {
+ struct cx_iterator_s *iter = it;
+ struct cx_hash_map_element_s *elm = iter->elem_handle;
+ struct cx_hash_map_s *map = iter->src_handle.m;
+
+ // remove current element, if asked
+ if (iter->base.remove) {
+
+ // clear the flag
+ iter->base.remove = false;
+
+ // determine the next element
+ struct cx_hash_map_element_s *next = elm->next;
+
+ // search the previous element
+ struct cx_hash_map_element_s *prev = NULL;
+ if (map->buckets[iter->slot] != elm) {
+ prev = map->buckets[iter->slot];
+ while (prev->next != elm) {
+ prev = prev->next;
+ }
+ }
+
+ // destroy
+ cx_invoke_destructor((struct cx_map_s *) map, elm->data);
+
+ // unlink
+ cx_hash_map_unlink(map, iter->slot, prev, elm);
+
+ // advance
+ elm = next;
+ } else {
+ // just advance
+ elm = elm->next;
+ iter->index++;
+ }
+
+ // search the next bucket, if required
+ while (elm == NULL && ++iter->slot < map->bucket_count) {
+ elm = map->buckets[iter->slot];
+ }
+
+ // fill the struct with the next element
+ iter->elem_handle = elm;
+ if (elm == NULL) {
+ iter->kv_data.key = NULL;
+ iter->kv_data.value = NULL;
+ } else {
+ iter->kv_data.key = &elm->key;
+ if (map->base.collection.store_pointer) {
+ iter->kv_data.value = *(void **) elm->data;
+ } else {
+ iter->kv_data.value = elm->data;
+ }
+ }
+}
+
+static CxIterator cx_hash_map_iterator(
+ const CxMap *map,
+ enum cx_map_iterator_type type
+) {
+ CxIterator iter;
+
+ iter.src_handle.c = map;
+ iter.elem_count = map->collection.size;
+
+ switch (type) {
+ case CX_MAP_ITERATOR_PAIRS:
+ iter.elem_size = sizeof(CxMapEntry);
+ iter.base.current = cx_hash_map_iter_current_entry;
+ break;
+ case CX_MAP_ITERATOR_KEYS:
+ iter.elem_size = sizeof(CxHashKey);
+ iter.base.current = cx_hash_map_iter_current_key;
+ break;
+ case CX_MAP_ITERATOR_VALUES:
+ iter.elem_size = map->collection.elem_size;
+ iter.base.current = cx_hash_map_iter_current_value;
+ break;
+ default:
+ assert(false);
+ }
+
+ iter.base.valid = cx_hash_map_iter_valid;
+ iter.base.next = cx_hash_map_iter_next;
+ iter.base.remove = false;
+ iter.base.mutating = false;
+
+ iter.slot = 0;
+ iter.index = 0;
+
+ if (map->collection.size > 0) {
+ struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+ struct cx_hash_map_element_s *elm = hash_map->buckets[0];
+ while (elm == NULL) {
+ elm = hash_map->buckets[++iter.slot];
+ }
+ iter.elem_handle = elm;
+ iter.kv_data.key = &elm->key;
+ if (map->collection.store_pointer) {
+ iter.kv_data.value = *(void **) elm->data;
+ } else {
+ iter.kv_data.value = elm->data;
+ }
+ } else {
+ iter.elem_handle = NULL;
+ iter.kv_data.key = NULL;
+ iter.kv_data.value = NULL;
+ }
+
+ return iter;
+}
+
+static cx_map_class cx_hash_map_class = {
+ cx_hash_map_destructor,
+ cx_hash_map_clear,
+ cx_hash_map_put,
+ cx_hash_map_get,
+ cx_hash_map_remove,
+ cx_hash_map_iterator,
+};
+
+CxMap *cxHashMapCreate(
+ const CxAllocator *allocator,
+ size_t itemsize,
+ size_t buckets
+) {
+ if (buckets == 0) {
+ // implementation defined default
+ buckets = 16;
+ }
+
+ struct cx_hash_map_s *map = cxCalloc(allocator, 1,
+ sizeof(struct cx_hash_map_s));
+ if (map == NULL) return NULL;
+
+ // initialize hash map members
+ map->bucket_count = buckets;
+ map->buckets = cxCalloc(allocator, buckets,
+ sizeof(struct cx_hash_map_element_s *));
+ if (map->buckets == NULL) {
+ cxFree(allocator, map);
+ return NULL;
+ }
+
+ // initialize base members
+ map->base.cl = &cx_hash_map_class;
+ map->base.collection.allocator = allocator;
+
+ if (itemsize > 0) {
+ map->base.collection.store_pointer = false;
+ map->base.collection.elem_size = itemsize;
+ } else {
+ map->base.collection.store_pointer = true;
+ map->base.collection.elem_size = sizeof(void *);
+ }
+
+ return (CxMap *) map;
+}
+
+int cxMapRehash(CxMap *map) {
+ struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+ if (map->collection.size > ((hash_map->bucket_count * 3) >> 2)) {
+
+ size_t new_bucket_count = (map->collection.size * 5) >> 1;
+ struct cx_hash_map_element_s **new_buckets = cxCalloc(
+ map->collection.allocator,
+ new_bucket_count, sizeof(struct cx_hash_map_element_s *)
+ );
+
+ if (new_buckets == NULL) {
+ return 1;
+ }
+
+ // iterate through the elements and assign them to their new slots
+ cx_for_n(slot, hash_map->bucket_count) {
+ struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
+ while (elm != NULL) {
+ struct cx_hash_map_element_s *next = elm->next;
+ size_t new_slot = elm->key.hash % new_bucket_count;
+
+ // find position where to insert
+ struct cx_hash_map_element_s *bucket_next = new_buckets[new_slot];
+ struct cx_hash_map_element_s *bucket_prev = NULL;
+ while (bucket_next != NULL &&
+ bucket_next->key.hash < elm->key.hash) {
+ bucket_prev = bucket_next;
+ bucket_next = bucket_next->next;
+ }
+
+ // insert
+ if (bucket_prev == NULL) {
+ elm->next = new_buckets[new_slot];
+ new_buckets[new_slot] = elm;
+ } else {
+ bucket_prev->next = elm;
+ elm->next = bucket_next;
+ }
+
+ // advance
+ elm = next;
+ }
+ }
+
+ // assign result to the map
+ hash_map->bucket_count = new_bucket_count;
+ cxFree(map->collection.allocator, hash_map->buckets);
+ hash_map->buckets = new_buckets;
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/iterator.h"
+
+#include <string.h>
+
+static bool cx_iter_valid(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ return iter->index < iter->elem_count;
+}
+
+static void *cx_iter_current(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ return iter->elem_handle;
+}
+
+static void cx_iter_next_fast(void *it) {
+ struct cx_iterator_s *iter = it;
+ if (iter->base.remove) {
+ iter->base.remove = false;
+ iter->elem_count--;
+ // only move the last element when we are not currently aiming
+ // at the last element already
+ if (iter->index < iter->elem_count) {
+ void *last = ((char *) iter->src_handle.m)
+ + iter->elem_count * iter->elem_size;
+ memcpy(iter->elem_handle, last, iter->elem_size);
+ }
+ } else {
+ iter->index++;
+ iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size;
+ }
+}
+
+static void cx_iter_next_slow(void *it) {
+ struct cx_iterator_s *iter = it;
+ if (iter->base.remove) {
+ iter->base.remove = false;
+ iter->elem_count--;
+
+ // number of elements to move
+ size_t remaining = iter->elem_count - iter->index;
+ if (remaining > 0) {
+ memmove(
+ iter->elem_handle,
+ ((char *) iter->elem_handle) + iter->elem_size,
+ remaining * iter->elem_size
+ );
+ }
+ } else {
+ iter->index++;
+ iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size;
+ }
+}
+
+CxIterator cxMutIterator(
+ void *array,
+ size_t elem_size,
+ size_t elem_count,
+ bool remove_keeps_order
+) {
+ CxIterator iter;
+
+ iter.index = 0;
+ iter.src_handle.m = array;
+ iter.elem_handle = array;
+ iter.elem_size = elem_size;
+ iter.elem_count = array == NULL ? 0 : elem_count;
+ iter.base.valid = cx_iter_valid;
+ iter.base.current = cx_iter_current;
+ iter.base.next = remove_keeps_order ? cx_iter_next_slow : cx_iter_next_fast;
+ iter.base.remove = false;
+ iter.base.mutating = true;
+
+ return iter;
+}
+
+CxIterator cxIterator(
+ const void *array,
+ size_t elem_size,
+ size_t elem_count
+) {
+ CxIterator iter = cxMutIterator((void*)array, elem_size, elem_count, false);
+ iter.base.mutating = false;
+ return iter;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/linked_list.h"
+#include "cx/utils.h"
+#include "cx/compare.h"
+#include <string.h>
+#include <assert.h>
+
+// LOW LEVEL LINKED LIST FUNCTIONS
+
+#define CX_LL_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
+#define ll_prev(node) CX_LL_PTR(node, loc_prev)
+#define ll_next(node) CX_LL_PTR(node, loc_next)
+#define ll_advance(node) CX_LL_PTR(node, loc_advance)
+#define ll_data(node) (((char*)(node))+loc_data)
+
+void *cx_linked_list_at(
+ const void *start,
+ size_t start_index,
+ ptrdiff_t loc_advance,
+ size_t index
+) {
+ assert(start != NULL);
+ assert(loc_advance >= 0);
+ size_t i = start_index;
+ const void *cur = start;
+ while (i != index && cur != NULL) {
+ cur = ll_advance(cur);
+ i < index ? i++ : i--;
+ }
+ return (void *) cur;
+}
+
+ssize_t cx_linked_list_find(
+ const void *start,
+ ptrdiff_t loc_advance,
+ ptrdiff_t loc_data,
+ cx_compare_func cmp_func,
+ const void *elem
+) {
+ void *dummy;
+ return cx_linked_list_find_node(
+ &dummy, start,
+ loc_advance, loc_data,
+ cmp_func, elem
+ );
+}
+
+ssize_t cx_linked_list_find_node(
+ void **result,
+ const void *start,
+ ptrdiff_t loc_advance,
+ ptrdiff_t loc_data,
+ cx_compare_func cmp_func,
+ const void *elem
+) {
+ assert(result != NULL);
+ assert(start != NULL);
+ assert(loc_advance >= 0);
+ assert(loc_data >= 0);
+ assert(cmp_func);
+
+ const void *node = start;
+ ssize_t index = 0;
+ do {
+ void *current = ll_data(node);
+ if (cmp_func(current, elem) == 0) {
+ *result = (void*) node;
+ return index;
+ }
+ node = ll_advance(node);
+ index++;
+ } while (node != NULL);
+ *result = NULL;
+ return -1;
+}
+
+void *cx_linked_list_first(
+ const void *node,
+ ptrdiff_t loc_prev
+) {
+ return cx_linked_list_last(node, loc_prev);
+}
+
+void *cx_linked_list_last(
+ const void *node,
+ ptrdiff_t loc_next
+) {
+ assert(node != NULL);
+ assert(loc_next >= 0);
+
+ const void *cur = node;
+ const void *last;
+ do {
+ last = cur;
+ } while ((cur = ll_next(cur)) != NULL);
+
+ return (void *) last;
+}
+
+void *cx_linked_list_prev(
+ const void *begin,
+ ptrdiff_t loc_next,
+ const void *node
+) {
+ assert(begin != NULL);
+ assert(node != NULL);
+ assert(loc_next >= 0);
+ if (begin == node) return NULL;
+ const void *cur = begin;
+ const void *next;
+ while (1) {
+ next = ll_next(cur);
+ if (next == node) return (void *) cur;
+ cur = next;
+ }
+}
+
+void cx_linked_list_link(
+ void *left,
+ void *right,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ assert(loc_next >= 0);
+ ll_next(left) = right;
+ if (loc_prev >= 0) {
+ ll_prev(right) = left;
+ }
+}
+
+void cx_linked_list_unlink(
+ void *left,
+ void *right,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ assert (loc_next >= 0);
+ assert(ll_next(left) == right);
+ ll_next(left) = NULL;
+ if (loc_prev >= 0) {
+ assert(ll_prev(right) == left);
+ ll_prev(right) = NULL;
+ }
+}
+
+void cx_linked_list_add(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *new_node
+) {
+ void *last;
+ if (end == NULL) {
+ assert(begin != NULL);
+ last = *begin == NULL ? NULL : cx_linked_list_last(*begin, loc_next);
+ } else {
+ last = *end;
+ }
+ cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, last, new_node, new_node);
+}
+
+void cx_linked_list_prepend(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *new_node
+) {
+ cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, NULL, new_node, new_node);
+}
+
+void cx_linked_list_insert(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *node,
+ void *new_node
+) {
+ cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, node, new_node, new_node);
+}
+
+void cx_linked_list_insert_chain(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *node,
+ void *insert_begin,
+ void *insert_end
+) {
+ // find the end of the chain, if not specified
+ if (insert_end == NULL) {
+ insert_end = cx_linked_list_last(insert_begin, loc_next);
+ }
+
+ // determine the successor
+ void *successor;
+ if (node == NULL) {
+ assert(begin != NULL || (end != NULL && loc_prev >= 0));
+ if (begin != NULL) {
+ successor = *begin;
+ *begin = insert_begin;
+ } else {
+ successor = *end == NULL ? NULL : cx_linked_list_first(*end, loc_prev);
+ }
+ } else {
+ successor = ll_next(node);
+ cx_linked_list_link(node, insert_begin, loc_prev, loc_next);
+ }
+
+ if (successor == NULL) {
+ // the list ends with the new chain
+ if (end != NULL) {
+ *end = insert_end;
+ }
+ } else {
+ cx_linked_list_link(insert_end, successor, loc_prev, loc_next);
+ }
+}
+
+void cx_linked_list_insert_sorted(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *new_node,
+ cx_compare_func cmp_func
+) {
+ assert(ll_next(new_node) == NULL);
+ cx_linked_list_insert_sorted_chain(
+ begin, end, loc_prev, loc_next, new_node, cmp_func);
+}
+
+void cx_linked_list_insert_sorted_chain(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *insert_begin,
+ cx_compare_func cmp_func
+) {
+ assert(begin != NULL);
+ assert(loc_next >= 0);
+ assert(insert_begin != NULL);
+
+ // track currently observed nodes
+ void *dest_prev = NULL;
+ void *dest = *begin;
+ void *src = insert_begin;
+
+ // special case: list is empty
+ if (dest == NULL) {
+ *begin = src;
+ if (end != NULL) {
+ *end = cx_linked_list_last(src, loc_next);
+ }
+ return;
+ }
+
+ // search the list for insertion points
+ while (dest != NULL && src != NULL) {
+ // compare current list node with source node
+ // if less or equal, skip
+ if (cmp_func(dest, src) <= 0) {
+ dest_prev = dest;
+ dest = ll_next(dest);
+ continue;
+ }
+
+ // determine chain of elements that can be inserted
+ void *end_of_chain = src;
+ void *next_in_chain = ll_next(src);
+ while (next_in_chain != NULL) {
+ // once we become larger than the list elem, break
+ if (cmp_func(dest, next_in_chain) <= 0) {
+ break;
+ }
+ // otherwise, we can insert one more
+ end_of_chain = next_in_chain;
+ next_in_chain = ll_next(next_in_chain);
+ }
+
+ // insert the elements
+ if (dest_prev == NULL) {
+ // new begin
+ *begin = src;
+ } else {
+ cx_linked_list_link(dest_prev, src, loc_prev, loc_next);
+ }
+ cx_linked_list_link(end_of_chain, dest, loc_prev, loc_next);
+
+ // continue with next
+ src = next_in_chain;
+ dest_prev = dest;
+ dest = ll_next(dest);
+ }
+
+ // insert remaining items
+ if (src != NULL) {
+ cx_linked_list_link(dest_prev, src, loc_prev, loc_next);
+ }
+
+ // determine new end of list, if requested
+ if (end != NULL) {
+ *end = cx_linked_list_last(
+ dest != NULL ? dest : dest_prev, loc_next);
+ }
+}
+
+void cx_linked_list_remove(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *node
+) {
+ assert(node != NULL);
+ assert(loc_next >= 0);
+ assert(loc_prev >= 0 || begin != NULL);
+
+ // find adjacent nodes
+ void *next = ll_next(node);
+ void *prev;
+ if (loc_prev >= 0) {
+ prev = ll_prev(node);
+ } else {
+ prev = cx_linked_list_prev(*begin, loc_next, node);
+ }
+
+ // update next pointer of prev node, or set begin
+ if (prev == NULL) {
+ if (begin != NULL) {
+ *begin = next;
+ }
+ } else {
+ ll_next(prev) = next;
+ }
+
+ // update prev pointer of next node, or set end
+ if (next == NULL) {
+ if (end != NULL) {
+ *end = prev;
+ }
+ } else if (loc_prev >= 0) {
+ ll_prev(next) = prev;
+ }
+}
+
+size_t cx_linked_list_size(
+ const void *node,
+ ptrdiff_t loc_next
+) {
+ assert(loc_next >= 0);
+ size_t size = 0;
+ while (node != NULL) {
+ node = ll_next(node);
+ size++;
+ }
+ return size;
+}
+
+#ifndef CX_LINKED_LIST_SORT_SBO_SIZE
+#define CX_LINKED_LIST_SORT_SBO_SIZE 1024
+#endif
+
+static void cx_linked_list_sort_merge(
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ ptrdiff_t loc_data,
+ size_t length,
+ void *ls,
+ void *le,
+ void *re,
+ cx_compare_func cmp_func,
+ void **begin,
+ void **end
+) {
+ void *sbo[CX_LINKED_LIST_SORT_SBO_SIZE];
+ void **sorted = length >= CX_LINKED_LIST_SORT_SBO_SIZE ?
+ malloc(sizeof(void *) * length) : sbo;
+ if (sorted == NULL) abort();
+ void *rc, *lc;
+
+ lc = ls;
+ rc = le;
+ size_t n = 0;
+ while (lc && lc != le && rc != re) {
+ if (cmp_func(ll_data(lc), ll_data(rc)) <= 0) {
+ sorted[n] = lc;
+ lc = ll_next(lc);
+ } else {
+ sorted[n] = rc;
+ rc = ll_next(rc);
+ }
+ n++;
+ }
+ while (lc && lc != le) {
+ sorted[n] = lc;
+ lc = ll_next(lc);
+ n++;
+ }
+ while (rc && rc != re) {
+ sorted[n] = rc;
+ rc = ll_next(rc);
+ n++;
+ }
+
+ // Update pointer
+ if (loc_prev >= 0) ll_prev(sorted[0]) = NULL;
+ cx_for_n (i, length - 1) {
+ cx_linked_list_link(sorted[i], sorted[i + 1], loc_prev, loc_next);
+ }
+ ll_next(sorted[length - 1]) = NULL;
+
+ *begin = sorted[0];
+ *end = sorted[length-1];
+ if (sorted != sbo) {
+ free(sorted);
+ }
+}
+
+void cx_linked_list_sort( // NOLINT(misc-no-recursion) - purposely recursive function
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ ptrdiff_t loc_data,
+ cx_compare_func cmp_func
+) {
+ assert(begin != NULL);
+ assert(loc_next >= 0);
+ assert(loc_data >= 0);
+ assert(cmp_func);
+
+ void *lc, *ls, *le, *re;
+
+ // set start node
+ ls = *begin;
+
+ // early exit when this list is empty
+ if (ls == NULL) return;
+
+ // check how many elements are already sorted
+ lc = ls;
+ size_t ln = 1;
+ while (ll_next(lc) != NULL && cmp_func(ll_data(ll_next(lc)), ll_data(lc)) > 0) {
+ lc = ll_next(lc);
+ ln++;
+ }
+ le = ll_next(lc);
+
+ // if first unsorted node is NULL, the list is already completely sorted
+ if (le != NULL) {
+ void *rc;
+ size_t rn = 1;
+ rc = le;
+ // skip already sorted elements
+ while (ll_next(rc) != NULL && cmp_func(ll_data(ll_next(rc)), ll_data(rc)) > 0) {
+ rc = ll_next(rc);
+ rn++;
+ }
+ re = ll_next(rc);
+
+ // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them
+ void *sorted_begin, *sorted_end;
+ cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
+ ln + rn, ls, le, re, cmp_func,
+ &sorted_begin, &sorted_end);
+
+ // Something left? Sort it!
+ size_t remainder_length = cx_linked_list_size(re, loc_next);
+ if (remainder_length > 0) {
+ void *remainder = re;
+ cx_linked_list_sort(&remainder, NULL, loc_prev, loc_next, loc_data, cmp_func);
+
+ // merge sorted list with (also sorted) remainder
+ cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
+ ln + rn + remainder_length,
+ sorted_begin, remainder, NULL, cmp_func,
+ &sorted_begin, &sorted_end);
+ }
+ *begin = sorted_begin;
+ if (end) *end = sorted_end;
+ }
+}
+
+int cx_linked_list_compare(
+ const void *begin_left,
+ const void *begin_right,
+ ptrdiff_t loc_advance,
+ ptrdiff_t loc_data,
+ cx_compare_func cmp_func
+) {
+ const void *left = begin_left, *right = begin_right;
+
+ while (left != NULL && right != NULL) {
+ const void *left_data = ll_data(left);
+ const void *right_data = ll_data(right);
+ int result = cmp_func(left_data, right_data);
+ if (result != 0) return result;
+ left = ll_advance(left);
+ right = ll_advance(right);
+ }
+
+ if (left != NULL) { return 1; }
+ else if (right != NULL) { return -1; }
+ else { return 0; }
+}
+
+void cx_linked_list_reverse(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ assert(begin != NULL);
+ assert(loc_next >= 0);
+
+ // swap all links
+ void *prev = NULL;
+ void *cur = *begin;
+ while (cur != NULL) {
+ void *next = ll_next(cur);
+
+ ll_next(cur) = prev;
+ if (loc_prev >= 0) {
+ ll_prev(cur) = next;
+ }
+
+ prev = cur;
+ cur = next;
+ }
+
+ // update begin and end
+ if (end != NULL) {
+ *end = *begin;
+ }
+ *begin = prev;
+}
+
+// HIGH LEVEL LINKED LIST IMPLEMENTATION
+
+typedef struct cx_linked_list_node cx_linked_list_node;
+struct cx_linked_list_node {
+ cx_linked_list_node *prev;
+ cx_linked_list_node *next;
+ char payload[];
+};
+
+#define CX_LL_LOC_PREV offsetof(cx_linked_list_node, prev)
+#define CX_LL_LOC_NEXT offsetof(cx_linked_list_node, next)
+#define CX_LL_LOC_DATA offsetof(cx_linked_list_node, payload)
+
+typedef struct {
+ struct cx_list_s base;
+ cx_linked_list_node *begin;
+ cx_linked_list_node *end;
+} cx_linked_list;
+
+static cx_linked_list_node *cx_ll_node_at(
+ const cx_linked_list *list,
+ size_t index
+) {
+ if (index >= list->base.collection.size) {
+ return NULL;
+ } else if (index > list->base.collection.size / 2) {
+ return cx_linked_list_at(list->end, list->base.collection.size - 1, CX_LL_LOC_PREV, index);
+ } else {
+ return cx_linked_list_at(list->begin, 0, CX_LL_LOC_NEXT, index);
+ }
+}
+
+static cx_linked_list_node *cx_ll_malloc_node(const struct cx_list_s *list) {
+ return cxMalloc(list->collection.allocator,
+ sizeof(cx_linked_list_node) + list->collection.elem_size);
+}
+
+static int cx_ll_insert_at(
+ struct cx_list_s *list,
+ cx_linked_list_node *node,
+ const void *elem
+) {
+
+ // create the new new_node
+ cx_linked_list_node *new_node = cx_ll_malloc_node(list);
+
+ // sortir if failed
+ if (new_node == NULL) return 1;
+
+ // initialize new new_node
+ new_node->prev = new_node->next = NULL;
+ memcpy(new_node->payload, elem, list->collection.elem_size);
+
+ // insert
+ cx_linked_list *ll = (cx_linked_list *) list;
+ cx_linked_list_insert_chain(
+ (void **) &ll->begin, (void **) &ll->end,
+ CX_LL_LOC_PREV, CX_LL_LOC_NEXT,
+ node, new_node, new_node
+ );
+
+ // increase the size and return
+ list->collection.size++;
+ return 0;
+}
+
+static size_t cx_ll_insert_array(
+ struct cx_list_s *list,
+ size_t index,
+ const void *array,
+ size_t n
+) {
+ // out-of bounds and corner case check
+ if (index > list->collection.size || n == 0) return 0;
+
+ // find position efficiently
+ cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1);
+
+ // perform first insert
+ if (0 != cx_ll_insert_at(list, node, array)) {
+ return 1;
+ }
+
+ // is there more?
+ if (n == 1) return 1;
+
+ // we now know exactly where we are
+ node = node == NULL ? ((cx_linked_list *) list)->begin : node->next;
+
+ // we can add the remaining nodes and immediately advance to the inserted node
+ const char *source = array;
+ for (size_t i = 1; i < n; i++) {
+ source += list->collection.elem_size;
+ if (0 != cx_ll_insert_at(list, node, source)) {
+ return i;
+ }
+ node = node->next;
+ }
+ return n;
+}
+
+static int cx_ll_insert_element(
+ struct cx_list_s *list,
+ size_t index,
+ const void *element
+) {
+ return 1 != cx_ll_insert_array(list, index, element, 1);
+}
+
+static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func;
+
+static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r) {
+ const cx_linked_list_node *left = l;
+ const cx_linked_list_node *right = r;
+ return cx_ll_insert_sorted_cmp_func(left->payload, right->payload);
+}
+
+static size_t cx_ll_insert_sorted(
+ struct cx_list_s *list,
+ const void *array,
+ size_t n
+) {
+ // special case
+ if (n == 0) return 0;
+
+ // create a new chain of nodes
+ cx_linked_list_node *chain = cx_ll_malloc_node(list);
+ if (chain == NULL) return 0;
+
+ memcpy(chain->payload, array, list->collection.elem_size);
+ chain->prev = NULL;
+ chain->next = NULL;
+
+ // add all elements from the array to that chain
+ cx_linked_list_node *prev = chain;
+ const char *src = array;
+ size_t inserted = 1;
+ for (; inserted < n; inserted++) {
+ cx_linked_list_node *next = cx_ll_malloc_node(list);
+ if (next == NULL) break;
+ src += list->collection.elem_size;
+ memcpy(next->payload, src, list->collection.elem_size);
+ prev->next = next;
+ next->prev = prev;
+ prev = next;
+ }
+ prev->next = NULL;
+
+ // invoke the low level function
+ cx_linked_list *ll = (cx_linked_list *) list;
+ cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc;
+ cx_linked_list_insert_sorted_chain(
+ (void **) &ll->begin,
+ (void **) &ll->end,
+ CX_LL_LOC_PREV,
+ CX_LL_LOC_NEXT,
+ chain,
+ cx_ll_insert_sorted_cmp_helper
+ );
+
+ // adjust the list metadata
+ list->collection.size += inserted;
+
+ return inserted;
+}
+
+static int cx_ll_remove(
+ struct cx_list_s *list,
+ size_t index
+) {
+ cx_linked_list *ll = (cx_linked_list *) list;
+ cx_linked_list_node *node = cx_ll_node_at(ll, index);
+
+ // out-of-bounds check
+ if (node == NULL) return 1;
+
+ // element destruction
+ cx_invoke_destructor(list, node->payload);
+
+ // remove
+ cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+ CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+
+ // adjust size
+ list->collection.size--;
+
+ // free and return
+ cxFree(list->collection.allocator, node);
+
+ return 0;
+}
+
+static void cx_ll_clear(struct cx_list_s *list) {
+ if (list->collection.size == 0) return;
+
+ cx_linked_list *ll = (cx_linked_list *) list;
+ cx_linked_list_node *node = ll->begin;
+ while (node != NULL) {
+ cx_invoke_destructor(list, node->payload);
+ cx_linked_list_node *next = node->next;
+ cxFree(list->collection.allocator, node);
+ node = next;
+ }
+ ll->begin = ll->end = NULL;
+ list->collection.size = 0;
+}
+
+#ifndef CX_LINKED_LIST_SWAP_SBO_SIZE
+#define CX_LINKED_LIST_SWAP_SBO_SIZE 128
+#endif
+unsigned cx_linked_list_swap_sbo_size = CX_LINKED_LIST_SWAP_SBO_SIZE;
+
+static int cx_ll_swap(
+ struct cx_list_s *list,
+ size_t i,
+ size_t j
+) {
+ if (i >= list->collection.size || j >= list->collection.size) return 1;
+ if (i == j) return 0;
+
+ // perform an optimized search that finds both elements in one run
+ cx_linked_list *ll = (cx_linked_list *) list;
+ size_t mid = list->collection.size / 2;
+ size_t left, right;
+ if (i < j) {
+ left = i;
+ right = j;
+ } else {
+ left = j;
+ right = i;
+ }
+ cx_linked_list_node *nleft, *nright;
+ if (left < mid && right < mid) {
+ // case 1: both items left from mid
+ nleft = cx_ll_node_at(ll, left);
+ assert(nleft != NULL);
+ nright = nleft;
+ for (size_t c = left; c < right; c++) {
+ nright = nright->next;
+ }
+ } else if (left >= mid && right >= mid) {
+ // case 2: both items right from mid
+ nright = cx_ll_node_at(ll, right);
+ assert(nright != NULL);
+ nleft = nright;
+ for (size_t c = right; c > left; c--) {
+ nleft = nleft->prev;
+ }
+ } else {
+ // case 3: one item left, one item right
+
+ // chose the closest to begin / end
+ size_t closest;
+ size_t other;
+ size_t diff2boundary = list->collection.size - right - 1;
+ if (left <= diff2boundary) {
+ closest = left;
+ other = right;
+ nleft = cx_ll_node_at(ll, left);
+ } else {
+ closest = right;
+ other = left;
+ diff2boundary = left;
+ nright = cx_ll_node_at(ll, right);
+ }
+
+ // is other element closer to us or closer to boundary?
+ if (right - left <= diff2boundary) {
+ // search other element starting from already found element
+ if (closest == left) {
+ nright = nleft;
+ for (size_t c = left; c < right; c++) {
+ nright = nright->next;
+ }
+ } else {
+ nleft = nright;
+ for (size_t c = right; c > left; c--) {
+ nleft = nleft->prev;
+ }
+ }
+ } else {
+ // search other element starting at the boundary
+ if (closest == left) {
+ nright = cx_ll_node_at(ll, other);
+ } else {
+ nleft = cx_ll_node_at(ll, other);
+ }
+ }
+ }
+
+ if (list->collection.elem_size > CX_LINKED_LIST_SWAP_SBO_SIZE) {
+ cx_linked_list_node *prev = nleft->prev;
+ cx_linked_list_node *next = nright->next;
+ cx_linked_list_node *midstart = nleft->next;
+ cx_linked_list_node *midend = nright->prev;
+
+ if (prev == NULL) {
+ ll->begin = nright;
+ } else {
+ prev->next = nright;
+ }
+ nright->prev = prev;
+ if (midstart == nright) {
+ // special case: both nodes are adjacent
+ nright->next = nleft;
+ nleft->prev = nright;
+ } else {
+ // likely case: a chain is between the two nodes
+ nright->next = midstart;
+ midstart->prev = nright;
+ midend->next = nleft;
+ nleft->prev = midend;
+ }
+ nleft->next = next;
+ if (next == NULL) {
+ ll->end = nleft;
+ } else {
+ next->prev = nleft;
+ }
+ } else {
+ // swap payloads to avoid relinking
+ char buf[CX_LINKED_LIST_SWAP_SBO_SIZE];
+ memcpy(buf, nleft->payload, list->collection.elem_size);
+ memcpy(nleft->payload, nright->payload, list->collection.elem_size);
+ memcpy(nright->payload, buf, list->collection.elem_size);
+ }
+
+ return 0;
+}
+
+static void *cx_ll_at(
+ const struct cx_list_s *list,
+ size_t index
+) {
+ cx_linked_list *ll = (cx_linked_list *) list;
+ cx_linked_list_node *node = cx_ll_node_at(ll, index);
+ return node == NULL ? NULL : node->payload;
+}
+
+static ssize_t cx_ll_find_remove(
+ struct cx_list_s *list,
+ const void *elem,
+ bool remove
+) {
+ if (remove) {
+ cx_linked_list *ll = ((cx_linked_list *) list);
+ cx_linked_list_node *node;
+ ssize_t index = cx_linked_list_find_node(
+ (void **) &node,
+ ll->begin,
+ CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+ list->collection.cmpfunc, elem
+ );
+ if (node != NULL) {
+ cx_invoke_destructor(list, node->payload);
+ cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+ CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+ list->collection.size--;
+ cxFree(list->collection.allocator, node);
+ }
+ return index;
+ } else {
+ return cx_linked_list_find(
+ ((cx_linked_list *) list)->begin,
+ CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+ list->collection.cmpfunc, elem
+ );
+ }
+}
+
+static void cx_ll_sort(struct cx_list_s *list) {
+ cx_linked_list *ll = (cx_linked_list *) list;
+ cx_linked_list_sort((void **) &ll->begin, (void **) &ll->end,
+ CX_LL_LOC_PREV, CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+ list->collection.cmpfunc);
+}
+
+static void cx_ll_reverse(struct cx_list_s *list) {
+ cx_linked_list *ll = (cx_linked_list *) list;
+ cx_linked_list_reverse((void **) &ll->begin, (void **) &ll->end, CX_LL_LOC_PREV, CX_LL_LOC_NEXT);
+}
+
+static int cx_ll_compare(
+ const struct cx_list_s *list,
+ const struct cx_list_s *other
+) {
+ cx_linked_list *left = (cx_linked_list *) list;
+ cx_linked_list *right = (cx_linked_list *) other;
+ return cx_linked_list_compare(left->begin, right->begin,
+ CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+ list->collection.cmpfunc);
+}
+
+static bool cx_ll_iter_valid(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ return iter->elem_handle != NULL;
+}
+
+static void cx_ll_iter_next(void *it) {
+ struct cx_iterator_s *iter = it;
+ if (iter->base.remove) {
+ iter->base.remove = false;
+ struct cx_list_s *list = iter->src_handle.m;
+ cx_linked_list *ll = iter->src_handle.m;
+ cx_linked_list_node *node = iter->elem_handle;
+ iter->elem_handle = node->next;
+ cx_invoke_destructor(list, node->payload);
+ cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+ CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+ list->collection.size--;
+ cxFree(list->collection.allocator, node);
+ } else {
+ iter->index++;
+ cx_linked_list_node *node = iter->elem_handle;
+ iter->elem_handle = node->next;
+ }
+}
+
+static void cx_ll_iter_prev(void *it) {
+ struct cx_iterator_s *iter = it;
+ if (iter->base.remove) {
+ iter->base.remove = false;
+ struct cx_list_s *list = iter->src_handle.m;
+ cx_linked_list *ll = iter->src_handle.m;
+ cx_linked_list_node *node = iter->elem_handle;
+ iter->elem_handle = node->prev;
+ iter->index--;
+ cx_invoke_destructor(list, node->payload);
+ cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+ CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+ list->collection.size--;
+ cxFree(list->collection.allocator, node);
+ } else {
+ iter->index--;
+ cx_linked_list_node *node = iter->elem_handle;
+ iter->elem_handle = node->prev;
+ }
+}
+
+static void *cx_ll_iter_current(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ cx_linked_list_node *node = iter->elem_handle;
+ return node->payload;
+}
+
+static CxIterator cx_ll_iterator(
+ const struct cx_list_s *list,
+ size_t index,
+ bool backwards
+) {
+ CxIterator iter;
+ iter.index = index;
+ iter.src_handle.c = list;
+ iter.elem_handle = cx_ll_node_at((const cx_linked_list *) list, index);
+ iter.elem_size = list->collection.elem_size;
+ iter.elem_count = list->collection.size;
+ iter.base.valid = cx_ll_iter_valid;
+ iter.base.current = cx_ll_iter_current;
+ iter.base.next = backwards ? cx_ll_iter_prev : cx_ll_iter_next;
+ iter.base.mutating = false;
+ iter.base.remove = false;
+ return iter;
+}
+
+static int cx_ll_insert_iter(
+ CxIterator *iter,
+ const void *elem,
+ int prepend
+) {
+ struct cx_list_s *list = iter->src_handle.m;
+ cx_linked_list_node *node = iter->elem_handle;
+ if (node != NULL) {
+ assert(prepend >= 0 && prepend <= 1);
+ cx_linked_list_node *choice[2] = {node, node->prev};
+ int result = cx_ll_insert_at(list, choice[prepend], elem);
+ if (result == 0) {
+ iter->elem_count++;
+ if (prepend) {
+ iter->index++;
+ }
+ }
+ return result;
+ } else {
+ int result = cx_ll_insert_element(list, list->collection.size, elem);
+ if (result == 0) {
+ iter->elem_count++;
+ iter->index = list->collection.size;
+ }
+ return result;
+ }
+}
+
+static void cx_ll_destructor(CxList *list) {
+ cx_linked_list *ll = (cx_linked_list *) list;
+
+ cx_linked_list_node *node = ll->begin;
+ while (node) {
+ cx_invoke_destructor(list, node->payload);
+ void *next = node->next;
+ cxFree(list->collection.allocator, node);
+ node = next;
+ }
+
+ cxFree(list->collection.allocator, list);
+}
+
+static cx_list_class cx_linked_list_class = {
+ cx_ll_destructor,
+ cx_ll_insert_element,
+ cx_ll_insert_array,
+ cx_ll_insert_sorted,
+ cx_ll_insert_iter,
+ cx_ll_remove,
+ cx_ll_clear,
+ cx_ll_swap,
+ cx_ll_at,
+ cx_ll_find_remove,
+ cx_ll_sort,
+ cx_ll_compare,
+ cx_ll_reverse,
+ cx_ll_iterator,
+};
+
+CxList *cxLinkedListCreate(
+ const CxAllocator *allocator,
+ cx_compare_func comparator,
+ size_t elem_size
+) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+
+ cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list));
+ if (list == NULL) return NULL;
+
+ list->base.cl = &cx_linked_list_class;
+ list->base.collection.allocator = allocator;
+
+ if (elem_size > 0) {
+ list->base.collection.elem_size = elem_size;
+ list->base.collection.cmpfunc = comparator;
+ } else {
+ list->base.collection.cmpfunc = comparator == NULL ? cx_cmp_ptr : comparator;
+ cxListStorePointers((CxList *) list);
+ }
+
+ return (CxList *) list;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/list.h"
+
+#include <string.h>
+
+// <editor-fold desc="Store Pointers Functionality">
+
+static _Thread_local cx_compare_func cx_pl_cmpfunc_impl;
+
+static int cx_pl_cmpfunc(
+ const void *l,
+ const void *r
+) {
+ void *const *lptr = l;
+ void *const *rptr = r;
+ const void *left = lptr == NULL ? NULL : *lptr;
+ const void *right = rptr == NULL ? NULL : *rptr;
+ return cx_pl_cmpfunc_impl(left, right);
+}
+
+static void cx_pl_hack_cmpfunc(const struct cx_list_s *list) {
+ // cast away const - this is the hacky thing
+ struct cx_collection_s *l = (struct cx_collection_s*) &list->collection;
+ cx_pl_cmpfunc_impl = l->cmpfunc;
+ l->cmpfunc = cx_pl_cmpfunc;
+}
+
+static void cx_pl_unhack_cmpfunc(const struct cx_list_s *list) {
+ // cast away const - this is the hacky thing
+ struct cx_collection_s *l = (struct cx_collection_s*) &list->collection;
+ l->cmpfunc = cx_pl_cmpfunc_impl;
+}
+
+static void cx_pl_destructor(struct cx_list_s *list) {
+ list->climpl->destructor(list);
+}
+
+static int cx_pl_insert_element(
+ struct cx_list_s *list,
+ size_t index,
+ const void *element
+) {
+ return list->climpl->insert_element(list, index, &element);
+}
+
+static size_t cx_pl_insert_array(
+ struct cx_list_s *list,
+ size_t index,
+ const void *array,
+ size_t n
+) {
+ return list->climpl->insert_array(list, index, array, n);
+}
+
+static size_t cx_pl_insert_sorted(
+ struct cx_list_s *list,
+ const void *array,
+ size_t n
+) {
+ cx_pl_hack_cmpfunc(list);
+ size_t result = list->climpl->insert_sorted(list, array, n);
+ cx_pl_unhack_cmpfunc(list);
+ return result;
+}
+
+static int cx_pl_insert_iter(
+ struct cx_iterator_s *iter,
+ const void *elem,
+ int prepend
+) {
+ struct cx_list_s *list = iter->src_handle.m;
+ return list->climpl->insert_iter(iter, &elem, prepend);
+}
+
+static int cx_pl_remove(
+ struct cx_list_s *list,
+ size_t index
+) {
+ return list->climpl->remove(list, index);
+}
+
+static void cx_pl_clear(struct cx_list_s *list) {
+ list->climpl->clear(list);
+}
+
+static int cx_pl_swap(
+ struct cx_list_s *list,
+ size_t i,
+ size_t j
+) {
+ return list->climpl->swap(list, i, j);
+}
+
+static void *cx_pl_at(
+ const struct cx_list_s *list,
+ size_t index
+) {
+ void **ptr = list->climpl->at(list, index);
+ return ptr == NULL ? NULL : *ptr;
+}
+
+static ssize_t cx_pl_find_remove(
+ struct cx_list_s *list,
+ const void *elem,
+ bool remove
+) {
+ cx_pl_hack_cmpfunc(list);
+ ssize_t ret = list->climpl->find_remove(list, &elem, remove);
+ cx_pl_unhack_cmpfunc(list);
+ return ret;
+}
+
+static void cx_pl_sort(struct cx_list_s *list) {
+ cx_pl_hack_cmpfunc(list);
+ list->climpl->sort(list);
+ cx_pl_unhack_cmpfunc(list);
+}
+
+static int cx_pl_compare(
+ const struct cx_list_s *list,
+ const struct cx_list_s *other
+) {
+ cx_pl_hack_cmpfunc(list);
+ int ret = list->climpl->compare(list, other);
+ cx_pl_unhack_cmpfunc(list);
+ return ret;
+}
+
+static void cx_pl_reverse(struct cx_list_s *list) {
+ list->climpl->reverse(list);
+}
+
+static void *cx_pl_iter_current(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ void **ptr = iter->base.current_impl(it);
+ return ptr == NULL ? NULL : *ptr;
+}
+
+static struct cx_iterator_s cx_pl_iterator(
+ const struct cx_list_s *list,
+ size_t index,
+ bool backwards
+) {
+ struct cx_iterator_s iter = list->climpl->iterator(list, index, backwards);
+ iter.base.current_impl = iter.base.current;
+ iter.base.current = cx_pl_iter_current;
+ return iter;
+}
+
+static cx_list_class cx_pointer_list_class = {
+ cx_pl_destructor,
+ cx_pl_insert_element,
+ cx_pl_insert_array,
+ cx_pl_insert_sorted,
+ cx_pl_insert_iter,
+ cx_pl_remove,
+ cx_pl_clear,
+ cx_pl_swap,
+ cx_pl_at,
+ cx_pl_find_remove,
+ cx_pl_sort,
+ cx_pl_compare,
+ cx_pl_reverse,
+ cx_pl_iterator,
+};
+
+void cxListStoreObjects(CxList *list) {
+ list->collection.store_pointer = false;
+ if (list->climpl != NULL) {
+ list->cl = list->climpl;
+ list->climpl = NULL;
+ }
+}
+
+void cxListStorePointers(CxList *list) {
+ list->collection.elem_size = sizeof(void *);
+ list->collection.store_pointer = true;
+ list->climpl = list->cl;
+ list->cl = &cx_pointer_list_class;
+}
+
+// </editor-fold>
+
+// <editor-fold desc="empty list implementation">
+
+static void cx_emptyl_noop(__attribute__((__unused__)) CxList *list) {
+ // this is a noop, but MUST be implemented
+}
+
+static void *cx_emptyl_at(
+ __attribute__((__unused__)) const struct cx_list_s *list,
+ __attribute__((__unused__)) size_t index
+) {
+ return NULL;
+}
+
+static ssize_t cx_emptyl_find_remove(
+ __attribute__((__unused__)) struct cx_list_s *list,
+ __attribute__((__unused__)) const void *elem,
+ __attribute__((__unused__)) bool remove
+) {
+ return -1;
+}
+
+static bool cx_emptyl_iter_valid(__attribute__((__unused__)) const void *iter) {
+ return false;
+}
+
+static CxIterator cx_emptyl_iterator(
+ const struct cx_list_s *list,
+ size_t index,
+ __attribute__((__unused__)) bool backwards
+) {
+ CxIterator iter = {0};
+ iter.src_handle.c = list;
+ iter.index = index;
+ iter.base.valid = cx_emptyl_iter_valid;
+ return iter;
+}
+
+static cx_list_class cx_empty_list_class = {
+ cx_emptyl_noop,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ cx_emptyl_noop,
+ NULL,
+ cx_emptyl_at,
+ cx_emptyl_find_remove,
+ cx_emptyl_noop,
+ NULL,
+ cx_emptyl_noop,
+ cx_emptyl_iterator,
+};
+
+CxList cx_empty_list = {
+ {
+ NULL,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ false
+ },
+ &cx_empty_list_class,
+ NULL
+};
+
+CxList *const cxEmptyList = &cx_empty_list;
+
+// </editor-fold>
+
+#define invoke_list_func(name, list, ...) \
+ ((list)->climpl == NULL ? (list)->cl->name : (list)->climpl->name) \
+ (list, __VA_ARGS__)
+
+size_t cx_list_default_insert_array(
+ struct cx_list_s *list,
+ size_t index,
+ const void *data,
+ size_t n
+) {
+ size_t elem_size = list->collection.elem_size;
+ const char *src = data;
+ size_t i = 0;
+ for (; i < n; i++) {
+ if (0 != invoke_list_func(insert_element,
+ list, index + i, src + (i * elem_size))) {
+ return i;
+ }
+ }
+ return i;
+}
+
+size_t cx_list_default_insert_sorted(
+ struct cx_list_s *list,
+ const void *sorted_data,
+ size_t n
+) {
+ // corner case
+ if (n == 0) return 0;
+
+ size_t elem_size = list->collection.elem_size;
+ cx_compare_func cmp = list->collection.cmpfunc;
+ const char *src = sorted_data;
+
+ // track indices and number of inserted items
+ size_t di = 0, si = 0, inserted = 0;
+
+ // search the list for insertion points
+ for (; di < list->collection.size; di++) {
+ const void *list_elm = invoke_list_func(at, list, di);
+
+ // compare current list element with first source element
+ // if less or equal, skip
+ if (cmp(list_elm, src) <= 0) {
+ continue;
+ }
+
+ // determine number of consecutive elements that can be inserted
+ size_t ins = 1;
+ const char *next = src;
+ while (++si < n) {
+ next += elem_size;
+ // once we become larger than the list elem, break
+ if (cmp(list_elm, next) <= 0) {
+ break;
+ }
+ // otherwise, we can insert one more
+ ins++;
+ }
+
+ // insert the elements at location si
+ if (ins == 1) {
+ if (0 != invoke_list_func(insert_element,
+ list, di, src))
+ return inserted;
+ } else {
+ size_t r = invoke_list_func(insert_array, list, di, src, ins);
+ if (r < ins) return inserted + r;
+ }
+ inserted += ins;
+ di += ins;
+
+ // everything inserted?
+ if (inserted == n) return inserted;
+ src = next;
+ }
+
+ // insert remaining items
+ if (si < n) {
+ inserted += invoke_list_func(insert_array, list, di, src, n - si);
+ }
+
+ return inserted;
+}
+
+void cx_list_default_sort(struct cx_list_s *list) {
+ size_t elem_size = list->collection.elem_size;
+ size_t list_size = list->collection.size;
+ void *tmp = malloc(elem_size * list_size);
+ if (tmp == NULL) abort();
+
+ // copy elements from source array
+ char *loc = tmp;
+ for (size_t i = 0; i < list_size; i++) {
+ void *src = invoke_list_func(at, list, i);
+ memcpy(loc, src, elem_size);
+ loc += elem_size;
+ }
+
+ // qsort
+ qsort(tmp, list_size, elem_size,
+ list->collection.cmpfunc);
+
+ // copy elements back
+ loc = tmp;
+ for (size_t i = 0; i < list_size; i++) {
+ void *dest = invoke_list_func(at, list, i);
+ memcpy(dest, loc, elem_size);
+ loc += elem_size;
+ }
+
+ free(tmp);
+}
+
+int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j) {
+ if (i == j) return 0;
+ if (i >= list->collection.size) return 1;
+ if (j >= list->collection.size) return 1;
+
+ size_t elem_size = list->collection.elem_size;
+
+ void *tmp = malloc(elem_size);
+ if (tmp == NULL) return 1;
+
+ void *ip = invoke_list_func(at, list, i);
+ void *jp = invoke_list_func(at, list, j);
+
+ memcpy(tmp, ip, elem_size);
+ memcpy(ip, jp, elem_size);
+ memcpy(jp, tmp, elem_size);
+
+ free(tmp);
+
+ return 0;
+}
+
+void cxListDestroy(CxList *list) {
+ list->cl->destructor(list);
+}
+
+int cxListCompare(
+ const CxList *list,
+ const CxList *other
+) {
+ bool cannot_optimize = false;
+
+ // if one is storing pointers but the other is not
+ cannot_optimize |= list->collection.store_pointer ^ other->collection.store_pointer;
+
+ // if one class is wrapped but the other is not
+ cannot_optimize |= (list->climpl == NULL) ^ (other->climpl == NULL);
+
+ // if the compare functions do not match or both are NULL
+ if (!cannot_optimize) {
+ cx_compare_func list_cmp = (cx_compare_func) (list->climpl != NULL ?
+ list->climpl->compare : list->cl->compare);
+ cx_compare_func other_cmp = (cx_compare_func) (other->climpl != NULL ?
+ other->climpl->compare : other->cl->compare);
+ cannot_optimize |= list_cmp != other_cmp;
+ cannot_optimize |= list_cmp == NULL;
+ }
+
+ if (cannot_optimize) {
+ // lists are definitely different - cannot use internal compare function
+ if (list->collection.size == other->collection.size) {
+ CxIterator left = list->cl->iterator(list, 0, false);
+ CxIterator right = other->cl->iterator(other, 0, false);
+ for (size_t i = 0; i < list->collection.size; i++) {
+ void *leftValue = cxIteratorCurrent(left);
+ void *rightValue = cxIteratorCurrent(right);
+ int d = list->collection.cmpfunc(leftValue, rightValue);
+ if (d != 0) {
+ return d;
+ }
+ cxIteratorNext(left);
+ cxIteratorNext(right);
+ }
+ return 0;
+ } else {
+ return list->collection.size < other->collection.size ? -1 : 1;
+ }
+ } else {
+ // lists are compatible
+ return list->cl->compare(list, other);
+ }
+}
+
+CxIterator cxListMutIteratorAt(
+ CxList *list,
+ size_t index
+) {
+ CxIterator it = list->cl->iterator(list, index, false);
+ it.base.mutating = true;
+ return it;
+}
+
+CxIterator cxListMutBackwardsIteratorAt(
+ CxList *list,
+ size_t index
+) {
+ CxIterator it = list->cl->iterator(list, index, true);
+ it.base.mutating = true;
+ return it;
+}
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Mike Becker, Olaf Wintermann All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "cx/map.h"\r
+#include <string.h>\r
+\r
+// <editor-fold desc="empty map implementation">\r
+\r
+static void cx_empty_map_noop(__attribute__((__unused__)) CxMap *map) {\r
+ // this is a noop, but MUST be implemented\r
+}\r
+\r
+static void *cx_empty_map_get(\r
+ __attribute__((__unused__)) const CxMap *map,\r
+ __attribute__((__unused__)) CxHashKey key\r
+) {\r
+ return NULL;\r
+}\r
+\r
+static bool cx_empty_map_iter_valid(__attribute__((__unused__)) const void *iter) {\r
+ return false;\r
+}\r
+\r
+static CxIterator cx_empty_map_iterator(\r
+ const struct cx_map_s *map,\r
+ __attribute__((__unused__)) enum cx_map_iterator_type type\r
+) {\r
+ CxIterator iter = {0};\r
+ iter.src_handle.c = map;\r
+ iter.base.valid = cx_empty_map_iter_valid;\r
+ return iter;\r
+}\r
+\r
+static struct cx_map_class_s cx_empty_map_class = {\r
+ cx_empty_map_noop,\r
+ cx_empty_map_noop,\r
+ NULL,\r
+ cx_empty_map_get,\r
+ NULL,\r
+ cx_empty_map_iterator\r
+};\r
+\r
+CxMap cx_empty_map = {\r
+ {\r
+ NULL,\r
+ NULL,\r
+ 0,\r
+ 0,\r
+ NULL,\r
+ NULL,\r
+ NULL,\r
+ false\r
+ },\r
+ &cx_empty_map_class\r
+};\r
+\r
+CxMap *const cxEmptyMap = &cx_empty_map;\r
+\r
+// </editor-fold>\r
+\r
+CxIterator cxMapMutIteratorValues(CxMap *map) {\r
+ CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);\r
+ it.base.mutating = true;\r
+ return it;\r
+}\r
+\r
+CxIterator cxMapMutIteratorKeys(CxMap *map) {\r
+ CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);\r
+ it.base.mutating = true;\r
+ return it;\r
+}\r
+\r
+CxIterator cxMapMutIterator(CxMap *map) {\r
+ CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);\r
+ it.base.mutating = true;\r
+ return it;\r
+}\r
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/mempool.h"
+#include "cx/utils.h"
+#include <string.h>
+
+struct cx_mempool_memory_s {
+ /** The destructor. */
+ cx_destructor_func destructor;
+ /** The actual memory. */
+ char c[];
+};
+
+static void *cx_mempool_malloc(
+ void *p,
+ size_t n
+) {
+ struct cx_mempool_s *pool = p;
+
+ if (pool->size >= pool->capacity) {
+ size_t newcap = pool->capacity - (pool->capacity % 16) + 16;
+ struct cx_mempool_memory_s **newdata = realloc(pool->data, newcap*sizeof(struct cx_mempool_memory_s*));
+ if (newdata == NULL) {
+ return NULL;
+ }
+ pool->data = newdata;
+ pool->capacity = newcap;
+ }
+
+ struct cx_mempool_memory_s *mem = malloc(sizeof(cx_destructor_func) + n);
+ if (mem == NULL) {
+ return NULL;
+ }
+
+ mem->destructor = pool->auto_destr;
+ pool->data[pool->size] = mem;
+ pool->size++;
+
+ return mem->c;
+}
+
+static void *cx_mempool_calloc(
+ void *p,
+ size_t nelem,
+ size_t elsize
+) {
+ size_t msz;
+ if (cx_szmul(nelem, elsize, &msz)) {
+ return NULL;
+ }
+ void *ptr = cx_mempool_malloc(p, msz);
+ if (ptr == NULL) {
+ return NULL;
+ }
+ memset(ptr, 0, nelem * elsize);
+ return ptr;
+}
+
+static void *cx_mempool_realloc(
+ void *p,
+ void *ptr,
+ size_t n
+) {
+ struct cx_mempool_s *pool = p;
+
+ struct cx_mempool_memory_s *mem, *newm;
+ mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func));
+ newm = realloc(mem, n + sizeof(cx_destructor_func));
+
+ if (newm == NULL) {
+ return NULL;
+ }
+ if (mem != newm) {
+ cx_for_n(i, pool->size) {
+ if (pool->data[i] == mem) {
+ pool->data[i] = newm;
+ return ((char*)newm) + sizeof(cx_destructor_func);
+ }
+ }
+ abort();
+ } else {
+ return ptr;
+ }
+}
+
+static void cx_mempool_free(
+ void *p,
+ void *ptr
+) {
+ if (!ptr) return;
+ struct cx_mempool_s *pool = p;
+
+ struct cx_mempool_memory_s *mem = (struct cx_mempool_memory_s *)
+ ((char *) ptr - sizeof(cx_destructor_func));
+
+ cx_for_n(i, pool->size) {
+ if (mem == pool->data[i]) {
+ if (mem->destructor) {
+ mem->destructor(mem->c);
+ }
+ free(mem);
+ size_t last_index = pool->size - 1;
+ if (i != last_index) {
+ pool->data[i] = pool->data[last_index];
+ pool->data[last_index] = NULL;
+ }
+ pool->size--;
+ return;
+ }
+ }
+ abort();
+}
+
+void cxMempoolDestroy(CxMempool *pool) {
+ struct cx_mempool_memory_s *mem;
+ cx_for_n(i, pool->size) {
+ mem = pool->data[i];
+ if (mem->destructor) {
+ mem->destructor(mem->c);
+ }
+ free(mem);
+ }
+ free(pool->data);
+ free((void*) pool->allocator);
+ free(pool);
+}
+
+void cxMempoolSetDestructor(
+ void *ptr,
+ cx_destructor_func func
+) {
+ *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func;
+}
+
+struct cx_mempool_foreign_mem_s {
+ cx_destructor_func destr;
+ void* mem;
+};
+
+static void cx_mempool_destr_foreign_mem(void* ptr) {
+ struct cx_mempool_foreign_mem_s *fm = ptr;
+ fm->destr(fm->mem);
+}
+
+int cxMempoolRegister(
+ CxMempool *pool,
+ void *memory,
+ cx_destructor_func destr
+) {
+ struct cx_mempool_foreign_mem_s *fm = cx_mempool_malloc(
+ pool,
+ sizeof(struct cx_mempool_foreign_mem_s)
+ );
+ if (fm == NULL) return 1;
+
+ fm->mem = memory;
+ fm->destr = destr;
+ *(cx_destructor_func *) ((char *) fm - sizeof(cx_destructor_func)) = cx_mempool_destr_foreign_mem;
+
+ return 0;
+}
+
+static cx_allocator_class cx_mempool_allocator_class = {
+ cx_mempool_malloc,
+ cx_mempool_realloc,
+ cx_mempool_calloc,
+ cx_mempool_free
+};
+
+CxMempool *cxMempoolCreate(
+ size_t capacity,
+ cx_destructor_func destr
+) {
+ size_t poolsize;
+ if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) {
+ return NULL;
+ }
+
+ struct cx_mempool_s *pool =
+ malloc(sizeof(struct cx_mempool_s));
+ if (pool == NULL) {
+ return NULL;
+ }
+
+ CxAllocator *provided_allocator = malloc(sizeof(CxAllocator));
+ if (provided_allocator == NULL) {
+ free(pool);
+ return NULL;
+ }
+ provided_allocator->cl = &cx_mempool_allocator_class;
+ provided_allocator->data = pool;
+
+ pool->allocator = provided_allocator;
+
+ pool->data = malloc(poolsize);
+ if (pool->data == NULL) {
+ free(provided_allocator);
+ free(pool);
+ return NULL;
+ }
+
+ pool->size = 0;
+ pool->capacity = capacity;
+ pool->auto_destr = destr;
+
+ return (CxMempool *) pool;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/printf.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#ifndef CX_PRINTF_SBO_SIZE
+#define CX_PRINTF_SBO_SIZE 512
+#endif
+unsigned const cx_printf_sbo_size = CX_PRINTF_SBO_SIZE;
+
+int cx_fprintf(
+ void *stream,
+ cx_write_func wfc,
+ const char *fmt,
+ ...
+) {
+ int ret;
+ va_list ap;
+ va_start(ap, fmt);
+ ret = cx_vfprintf(stream, wfc, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+int cx_vfprintf(
+ void *stream,
+ cx_write_func wfc,
+ const char *fmt,
+ va_list ap
+) {
+ char buf[CX_PRINTF_SBO_SIZE];
+ va_list ap2;
+ va_copy(ap2, ap);
+ int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
+ if (ret < 0) {
+ va_end(ap2);
+ return ret;
+ } else if (ret < CX_PRINTF_SBO_SIZE) {
+ va_end(ap2);
+ return (int) wfc(buf, 1, ret, stream);
+ } else {
+ int len = ret + 1;
+ char *newbuf = malloc(len);
+ if (!newbuf) {
+ va_end(ap2);
+ return -1;
+ }
+
+ ret = vsnprintf(newbuf, len, fmt, ap2);
+ va_end(ap2);
+ if (ret > 0) {
+ ret = (int) wfc(newbuf, 1, ret, stream);
+ }
+ free(newbuf);
+ }
+ return ret;
+}
+
+cxmutstr cx_asprintf_a(
+ const CxAllocator *allocator,
+ const char *fmt,
+ ...
+) {
+ va_list ap;
+ va_start(ap, fmt);
+ cxmutstr ret = cx_vasprintf_a(allocator, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+cxmutstr cx_vasprintf_a(
+ const CxAllocator *a,
+ const char *fmt,
+ va_list ap
+) {
+ cxmutstr s;
+ s.ptr = NULL;
+ s.length = 0;
+ char buf[CX_PRINTF_SBO_SIZE];
+ va_list ap2;
+ va_copy(ap2, ap);
+ int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
+ if (ret >= 0 && ret < CX_PRINTF_SBO_SIZE) {
+ s.ptr = cxMalloc(a, ret + 1);
+ if (s.ptr) {
+ s.length = (size_t) ret;
+ memcpy(s.ptr, buf, ret);
+ s.ptr[s.length] = '\0';
+ }
+ } else {
+ int len = ret + 1;
+ s.ptr = cxMalloc(a, len);
+ if (s.ptr) {
+ ret = vsnprintf(s.ptr, len, fmt, ap2);
+ if (ret < 0) {
+ free(s.ptr);
+ s.ptr = NULL;
+ } else {
+ s.length = (size_t) ret;
+ }
+ }
+ }
+ va_end(ap2);
+ return s;
+}
+
+int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... ) {
+ va_list ap;
+ va_start(ap, fmt);
+ int ret = cx_vsprintf_a(alloc, str, len, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap) {
+ va_list ap2;
+ va_copy(ap2, ap);
+ int ret = vsnprintf(*str, *len, fmt, ap);
+ if ((unsigned) ret >= *len) {
+ unsigned newlen = ret + 1;
+ char *ptr = cxRealloc(alloc, *str, newlen);
+ if (ptr) {
+ int newret = vsnprintf(ptr, newlen, fmt, ap2);
+ if (newret < 0) {
+ cxFree(alloc, ptr);
+ } else {
+ *len = newlen;
+ *str = ptr;
+ ret = newret;
+ }
+ }
+ }
+ va_end(ap2);
+ return ret;
+}
+
+int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... ) {
+ va_list ap;
+ va_start(ap, fmt);
+ int ret = cx_vsprintf_sa(alloc, buf, len, str, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap) {
+ va_list ap2;
+ va_copy(ap2, ap);
+ int ret = vsnprintf(buf, *len, fmt, ap);
+ *str = buf;
+ if ((unsigned) ret >= *len) {
+ unsigned newlen = ret + 1;
+ char *ptr = cxMalloc(alloc, newlen);
+ if (ptr) {
+ int newret = vsnprintf(ptr, newlen, fmt, ap2);
+ if (newret < 0) {
+ cxFree(alloc, ptr);
+ } else {
+ *len = newlen;
+ *str = ptr;
+ ret = newret;
+ }
+ }
+ }
+ va_end(ap2);
+ return ret;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/string.h"
+#include "cx/utils.h"
+
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+#ifndef _WIN32
+
+#include <strings.h> // for strncasecmp()
+
+#endif // _WIN32
+
+cxmutstr cx_mutstr(char *cstring) {
+ return (cxmutstr) {cstring, strlen(cstring)};
+}
+
+cxmutstr cx_mutstrn(
+ char *cstring,
+ size_t length
+) {
+ return (cxmutstr) {cstring, length};
+}
+
+cxstring cx_str(const char *cstring) {
+ return (cxstring) {cstring, strlen(cstring)};
+}
+
+cxstring cx_strn(
+ const char *cstring,
+ size_t length
+) {
+ return (cxstring) {cstring, length};
+}
+
+cxstring cx_strcast(cxmutstr str) {
+ return (cxstring) {str.ptr, str.length};
+}
+
+void cx_strfree(cxmutstr *str) {
+ free(str->ptr);
+ str->ptr = NULL;
+ str->length = 0;
+}
+
+void cx_strfree_a(
+ const CxAllocator *alloc,
+ cxmutstr *str
+) {
+ cxFree(alloc, str->ptr);
+ str->ptr = NULL;
+ str->length = 0;
+}
+
+size_t cx_strlen(
+ size_t count,
+ ...
+) {
+ if (count == 0) return 0;
+
+ va_list ap;
+ va_start(ap, count);
+ size_t size = 0;
+ cx_for_n(i, count) {
+ cxstring str = va_arg(ap, cxstring);
+ size += str.length;
+ }
+ va_end(ap);
+
+ return size;
+}
+
+cxmutstr cx_strcat_ma(
+ const CxAllocator *alloc,
+ cxmutstr str,
+ size_t count,
+ ...
+) {
+ if (count == 0) return str;
+
+ cxstring *strings = calloc(count, sizeof(cxstring));
+ if (!strings) abort();
+
+ va_list ap;
+ va_start(ap, count);
+
+ // get all args and overall length
+ size_t slen = str.length;
+ cx_for_n(i, count) {
+ cxstring s = va_arg (ap, cxstring);
+ strings[i] = s;
+ slen += s.length;
+ }
+ va_end(ap);
+
+ // reallocate or create new string
+ if (str.ptr == NULL) {
+ str.ptr = cxMalloc(alloc, slen + 1);
+ } else {
+ str.ptr = cxRealloc(alloc, str.ptr, slen + 1);
+ }
+ if (str.ptr == NULL) abort();
+
+ // concatenate strings
+ size_t pos = str.length;
+ str.length = slen;
+ cx_for_n(i, count) {
+ cxstring s = strings[i];
+ memcpy(str.ptr + pos, s.ptr, s.length);
+ pos += s.length;
+ }
+
+ // terminate string
+ str.ptr[str.length] = '\0';
+
+ // free temporary array
+ free(strings);
+
+ return str;
+}
+
+cxstring cx_strsubs(
+ cxstring string,
+ size_t start
+) {
+ return cx_strsubsl(string, start, string.length - start);
+}
+
+cxmutstr cx_strsubs_m(
+ cxmutstr string,
+ size_t start
+) {
+ return cx_strsubsl_m(string, start, string.length - start);
+}
+
+cxstring cx_strsubsl(
+ cxstring string,
+ size_t start,
+ size_t length
+) {
+ if (start > string.length) {
+ return (cxstring) {NULL, 0};
+ }
+
+ size_t rem_len = string.length - start;
+ if (length > rem_len) {
+ length = rem_len;
+ }
+
+ return (cxstring) {string.ptr + start, length};
+}
+
+cxmutstr cx_strsubsl_m(
+ cxmutstr string,
+ size_t start,
+ size_t length
+) {
+ cxstring result = cx_strsubsl(cx_strcast(string), start, length);
+ return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+cxstring cx_strchr(
+ cxstring string,
+ int chr
+) {
+ chr = 0xFF & chr;
+ // TODO: improve by comparing multiple bytes at once
+ cx_for_n(i, string.length) {
+ if (string.ptr[i] == chr) {
+ return cx_strsubs(string, i);
+ }
+ }
+ return (cxstring) {NULL, 0};
+}
+
+cxmutstr cx_strchr_m(
+ cxmutstr string,
+ int chr
+) {
+ cxstring result = cx_strchr(cx_strcast(string), chr);
+ return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+cxstring cx_strrchr(
+ cxstring string,
+ int chr
+) {
+ chr = 0xFF & chr;
+ size_t i = string.length;
+ while (i > 0) {
+ i--;
+ // TODO: improve by comparing multiple bytes at once
+ if (string.ptr[i] == chr) {
+ return cx_strsubs(string, i);
+ }
+ }
+ return (cxstring) {NULL, 0};
+}
+
+cxmutstr cx_strrchr_m(
+ cxmutstr string,
+ int chr
+) {
+ cxstring result = cx_strrchr(cx_strcast(string), chr);
+ return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+#ifndef CX_STRSTR_SBO_SIZE
+#define CX_STRSTR_SBO_SIZE 512
+#endif
+unsigned const cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE;
+
+cxstring cx_strstr(
+ cxstring haystack,
+ cxstring needle
+) {
+ if (needle.length == 0) {
+ return haystack;
+ }
+
+ // optimize for single-char needles
+ if (needle.length == 1) {
+ return cx_strchr(haystack, *needle.ptr);
+ }
+
+ /*
+ * IMPORTANT:
+ * Our prefix table contains the prefix length PLUS ONE
+ * this is our decision, because we want to use the full range of size_t.
+ * The original algorithm needs a (-1) at one single place,
+ * and we want to avoid that.
+ */
+
+ // local prefix table
+ size_t s_prefix_table[CX_STRSTR_SBO_SIZE];
+
+ // check needle length and use appropriate prefix table
+ // if the pattern exceeds static prefix table, allocate on the heap
+ bool useheap = needle.length >= CX_STRSTR_SBO_SIZE;
+ register size_t *ptable = useheap ? calloc(needle.length + 1,
+ sizeof(size_t)) : s_prefix_table;
+
+ // keep counter in registers
+ register size_t i, j;
+
+ // fill prefix table
+ i = 0;
+ j = 0;
+ ptable[i] = j;
+ while (i < needle.length) {
+ while (j >= 1 && needle.ptr[j - 1] != needle.ptr[i]) {
+ j = ptable[j - 1];
+ }
+ i++;
+ j++;
+ ptable[i] = j;
+ }
+
+ // search
+ cxstring result = {NULL, 0};
+ i = 0;
+ j = 1;
+ while (i < haystack.length) {
+ while (j >= 1 && haystack.ptr[i] != needle.ptr[j - 1]) {
+ j = ptable[j - 1];
+ }
+ i++;
+ j++;
+ if (j - 1 == needle.length) {
+ size_t start = i - needle.length;
+ result.ptr = haystack.ptr + start;
+ result.length = haystack.length - start;
+ break;
+ }
+ }
+
+ // if prefix table was allocated on the heap, free it
+ if (ptable != s_prefix_table) {
+ free(ptable);
+ }
+
+ return result;
+}
+
+cxmutstr cx_strstr_m(
+ cxmutstr haystack,
+ cxstring needle
+) {
+ cxstring result = cx_strstr(cx_strcast(haystack), needle);
+ return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+size_t cx_strsplit(
+ cxstring string,
+ cxstring delim,
+ size_t limit,
+ cxstring *output
+) {
+ // special case: output limit is zero
+ if (limit == 0) return 0;
+
+ // special case: delimiter is empty
+ if (delim.length == 0) {
+ output[0] = string;
+ return 1;
+ }
+
+ // special cases: delimiter is at least as large as the string
+ if (delim.length >= string.length) {
+ // exact match
+ if (cx_strcmp(string, delim) == 0) {
+ output[0] = cx_strn(string.ptr, 0);
+ output[1] = cx_strn(string.ptr + string.length, 0);
+ return 2;
+ } else {
+ // no match possible
+ output[0] = string;
+ return 1;
+ }
+ }
+
+ size_t n = 0;
+ cxstring curpos = string;
+ while (1) {
+ ++n;
+ cxstring match = cx_strstr(curpos, delim);
+ if (match.length > 0) {
+ // is the limit reached?
+ if (n < limit) {
+ // copy the current string to the array
+ cxstring item = cx_strn(curpos.ptr, match.ptr - curpos.ptr);
+ output[n - 1] = item;
+ size_t processed = item.length + delim.length;
+ curpos.ptr += processed;
+ curpos.length -= processed;
+ } else {
+ // limit reached, copy the _full_ remaining string
+ output[n - 1] = curpos;
+ break;
+ }
+ } else {
+ // no more matches, copy last string
+ output[n - 1] = curpos;
+ break;
+ }
+ }
+
+ return n;
+}
+
+size_t cx_strsplit_a(
+ const CxAllocator *allocator,
+ cxstring string,
+ cxstring delim,
+ size_t limit,
+ cxstring **output
+) {
+ // find out how many splits we're going to make and allocate memory
+ size_t n = 0;
+ cxstring curpos = string;
+ while (1) {
+ ++n;
+ cxstring match = cx_strstr(curpos, delim);
+ if (match.length > 0) {
+ // is the limit reached?
+ if (n < limit) {
+ size_t processed = match.ptr - curpos.ptr + delim.length;
+ curpos.ptr += processed;
+ curpos.length -= processed;
+ } else {
+ // limit reached
+ break;
+ }
+ } else {
+ // no more matches
+ break;
+ }
+ }
+ *output = cxCalloc(allocator, n, sizeof(cxstring));
+ return cx_strsplit(string, delim, n, *output);
+}
+
+size_t cx_strsplit_m(
+ cxmutstr string,
+ cxstring delim,
+ size_t limit,
+ cxmutstr *output
+) {
+ return cx_strsplit(cx_strcast(string),
+ delim, limit, (cxstring *) output);
+}
+
+size_t cx_strsplit_ma(
+ const CxAllocator *allocator,
+ cxmutstr string,
+ cxstring delim,
+ size_t limit,
+ cxmutstr **output
+) {
+ return cx_strsplit_a(allocator, cx_strcast(string),
+ delim, limit, (cxstring **) output);
+}
+
+int cx_strcmp(
+ cxstring s1,
+ cxstring s2
+) {
+ if (s1.length == s2.length) {
+ return memcmp(s1.ptr, s2.ptr, s1.length);
+ } else if (s1.length > s2.length) {
+ return 1;
+ } else {
+ return -1;
+ }
+}
+
+int cx_strcasecmp(
+ cxstring s1,
+ cxstring s2
+) {
+ if (s1.length == s2.length) {
+#ifdef _WIN32
+ return _strnicmp(s1.ptr, s2.ptr, s1.length);
+#else
+ return strncasecmp(s1.ptr, s2.ptr, s1.length);
+#endif
+ } else if (s1.length > s2.length) {
+ return 1;
+ } else {
+ return -1;
+ }
+}
+
+int cx_strcmp_p(
+ const void *s1,
+ const void *s2
+) {
+ const cxstring *left = s1;
+ const cxstring *right = s2;
+ return cx_strcmp(*left, *right);
+}
+
+int cx_strcasecmp_p(
+ const void *s1,
+ const void *s2
+) {
+ const cxstring *left = s1;
+ const cxstring *right = s2;
+ return cx_strcasecmp(*left, *right);
+}
+
+cxmutstr cx_strdup_a(
+ const CxAllocator *allocator,
+ cxstring string
+) {
+ cxmutstr result = {
+ cxMalloc(allocator, string.length + 1),
+ string.length
+ };
+ if (result.ptr == NULL) {
+ result.length = 0;
+ return result;
+ }
+ memcpy(result.ptr, string.ptr, string.length);
+ result.ptr[string.length] = '\0';
+ return result;
+}
+
+cxstring cx_strtrim(cxstring string) {
+ cxstring result = string;
+ // TODO: optimize by comparing multiple bytes at once
+ while (result.length > 0 && isspace(*result.ptr)) {
+ result.ptr++;
+ result.length--;
+ }
+ while (result.length > 0 && isspace(result.ptr[result.length - 1])) {
+ result.length--;
+ }
+ return result;
+}
+
+cxmutstr cx_strtrim_m(cxmutstr string) {
+ cxstring result = cx_strtrim(cx_strcast(string));
+ return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+bool cx_strprefix(
+ cxstring string,
+ cxstring prefix
+) {
+ if (string.length < prefix.length) return false;
+ return memcmp(string.ptr, prefix.ptr, prefix.length) == 0;
+}
+
+bool cx_strsuffix(
+ cxstring string,
+ cxstring suffix
+) {
+ if (string.length < suffix.length) return false;
+ return memcmp(string.ptr + string.length - suffix.length,
+ suffix.ptr, suffix.length) == 0;
+}
+
+bool cx_strcaseprefix(
+ cxstring string,
+ cxstring prefix
+) {
+ if (string.length < prefix.length) return false;
+#ifdef _WIN32
+ return _strnicmp(string.ptr, prefix.ptr, prefix.length) == 0;
+#else
+ return strncasecmp(string.ptr, prefix.ptr, prefix.length) == 0;
+#endif
+}
+
+bool cx_strcasesuffix(
+ cxstring string,
+ cxstring suffix
+) {
+ if (string.length < suffix.length) return false;
+#ifdef _WIN32
+ return _strnicmp(string.ptr+string.length-suffix.length,
+ suffix.ptr, suffix.length) == 0;
+#else
+ return strncasecmp(string.ptr + string.length - suffix.length,
+ suffix.ptr, suffix.length) == 0;
+#endif
+}
+
+void cx_strlower(cxmutstr string) {
+ cx_for_n(i, string.length) {
+ string.ptr[i] = (char) tolower(string.ptr[i]);
+ }
+}
+
+void cx_strupper(cxmutstr string) {
+ cx_for_n(i, string.length) {
+ string.ptr[i] = (char) toupper(string.ptr[i]);
+ }
+}
+
+#ifndef CX_STRREPLACE_INDEX_BUFFER_SIZE
+#define CX_STRREPLACE_INDEX_BUFFER_SIZE 64
+#endif
+
+struct cx_strreplace_ibuf {
+ size_t *buf;
+ struct cx_strreplace_ibuf *next;
+ unsigned int len;
+};
+
+static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) {
+ while (buf) {
+ struct cx_strreplace_ibuf *next = buf->next;
+ free(buf->buf);
+ free(buf);
+ buf = next;
+ }
+}
+
+cxmutstr cx_strreplacen_a(
+ const CxAllocator *allocator,
+ cxstring str,
+ cxstring pattern,
+ cxstring replacement,
+ size_t replmax
+) {
+
+ if (pattern.length == 0 || pattern.length > str.length || replmax == 0)
+ return cx_strdup_a(allocator, str);
+
+ // Compute expected buffer length
+ size_t ibufmax = str.length / pattern.length;
+ size_t ibuflen = replmax < ibufmax ? replmax : ibufmax;
+ if (ibuflen > CX_STRREPLACE_INDEX_BUFFER_SIZE) {
+ ibuflen = CX_STRREPLACE_INDEX_BUFFER_SIZE;
+ }
+
+ // Allocate first index buffer
+ struct cx_strreplace_ibuf *firstbuf, *curbuf;
+ firstbuf = curbuf = calloc(1, sizeof(struct cx_strreplace_ibuf));
+ if (!firstbuf) return cx_mutstrn(NULL, 0);
+ firstbuf->buf = calloc(ibuflen, sizeof(size_t));
+ if (!firstbuf->buf) {
+ free(firstbuf);
+ return cx_mutstrn(NULL, 0);
+ }
+
+ // Search occurrences
+ cxstring searchstr = str;
+ size_t found = 0;
+ do {
+ cxstring match = cx_strstr(searchstr, pattern);
+ if (match.length > 0) {
+ // Allocate next buffer in chain, if required
+ if (curbuf->len == ibuflen) {
+ struct cx_strreplace_ibuf *nextbuf =
+ calloc(1, sizeof(struct cx_strreplace_ibuf));
+ if (!nextbuf) {
+ cx_strrepl_free_ibuf(firstbuf);
+ return cx_mutstrn(NULL, 0);
+ }
+ nextbuf->buf = calloc(ibuflen, sizeof(size_t));
+ if (!nextbuf->buf) {
+ free(nextbuf);
+ cx_strrepl_free_ibuf(firstbuf);
+ return cx_mutstrn(NULL, 0);
+ }
+ curbuf->next = nextbuf;
+ curbuf = nextbuf;
+ }
+
+ // Record match index
+ found++;
+ size_t idx = match.ptr - str.ptr;
+ curbuf->buf[curbuf->len++] = idx;
+ searchstr.ptr = match.ptr + pattern.length;
+ searchstr.length = str.length - idx - pattern.length;
+ } else {
+ break;
+ }
+ } while (searchstr.length > 0 && found < replmax);
+
+ // Allocate result string
+ cxmutstr result;
+ {
+ ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length;
+ size_t rcount = 0;
+ curbuf = firstbuf;
+ do {
+ rcount += curbuf->len;
+ curbuf = curbuf->next;
+ } while (curbuf);
+ result.length = str.length + rcount * adjlen;
+ result.ptr = cxMalloc(allocator, result.length + 1);
+ if (!result.ptr) {
+ cx_strrepl_free_ibuf(firstbuf);
+ return cx_mutstrn(NULL, 0);
+ }
+ }
+
+ // Build result string
+ curbuf = firstbuf;
+ size_t srcidx = 0;
+ char *destptr = result.ptr;
+ do {
+ for (size_t i = 0; i < curbuf->len; i++) {
+ // Copy source part up to next match
+ size_t idx = curbuf->buf[i];
+ size_t srclen = idx - srcidx;
+ if (srclen > 0) {
+ memcpy(destptr, str.ptr + srcidx, srclen);
+ destptr += srclen;
+ srcidx += srclen;
+ }
+
+ // Copy the replacement and skip the source pattern
+ srcidx += pattern.length;
+ memcpy(destptr, replacement.ptr, replacement.length);
+ destptr += replacement.length;
+ }
+ curbuf = curbuf->next;
+ } while (curbuf);
+ memcpy(destptr, str.ptr + srcidx, str.length - srcidx);
+
+ // Result is guaranteed to be zero-terminated
+ result.ptr[result.length] = '\0';
+
+ // Free index buffer
+ cx_strrepl_free_ibuf(firstbuf);
+
+ return result;
+}
+
+CxStrtokCtx cx_strtok(
+ cxstring str,
+ cxstring delim,
+ size_t limit
+) {
+ CxStrtokCtx ctx;
+ ctx.str = str;
+ ctx.delim = delim;
+ ctx.limit = limit;
+ ctx.pos = 0;
+ ctx.next_pos = 0;
+ ctx.delim_pos = 0;
+ ctx.found = 0;
+ ctx.delim_more = NULL;
+ ctx.delim_more_count = 0;
+ return ctx;
+}
+
+CxStrtokCtx cx_strtok_m(
+ cxmutstr str,
+ cxstring delim,
+ size_t limit
+) {
+ return cx_strtok(cx_strcast(str), delim, limit);
+}
+
+bool cx_strtok_next(
+ CxStrtokCtx *ctx,
+ cxstring *token
+) {
+ // abortion criteria
+ if (ctx->found >= ctx->limit || ctx->delim_pos >= ctx->str.length) {
+ return false;
+ }
+
+ // determine the search start
+ cxstring haystack = cx_strsubs(ctx->str, ctx->next_pos);
+
+ // search the next delimiter
+ cxstring delim = cx_strstr(haystack, ctx->delim);
+
+ // if found, make delim capture exactly the delimiter
+ if (delim.length > 0) {
+ delim.length = ctx->delim.length;
+ }
+
+ // if more delimiters are specified, check them now
+ if (ctx->delim_more_count > 0) {
+ cx_for_n(i, ctx->delim_more_count) {
+ cxstring d = cx_strstr(haystack, ctx->delim_more[i]);
+ if (d.length > 0 && (delim.length == 0 || d.ptr < delim.ptr)) {
+ delim.ptr = d.ptr;
+ delim.length = ctx->delim_more[i].length;
+ }
+ }
+ }
+
+ // store the token information and adjust the context
+ ctx->found++;
+ ctx->pos = ctx->next_pos;
+ token->ptr = &ctx->str.ptr[ctx->pos];
+ ctx->delim_pos = delim.length == 0 ?
+ ctx->str.length : (size_t) (delim.ptr - ctx->str.ptr);
+ token->length = ctx->delim_pos - ctx->pos;
+ ctx->next_pos = ctx->delim_pos + delim.length;
+
+ return true;
+}
+
+bool cx_strtok_next_m(
+ CxStrtokCtx *ctx,
+ cxmutstr *token
+) {
+ return cx_strtok_next(ctx, (cxstring *) token);
+}
+
+void cx_strtok_delim(
+ CxStrtokCtx *ctx,
+ const cxstring *delim,
+ size_t count
+) {
+ ctx->delim_more = delim;
+ ctx->delim_more_count = count;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+int cx_szmul_impl(
+ size_t a,
+ size_t b,
+ size_t *result
+) {
+ if (a == 0 || b == 0) {
+ *result = 0;
+ return 0;
+ }
+ size_t r = a * b;
+ if (r / b == a) {
+ *result = r;
+ return 0;
+ } else {
+ *result = 0;
+ return 1;
+ }
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/tree.h"
+
+#include "cx/array_list.h"
+
+#include <assert.h>
+
+#define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
+#define tree_parent(node) CX_TREE_PTR(node, loc_parent)
+#define tree_children(node) CX_TREE_PTR(node, loc_children)
+#define tree_last_child(node) CX_TREE_PTR(node, loc_last_child)
+#define tree_prev(node) CX_TREE_PTR(node, loc_prev)
+#define tree_next(node) CX_TREE_PTR(node, loc_next)
+
+#define cx_tree_ptr_locations \
+ loc_parent, loc_children, loc_last_child, loc_prev, loc_next
+
+static void cx_tree_zero_pointers(
+ void *node,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ tree_parent(node) = NULL;
+ tree_prev(node) = NULL;
+ tree_next(node) = NULL;
+ tree_children(node) = NULL;
+ if (loc_last_child >= 0) {
+ tree_last_child(node) = NULL;
+ }
+}
+
+void cx_tree_link(
+ void *restrict parent,
+ void *restrict node,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ void *current_parent = tree_parent(node);
+ if (current_parent == parent) return;
+ if (current_parent != NULL) {
+ cx_tree_unlink(node, cx_tree_ptr_locations);
+ }
+
+ if (tree_children(parent) == NULL) {
+ tree_children(parent) = node;
+ if (loc_last_child >= 0) {
+ tree_last_child(parent) = node;
+ }
+ } else {
+ if (loc_last_child >= 0) {
+ void *child = tree_last_child(parent);
+ tree_prev(node) = child;
+ tree_next(child) = node;
+ tree_last_child(parent) = node;
+ } else {
+ void *child = tree_children(parent);
+ void *next;
+ while ((next = tree_next(child)) != NULL) {
+ child = next;
+ }
+ tree_prev(node) = child;
+ tree_next(child) = node;
+ }
+ }
+ tree_parent(node) = parent;
+}
+
+void cx_tree_unlink(
+ void *node,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ if (tree_parent(node) == NULL) return;
+
+ void *left = tree_prev(node);
+ void *right = tree_next(node);
+ void *parent = tree_parent(node);
+ assert(left == NULL || tree_children(parent) != node);
+ assert(right == NULL || loc_last_child < 0 ||
+ tree_last_child(parent) != node);
+
+ if (left == NULL) {
+ tree_children(parent) = right;
+ } else {
+ tree_next(left) = right;
+ }
+ if (right == NULL) {
+ if (loc_last_child >= 0) {
+ tree_last_child(parent) = left;
+ }
+ } else {
+ tree_prev(right) = left;
+ }
+
+ tree_parent(node) = NULL;
+ tree_prev(node) = NULL;
+ tree_next(node) = NULL;
+}
+
+int cx_tree_search(
+ const void *root,
+ const void *node,
+ cx_tree_search_func sfunc,
+ void **result,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+) {
+ int ret;
+ *result = NULL;
+
+ // shortcut: compare root before doing anything else
+ ret = sfunc(root, node);
+ if (ret < 0) {
+ return ret;
+ } else if (ret == 0 || tree_children(root) == NULL) {
+ *result = (void*)root;
+ return ret;
+ }
+
+ // create a working stack
+ CX_ARRAY_DECLARE(const void *, work);
+ cx_array_initialize(work, 32);
+
+ // add the children of root to the working stack
+ {
+ void *c = tree_children(root);
+ while (c != NULL) {
+ cx_array_simple_add(work, c);
+ c = tree_next(c);
+ }
+ }
+
+ // remember a candidate for adding the data
+ // also remember the exact return code from sfunc
+ void *candidate = (void *) root;
+ int ret_candidate = ret;
+
+ // process the working stack
+ while (work_size > 0) {
+ // pop element
+ const void *elem = work[--work_size];
+
+ // apply the search function
+ ret = sfunc(elem, node);
+
+ if (ret == 0) {
+ // if found, exit the search
+ *result = (void *) elem;
+ work_size = 0;
+ break;
+ } else if (ret > 0) {
+ // if children might contain the data, add them to the stack
+ void *c = tree_children(elem);
+ while (c != NULL) {
+ cx_array_simple_add(work, c);
+ c = tree_next(c);
+ }
+
+ // remember this node in case no child is suitable
+ if (ret < ret_candidate) {
+ candidate = (void *) elem;
+ ret_candidate = ret;
+ }
+ }
+ }
+
+ // not found, but was there a candidate?
+ if (ret != 0 && candidate != NULL) {
+ ret = ret_candidate;
+ *result = candidate;
+ }
+
+ // free the working queue and return
+ free(work);
+ return ret;
+}
+
+int cx_tree_search_data(
+ const void *root,
+ const void *data,
+ cx_tree_search_data_func sfunc,
+ void **result,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+) {
+ // it is basically the same implementation
+ return cx_tree_search(
+ root, data,
+ (cx_tree_search_func) sfunc,
+ result,
+ loc_children, loc_next);
+}
+
+static bool cx_tree_iter_valid(const void *it) {
+ const struct cx_tree_iterator_s *iter = it;
+ return iter->node != NULL;
+}
+
+static void *cx_tree_iter_current(const void *it) {
+ const struct cx_tree_iterator_s *iter = it;
+ return iter->node;
+}
+
+static void cx_tree_iter_next(void *it) {
+ struct cx_tree_iterator_s *iter = it;
+ ptrdiff_t const loc_next = iter->loc_next;
+ ptrdiff_t const loc_children = iter->loc_children;
+ // protect us from misuse
+ if (!iter->base.valid(iter)) return;
+
+ void *children;
+
+ // check if we are currently exiting or entering nodes
+ if (iter->exiting) {
+ children = NULL;
+ // skipping on exit is pointless, just clear the flag
+ iter->skip = false;
+ } else {
+ if (iter->skip) {
+ // skip flag is set, pretend that there are no children
+ iter->skip = false;
+ children = NULL;
+ } else {
+ // try to enter the children (if any)
+ children = tree_children(iter->node);
+ }
+ }
+
+ if (children == NULL) {
+ // search for the next node
+ void *next;
+ cx_tree_iter_search_next:
+ // check if there is a sibling
+ if (iter->exiting) {
+ next = iter->node_next;
+ } else {
+ next = tree_next(iter->node);
+ iter->node_next = next;
+ }
+ if (next == NULL) {
+ // no sibling, we are done with this node and exit
+ if (iter->visit_on_exit && !iter->exiting) {
+ // iter is supposed to visit the node again
+ iter->exiting = true;
+ } else {
+ iter->exiting = false;
+ if (iter->depth == 1) {
+ // there is no parent - we have iterated the entire tree
+ // invalidate the iterator and free the node stack
+ iter->node = iter->node_next = NULL;
+ iter->stack_capacity = iter->depth = 0;
+ free(iter->stack);
+ iter->stack = NULL;
+ } else {
+ // the parent node can be obtained from the top of stack
+ // this way we can avoid the loc_parent in the iterator
+ iter->depth--;
+ iter->node = iter->stack[iter->depth - 1];
+ // retry with the parent node to find a sibling
+ goto cx_tree_iter_search_next;
+ }
+ }
+ } else {
+ if (iter->visit_on_exit && !iter->exiting) {
+ // iter is supposed to visit the node again
+ iter->exiting = true;
+ } else {
+ iter->exiting = false;
+ // move to the sibling
+ iter->counter++;
+ iter->node = next;
+ // new top of stack is the sibling
+ iter->stack[iter->depth - 1] = next;
+ }
+ }
+ } else {
+ // node has children, push the first child onto the stack and enter it
+ cx_array_simple_add(iter->stack, children);
+ iter->node = children;
+ iter->counter++;
+ }
+}
+
+CxTreeIterator cx_tree_iterator(
+ void *root,
+ bool visit_on_exit,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+) {
+ CxTreeIterator iter;
+ iter.loc_children = loc_children;
+ iter.loc_next = loc_next;
+ iter.visit_on_exit = visit_on_exit;
+
+ // initialize members
+ iter.node_next = NULL;
+ iter.exiting = false;
+ iter.skip = false;
+
+ // assign base iterator functions
+ iter.base.mutating = false;
+ iter.base.remove = false;
+ iter.base.current_impl = NULL;
+ iter.base.valid = cx_tree_iter_valid;
+ iter.base.next = cx_tree_iter_next;
+ iter.base.current = cx_tree_iter_current;
+
+ // visit the root node
+ iter.node = root;
+ if (root != NULL) {
+ iter.stack_capacity = 16;
+ iter.stack = malloc(sizeof(void *) * 16);
+ iter.stack[0] = root;
+ iter.counter = 1;
+ iter.depth = 1;
+ } else {
+ iter.stack_capacity = 0;
+ iter.stack = NULL;
+ iter.counter = 0;
+ iter.depth = 0;
+ }
+
+ return iter;
+}
+
+static bool cx_tree_visitor_valid(const void *it) {
+ const struct cx_tree_visitor_s *iter = it;
+ return iter->node != NULL;
+}
+
+static void *cx_tree_visitor_current(const void *it) {
+ const struct cx_tree_visitor_s *iter = it;
+ return iter->node;
+}
+
+__attribute__((__nonnull__))
+static void cx_tree_visitor_enqueue_siblings(
+ struct cx_tree_visitor_s *iter, void *node, ptrdiff_t loc_next) {
+ node = tree_next(node);
+ while (node != NULL) {
+ struct cx_tree_visitor_queue_s *q;
+ q = malloc(sizeof(struct cx_tree_visitor_queue_s));
+ q->depth = iter->queue_last->depth;
+ q->node = node;
+ iter->queue_last->next = q;
+ iter->queue_last = q;
+ node = tree_next(node);
+ }
+ iter->queue_last->next = NULL;
+}
+
+static void cx_tree_visitor_next(void *it) {
+ struct cx_tree_visitor_s *iter = it;
+ // protect us from misuse
+ if (!iter->base.valid(iter)) return;
+
+ ptrdiff_t const loc_next = iter->loc_next;
+ ptrdiff_t const loc_children = iter->loc_children;
+
+ // add the children of the current node to the queue
+ // unless the skip flag is set
+ void *children;
+ if (iter->skip) {
+ iter->skip = false;
+ children = NULL;
+ } else {
+ children = tree_children(iter->node);
+ }
+ if (children != NULL) {
+ struct cx_tree_visitor_queue_s *q;
+ q = malloc(sizeof(struct cx_tree_visitor_queue_s));
+ q->depth = iter->depth + 1;
+ q->node = children;
+ if (iter->queue_last == NULL) {
+ assert(iter->queue_next == NULL);
+ iter->queue_next = q;
+ } else {
+ iter->queue_last->next = q;
+ }
+ iter->queue_last = q;
+ cx_tree_visitor_enqueue_siblings(iter, children, loc_next);
+ }
+
+ // check if there is a next node
+ if (iter->queue_next == NULL) {
+ iter->node = NULL;
+ return;
+ }
+
+ // dequeue the next node
+ iter->node = iter->queue_next->node;
+ iter->depth = iter->queue_next->depth;
+ {
+ struct cx_tree_visitor_queue_s *q = iter->queue_next;
+ iter->queue_next = q->next;
+ if (iter->queue_next == NULL) {
+ assert(iter->queue_last == q);
+ iter->queue_last = NULL;
+ }
+ free(q);
+ }
+
+ // increment the node counter
+ iter->counter++;
+}
+
+CxTreeVisitor cx_tree_visitor(
+ void *root,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+) {
+ CxTreeVisitor iter;
+ iter.loc_children = loc_children;
+ iter.loc_next = loc_next;
+
+ // initialize members
+ iter.skip = false;
+ iter.queue_next = NULL;
+ iter.queue_last = NULL;
+
+ // assign base iterator functions
+ iter.base.mutating = false;
+ iter.base.remove = false;
+ iter.base.current_impl = NULL;
+ iter.base.valid = cx_tree_visitor_valid;
+ iter.base.next = cx_tree_visitor_next;
+ iter.base.current = cx_tree_visitor_current;
+
+ // visit the root node
+ iter.node = root;
+ if (root != NULL) {
+ iter.counter = 1;
+ iter.depth = 1;
+ } else {
+ iter.counter = 0;
+ iter.depth = 0;
+ }
+
+ return iter;
+}
+
+static void cx_tree_add_link_duplicate(
+ void *original, void *duplicate,
+ ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev, ptrdiff_t loc_next
+) {
+ void *shared_parent = tree_parent(original);
+ if (shared_parent == NULL) {
+ cx_tree_link(original, duplicate, cx_tree_ptr_locations);
+ } else {
+ cx_tree_link(shared_parent, duplicate, cx_tree_ptr_locations);
+ }
+}
+
+static void cx_tree_add_link_new(
+ void *parent, void *node, cx_tree_search_func sfunc,
+ ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev, ptrdiff_t loc_next
+) {
+ // check the current children one by one,
+ // if they could be children of the new node
+ void *child = tree_children(parent);
+ while (child != NULL) {
+ void *next = tree_next(child);
+
+ if (sfunc(node, child) > 0) {
+ // the sibling could be a child -> re-link
+ cx_tree_link(node, child, cx_tree_ptr_locations);
+ }
+
+ child = next;
+ }
+
+ // add new node as new child
+ cx_tree_link(parent, node, cx_tree_ptr_locations);
+}
+
+int cx_tree_add(
+ const void *src,
+ cx_tree_search_func sfunc,
+ cx_tree_node_create_func cfunc,
+ void *cdata,
+ void **cnode,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ *cnode = cfunc(src, cdata);
+ if (*cnode == NULL) return 1;
+ cx_tree_zero_pointers(*cnode, cx_tree_ptr_locations);
+
+ void *match = NULL;
+ int result = cx_tree_search(
+ root,
+ *cnode,
+ sfunc,
+ &match,
+ loc_children,
+ loc_next
+ );
+
+ if (result < 0) {
+ // node does not fit into the tree - return non-zero value
+ return 1;
+ } else if (result == 0) {
+ // data already found in the tree, link duplicate
+ cx_tree_add_link_duplicate(match, *cnode, cx_tree_ptr_locations);
+ } else {
+ // closest match found, add new node
+ cx_tree_add_link_new(match, *cnode, sfunc, cx_tree_ptr_locations);
+ }
+
+ return 0;
+}
+
+unsigned int cx_tree_add_look_around_depth = 3;
+
+size_t cx_tree_add_iter(
+ struct cx_iterator_base_s *iter,
+ size_t num,
+ cx_tree_search_func sfunc,
+ cx_tree_node_create_func cfunc,
+ void *cdata,
+ void **failed,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ // erase the failed pointer
+ *failed = NULL;
+
+ // iter not valid? cancel...
+ if (!iter->valid(iter)) return 0;
+
+ size_t processed = 0;
+ void *current_node = root;
+ const void *elem;
+
+ for (void **eptr; processed < num &&
+ iter->valid(iter) && (eptr = iter->current(iter)) != NULL;
+ iter->next(iter)) {
+ elem = *eptr;
+
+ // create the new node
+ void *new_node = cfunc(elem, cdata);
+ if (new_node == NULL) return processed;
+ cx_tree_zero_pointers(new_node, cx_tree_ptr_locations);
+
+ // start searching from current node
+ void *match;
+ int result;
+ unsigned int look_around_retries = cx_tree_add_look_around_depth;
+ cx_tree_add_look_around_retry:
+ result = cx_tree_search(
+ current_node,
+ new_node,
+ sfunc,
+ &match,
+ loc_children,
+ loc_next
+ );
+
+ if (result < 0) {
+ // traverse upwards and try to find better parents
+ void *parent = tree_parent(current_node);
+ if (parent != NULL) {
+ if (look_around_retries > 0) {
+ look_around_retries--;
+ current_node = parent;
+ } else {
+ // look around retries exhausted, start from the root
+ current_node = root;
+ }
+ goto cx_tree_add_look_around_retry;
+ } else {
+ // no parents. so we failed
+ *failed = new_node;
+ return processed;
+ }
+ } else if (result == 0) {
+ // data already found in the tree, link duplicate
+ cx_tree_add_link_duplicate(match, new_node, cx_tree_ptr_locations);
+ // but stick with the original match, in case we needed a new root
+ current_node = match;
+ } else {
+ // closest match found, add new node as child
+ cx_tree_add_link_new(match, new_node, sfunc,
+ cx_tree_ptr_locations);
+ current_node = match;
+ }
+
+ processed++;
+ }
+ return processed;
+}
+
+size_t cx_tree_add_array(
+ const void *src,
+ size_t num,
+ size_t elem_size,
+ cx_tree_search_func sfunc,
+ cx_tree_node_create_func cfunc,
+ void *cdata,
+ void **failed,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ // erase failed pointer
+ *failed = NULL;
+
+ // super special case: zero elements
+ if (num == 0) {
+ return 0;
+ }
+
+ // special case: one element does not need an iterator
+ if (num == 1) {
+ void *node;
+ if (0 == cx_tree_add(
+ src, sfunc, cfunc, cdata, &node, root,
+ loc_parent, loc_children, loc_last_child,
+ loc_prev, loc_next)) {
+ return 1;
+ } else {
+ *failed = node;
+ return 0;
+ }
+ }
+
+ // otherwise, create iterator and hand over to other function
+ CxIterator iter = cxIterator(src, elem_size, num);
+ return cx_tree_add_iter(cxIteratorRef(iter), num, sfunc,
+ cfunc, cdata, failed, root,
+ loc_parent, loc_children, loc_last_child,
+ loc_prev, loc_next);
+}
+
+static void cx_tree_default_destructor(CxTree *tree) {
+ if (tree->simple_destructor != NULL || tree->advanced_destructor != NULL) {
+ CxTreeIterator iter = tree->cl->iterator(tree, true);
+ cx_foreach(void *, node, iter) {
+ if (iter.exiting) {
+ if (tree->simple_destructor) {
+ tree->simple_destructor(node);
+ }
+ if (tree->advanced_destructor) {
+ tree->advanced_destructor(tree->destructor_data, node);
+ }
+ }
+ }
+ }
+ cxFree(tree->allocator, tree);
+}
+
+static CxTreeIterator cx_tree_default_iterator(
+ CxTree *tree,
+ bool visit_on_exit
+) {
+ return cx_tree_iterator(
+ tree->root, visit_on_exit,
+ tree->loc_children, tree->loc_next
+ );
+}
+
+static CxTreeVisitor cx_tree_default_visitor(CxTree *tree) {
+ return cx_tree_visitor(tree->root, tree->loc_children, tree->loc_next);
+}
+
+static int cx_tree_default_insert_element(
+ CxTree *tree,
+ const void *data
+) {
+ void *node;
+ if (tree->root == NULL) {
+ node = tree->node_create(data, tree);
+ if (node == NULL) return 1;
+ cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
+ tree->root = node;
+ tree->size = 1;
+ return 0;
+ }
+ int result = cx_tree_add(data, tree->search, tree->node_create,
+ tree, &node, tree->root, cx_tree_node_layout(tree));
+ if (0 == result) {
+ tree->size++;
+ } else {
+ cxFree(tree->allocator, node);
+ }
+ return result;
+}
+
+static size_t cx_tree_default_insert_many(
+ CxTree *tree,
+ struct cx_iterator_base_s *iter,
+ size_t n
+) {
+ size_t ins = 0;
+ if (!iter->valid(iter)) return 0;
+ if (tree->root == NULL) {
+ // use the first element from the iter to create the root node
+ void **eptr = iter->current(iter);
+ void *node = tree->node_create(*eptr, tree);
+ if (node == NULL) return 0;
+ cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
+ tree->root = node;
+ ins = 1;
+ iter->next(iter);
+ }
+ void *failed;
+ ins += cx_tree_add_iter(iter, n, tree->search, tree->node_create,
+ tree, &failed, tree->root, cx_tree_node_layout(tree));
+ tree->size += ins;
+ if (ins < n) {
+ cxFree(tree->allocator, failed);
+ }
+ return ins;
+}
+
+static void *cx_tree_default_find(
+ CxTree *tree,
+ const void *subtree,
+ const void *data
+) {
+ if (tree->root == NULL) return NULL;
+
+ void *found;
+ if (0 == cx_tree_search_data(
+ subtree,
+ data,
+ tree->search_data,
+ &found,
+ tree->loc_children,
+ tree->loc_next
+ )) {
+ return found;
+ } else {
+ return NULL;
+ }
+}
+
+static cx_tree_class cx_tree_default_class = {
+ cx_tree_default_destructor,
+ cx_tree_default_insert_element,
+ cx_tree_default_insert_many,
+ cx_tree_default_find,
+ cx_tree_default_iterator,
+ cx_tree_default_visitor
+};
+
+CxTree *cxTreeCreate(
+ const CxAllocator *allocator,
+ cx_tree_node_create_func create_func,
+ cx_tree_search_func search_func,
+ cx_tree_search_data_func search_data_func,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
+ if (tree == NULL) return NULL;
+
+ tree->cl = &cx_tree_default_class;
+ tree->allocator = allocator;
+ tree->node_create = create_func;
+ tree->search = search_func;
+ tree->search_data = search_data_func;
+ tree->advanced_destructor = (cx_destructor_func2) cxFree;
+ tree->destructor_data = (void *) allocator;
+ tree->loc_parent = loc_parent;
+ tree->loc_children = loc_children;
+ tree->loc_last_child = loc_last_child;
+ tree->loc_prev = loc_prev;
+ tree->loc_next = loc_next;
+ tree->root = NULL;
+ tree->size = 0;
+
+ return tree;
+}
+
+CxTree *cxTreeCreateWrapped(
+ const CxAllocator *allocator,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
+ if (tree == NULL) return NULL;
+
+ tree->cl = &cx_tree_default_class;
+ // set the allocator anyway, just in case...
+ tree->allocator = allocator;
+ tree->node_create = NULL;
+ tree->search = NULL;
+ tree->search_data = NULL;
+ tree->simple_destructor = NULL;
+ tree->advanced_destructor = NULL;
+ tree->destructor_data = NULL;
+ tree->loc_parent = loc_parent;
+ tree->loc_children = loc_children;
+ tree->loc_last_child = loc_last_child;
+ tree->loc_prev = loc_prev;
+ tree->loc_next = loc_next;
+ tree->root = root;
+ tree->size = cxTreeSubtreeSize(tree, root);
+ return tree;
+}
+
+int cxTreeAddChild(
+ CxTree *tree,
+ void *parent,
+ const void *data) {
+ void *node = tree->node_create(data, tree);
+ if (node == NULL) return 1;
+ cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
+ cx_tree_link(parent, node, cx_tree_node_layout(tree));
+ tree->size++;
+ return 0;
+}
+
+size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root) {
+ CxTreeVisitor visitor = cx_tree_visitor(
+ subtree_root,
+ tree->loc_children,
+ tree->loc_next
+ );
+ while (cxIteratorValid(visitor)) {
+ cxIteratorNext(visitor);
+ }
+ return visitor.counter;
+}
+
+size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root) {
+ CxTreeVisitor visitor = cx_tree_visitor(
+ subtree_root,
+ tree->loc_children,
+ tree->loc_next
+ );
+ while (cxIteratorValid(visitor)) {
+ cxIteratorNext(visitor);
+ }
+ return visitor.depth;
+}
+
+size_t cxTreeDepth(CxTree *tree) {
+ CxTreeVisitor visitor = tree->cl->visitor(tree);
+ while (cxIteratorValid(visitor)) {
+ cxIteratorNext(visitor);
+ }
+ return visitor.depth;
+}
+
+int cxTreeRemove(
+ CxTree *tree,
+ void *node,
+ cx_tree_relink_func relink_func
+) {
+ if (node == tree->root) return 1;
+
+ // determine the new parent
+ ptrdiff_t loc_parent = tree->loc_parent;
+ void *new_parent = tree_parent(node);
+
+ // first, unlink from the parent
+ cx_tree_unlink(node, cx_tree_node_layout(tree));
+
+ // then relink each child
+ ptrdiff_t loc_children = tree->loc_children;
+ ptrdiff_t loc_next = tree->loc_next;
+ void *child = tree_children(node);
+ while (child != NULL) {
+ // forcibly set the parent to NULL - we do not use the unlink function
+ // because that would unnecessarily modify the children linked list
+ tree_parent(child) = NULL;
+
+ // update contents, if required
+ if (relink_func != NULL) {
+ relink_func(child, node, new_parent);
+ }
+
+ // link to new parent
+ cx_tree_link(new_parent, child, cx_tree_node_layout(tree));
+
+ // proceed to next child
+ child = tree_next(child);
+ }
+
+ // clear the linked list of the removed node
+ tree_children(node) = NULL;
+ ptrdiff_t loc_last_child = tree->loc_last_child;
+ if (loc_last_child >= 0) tree_last_child(node) = NULL;
+
+ // the tree now has one member less
+ tree->size--;
+
+ return 0;
+}
+
+void cxTreeRemoveSubtree(CxTree *tree, void *node) {
+ if (node == tree->root) {
+ tree->root = NULL;
+ tree->size = 0;
+ return;
+ }
+ size_t subtree_size = cxTreeSubtreeSize(tree, node);
+ cx_tree_unlink(node, cx_tree_node_layout(tree));
+ tree->size -= subtree_size;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/utils.h"
+
+#ifndef CX_STREAM_BCOPY_BUF_SIZE
+#define CX_STREAM_BCOPY_BUF_SIZE 8192
+#endif
+
+#ifndef CX_STREAM_COPY_BUF_SIZE
+#define CX_STREAM_COPY_BUF_SIZE 1024
+#endif
+
+size_t cx_stream_bncopy(
+ void *src,
+ void *dest,
+ cx_read_func rfnc,
+ cx_write_func wfnc,
+ char *buf,
+ size_t bufsize,
+ size_t n
+) {
+ if (n == 0) {
+ return 0;
+ }
+
+ char *lbuf;
+ size_t ncp = 0;
+
+ if (buf) {
+ if (bufsize == 0) return 0;
+ lbuf = buf;
+ } else {
+ if (bufsize == 0) bufsize = CX_STREAM_BCOPY_BUF_SIZE;
+ lbuf = malloc(bufsize);
+ if (lbuf == NULL) {
+ return 0;
+ }
+ }
+
+ size_t r;
+ size_t rn = bufsize > n ? n : bufsize;
+ while ((r = rfnc(lbuf, 1, rn, src)) != 0) {
+ r = wfnc(lbuf, 1, r, dest);
+ ncp += r;
+ n -= r;
+ rn = bufsize > n ? n : bufsize;
+ if (r == 0 || n == 0) {
+ break;
+ }
+ }
+
+ if (lbuf != buf) {
+ free(lbuf);
+ }
+
+ return ncp;
+}
+
+size_t cx_stream_ncopy(
+ void *src,
+ void *dest,
+ cx_read_func rfnc,
+ cx_write_func wfnc,
+ size_t n
+) {
+ char buf[CX_STREAM_COPY_BUF_SIZE];
+ return cx_stream_bncopy(src, dest, rfnc, wfnc,
+ buf, CX_STREAM_COPY_BUF_SIZE, n);
+}
+
+#ifndef CX_SZMUL_BUILTIN
+#include "szmul.c"
+#endif
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+BUILD_ROOT = ../
+include ../config.mk
+
+OBJ_DIR = ../build/
+
+include common/objs.mk
+
+UI_LIB = ../build/lib/libuitk$(LIB_EXT)
+
+include $(TOOLKIT)/objs.mk
+OBJ = $(TOOLKITOBJS) $(COMMONOBJS)
+
+all: $(UI_LIB)
+
+include $(TOOLKIT)/Makefile
+
+$(COMMON_OBJPRE)%.o: common/%.c
+ $(CC) -o $@ -c -I../ucx/ $(CFLAGS) $(TK_CFLAGS) $<
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+$(COCOA_OBJPRE)%.o: cocoa/%.m
+ $(CC) -o $@ -c $(CFLAGS) $<
+
+$(UI_LIB): $(OBJ)
+ $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "../ui/toolkit.h"
+#import "toolkit.h"
+
+typedef void(*ui_container_add_f)(UiContainer*, NSView*);
+
+struct UiContainer {
+ NSView* widget;
+ void (*add)(UiContainer*, NSView*);
+ NSRect (*getframe)(UiContainer*);
+};
+
+UiContainer* ui_window_container(UiObject *obj, NSWindow *window);
+
+NSRect ui_container_getframe(UiContainer *ct);
+void ui_container_add(UiContainer *ct, NSView *view);
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+
+#import "container.h"
+
+
+UiContainer* ui_window_container(UiObject *obj, NSWindow *window) {
+ UiContainer *ct = ucx_mempool_malloc(
+ obj->ctx->mempool,
+ sizeof(UiContainer));
+ ct->widget = [window contentView];
+ ct->add = ui_container_add;
+ ct->getframe = ui_container_getframe;
+ return ct;
+}
+
+UIWIDGET ui_sidebar(UiObject *obj) {
+ UiContainer *ct = uic_get_current_container(obj);
+ NSRect frame = ct->getframe(ct);
+
+ // create and add views
+ NSSplitView *splitview = [[NSSplitView alloc] initWithFrame:frame];
+ [splitview setVertical:YES];
+ [splitview setDividerStyle:NSSplitViewDividerStyleThin];
+ ct->add(ct, splitview);
+
+ NSRect lframe;
+ lframe.origin.x = 0;
+ lframe.origin.y = 0;
+ lframe.size.width = 200;
+ lframe.size.height = frame.size.height;
+
+ NSRect rframe;
+ rframe.origin.x = 0;
+ rframe.origin.y = 0;
+ rframe.size.width = frame.size.width - 201;
+ rframe.size.height = frame.size.height;
+
+ NSView *sidebar = [[NSView alloc]initWithFrame:lframe];
+ NSView *contentarea = [[NSView alloc]initWithFrame:rframe];
+
+ [splitview addSubview:sidebar];
+ [splitview addSubview:contentarea];
+
+ // add ui objects for the sidebar and contentarea
+ // the sidebar is added last, so that new views are added first to it
+ UiObject *left = uic_object_new(obj, sidebar);
+ UiContainer *ct1 = ucx_mempool_malloc(
+ obj->ctx->mempool,
+ sizeof(UiContainer));
+ ct1->widget = sidebar;
+ ct1->add = ui_container_add;
+ ct1->getframe = ui_container_getframe;
+ left->container = ct1;
+
+ UiObject *right = uic_object_new(obj, sidebar);
+ UiContainer *ct2 = ucx_mempool_malloc(
+ obj->ctx->mempool,
+ sizeof(UiContainer));
+ ct2->widget = contentarea;
+ ct2->add = ui_container_add;
+ ct2->getframe = ui_container_getframe;
+ right->container = ct2;
+
+ uic_obj_add(obj, right);
+ uic_obj_add(obj, left);
+
+ return splitview;
+}
+
+
+NSRect ui_container_getframe(UiContainer *ct) {
+ return [ct->widget frame];
+}
+
+void ui_container_add(UiContainer *ct, NSView *view) {
+ [ct->widget addSubview: view];
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "../ui/graphics.h"
+#import "toolkit.h"
+
+
+@interface UiCanvas : NSView {
+ UiObject *object;
+ ui_drawfunc callback;
+ void *userdata;
+}
+
+- (UiObject*) object;
+- (void) setObject:(void*)obj;
+
+- (void*) userdata;
+- (void) setUserdata:(void*)d;
+
+- (ui_drawfunc) callback;
+- (void) setCallback: (ui_drawfunc)f;
+
+
+- (void)drawRect:(NSRect)rect;
+
+@end
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <string.h>
+
+#import "graphics.h"
+#import "container.h"
+#import "../common/context.h"
+
+
+
+@implementation UiCanvas
+
+- (UiObject*) object {
+ return object;
+}
+
+- (void) setObject:(void*)obj {
+ object = obj;
+}
+
+- (void*) userdata {
+ return userdata;
+}
+
+- (void) setUserdata:(void*)d {
+ userdata = d;
+}
+
+- (ui_drawfunc) callback {
+ return callback;
+}
+- (void) setCallback: (ui_drawfunc)f {
+ callback = f;
+}
+
+- (void) drawRect:(NSRect)rect {
+ UiGraphics g;
+ NSRect bounds = [self bounds];
+ g.width = bounds.size.width;
+ g.height = bounds.size.height;
+
+ UiEvent ev;
+ ev.obj = object;
+ ev.window = object->window;
+ ev.document = object->ctx->document;
+
+ callback(&ev, &g, userdata);
+}
+
+@end
+
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
+ UiContainer *ct = uic_get_current_container(obj);
+
+ NSRect frame = ct->getframe(ct);
+
+ UiCanvas *canvas = [[UiCanvas alloc]initWithFrame:frame];
+ [canvas setObject: obj];
+ [canvas setCallback: f];
+ [canvas setUserdata: userdata];
+ ct->add(ct, canvas);
+
+ return canvas;
+}
+
+
+// drawing functions
+void ui_graphics_color(UiGraphics *gr, int red, int green, int blue) {
+ float r = ((float)red) / 255.f;
+ float g = ((float)green) / 255.f;
+ float b = ((float)blue) / 255.f;
+
+ NSColor *color = [NSColor colorWithCalibratedRed:r green:g blue:b alpha:1];
+ [color set];
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+ // translate y
+ y = g->height - y - h;
+
+ NSRect bounds;
+ bounds.origin.x = x;
+ bounds.origin.y = y;
+ bounds.size.width = w;
+ bounds.size.height = h;
+
+ if(fill) {
+ NSRectFill(bounds);
+ } else {
+ NSFrameRect(bounds);
+ }
+}
+
+
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "../ui/menu.h"
+#import "toolkit.h"
+#import <ucx/list.h>
+
+typedef struct UiAbstractMenuItem {
+ int (*update)(id window, void *item);
+ void *item_data;
+} UiAbstractMenuItem;
+
+typedef struct UiMenuItem {
+ NSMenuItem *item;
+ int state;
+} UiMenuItem;
+
+typedef struct UiStateItem {
+ NSMenuItem *item;
+ char *var;
+} UiStateItem;
+
+typedef struct UiMenuItemList {
+ NSMenu *menu;
+ NSMenuItem *first;
+ UiList *list;
+ int index;
+ int oldcount;
+ ui_callback callback;
+ void *data;
+} UiMenuItemList;
+
+@interface UiMenuDelegate : NSObject <NSMenuDelegate> {
+ UcxList *items; // UiStateItem*
+ UcxList *itemlists; // UiMenuItemList*
+}
+
+- (void) menuNeedsUpdate:(NSMenu*) menu;
+
+- (void) addItem:(NSMenuItem*) item var: (char*)name;
+
+- (void) addList:(UiList*) list menu:(NSMenu*)menu index: (int)i callback: (ui_callback)f data:(void*) data;
+
+- (UcxList*) items;
+
+- (UcxList*) lists;
+
+@end
+
+@interface UiGroupMenuItem : NSMenuItem {
+ NSMutableArray *groups;
+}
+
+- (id)initWithTitle:(NSString*)title action:(SEL)action keyEquivalent:(NSString*)s;
+
+- (void) addGroup:(int)group;
+
+- (void) checkGroups:(int*)g count:(int)n;
+
+@end
+
+void ui_menu_init();
+UiMenuDelegate* ui_menu_delegate();
+
+int ui_menuitem_get(UiInteger *i);
+void ui_menuitem_set(UiInteger *i, int value);
+
+int ui_update_item(id window, void *data);
+int ui_update_item_list(id window, void *data);
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <string.h>
+#import <stdarg.h>
+
+#import "menu.h"
+#import "window.h"
+#import "stock.h"
+
+@implementation UiMenuDelegate
+
+- (UiMenuDelegate*) init {
+ items = NULL;
+ itemlists = NULL;
+ return self;
+}
+
+- (void) menuNeedsUpdate:(NSMenu *) menu {
+ NSWindow *activeWindow = [NSApp keyWindow];
+ [(UiCocoaWindow*)activeWindow updateMenu: menu];
+}
+
+- (void) addItem:(NSMenuItem*) item var: (char*)name {
+ UiStateItem *i = malloc(sizeof(UiStateItem));
+ i->item = item;
+ i->var = name;
+ items = ucx_list_append(items, i);
+}
+
+- (void) addList:(UiList*) list menu:(NSMenu*)menu index: (int)i callback: (ui_callback)f data:(void*) data {
+ UiMenuItemList *itemList = malloc(sizeof(UiMenuItemList));
+ itemList->list = list;
+ itemList->menu = menu;
+ itemList->first = NULL;
+ itemList->index = i;
+ itemList->oldcount = 0;
+ itemList->callback = f;
+ itemList->data = data;
+ itemlists = ucx_list_append(itemlists, itemList);
+}
+
+- (UcxList*) items {
+ return items;
+}
+
+- (UcxList*) lists {
+ return itemlists;
+
+}
+
+@end
+
+
+@implementation UiGroupMenuItem
+
+- (id)initWithTitle:(NSString*)title action:(SEL)action keyEquivalent:(NSString*)s {
+ [super initWithTitle:title action:action keyEquivalent:s];
+ groups = [[NSMutableArray alloc]initWithCapacity: 8];
+ return self;
+}
+
+- (void) addGroup:(int)group {
+ NSNumber *groupNumber = [NSNumber numberWithInteger:group];
+ [groups addObject:groupNumber];
+}
+
+- (void) checkGroups:(int*)g count:(int)n {
+ int c = [groups count];
+
+ char *check = calloc(1, c);
+ for(int i=0;i<n;i++) {
+ for(int k=0;k<c;k++) {
+ NSNumber *groupNumber = [groups objectAtIndex:k];
+ if([groupNumber intValue] == g[i]) {
+ check[k] = 1;
+ break;
+ }
+ }
+ }
+
+ for(int j=0;j<c;j++) {
+ if(check[j] == 0) {
+ [self setEnabled:NO];
+ return;
+ }
+ }
+ [self setEnabled:YES];
+}
+
+@end
+
+
+//static NSMenu *currentMenu = NULL;
+
+static UcxList *current;
+
+static int currentItemIndex = 0;
+static UiMenuDelegate *delegate;
+
+void ui_menu_init() {
+ delegate = [[UiMenuDelegate alloc]init];
+}
+
+UiMenuDelegate* ui_menu_delegate() {
+ return delegate;
+}
+
+
+void ui_menu(char *title) {
+ NSString *str = [[NSString alloc] initWithUTF8String:title];
+
+ NSMenu *menu = [[NSMenu alloc] initWithTitle: str];
+ NSMenuItem *menuItem = [[NSApp mainMenu] addItemWithTitle:str
+ action:nil keyEquivalent:@""];
+ [menu setDelegate: delegate];
+ [menu setAutoenablesItems:NO];
+
+ [[NSApp mainMenu] setSubmenu:menu forItem:menuItem];
+ //currentMenu = menu;
+ currentItemIndex = 0;
+
+ current = ucx_list_prepend(NULL, menu);
+}
+
+void ui_submenu(char *title) {
+ NSString *str = [[NSString alloc] initWithUTF8String:title];
+ NSMenu *currentMenu = current->data;
+
+ NSMenu *menu = [[NSMenu alloc] initWithTitle: str];
+ NSMenuItem *menuItem = [currentMenu addItemWithTitle:str
+ action:nil keyEquivalent:@""];
+ [menu setDelegate: delegate];
+ [menu setAutoenablesItems:NO];
+
+ [currentMenu setSubmenu:menu forItem:menuItem];
+ //currentMenu = menu;
+ currentItemIndex = 0;
+
+ current = ucx_list_prepend(current, menu);
+}
+
+void ui_submenu_end() {
+ if(ucx_list_size(current) < 2) {
+ return;
+ }
+ current = ucx_list_remove(current, current);
+}
+
+void ui_menuitem(char *label, ui_callback f, void *data) {
+ ui_menuitem_gr(label, f, data, -1);
+}
+
+void ui_menuitem_st(char *stockid, ui_callback f, void *data) {
+ ui_menuitem_stgr(stockid, f, data, -1);
+}
+
+void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...) {
+ // create menu item
+ EventWrapper *event = [[EventWrapper alloc]initWithData:userdata callback:f];
+ NSString *title = [[NSString alloc] initWithUTF8String:label];
+ UiGroupMenuItem *item = [[UiGroupMenuItem alloc]initWithTitle:title action:@selector(handleEvent:) keyEquivalent:@""];
+ [item setTarget:event];
+
+ // add groups
+ va_list ap;
+ va_start(ap, userdata);
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ [item addGroup: group];
+ }
+ va_end(ap);
+
+ NSMenu *currentMenu = current->data;
+ [currentMenu addItem:item];
+
+ currentItemIndex++;
+}
+
+void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...) {
+ // create menu item
+ EventWrapper *event = [[EventWrapper alloc]initWithData:userdata callback:f];
+ UiStockItem *si = ui_get_stock_item(stockid);
+ UiGroupMenuItem *item = [[UiGroupMenuItem alloc]initWithTitle:si->label
+ action:@selector(handleEvent:)
+ keyEquivalent:si->keyEquivalent];
+ [item setTarget:event];
+
+ // add groups
+ va_list ap;
+ va_start(ap, userdata);
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ [item addGroup: group];
+ }
+ va_end(ap);
+
+ NSMenu *currentMenu = current->data;
+ [currentMenu addItem:item];
+
+ currentItemIndex++;
+}
+
+void ui_checkitem(char *label, ui_callback f, void *data) {
+ EventWrapper *event = [[EventWrapper alloc]initWithData:data callback:f];
+ NSString *str = [[NSString alloc] initWithUTF8String:label];
+
+ NSMenu *currentMenu = current->data;
+ NSMenuItem *item = [currentMenu addItemWithTitle:str
+ action:@selector(handleStateEvent:) keyEquivalent:@""];
+ [item setTarget:event];
+
+ [delegate addItem: item var:NULL];
+ currentItemIndex++;
+}
+
+void ui_checkitem_nv(char *label, char *vname) {
+ EventWrapper *event = [[EventWrapper alloc]initWithData:NULL callback:NULL];
+ NSString *str = [[NSString alloc] initWithUTF8String:label];
+
+ NSMenu *currentMenu = current->data;
+ NSMenuItem *item = [currentMenu addItemWithTitle:str
+ action:@selector(handleStateEvent:) keyEquivalent:@""];
+ [item setTarget:event];
+
+ [delegate addItem: item var:vname];
+ currentItemIndex++;
+}
+
+void ui_menuseparator() {
+ NSMenu *currentMenu = current->data;
+ [currentMenu addItem: [NSMenuItem separatorItem]];
+ currentItemIndex++;
+}
+
+void ui_menuitem_list (UiList *items, ui_callback f, void *data) {
+ NSMenu *currentMenu = current->data;
+ [delegate addList:items menu:currentMenu index:currentItemIndex callback:f data:data];
+}
+
+
+
+int ui_menuitem_get(UiInteger *i) {
+ UiMenuItem *item = i->obj;
+ i->value = [item->item state];
+ return i->value;
+}
+
+void ui_menuitem_set(UiInteger *i, int value) {
+ UiMenuItem *item = i->obj;
+ [item->item setState: value];
+ i->value = value;
+ item->state = value;
+}
+
+
+int ui_update_item(UiCocoaWindow *window, void *data) {
+ UiMenuItem *item = data;
+ [item->item setState: item->state];
+ return 0;
+}
+
+int ui_update_item_list(UiCocoaWindow *window, void *data) {
+ UiMenuItemList *itemList = data;
+ UiList *list = itemList->list;
+
+ for(int r=0;r<itemList->oldcount;r++) {
+ [itemList->menu removeItemAtIndex:itemList->index];
+ }
+
+ char *str = ui_list_first(list);
+ int i = itemList->index;
+ [itemList->menu insertItem: [NSMenuItem separatorItem] atIndex: i];
+ i++;
+ while(str) {
+ EventWrapper *event = [[EventWrapper alloc]initWithData:itemList->data callback:itemList->callback];
+ [event setIntval: i - itemList->index - 1];
+
+ NSString *title = [[NSString alloc] initWithUTF8String:str];
+ NSMenuItem *item = [[NSMenuItem alloc]initWithTitle:title action:@selector(handleEvent:) keyEquivalent:@""];
+ [item setTarget:event];
+
+ [itemList->menu insertItem:item atIndex:i];
+
+ str = ui_list_next(list);
+ i++;
+ }
+
+ itemList->oldcount = i - itemList->index;
+
+ return 0;
+}
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+COCOA_SRC_DIR = ui/cocoa/
+COCOA_OBJPRE = $(OBJ_DIR)/$(COCOA_SRC_DIR)
+
+COCOAOBJ = toolkit.o
+COCOAOBJ += window.o
+COCOAOBJ += menu.o
+COCOAOBJ += stock.o
+COCOAOBJ += toolbar.o
+COCOAOBJ += container.o
+COCOAOBJ += text.o
+COCOAOBJ += resource.o
+COCOAOBJ += tree.o
+COCOAOBJ += graphics.o
+
+
+TOOLKITOBJS += $(COCOAOBJ:%=$(COCOA_OBJPRE)%)
+TOOLKITSOURCE += $(COCOAOBJ:%.o=cocoa/%.m)
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "../ui/toolkit.h"
+#import "../ui/properties.h"
+
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <string.h>
+
+#import "resource.h"
+#import "../common/properties.h"
+
+
+
+void ui_load_lang_def(char *locale, char *default_locale) {
+ NSString *localeString = nil;
+ char tmp[6];
+ if(!locale) {
+ NSString* lang = [[NSLocale currentLocale] localeIdentifier];
+ if(lang) {
+ localeString = lang;
+ } else {
+ [[NSString alloc]initWithUTF8String:default_locale];
+ }
+ } else {
+ localeString = [[NSString alloc]initWithUTF8String:locale];
+ }
+
+ NSString *path = [[NSBundle mainBundle] pathForResource:localeString ofType:@"properties" inDirectory:@"locales"];
+
+ const char *p = [path UTF8String];
+
+ if(uic_load_language_file((char*)p)) {
+ if(default_locale) {
+ ui_load_lang_def(default_locale, NULL);
+ } else {
+ // cannot find any language file
+ fprintf(stderr, "Ui Error: Cannot load language.\n");
+ exit(-1);
+ }
+ }
+}
+
+void ui_locales_dir(char *path) {
+ // empty
+}
+
+void ui_pixmaps_dir(char *path) {
+ // empty
+}
\ No newline at end of file
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "toolkit.h"
+#import "../ui/stock.h"
+#import <ucx/map.h>
+
+typedef struct UiStockItem {
+ NSString *label;
+ NSString *keyEquivalent;
+ NSImage *image;
+} UiStockItem;
+
+void ui_stock_init();
+
+void ui_add_stock_item(char *stock_id, NSString *label, NSString *keyEquivalent, NSImage *image);
+
+UiStockItem* ui_get_stock_item(char *stock_id);
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+
+#import "stock.h"
+#import "../common/properties.h"
+
+static UcxMap *stock_items;
+
+void ui_stock_init() {
+ stock_items = ucx_map_new(64);
+
+ ui_add_stock_item(UI_STOCK_NEW, @"New", @"n", nil);
+ ui_add_stock_item(UI_STOCK_OPEN, @"Open", @"o", nil);
+ ui_add_stock_item(UI_STOCK_SAVE, @"Save", @"s", nil);
+ ui_add_stock_item(UI_STOCK_SAVE_AS, @"Save as ...", @"", nil);
+ ui_add_stock_item(UI_STOCK_CLOSE, @"Close", @"w", nil);
+ ui_add_stock_item(UI_STOCK_UNDO, @"Undo", @"z", nil);
+ ui_add_stock_item(UI_STOCK_REDO, @"Redo", @"", nil);
+ ui_add_stock_item(UI_STOCK_CUT, @"Cut", @"x", nil);
+ ui_add_stock_item(UI_STOCK_COPY, @"Copy", @"c", nil);
+ ui_add_stock_item(UI_STOCK_PASTE, @"Paste", @"v", nil);
+ ui_add_stock_item(UI_STOCK_DELETE, @"Delete", @"", nil);
+
+ ui_add_stock_item(UI_STOCK_GO_BACK, @"Back", @"", [NSImage imageNamed: NSImageNameGoLeftTemplate]);
+ ui_add_stock_item(UI_STOCK_GO_FORWARD, @"Forward", @"", [NSImage imageNamed: NSImageNameGoRightTemplate]);
+}
+
+void ui_add_stock_item(char *stock_id, NSString *label, NSString *keyEquivalent, NSImage *image) {
+ UiStockItem *i = malloc(sizeof(UiStockItem));
+ i->label = label;
+ i->keyEquivalent = keyEquivalent;
+ i->image = image;
+
+ ucx_map_cstr_put(stock_items, stock_id, i);
+}
+
+UiStockItem* ui_get_stock_item(char *stock_id) {
+ UiStockItem *item = ucx_map_cstr_get(stock_items, stock_id);
+ if(item) {
+ char *label = uistr_n(stock_id);
+ if(label) {
+ NSString *str = [[NSString alloc]initWithUTF8String:label];
+ item->label = str;
+ }
+ }
+ return item;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "../ui/text.h"
+#import "toolkit.h"
+#import <ucx/list.h>
+
+@interface TextChangeMgr : NSObject<NSTextViewDelegate> {
+ UiContext *context;
+ UiText *value;
+ int last_length;
+}
+
+- (TextChangeMgr*)initWithValue:(UiText*)text context:(UiContext*)ctx;
+
+- (NSUndoManager*)undoManagerForTextView:(NSTextView*)textview;
+
+@end
+
+#define UI_TEXTBUF_INSERT 0
+#define UI_TEXTBUF_DELETE 1
+typedef struct UiTextBufOp {
+ int type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE
+ int start;
+ int end;
+ int len;
+ char *text;
+} UiTextBufOp;
+
+
+
+char* ui_textarea_get(UiText *text);
+void ui_textarea_set(UiText *text, char *str);
+char* ui_textarea_getsubstr(UiText *text, int begin, int end);
+void ui_textarea_insert(UiText *text, int pos, char *str);
+int ui_textarea_position(UiText *text);
+void ui_textarea_selection(UiText *text, int *begin, int *end);
+int ui_textarea_length(UiText *text);
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <string.h>
+
+#import "text.h"
+#import "container.h"
+
+@implementation TextChangeMgr
+
+- (TextChangeMgr*)initWithValue:(UiText*)text context:(UiContext*)ctx {
+ value = text;
+ context = ctx;
+ last_length = 0;
+ return self;
+}
+
+- (NSUndoManager*)undoManagerForTextView:(NSTextView*)textview {
+ return (NSUndoManager*)value->undomgr;
+}
+
+- (NSRange)textView:(NSTextView *)textview
+ willChangeSelectionFromCharacterRange:(NSRange)oldrange
+ toCharacterRange:(NSRange)newrange
+{
+ if(newrange.length != last_length) {
+ if(newrange.length == 0) {
+ ui_unset_group(context, UI_GROUP_SELECTION);
+ } else {
+ ui_set_group(context, UI_GROUP_SELECTION);
+ }
+ }
+
+ last_length = newrange.length;
+ return newrange;
+}
+
+@end
+
+
+UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
+ UiContainer *ct = uic_get_current_container(obj);
+
+ NSRect frame = ct->getframe(ct);
+
+ NSScrollView *scrollview = [[NSScrollView alloc] initWithFrame:frame];
+ [scrollview setHasVerticalScroller:YES];
+ //[scrollvew setHasHorizontalScroller:YES];
+ [scrollview setBorderType:NSNoBorder];
+ //[scrollview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
+
+ //frame.size.width = frame.size.width - 15;
+ NSTextView *textview = [[NSTextView alloc]initWithFrame:frame];
+ [textview setAllowsUndo:TRUE];
+ [textview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
+
+ [textview setFont:[NSFont fontWithName:@"Menlo" size:12]];
+
+ [scrollview setDocumentView:textview];
+
+ ct->add(ct, scrollview);
+
+ // bind value
+ if(value) {
+ value->get = ui_textarea_get;
+ value->set = ui_textarea_set;
+ value->getsubstr = ui_textarea_getsubstr;
+ value->insert = ui_textarea_insert;
+ value->position = ui_textarea_position;
+ value->selection = ui_textarea_selection;
+ value->length = ui_textarea_length;
+ value->value = NULL;
+ value->obj = textview;
+
+ TextChangeMgr *delegate = [[TextChangeMgr alloc]initWithValue:value context:obj->ctx];
+ [textview setDelegate:delegate];
+
+ NSUndoManager *undomgr = [[NSUndoManager alloc]init];
+ value->undomgr = undomgr;
+ }
+
+ return textview;
+}
+
+char* ui_textarea_get(UiText *text) {
+ if(text->value) {
+ free(text->value);
+ }
+ NSTextView *textview = (NSTextView*)text->obj;
+ NSString *str = [[textview textStorage]string];
+ size_t length = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ const char *cstr = [str UTF8String];
+ char *value = malloc(length + 1);
+ memcpy(value, cstr, length);
+ value[length] = '\0';
+ text->value = value;
+ return value;
+}
+
+void ui_textarea_set(UiText *text, char *str) {
+ if(text->value) {
+ free(text->value);
+ }
+ NSTextView *textview = (NSTextView*)text->obj;
+ NSString *s = [[NSString alloc]initWithUTF8String:str];
+ NSAttributedString *as = [[NSAttributedString alloc]initWithString:s];
+ [[textview textStorage] setAttributedString:as];
+ text->value = NULL;
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+ if(text->value) {
+ free(text->value);
+ }
+ NSTextView *textview = (NSTextView*)text->obj;
+ NSString *str = [[textview textStorage]string];
+ NSRange range;
+ range.location = begin;
+ range.length = end - begin;
+
+ NSString *substr = [str substringWithRange:range];
+ size_t length = [substr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ const char *cstr = [substr UTF8String];
+ char *value = malloc(length + 1);
+ memcpy(value, cstr, length);
+ value[length] = '\0';
+ text->value = value;
+ return value;
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+ if(text->value) {
+ free(text->value);
+ }
+ NSTextView *textview = (NSTextView*)text->obj;
+ NSString *s = [[NSString alloc]initWithUTF8String:str];
+ NSAttributedString *as = [[NSAttributedString alloc]initWithString:s];
+ [[textview textStorage] insertAttributedString:as atIndex: pos];
+ text->value = NULL;
+}
+
+int ui_textarea_position(UiText *text) {
+ return [[[(NSTextView*)text->obj selectedRanges] objectAtIndex:0] rangeValue].location;
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+ NSRange range = [[[(NSTextView*)text->obj selectedRanges] objectAtIndex:0] rangeValue];
+ *begin = range.location;
+ *end = range.location + range.length;
+}
+
+int ui_textarea_length(UiText *text) {
+ return [[(NSTextView*)text->obj textStorage] length];
+}
+
+void ui_text_undo(UiText *text) {
+ [(NSUndoManager*)text->undomgr undo];
+}
+
+void ui_text_redo(UiText *text) {
+ [(NSUndoManager*)text->undomgr redo];
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "../ui/toolbar.h"
+#import "toolkit.h"
+#import <stdarg.h>
+
+
+@protocol UiToolItem
+- (NSToolbarItem *) createItem:(NSToolbar*)toolbar
+ identifier:(NSString*)identifier
+ object:(UiObject*)obj;
+
+- (void) addGroup:(int)group;
+
+- (UcxList*) groups;
+
+@end
+
+
+/*
+ * UiToolbarStockItem
+ *
+ * creates a toolbar item from stock description
+ */
+@interface UiToolbarStockItem : NSObject <UiToolItem> {
+ char *name;
+ char *stockid;
+ ui_callback callback;
+ void *userdata;
+ UcxList *groups;
+ BOOL isToggleButton;
+}
+
+- (UiToolbarStockItem*) initWithIdentifier:(char*)identifier
+ stockID:(char*)sid
+ callback:(ui_callback)f
+ userdata:(void*)data;
+
+- (void) setIsToggleButton:(BOOL)t;
+
+
+@end
+
+/*
+ * UiToolbarItem
+ *
+ * toolbar item with label and icon
+ */
+@interface UiToolbarItem : NSObject <UiToolItem> {
+ char *name;
+ char *label;
+ // icon
+ ui_callback callback;
+ void *userdata;
+ UcxList *groups;
+ BOOL isToggleButton;
+}
+
+- (UiToolbarItem*) initWithIdentifier:(char*)identifier
+ label:(char*)lbl
+ callback:(ui_callback)f
+ userdata:(void*)data;
+
+- (void) setIsToggleButton:(BOOL)t;
+
+
+@end
+
+
+
+/*
+ * UiToolbarDelegate
+ */
+@interface UiToolbarDelegate : NSObject <NSToolbarDelegate> {
+ NSMutableArray *allowedItems;
+ NSMutableArray *defaultItems;
+ NSMutableDictionary *items;
+}
+
+- (UiToolbarDelegate*) init;
+
+- (void) addDefault:(NSString*)identifier;
+
+- (void) addItem: (NSString*) identifier
+ item: (NSObject<UiToolItem>*) item;
+
+@end
+
+
+/*
+ * UiToolbar
+ */
+@interface UiToolbar : NSToolbar {
+ UiObject *obj;
+}
+
+- (UiToolbar*) initWithObject:(UiObject*)object;
+
+- (UiObject*) object;
+
+@end
+
+void ui_toolbar_init();
+void ui_toolbar_stock_button(char *name, char *stockid, BOOL toggle, ui_callback f, void *udata, va_list ap);
+NSToolbar* ui_create_toolbar(UiObject *obj);
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <string.h>
+#import <inttypes.h>
+#import <stdarg.h>
+
+#import "toolbar.h"
+#import "window.h"
+#import "stock.h"
+
+
+static UiToolbarDelegate* toolbar_delegate;
+
+/* --------------------- UiToolbarStockItem --------------------- */
+
+@implementation UiToolbarStockItem
+
+- (UiToolbarStockItem*) initWithIdentifier:(char*)identifier
+ stockID:(char*)sid
+ callback:(ui_callback)f
+ userdata:(void*)data
+{
+ name = identifier;
+ stockid = sid;
+ callback = f;
+ userdata = data;
+ groups = NULL;
+ isToggleButton = NO;
+ return self;
+}
+
+- (void) setIsToggleButton:(BOOL)t {
+ isToggleButton = t;
+}
+
+- (void) addGroup:(int)group {
+ groups = ucx_list_append(groups, (void*)(intptr_t)group);
+}
+
+
+- (NSToolbarItem *) createItem:(NSToolbar*)toolbar
+ identifier:(NSString*)identifier
+ object:(UiObject*)obj
+{
+ UiStockItem *s = ui_get_stock_item(stockid);
+ if(s == nil) {
+ printf("cannot find stock item\n");
+ return nil;
+ }
+
+ NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:
+ identifier] autorelease];
+ //[item setLabel:[s label]];
+ //[item setPaletteLabel:[s label]];
+ [item setLabel:s->label];
+ [item setPaletteLabel:@"Operation"];
+
+ // create button ...
+ NSRect frame = NSMakeRect(0, 0, 40, 22);
+ //NSSearchField *sf = [[NSSearchField alloc]initWithFrame:frame];
+ NSButton *button = [[NSButton alloc]initWithFrame:frame];
+ //[button setImage:[s buttonImage]];
+ //[button setImage:[NSImage imageNamed: NSImageNameAddTemplate]];
+ if(s->image) {
+ [button setImage:s->image];
+ } else {
+ [button setImage:[NSImage imageNamed: NSImageNameRemoveTemplate]];
+ }
+ [button setBezelStyle: NSTexturedRoundedBezelStyle];
+
+ // event
+ EventWrapper *event = [[EventWrapper alloc]
+ initWithData:userdata callback:callback];
+ if(isToggleButton) {
+ [button setButtonType: NSPushOnPushOffButton];
+ [button setAction:@selector(handleToggleEvent:)];
+ } else {
+ [button setAction:@selector(handleEvent:)];
+ }
+ [button setTarget:event];
+
+ if(groups) {
+ uic_add_group_widget(obj->ctx, item, groups);
+ }
+
+ [item setView:button];
+ return item;
+}
+
+- (UcxList*) groups {
+ return groups;
+}
+
+@end
+
+
+/* --------------------- UiToolbarItem --------------------- */
+
+@implementation UiToolbarItem
+
+- (UiToolbarItem*) initWithIdentifier:(char*)identifier
+ label:(char*)lbl
+ callback:(ui_callback)f
+ userdata:(void*)data
+{
+ name = identifier;
+ label = lbl;
+ callback = f;
+ userdata = data;
+ groups = NULL;
+ isToggleButton = NO;
+ return self;
+}
+
+- (void) setIsToggleButton:(BOOL)t {
+ isToggleButton = t;
+}
+
+- (void) addGroup:(int)group {
+ groups = ucx_list_append(groups, (void*)(intptr_t)group);
+}
+
+
+- (NSToolbarItem *) createItem:(NSToolbar*)toolbar
+ identifier:(NSString*)identifier
+ object:(UiObject*)obj
+{
+ NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:
+ identifier] autorelease];
+ //[item setLabel:[s label]];
+ //[item setPaletteLabel:[s label]];
+ NSString *l = [[NSString alloc]initWithUTF8String:label];
+ [item setLabel:l];
+ [item setPaletteLabel:@"Operation"];
+
+ // create button ...
+ NSRect frame = NSMakeRect(0, 0, 40, 22);
+ //NSSearchField *sf = [[NSSearchField alloc]initWithFrame:frame];
+ NSButton *button = [[NSButton alloc]initWithFrame:frame];
+ //[button setImage:[s buttonImage]];
+ //[button setImage:[NSImage imageNamed: NSImageNameAddTemplate]];
+
+ // TODO: image
+ [button setImage:[NSImage imageNamed: NSImageNameRemoveTemplate]];
+
+ [button setBezelStyle: NSTexturedRoundedBezelStyle];
+
+ // event
+ EventWrapper *event = [[EventWrapper alloc]
+ initWithData:userdata callback:callback];
+ if(isToggleButton) {
+ [button setButtonType: NSPushOnPushOffButton];
+ [button setAction:@selector(handleToggleEvent:)];
+ } else {
+ [button setAction:@selector(handleEvent:)];
+ }
+ [button setTarget:event];
+
+ if(groups) {
+ uic_add_group_widget(obj->ctx, item, groups);
+ }
+
+ [item setView:button];
+ return item;
+}
+
+- (UcxList*) groups {
+ return groups;
+}
+
+@end
+
+
+/* --------------------- UiToolbarDelegate --------------------- */
+
+@implementation UiToolbarDelegate
+
+- (UiToolbarDelegate*) init {
+ allowedItems = [[NSMutableArray alloc]initWithCapacity: 16];
+ defaultItems = [[NSMutableArray alloc]initWithCapacity: 16];
+ items = [[NSMutableDictionary alloc] init];
+ return self;
+}
+
+- (void) addDefault:(NSString*)identifier {
+ [defaultItems addObject: identifier];
+}
+
+- (void) addItem: (NSString*) identifier
+ item: (NSObject<UiToolItem>*) item
+{
+ [allowedItems addObject: identifier];
+ [items setObject: item forKey:identifier];
+}
+
+/*
+- (void) addStockItem:(char*)name
+ stockID:(char*)sid
+ callback:(ui_callback)f
+ data:(void*)userdata
+{
+ UiToolbarStockItem *item = [[UiToolbarStockItem alloc]initWithData:name
+ stockID:sid callback:f data:userdata];
+
+ NSString *s = [[NSString alloc]initWithUTF8String:name];
+ [allowedItems addObject: s];
+ [items setObject: item forKey:s];
+}
+*/
+
+// implementation of NSToolbarDelegate methods
+- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
+ NSMutableArray *i = [[NSMutableArray alloc]
+ initWithCapacity:[allowedItems count] + 3];
+ [i addObject: NSToolbarFlexibleSpaceItemIdentifier];
+ [i addObject: NSToolbarSpaceItemIdentifier];
+ [i addObject: NSToolbarSeparatorItemIdentifier];
+ for(id item in allowedItems) {
+ [i addObject: item];
+ }
+
+ return i;
+}
+
+- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
+ return defaultItems;
+}
+
+- (NSToolbarItem *) toolbar:(NSToolbar*)toolbar
+ itemForItemIdentifier:(NSString*)identifier
+ willBeInsertedIntoToolbar:(BOOL)flag
+{
+ Protocol *item = @protocol(UiToolItem);
+ item = [items objectForKey: identifier];
+
+ // get UiObject from toolbar
+ UiObject *obj = [(UiToolbar*)toolbar object];
+
+ // create new NSToolbarItem
+ return [item createItem:toolbar identifier:identifier object:obj];
+}
+
+@end
+
+
+@implementation UiToolbar
+
+- (UiToolbar*) initWithObject:(UiObject*)object {
+ [self initWithIdentifier: @"MainToolbar"];
+ obj = object;
+ return self;
+}
+
+- (UiObject*) object {
+ return obj;
+}
+
+@end
+
+
+/* --------------------- functions --------------------- */
+
+void ui_toolbar_init() {
+ toolbar_delegate = [[UiToolbarDelegate alloc]init];
+}
+
+void ui_toolitem(char *name, char *label, ui_callback f, void *udata) {
+ UiToolbarItem *item = [[UiToolbarItem alloc]
+ initWithIdentifier: name
+ label: label
+ callback: f
+ userdata: udata];
+
+ NSString *identifier = [[NSString alloc]initWithUTF8String:name];
+ [toolbar_delegate addItem: identifier item: item];
+}
+
+void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *udata) {
+ ui_toolitem_stgr(name, stockid, f, udata, -1);
+}
+
+void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *udata, ...) {
+ va_list ap;
+ va_start(ap, udata);
+ ui_toolbar_stock_button(name, stockid, NO, f, udata, ap);
+ va_end(ap);
+}
+
+void ui_toolitem_toggle_st(char *name, char *stockid, ui_callback f, void *udata) {
+ ui_toolitem_toggle_stgr(name, stockid, f, udata, -1);
+}
+
+void ui_toolitem_toggle_stgr(char *name, char *stockid, ui_callback f, void *udata, ...) {
+ va_list ap;
+ va_start(ap, udata);
+ ui_toolbar_stock_button(name, stockid, YES, f, udata, ap);
+ va_end(ap);
+}
+
+
+void ui_toolbar_stock_button(char *name, char *stockid, BOOL toggle, ui_callback f, void *udata, va_list ap) {
+ UiToolbarStockItem *item = [[UiToolbarStockItem alloc]
+ initWithIdentifier: name
+ stockID: stockid
+ callback: f
+ userdata: udata];
+ [item setIsToggleButton: toggle];
+ NSString *identifier = [[NSString alloc]initWithUTF8String:name];
+ [toolbar_delegate addItem: identifier item: item];
+
+ // add groups
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ [item addGroup: group];
+ }
+}
+
+
+void ui_toolbar_add_default(char *name) {
+ NSString *identifier = [[NSString alloc]initWithUTF8String:name];
+ [toolbar_delegate addDefault: identifier];
+}
+
+NSToolbar* ui_create_toolbar(UiObject *obj) {
+ UiToolbar *toolbar = [[UiToolbar alloc] initWithObject:obj];
+ [toolbar setDelegate: toolbar_delegate];
+ [toolbar setAllowsUserCustomization: true];
+ return toolbar;
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+@interface UiApplicationDelegate : NSObject<NSApplicationDelegate> {
+
+}
+
+- (void)applicationWillTerminate:(NSNotification*)notification;
+
+- (BOOL)applicationShouldHandleReopen:(NSApplication *)app hasVisibleWindows:(BOOL)visible;
+
+- (BOOL)application:(NSApplication *)application openFile:(NSString *)filename;
+
+@end
+
+@interface EventWrapper : NSObject {
+ void *data;
+ ui_callback callback;
+ int value;
+}
+
+- (EventWrapper*) initWithData: (void*)data callback:(ui_callback) f;
+
+- (void*) data;
+- (void) setData:(void*)d;
+- (ui_callback) callback;
+- (void) setCallback: (ui_callback)f;
+- (int) intval;
+- (void) setIntval:(int)i;
+
+- (BOOL)handleEvent:(id)sender;
+- (BOOL)handleStateEvent:(id)sender;
+- (BOOL)handleToggleEvent:(id)sender;
+
+@end
+
+@interface UiThread : NSObject {
+ UiObject *obj;
+ ui_threadfunc job_func;
+ void *job_data;
+ ui_callback finish_callback;
+ void *finish_data;
+}
+
+- (id) initWithObject:(UiObject*)object;
+- (void) setJobFunction:(ui_threadfunc)func;
+- (void) setJobData:(void*)data;
+- (void) setFinishCallback:(ui_callback)callback;
+- (void) setFinishData:(void*)data;
+
+- (void) start;
+- (void) runJob:(id)n;
+- (void) finish:(id)n;
+
+@end
+
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <sys/stat.h>
+#import <sys/types.h>
+#import <errno.h>
+
+#import "../common/context.h"
+#import "../common/document.h"
+#import "../common/properties.h"
+
+#import "toolkit.h"
+#import "window.h"
+#import "menu.h"
+#import "toolbar.h"
+#import "stock.h"
+
+NSAutoreleasePool *pool;
+
+static char *application_name;
+
+static ui_callback appclose_fnc;
+static void *appclose_udata;
+
+static ui_callback openfile_fnc;
+static void *openfile_udata;
+
+void ui_init(char *appname, int argc, char **argv) {
+ pool = [[NSAutoreleasePool alloc] init];
+ [NSApplication sharedApplication];
+ [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
+
+ UiApplicationDelegate *delegate = [[UiApplicationDelegate alloc]init];
+ [NSApp setDelegate: delegate];
+
+
+ uic_docmgr_init();
+ ui_menu_init();
+ ui_toolbar_init();
+ ui_stock_init();
+
+ uic_load_app_properties();
+}
+
+char* ui_appname() {
+ return application_name;
+}
+
+void ui_exitfunc(ui_callback f, void *userdata) {
+ appclose_fnc = f;
+ appclose_udata = userdata;
+}
+
+void ui_openfilefunc(ui_callback f, void *userdata) {
+ openfile_fnc = f;
+ openfile_udata = userdata;
+}
+
+void ui_show(UiObject *obj) {
+ uic_check_group_widgets(obj->ctx);
+ if([obj->widget class] == [UiCocoaWindow class]) {
+ UiCocoaWindow *window = (UiCocoaWindow*)obj->widget;
+ [window makeKeyAndOrderFront:nil];
+ } else {
+ printf("Error: ui_show: Object is not a Window!\n");
+ }
+}
+
+void ui_set_show_all(UIWIDGET widget, int value) {
+ // TODO
+}
+
+void ui_set_visible(UIWIDGET widget, int visible) {
+ // TODO
+}
+
+void ui_set_enabled(UIWIDGET widget, int enabled) {
+ [(id)widget setEnabled: enabled];
+}
+
+
+
+void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
+ UiThread *thread = [[UiThread alloc]initWithObject:obj];
+ [thread setJobFunction:tf];
+ [thread setJobData:td];
+ [thread setFinishCallback:f];
+ [thread setFinishData:fd];
+ [thread start];
+}
+
+void ui_main() {
+ [NSApp run];
+ [pool release];
+}
+
+
+void ui_clipboard_set(char *str) {
+ NSString *string = [[NSString alloc] initWithUTF8String:str];
+ NSPasteboard * pasteBoard = [NSPasteboard generalPasteboard];
+ [pasteBoard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
+ [pasteBoard setString:string forType:NSStringPboardType];
+}
+
+char* ui_clipboard_get() {
+ NSPasteboard * pasteBoard = [NSPasteboard generalPasteboard];
+ NSArray *classes = [[NSArray alloc] initWithObjects:[NSString class], nil];
+ NSDictionary *options = [NSDictionary dictionary];
+ NSArray *data = [pasteBoard readObjectsForClasses:classes options:options];
+
+ if(data != nil) {
+ NSString *str = [data componentsJoinedByString: @""];
+
+ // copy C string
+ size_t length = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ const char *cstr = [str UTF8String];
+ char *value = malloc(length + 1);
+ memcpy(value, cstr, length);
+ value[length] = '\0';
+
+ return value;
+ } else {
+ return NULL;
+ }
+}
+
+
+@implementation UiApplicationDelegate
+
+- (void)applicationWillTerminate:(NSNotification*)notification {
+ printf("terminate\n");
+}
+
+- (BOOL)applicationShouldHandleReopen:(NSApplication *)app hasVisibleWindows:(BOOL)visible; {
+ if(!visible) {
+ printf("reopen\n");
+ }
+ return NO;
+}
+
+- (BOOL)application:(NSApplication*)application openFile:(NSString*)filename {
+ if(openfile_fnc) {
+ UiEvent event;
+ event.obj = NULL;
+ event.document = NULL;
+ event.window = NULL;
+ event.eventdata = (void*)[filename UTF8String];
+ event.intval = 0;
+ openfile_fnc(&event, openfile_udata);
+ }
+
+ return NO;
+}
+
+@end
+
+
+@implementation EventWrapper
+
+- (EventWrapper*) initWithData: (void*)d callback:(ui_callback) f {
+ data = d;
+ callback = f;
+ value = 0;
+ return self;
+}
+
+
+- (void*) data {
+ return data;
+}
+
+- (void) setData:(void*)d {
+ data = d;
+}
+
+
+- (ui_callback) callback {
+ return callback;
+}
+
+- (void) setCallback: (ui_callback)f {
+ callback = f;
+}
+
+- (int) intval {
+ return value;
+}
+
+- (void) setIntval:(int)i {
+ value = i;
+}
+
+
+- (BOOL)handleEvent:(id)sender {
+ NSWindow *activeWindow = [NSApp keyWindow];
+
+ UiEvent event;
+ event.eventdata = NULL;
+ if([activeWindow class] == [UiCocoaWindow class]) {
+ event.obj = [(UiCocoaWindow*)activeWindow object];
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ event.intval = value;
+ }
+ if(callback) {
+ callback(&event, data);
+ }
+
+ return true;
+}
+
+- (BOOL)handleStateEvent:(id)sender {
+ NSWindow *activeWindow = [NSApp keyWindow];
+ int state = [sender state] ? NSOffState : NSOnState;
+
+ UiEvent event;
+ event.intval = state;
+ event.eventdata = NULL;
+ if([activeWindow class] == [UiCocoaWindow class]) {
+ event.obj = [(UiCocoaWindow*)activeWindow object];
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ // if the sender is a menu item, we have to save the state for this
+ // window
+ UiMenuItem *wmi = [(UiCocoaWindow*)activeWindow getMenuItem: sender];
+ if(wmi) {
+ // update state in window data
+ wmi->state = state;
+ }
+ } else {
+ event.window = NULL;
+ event.document = NULL;
+ }
+ if(callback) {
+ callback(&event, data);
+ }
+ [sender setState: state];
+
+ return true;
+}
+
+- (BOOL)handleToggleEvent:(id)sender {
+ NSWindow *activeWindow = [NSApp keyWindow];
+
+ UiEvent event;
+ event.intval = [sender state];
+ event.eventdata = NULL;
+ if([activeWindow class] == [UiCocoaWindow class]) {
+ event.obj = [(UiCocoaWindow*)activeWindow object];
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ } else {
+ event.window = NULL;
+ event.document = NULL;
+ }
+ if(callback) {
+ callback(&event, data);
+ }
+
+ return true;
+}
+
+@end
+
+@implementation UiThread
+
+- (id) initWithObject:(UiObject*)object {
+ obj = object;
+ job_func = NULL;
+ job_data = NULL;
+ finish_callback = NULL;
+ finish_data = NULL;
+ return self;
+}
+
+- (void) setJobFunction:(ui_threadfunc)func {
+ job_func = func;
+}
+
+- (void) setJobData:(void*)data {
+ job_data = data;
+}
+
+- (void) setFinishCallback:(ui_callback)callback {
+ finish_callback = callback;
+}
+
+- (void) setFinishData:(void*)data {
+ finish_data = data;
+}
+
+- (void) start {
+ [NSThread detachNewThreadSelector:@selector(runJob:)
+ toTarget:self
+ withObject:nil];
+}
+
+- (void) runJob:(id)n {
+ int result = job_func(job_data);
+ if(!result) {
+ [self performSelectorOnMainThread:@selector(finish:)
+ withObject:nil
+ waitUntilDone:NO];
+ }
+}
+
+- (void) finish:(id)n {
+ UiEvent event;
+ event.obj = obj;
+ event.window = obj->window;
+ event.document = obj->ctx->document;
+ event.eventdata = NULL;
+ event.intval = 0;
+ finish_callback(&event, finish_data);
+}
+
+@end
+
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ #import "../ui/tree.h"
+ #import "toolkit.h"
+
+
+@interface UiTableDataSource : NSObject<NSTableViewDataSource, NSTableViewDelegate> {
+ UiList *data;
+ UiModelInfo *info;
+ UiListSelection *lastSelection;
+}
+
+- (id)initWithData:(UiList*)list modelInfo:(UiModelInfo*)modelinfo;
+
+- (void)handleDoubleAction:(id)sender;
+
+@end
+
+
+char* ui_type_to_string(UiModelType type, void *data, BOOL *free);
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <stdio.h>
+#import <stdlib.h>
+
+#import "tree.h"
+#import "container.h"
+#import "window.h"
+#import "../common/context.h"
+#import <ucx/utils.h>
+
+@implementation UiTableDataSource
+
+- (id)initWithData:(UiList*)list modelInfo:(UiModelInfo*)modelinfo {
+ data = list;
+ info = modelinfo;
+ lastSelection = NULL;
+ return self;
+}
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableview {
+ return data->count(data);
+}
+
+- (id)tableView: (NSTableView*)tableview
+ objectValueForTableColumn:(NSTableColumn*)column
+ row:(NSInteger)row
+{
+ int column_index = [[column identifier]intValue];
+
+ void *row_data = data->get(data, row);
+ void *cell_data = info->getvalue(row_data, column_index);
+
+ BOOL f = false;
+ char *str = ui_type_to_string(info->types[column_index], cell_data, &f);
+ NSString *s = [[NSString alloc]initWithUTF8String:str];
+ return s;
+}
+
+- (void)tableView:(NSTableView *)tableview
+ setObjectValue:(id)object
+ forTableColumn:(NSTableColumn *)column
+ row:(NSInteger)row
+{
+ int column_index = [[column identifier]intValue];
+
+ // TODO
+}
+
+- (void)tableViewSelectionDidChange:(NSNotification *)notification {
+ NSTableView *tableview = (NSTableView*)notification.object;
+
+ // create selection object
+ UiListSelection *selection = malloc(sizeof(UiListSelection));
+ selection->count = [tableview numberOfSelectedRows];
+
+ selection->rows = calloc(selection->count, sizeof(int));
+ NSIndexSet *indices = [tableview selectedRowIndexes];
+ NSUInteger index = [indices firstIndex];
+ int i=0;
+ while (index!=NSNotFound) {
+ selection->rows[i] = index;
+ index = [indices indexGreaterThanIndex:index];
+ i++;
+ }
+
+ // create event object
+ UiEvent event;
+ NSWindow *activeWindow = [NSApp keyWindow];
+ if([activeWindow class] == [UiCocoaWindow class]) {
+ event.obj = [(UiCocoaWindow*)activeWindow object];
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ } else {
+ event.window = NULL;
+ event.document = NULL;
+ }
+ event.eventdata = selection;
+ event.intval = selection->count == 0 ? -1 : selection->rows[0];
+
+ // callback
+ info->selection(&event, info->userdata);
+
+ // cleanup
+ if(lastSelection) {
+ free(lastSelection->rows);
+ free(lastSelection);
+ }
+ lastSelection = selection;
+}
+
+- (void)handleDoubleAction:(id)sender {
+ // create event object
+ UiEvent event;
+ NSWindow *activeWindow = [NSApp keyWindow];
+ if([activeWindow class] == [UiCocoaWindow class]) {
+ event.obj = [(UiCocoaWindow*)activeWindow object];
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ } else {
+ event.window = NULL;
+ event.document = NULL;
+ }
+ event.eventdata = lastSelection;
+ event.intval = lastSelection->count == 0 ? -1 : lastSelection->rows[0];
+
+ info->activate(&event, info->userdata);
+}
+
+- (BOOL)tableView:(NSTableView *)tableview isGroupRow:(NSInteger)row {
+ return NO;
+}
+
+@end
+
+
+UIWIDGET ui_table(UiObject *obj, UiList *model, UiModelInfo *modelinfo) {
+ UiContainer *ct = uic_get_current_container(obj);
+ NSRect frame = ct->getframe(ct);
+
+ NSScrollView *scrollview = [[NSScrollView alloc] initWithFrame:frame];
+ [scrollview setHasVerticalScroller:YES];
+ //[scrollvew setHasHorizontalScroller:YES];
+ [scrollview setBorderType:NSNoBorder];
+ //[scrollview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
+
+ NSTableView *tableview = [[NSTableView alloc]initWithFrame:frame];
+ [scrollview setDocumentView:tableview];
+ [tableview setAllowsMultipleSelection: YES];
+ //[tableview setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
+
+ // add columns
+ for(int i=0;i<modelinfo->columns;i++) {
+ NSString *cid = [[NSString alloc]initWithFormat: @"%d", i];
+ NSTableColumn *column = [[NSTableColumn alloc]initWithIdentifier:cid];
+
+ NSString *title = [[NSString alloc]initWithUTF8String: modelinfo->titles[i]];
+ [[column headerCell] setStringValue:title];
+
+ [tableview addTableColumn:column];
+ }
+
+ UiTableDataSource *source = [[UiTableDataSource alloc]initWithData:model modelInfo:modelinfo];
+ [tableview setDataSource:source];
+ [tableview setDelegate:source];
+
+ [tableview setDoubleAction:@selector(handleDoubleAction:)];
+ [tableview setTarget:source];
+
+
+ ct->add(ct, scrollview);
+ return scrollview;
+}
+
+
+UIWIDGET ui_listview_var(UiObject *obj, UiListPtr *list, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
+ UiContainer *ct = uic_get_current_container(obj);
+ NSRect frame = ct->getframe(ct);
+
+ NSScrollView *scrollview = [[NSScrollView alloc] initWithFrame:frame];
+ [scrollview setHasVerticalScroller:YES];
+
+ [scrollview setBorderType:NSNoBorder];
+
+ NSTableView *tableview = [[NSTableView alloc]initWithFrame:frame];
+ [scrollview setDocumentView:tableview];
+ [tableview setAllowsMultipleSelection: NO];
+
+ // add single column
+ NSTableColumn *column = [[NSTableColumn alloc]initWithIdentifier:@"c"];
+ [tableview addTableColumn:column];
+
+ // create model info
+ UiModelInfo *modelinfo = ui_model_info(obj->ctx, UI_STRING, -1);
+
+ // add source
+ UiTableDataSource *source = [[UiTableDataSource alloc]initWithData:list->list modelInfo:modelinfo];
+
+ [tableview setDataSource:source];
+ [tableview setDelegate:source];
+
+ [tableview setDoubleAction:@selector(handleDoubleAction:)];
+ [tableview setTarget:source];
+
+ ct->add(ct, scrollview);
+ return scrollview;
+}
+
+UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
+ UiListPtr *listptr = ucx_mempool_malloc(obj->ctx->mempool, sizeof(UiListPtr));
+ listptr->list = list;
+ return ui_listview_var(obj, listptr, getvalue, f, udata);
+}
+
+UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
+ UiVar *var = uic_connect_var(obj->ctx, varname, UI_VAR_LIST);
+ if(var) {
+ UiListVar *value = var->value;
+ return ui_listview_var(obj, value->listptr, getvalue, f, udata);
+ } else {
+ // TODO: error
+ }
+ return NULL;
+}
+
+
+// TODO: motif code duplicate
+char* ui_type_to_string(UiModelType type, void *data, BOOL *free) {
+ switch(type) {
+ case UI_STRING: *free = FALSE; return data;
+ case UI_INTEGER: {
+ *free = TRUE;
+ int *val = data;
+ sstr_t str = ucx_asprintf(ucx_default_allocator(), "%d", *val);
+ return str.ptr;
+ }
+ }
+ *free = FALSE;
+ return NULL;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Cocoa/Cocoa.h>
+#import "../ui/window.h"
+#import <ucx/list.h>
+#import <ucx/map.h>
+
+#import "menu.h"
+
+
+
+@interface UiCocoaWindow : NSWindow {
+ UiObject *uiobj;
+ UcxMap *menus; // key: NSMenu value: UcxList of UiMenuItem
+ UcxMap *items; // key: NSMenuItem value: UiMenuItem
+}
+
+- (UiCocoaWindow*) init: (NSRect)frame object: (UiObject*)obj;
+- (UiObject*) object;
+- (void) setObject:(UiObject*)obj;
+- (void) setMenuItems:(UcxList*)menuItems;
+- (void) setMenuItemLists:(UcxList*)itemLists;
+- (UiMenuItem*) getMenuItem:(NSMenuItem*)item;
+- (void) updateMenu:(NSMenu*)menu;
+
+@end
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#import "window.h"
+#import "menu.h"
+#import "toolbar.h"
+#import "container.h"
+#import <ucx/mempool.h>
+#import "../common/context.h"
+
+static int window_default_width = 600;
+static int window_default_height = 500;
+
+@implementation UiCocoaWindow
+
+- (UiCocoaWindow*) init: (NSRect)frame object: (UiObject*)obj {
+ self = [self initWithContentRect:frame
+ styleMask:NSTitledWindowMask |
+ NSResizableWindowMask |
+ NSClosableWindowMask |
+ NSMiniaturizableWindowMask
+ backing:NSBackingStoreBuffered
+ defer:false];
+
+ uiobj = obj;
+ UcxAllocator *allocator = uiobj->ctx->mempool->allocator;
+ menus = ucx_map_new_a(allocator, 8);
+ items = ucx_map_new_a(allocator, 64);
+
+ return self;
+}
+
+- (UiObject*) object {
+ return uiobj;
+}
+
+- (void) setObject:(UiObject*)obj {
+ uiobj = obj;
+}
+
+- (void) setMenuItems:(UcxList*)menuItems {
+ UcxAllocator *allocator = uiobj->ctx->mempool->allocator;
+
+ UCX_FOREACH(elm, menuItems) {
+ UiStateItem *item = elm->data;
+ NSMenu *menu = [item->item menu];
+
+ // create UiMenuItem which represents an NSMenuItem for a Window
+ UiMenuItem *windowItem = ucx_mempool_malloc(uiobj->ctx->mempool, sizeof(UiMenuItem));
+ windowItem->item = item->item;
+ windowItem->state = 0;
+ if(item->var) {
+ // bind value
+ UiVar *var = uic_connect_var(uiobj->ctx, item->var, UI_VAR_INTEGER);
+ if(var) {
+ UiInteger *value = var->value;
+ value->obj = windowItem;
+ value->get = ui_menuitem_get;
+ value->set = ui_menuitem_set;
+ value = 0;
+ } else {
+ // TODO: error
+ }
+ }
+
+ // add item
+ UiAbstractMenuItem *abstractItem = malloc(sizeof(UiAbstractMenuItem));
+ abstractItem->update = ui_update_item;
+ abstractItem->item_data = windowItem;
+ UcxList *itemList = ucx_map_get(menus, ucx_key(&menu, sizeof(void*)));
+ itemList = ucx_list_append_a(allocator, itemList, abstractItem);
+ ucx_map_put(menus, ucx_key(&menu, sizeof(void*)), itemList);
+
+ ucx_map_put(items, ucx_key(&windowItem->item, sizeof(void*)), windowItem);
+ }
+}
+
+- (void) setMenuItemLists:(UcxList*)itemLists {
+ UcxAllocator *allocator = uiobj->ctx->mempool->allocator;
+
+ UCX_FOREACH(elm, itemLists) {
+ UiMenuItemList *list = elm->data;
+
+ UiAbstractMenuItem *abstractItem = malloc(sizeof(UiAbstractMenuItem));
+ abstractItem->update = ui_update_item_list;
+ abstractItem->item_data = list;
+
+ UcxList *itemList = ucx_map_get(menus, ucx_key(&list->menu, sizeof(void*)));
+ itemList = ucx_list_append_a(allocator, itemList, abstractItem);
+ ucx_map_put(menus, ucx_key(&list->menu, sizeof(void*)), itemList);
+
+ }
+}
+
+- (UiMenuItem*) getMenuItem:(NSMenuItem*)item {
+ return ucx_map_get(items, ucx_key(&item, sizeof(void*)));
+}
+
+- (void) updateMenu:(NSMenu*)menu {
+ UcxList *itemList = ucx_map_get(menus, ucx_key(&menu, sizeof(void*)));
+ UCX_FOREACH(elm, itemList) {
+ UiAbstractMenuItem *item = elm->data;
+ item->update(self, item->item_data);
+ }
+
+ // update group items
+ // TODO: use only one loop for all items
+ int ngroups = 0;
+ int *groups = ui_active_groups(uiobj->ctx, &ngroups);
+
+ NSArray *groupItems = [menu itemArray];
+ int count = [groupItems count];
+ for(int i=0;i<count;i++) {
+ id item = [groupItems objectAtIndex:i];
+ if([item class] == [UiGroupMenuItem class]) {
+ [item checkGroups: groups count:ngroups];
+ }
+ }
+ free(groups);
+}
+
+@end
+
+
+/* ------------------------------ public API ------------------------------ */
+
+UiObject* ui_window(char *title, void *window_data) {
+ UcxMempool *mp = ucx_mempool_new(256);
+ UiObject *obj = ucx_mempool_calloc(mp, 1, sizeof(UiObject));
+ obj->ctx = uic_context(obj, mp);
+
+ // create native window
+ NSRect frame = NSMakeRect(
+ 300,
+ 200,
+ window_default_width,
+ window_default_height);
+
+ /*
+ UiCocoaWindow *window = [[UiCocoaWindow alloc] initWithContentRect:frame
+ styleMask:NSTitledWindowMask | NSResizableWindowMask |
+ NSClosableWindowMask | NSMiniaturizableWindowMask
+ backing:NSBackingStoreBuffered
+ defer:false];
+ */
+ UiCocoaWindow *window = [[UiCocoaWindow alloc] init:frame object:obj];
+
+ NSString *titleStr = [[NSString alloc] initWithUTF8String:title];
+ [window setTitle:titleStr];
+
+ UiMenuDelegate *menuDelegate = ui_menu_delegate();
+ [window setMenuItems: [menuDelegate items]];
+ [window setMenuItemLists: [menuDelegate lists]];
+
+ NSToolbar *toolbar = ui_create_toolbar(obj);
+ [window setToolbar: toolbar];
+
+ obj->widget = (NSView*)window;
+ obj->window = window_data;
+ obj->container = ui_window_container(obj, window);
+
+
+ return obj;
+}
+
+void ui_close(UiObject *obj) {
+ // TODO
+}
+
+char* ui_openfiledialog(UiObject *obj) {
+ NSOpenPanel* op = [NSOpenPanel openPanel];
+ if ([op runModal] == NSOKButton) {
+ NSArray *urls = [op URLs];
+ NSURL *url = [urls objectAtIndex:0];
+
+ const char *str = [[url path] UTF8String];
+ return (char*)strdup(str);
+ }
+ return NULL;
+}
+
+char* ui_savefiledialog(UiObject *obj) {
+ NSSavePanel* sp = [NSSavePanel savePanel];
+ if ([sp runModal] == NSOKButton) {
+ NSURL *url = [sp URL];
+
+ const char *str = [[url path] UTF8String];
+ return (char*)strdup(str);
+ }
+ return NULL;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "condvar.h"
+
+
+
+UiCondVar* ui_condvar_create(void) {
+ UiPosixCondVar *var = malloc(sizeof(UiPosixCondVar));
+ var->var.data = NULL;
+ var->var.intdata = 0;
+ var->set = 0;
+ pthread_mutex_init(&var->lock, NULL);
+ pthread_cond_init(&var->cond, NULL);
+ return (UiCondVar*)var;
+}
+
+void ui_condvar_wait(UiCondVar *var) {
+ UiPosixCondVar *p = (UiPosixCondVar*)var;
+ pthread_mutex_lock(&p->lock);
+ if(!p->set) {
+ pthread_cond_wait(&p->cond, &p->lock);
+ }
+ p->set = 0;
+ pthread_mutex_unlock(&p->lock);
+
+}
+
+void ui_condvar_signal(UiCondVar *var, void *data, int intdata) {
+ UiPosixCondVar *p = (UiPosixCondVar*)var;
+ pthread_mutex_lock(&p->lock);
+ p->var.data = data;
+ p->var.intdata = intdata;
+ p->set = 1;
+ pthread_cond_signal(&p->cond);
+ pthread_mutex_unlock(&p->lock);
+}
+
+void ui_condvar_destroy(UiCondVar *var) {
+ UiPosixCondVar *p = (UiPosixCondVar*)var;
+ pthread_mutex_destroy(&p->lock);
+ pthread_cond_destroy(&p->cond);
+ free(p);
+
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_CONDVAR_H
+#define UIC_CONDVAR_H
+
+#include "../ui/toolkit.h"
+
+#include <pthread.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiPosixCondVar {
+ UiCondVar var;
+ int set;
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+} UiPosixCondVar;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_CONDVAR_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <stdarg.h>
+
+#include <cx/array_list.h>
+#include <cx/compare.h>
+#include <cx/mempool.h>
+
+#include "context.h"
+#include "../ui/window.h"
+#include "document.h"
+#include "types.h"
+
+
+static UiContext* global_context;
+
+void uic_init_global_context(void) {
+ CxMempool *mp = cxBasicMempoolCreate(32);
+ global_context = uic_context(NULL, mp);
+}
+
+UiContext* ui_global_context(void) {
+ return global_context;
+}
+
+UiContext* uic_context(UiObject *toplevel, CxMempool *mp) {
+ UiContext *ctx = cxMalloc(mp->allocator, sizeof(UiContext));
+ memset(ctx, 0, sizeof(UiContext));
+ ctx->mp = mp;
+ ctx->allocator = mp->allocator;
+ ctx->obj = toplevel;
+ ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16);
+
+ ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_intptr, CX_STORE_POINTERS);
+ ctx->group_widgets = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, sizeof(UiGroupWidget));
+ ctx->groups = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32);
+
+ ctx->attach_document = uic_context_attach_document;
+ ctx->detach_document2 = uic_context_detach_document2;
+
+#if UI_GTK2 || UI_GTK3
+ if(toplevel && toplevel->widget) {
+ ctx->accel_group = gtk_accel_group_new();
+ gtk_window_add_accel_group(GTK_WINDOW(toplevel->widget), ctx->accel_group);
+ }
+#endif
+
+ return ctx;
+}
+
+UiContext* uic_root_context(UiContext *ctx) {
+ return ctx->parent ? uic_root_context(ctx->parent) : ctx;
+}
+
+void uic_context_prepare_close(UiContext *ctx) {
+ cxListClear(ctx->groups);
+ cxListClear(ctx->group_widgets);
+}
+
+void uic_context_attach_document(UiContext *ctx, void *document) {
+ cxListAdd(ctx->documents, document);
+ ctx->document = document;
+
+ UiContext *doc_ctx = ui_document_context(document);
+
+ // check if any parent context has an unbound variable with the same name
+ // as any document variable
+ UiContext *var_ctx = ctx;
+ while(var_ctx) {
+ if(var_ctx->vars_unbound && cxMapSize(var_ctx->vars_unbound) > 0) {
+ CxIterator i = cxMapIterator(var_ctx->vars_unbound);
+ cx_foreach(CxMapEntry*, entry, i) {
+ UiVar *var = entry->value;
+ UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key);
+ if(docvar) {
+ // bind var to document var
+ uic_copy_binding(var, docvar, TRUE);
+ cxIteratorFlagRemoval(i);
+ }
+ }
+ }
+
+ var_ctx = ctx->parent;
+ }
+}
+
+static void uic_context_unbind_vars(UiContext *ctx) {
+ CxIterator i = cxMapIterator(ctx->vars);
+ cx_foreach(CxMapEntry*, entry, i) {
+ UiVar *var = entry->value;
+ if(var->from && var->from_ctx) {
+ uic_save_var2(var);
+ uic_copy_binding(var, var->from, FALSE);
+ cxMapPut(var->from_ctx->vars_unbound, *entry->key, var->from);
+ var->from_ctx = ctx;
+ }
+ }
+
+ if(ctx->documents) {
+ i = cxListIterator(ctx->documents);
+ cx_foreach(void *, doc, i) {
+ UiContext *subctx = ui_document_context(doc);
+ uic_context_unbind_vars(subctx);
+ }
+ }
+}
+
+void uic_context_detach_document2(UiContext *ctx, void *document) {
+ // find the document in the documents list
+ ssize_t docIndex = cxListFind(ctx->documents, document);
+ if(docIndex < 0) {
+ return;
+ }
+
+ cxListRemove(ctx->documents, docIndex);
+ ctx->document = cxListAt(ctx->documents, 0);
+
+ UiContext *docctx = ui_document_context(document);
+ uic_context_unbind_vars(docctx); // unbind all doc/subdoc vars from the parent
+}
+
+void uic_context_detach_all(UiContext *ctx) {
+ // copy list
+ CxList *ls = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+ CxIterator i = cxListIterator(ctx->documents);
+ cx_foreach(void *, doc, i) {
+ cxListAdd(ls, doc);
+ }
+
+ // detach documents
+ i = cxListIterator(ls);
+ cx_foreach(void *, doc, i) {
+ ctx->detach_document2(ctx, doc);
+ }
+
+ cxListDestroy(ls);
+}
+
+static UiVar* ctx_getvar(UiContext *ctx, CxHashKey key) {
+ UiVar *var = cxMapGet(ctx->vars, key);
+ if(!var && ctx->documents) {
+ CxIterator i = cxListIterator(ctx->documents);
+ cx_foreach(void *, doc, i) {
+ UiContext *subctx = ui_document_context(doc);
+ var = ctx_getvar(subctx, key);
+ if(var) {
+ break;
+ }
+ }
+ }
+ return var;
+}
+
+UiVar* uic_get_var(UiContext *ctx, const char *name) {
+ CxHashKey key = cx_hash_key(name, strlen(name));
+ return ctx_getvar(ctx, key);
+}
+
+UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) {
+ UiVar *var = uic_get_var(ctx, name);
+ if(var) {
+ if(var->type == type) {
+ return var;
+ } else {
+ fprintf(stderr, "UiError: var '%s' already bound with different type\n", name);
+ }
+ }
+
+ var = ui_malloc(ctx, sizeof(UiVar));
+ var->type = type;
+ var->value = uic_create_value(ctx, type);
+ var->from = NULL;
+ var->from_ctx = ctx;
+
+ cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var);
+
+ if(!ctx->vars_unbound) {
+ ctx->vars_unbound = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 16);
+ }
+ cxMapPut(ctx->vars_unbound, name, var);
+
+ return var;
+}
+
+UiVar* uic_create_value_var(UiContext* ctx, void* value) {
+ UiVar *var = (UiVar*)ui_malloc(ctx, sizeof(UiVar));
+ var->from = NULL;
+ var->from_ctx = ctx;
+ var->value = value;
+ var->type = UI_VAR_SPECIAL;
+ return var;
+}
+
+void* uic_create_value(UiContext *ctx, UiVarType type) {
+ void *val = NULL;
+ switch(type) {
+ case UI_VAR_SPECIAL: break;
+ case UI_VAR_INTEGER: {
+ val = ui_int_new(ctx, NULL);
+ break;
+ }
+ case UI_VAR_DOUBLE: {
+ val = ui_double_new(ctx, NULL);
+ break;
+ }
+ case UI_VAR_STRING: {
+ val = ui_string_new(ctx, NULL);
+ break;
+ }
+ case UI_VAR_TEXT: {
+ val = ui_text_new(ctx, NULL);
+ break;
+ }
+ case UI_VAR_LIST: {
+ val = ui_list_new(ctx, NULL);
+ break;
+ }
+ case UI_VAR_RANGE: {
+ val = ui_range_new(ctx, NULL);
+ break;
+ }
+ case UI_VAR_GENERIC: {
+ val = ui_generic_new(ctx, NULL);
+ }
+ }
+ return val;
+}
+
+
+UiVar* uic_widget_var(UiContext* toplevel, UiContext* current, void* value, const char* varname, UiVarType type) {
+ if (value) {
+ return uic_create_value_var(current, value);
+ }
+ if (varname) {
+ return uic_create_var(toplevel, varname, type);
+ }
+ return NULL;
+}
+
+
+void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) {
+ // check type
+ if(from->type != to->type) {
+ fprintf(stderr, "UI Error: var has incompatible type.\n");
+ return;
+ }
+
+ void *fromvalue = from->value;
+ // update var
+ if(copytodoc) {
+ to->from = from;
+ to->from_ctx = from->from_ctx;
+ }
+
+ // copy binding
+ // we don't copy the observer, because the from var has never one
+ switch(from->type) {
+ default: fprintf(stderr, "uic_copy_binding: wtf!\n"); break;
+ case UI_VAR_SPECIAL: break;
+ case UI_VAR_INTEGER: {
+ UiInteger *f = fromvalue;
+ UiInteger *t = to->value;
+ if(!f->obj) break;
+ uic_int_copy(f, t);
+ t->set(t, t->value);
+ break;
+ }
+ case UI_VAR_DOUBLE: {
+ UiDouble *f = fromvalue;
+ UiDouble *t = to->value;
+ if(!f->obj) break;
+ uic_double_copy(f, t);
+ t->set(t, t->value);
+ break;
+ }
+ case UI_VAR_STRING: {
+ UiString *f = fromvalue;
+ UiString *t = to->value;
+ if(!f->obj) break;
+ uic_string_copy(f, t);
+ char *tvalue = t->value.ptr ? t->value.ptr : "";
+ t->set(t, tvalue);
+ break;
+ }
+ case UI_VAR_TEXT: {
+ UiText *f = fromvalue;
+ UiText *t = to->value;
+ if(!f->obj) break;
+ uic_text_copy(f, t);
+ char *tvalue = t->value.ptr ? t->value.ptr : "";
+ t->set(t, tvalue);
+ t->setposition(t, t->pos);
+ break;
+ }
+ case UI_VAR_LIST: {
+ // TODO: not sure how correct this is
+
+ UiList *f = from->value;
+ UiList *t = to->value;
+ if (f->obj) {
+ t->obj = f->obj;
+ t->update = f->update;
+ t->getselection = f->getselection;
+ t->setselection = f->setselection;
+ }
+
+ UiVar tmp = *from;
+ *from = *to;
+ *to = tmp;
+
+ UiList* t2 = to->value;
+ ui_notify(t2->observers, NULL);
+
+ break;
+ }
+ case UI_VAR_RANGE: {
+ UiRange *f = fromvalue;
+ UiRange *t = to->value;
+ if(!f->obj) break;
+ uic_range_copy(f, t);
+ t->setextent(t, t->extent);
+ t->setrange(t, t->min, t->max);
+ t->set(t, t->value);
+ break;
+ }
+ case UI_VAR_GENERIC: {
+ UiGeneric *f = fromvalue;
+ UiGeneric *t = to->value;
+ if(!f->obj) break;
+ uic_generic_copy(f, t);
+ t->set(t, t->value, t->type);
+ break;
+ }
+ }
+}
+
+void uic_save_var2(UiVar *var) {
+ switch(var->type) {
+ case UI_VAR_SPECIAL: break;
+ case UI_VAR_INTEGER: uic_int_save(var->value); break;
+ case UI_VAR_DOUBLE: uic_double_save(var->value); break;
+ case UI_VAR_STRING: uic_string_save(var->value); break;
+ case UI_VAR_TEXT: uic_text_save(var->value); break;
+ case UI_VAR_LIST: break;
+ case UI_VAR_RANGE: uic_range_save(var->value); break;
+ case UI_VAR_GENERIC: uic_generic_save(var->value); break;
+ }
+}
+
+void uic_unbind_var(UiVar *var) {
+ switch(var->type) {
+ case UI_VAR_SPECIAL: break;
+ case UI_VAR_INTEGER: uic_int_unbind(var->value); break;
+ case UI_VAR_DOUBLE: uic_double_unbind(var->value); break;
+ case UI_VAR_STRING: uic_string_unbind(var->value); break;
+ case UI_VAR_TEXT: uic_text_unbind(var->value); break;
+ case UI_VAR_LIST: uic_list_unbind(var->value); break;
+ case UI_VAR_RANGE: uic_range_unbind(var->value); break;
+ case UI_VAR_GENERIC: uic_generic_unbind(var->value); break;
+ }
+}
+
+void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value) {
+ // TODO: do we need/want this? Why adding vars to a context after
+ // widgets reference these? Workarounds:
+ // 1. add vars to ctx before creating ui
+ // 2. create ui, create new document with vars, attach doc
+ // also it would be possible to create a function, that scans unbound vars
+ // and connects them to available vars
+ /*
+ UiContext *rootctx = uic_root_context(ctx);
+ UiVar *b = NULL;
+ if(rootctx->bound) {
+ // some widgets are already bound to some vars
+ b = ucx_map_cstr_get(rootctx->bound, name);
+ if(b) {
+ // a widget is bound to a var with this name
+ // if ctx is the root context we can remove the var from bound
+ // because set_doc or detach can't fuck things up
+ if(ctx == rootctx) {
+ ucx_map_cstr_remove(ctx->bound, name);
+ // TODO: free stuff
+ }
+ }
+ }
+ */
+
+ // create new var and add it to doc's vars
+ UiVar *var = ui_malloc(ctx, sizeof(UiVar));
+ var->type = type;
+ var->value = value;
+ var->from = NULL;
+ var->from_ctx = ctx;
+ size_t oldcount = cxMapSize(ctx->vars);
+ cxMapPut(ctx->vars, name, var);
+ if(cxMapSize(ctx->vars) != oldcount + 1) {
+ fprintf(stderr, "UiError: var '%s' already exists\n", name);
+ }
+
+ // TODO: remove?
+ // a widget is already bound to a var with this name
+ // copy the binding (like uic_context_set_document)
+ /*
+ if(b) {
+ uic_copy_binding(b, var, TRUE);
+ }
+ */
+}
+
+void uic_remove_bound_var(UiContext *ctx, UiVar *var) {
+ // TODO
+}
+
+
+// public API
+
+void ui_attach_document(UiContext *ctx, void *document) {
+ uic_context_attach_document(ctx, document);
+}
+
+void ui_detach_document2(UiContext *ctx, void *document) {
+ uic_context_detach_document2(ctx, document);
+}
+
+void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata) {
+ ctx->close_callback = fnc;
+ ctx->close_data = udata;
+}
+
+UIEXPORT void ui_context_destroy(UiContext *ctx) {
+ cxMempoolDestroy(ctx->mp);
+}
+
+
+void ui_set_group(UiContext *ctx, int group) {
+ if(cxListFind(ctx->groups, &group) == -1) {
+ cxListAdd(ctx->groups, &group);
+ }
+
+ // enable/disable group widgets
+ uic_check_group_widgets(ctx);
+}
+
+void ui_unset_group(UiContext *ctx, int group) {
+ int i = cxListFind(ctx->groups, &group);
+ if(i != -1) {
+ cxListRemove(ctx->groups, i);
+ }
+
+ // enable/disable group widgets
+ uic_check_group_widgets(ctx);
+}
+
+int* ui_active_groups(UiContext *ctx, int *ngroups) {
+ *ngroups = cxListSize(ctx->groups);
+ return cxListAt(ctx->groups, 0);
+}
+
+void uic_check_group_widgets(UiContext *ctx) {
+ int ngroups = 0;
+ int *groups = ui_active_groups(ctx, &ngroups);
+
+ CxIterator i = cxListIterator(ctx->group_widgets);
+ cx_foreach(UiGroupWidget *, gw, i) {
+ char *check = calloc(1, gw->numgroups);
+
+ for(int i=0;i<ngroups;i++) {
+ for(int k=0;k<gw->numgroups;k++) {
+ if(groups[i] == gw->groups[k]) {
+ check[k] = 1;
+ }
+ }
+ }
+
+ int enable = 1;
+ for(int i=0;i<gw->numgroups;i++) {
+ if(check[i] == 0) {
+ enable = 0;
+ break;
+ }
+ }
+ free(check);
+ gw->enable(gw->widget, enable);
+ }
+}
+
+void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) {
+ // get groups
+ CxList *groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
+ va_list ap;
+ va_start(ap, enable);
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ cxListAdd(groups, &group);
+ }
+ va_end(ap);
+
+ uic_add_group_widget(ctx, widget, enable, groups);
+
+ cxListDestroy(groups);
+}
+
+size_t uic_group_array_size(const int *groups) {
+ int i;
+ for(i=0;groups[i] >= 0;i++) { }
+ return i;
+}
+
+void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups) {
+ uic_add_group_widget_i(ctx, widget, enable, cxListAt(groups, 0), cxListSize(groups));
+}
+
+void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups) {
+ const CxAllocator *a = ctx->allocator;
+ UiGroupWidget gw;
+
+ gw.widget = widget;
+ gw.enable = enable;
+ gw.numgroups = numgroups;
+ gw.groups = cxCalloc(a, numgroups, sizeof(int));
+
+ // copy groups
+ if(groups) {
+ memcpy(gw.groups, groups, gw.numgroups * sizeof(int));
+ }
+
+ cxListAdd(ctx->group_widgets, &gw);
+}
+
+void uic_remove_group_widget(UiContext *ctx, void *widget) {
+ (void)cxListFindRemove(ctx->group_widgets, widget);
+}
+
+UIEXPORT void *ui_allocator(UiContext *ctx) {
+ return (void*)ctx->allocator;
+}
+
+void* ui_cx_mempool(UiContext *ctx) {
+ return ctx->mp;
+}
+
+void* ui_malloc(UiContext *ctx, size_t size) {
+ return ctx ? cxMalloc(ctx->allocator, size) : NULL;
+}
+
+void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize) {
+ return ctx ? cxCalloc(ctx->allocator, nelem, elsize) : NULL;
+}
+
+void ui_free(UiContext *ctx, void *ptr) {
+ if(ctx && ptr) {
+ cxFree(ctx->allocator, ptr);
+ }
+}
+
+void* ui_realloc(UiContext *ctx, void *ptr, size_t size) {
+ return ctx ? cxRealloc(ctx->allocator, ptr, size) : NULL;
+}
+
+UIEXPORT char* ui_strdup(UiContext *ctx, const char *str) {
+ if(!ctx) {
+ return NULL;
+ }
+ cxstring s = cx_str(str);
+ cxmutstr d = cx_strdup_a(ctx->allocator, s);
+ return d.ptr;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_CONTEXT_H
+#define UIC_CONTEXT_H
+
+#include "../ui/toolkit.h"
+#include <cx/map.h>
+#include <cx/hash_map.h>
+#include <cx/mempool.h>
+#include <cx/list.h>
+#include <cx/linked_list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiVar UiVar;
+typedef struct UiListPtr UiListPtr;
+typedef struct UiListVar UiListVar;
+typedef struct UiGroupWidget UiGroupWidget;
+
+typedef enum UiVarType UiVarType;
+
+enum UiVarType {
+ UI_VAR_SPECIAL = 0,
+ UI_VAR_INTEGER,
+ UI_VAR_DOUBLE,
+ UI_VAR_STRING,
+ UI_VAR_TEXT,
+ UI_VAR_LIST,
+ UI_VAR_RANGE,
+ UI_VAR_GENERIC
+};
+
+struct UiContext {
+ UiContext *parent;
+ UiObject *obj;
+ CxMempool *mp;
+ const CxAllocator *allocator;
+
+ void *document;
+ CxList *documents;
+
+ CxMap *vars; // manually created context vars
+ CxMap *vars_unbound; // unbound vars created by widgets
+
+ CxList *groups; // int list
+ CxList *group_widgets; // UiGroupWidget list
+
+ void (*attach_document)(UiContext *ctx, void *document);
+ void (*detach_document2)(UiContext *ctx, void *document);
+
+ char *title;
+
+#ifdef UI_GTK
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GActionMap *action_map;
+#elif UI_GTK2 || UI_GTK3
+ GtkAccelGroup *accel_group;
+#endif
+#endif
+
+
+
+ ui_callback close_callback;
+ void *close_data;
+};
+
+// UiVar replacement, rename it to UiVar when finished
+struct UiVar {
+ void *value;
+ UiVarType type;
+ UiVar *from;
+ UiContext *from_ctx;
+};
+
+struct UiGroupWidget {
+ void *widget;
+ ui_enablefunc enable;
+ int *groups;
+ int numgroups;
+};
+
+
+void uic_init_global_context(void);
+
+UiContext* uic_context(UiObject *toplevel, CxMempool *mp);
+UiContext* uic_root_context(UiContext *ctx);
+void uic_context_set_document(UiContext *ctx, void *document); // deprecated
+void uic_context_detach_document(UiContext *ctx); // deprecated
+
+void uic_context_prepare_close(UiContext *ctx);
+
+void uic_context_attach_document(UiContext *ctx, void *document);
+void uic_context_detach_document2(UiContext *ctx, void *document);
+void uic_context_detach_all(UiContext *ctx);
+
+UiVar* uic_get_var(UiContext *ctx, const char *name);
+UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type);
+UiVar* uic_create_value_var(UiContext *ctx, void *value);
+void* uic_create_value(UiContext *ctx, UiVarType type);
+
+UiVar* uic_widget_var(UiContext* toplevel, UiContext* current, void* value, const char* varname, UiVarType type);
+
+void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc);
+void uic_save_var2(UiVar *var);
+void uic_unbind_var(UiVar *var);
+
+void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value);
+
+void uic_remove_bound_var(UiContext *ctx, UiVar *var);
+
+size_t uic_group_array_size(const int *groups);
+void uic_check_group_widgets(UiContext *ctx);
+void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups);
+void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups);
+void uic_remove_group_widget(UiContext *ctx, void *widget);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_CONTEXT_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "document.h"
+
+#include <cx/hash_map.h>
+#include <cx/mempool.h>
+
+
+static CxMap *documents;
+
+void uic_docmgr_init() {
+ if (!documents) {
+ documents = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
+ }
+}
+
+void ui_set_document(UiObject *obj, void *document) {
+ uic_context_detach_all(obj->ctx);
+ obj->ctx->attach_document(obj->ctx, document);
+}
+
+void ui_detach_document(UiObject *obj) {
+ uic_context_detach_all(obj->ctx);
+}
+
+void* ui_get_document(UiObject *obj) {
+ return obj->ctx->document;
+}
+
+void ui_set_subdocument(void *document, void *sub) {
+ UiContext *ctx = ui_document_context(document);
+ if(!ctx) {
+ fprintf(stderr, "UI Error: pointer is not a document\n");
+ }
+ // TODO
+}
+
+void ui_detach_subdocument(void *document, void *sub) {
+ UiContext *ctx = ui_document_context(document);
+ if(!ctx) {
+ fprintf(stderr, "UI Error: pointer is not a document\n");
+ }
+ // TODO
+}
+
+void* ui_get_subdocument(void *document) {
+ UiContext *ctx = ui_document_context(document);
+ if(!ctx) {
+ fprintf(stderr, "UI Error: pointer is not a document\n");
+ }
+ // TODO
+ return NULL;
+}
+
+void* ui_document_new(size_t size) {
+ CxMempool *mp = cxMempoolCreate(256, NULL);
+ const CxAllocator *a = mp->allocator;
+ UiContext *ctx = cxCalloc(a, 1, sizeof(UiContext));
+ ctx->mp = mp;
+ ctx->attach_document = uic_context_attach_document;
+ ctx->detach_document2 = uic_context_detach_document2;
+ ctx->allocator = a;
+ ctx->vars = cxHashMapCreate(a, CX_STORE_POINTERS, 16);
+
+ void *document = cxCalloc(a, size, 1);
+ cxMapPut(documents, cx_hash_key(&document, sizeof(void*)), ctx);
+ return document;
+}
+
+void ui_document_destroy(void *doc) {
+ UiContext *ctx = ui_document_context(doc);
+ if(ctx) {
+ UiEvent ev;
+ ev.window = NULL;
+ ev.document = doc;
+ ev.obj = NULL;
+ ev.eventdata = NULL;
+ ev.intval = 0;
+
+ if(ctx->close_callback) {
+ ctx->close_callback(&ev, ctx->close_data);
+ }
+ cxMempoolDestroy(ctx->mp);
+ }
+}
+
+UiContext* ui_document_context(void *doc) {
+ if(doc) {
+ return cxMapGet(documents, cx_hash_key(&doc, sizeof(void*)));
+ } else {
+ return NULL;
+ }
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_DOCUMENT_H
+#define UIC_DOCUMENT_H
+
+#include "../ui/toolkit.h"
+#include "context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void uic_docmgr_init();
+void uic_document_addvar(void *doc, char *name, int type, size_t vs);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_DOCUMENT_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "menu.h"
+
+#include <stdarg.h>
+#include <string.h>
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+
+static UiMenuBuilder *current_builder;
+static UiMenuBuilder global_builder;
+
+static int menu_item_counter = 0;
+
+void uic_menu_init(void) {
+ global_builder.current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+ current_builder = &global_builder;
+}
+
+static void add_menu(UiMenu *menu) {
+ cx_linked_list_add(
+ (void**)¤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_CHECK_ITEM;
+
+ item->label = nl_strdup(args.label);
+ item->stockid = nl_strdup(args.stockid);
+ item->icon = nl_strdup(args.icon);
+ item->varname = nl_strdup(args.varname);
+ item->userdata = args.onchangedata;
+ item->callback = args.onchange;
+ item->groups = uic_copy_groups(args.groups, &item->ngroups);
+
+ add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_itemlist_create(UiMenuItemListArgs args) {
+ UiMenuItemList*item = malloc(sizeof(UiMenuItemList));
+ mitem_set_id(&item->item);
+ item->item.prev = NULL;
+ item->item.next = NULL;
+ item->item.type = UI_MENU_ITEM_LIST;
+ item->getvalue = args.getvalue;
+ item->callback = args.onselect;
+ item->userdata = args.onselectdata;
+ item->varname = nl_strdup(args.varname);
+
+ add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_checkitemlist_create(UiMenuItemListArgs args) {
+ UiMenuItemList* item = malloc(sizeof(UiMenuItemList));
+ mitem_set_id(&item->item);
+ item->item.prev = NULL;
+ item->item.next = NULL;
+ item->item.type = UI_MENU_CHECKITEM_LIST;
+ item->callback = args.onselect;
+ item->userdata = args.onselectdata;
+ item->varname = nl_strdup(args.varname);
+
+ add_item((UiMenuItemI*)item);
+}
+
+void ui_menu_radioitemlist_create(UiMenuItemListArgs args) {
+ UiMenuItemList* item = malloc(sizeof(UiMenuItemList));
+ mitem_set_id(&item->item);
+ item->item.prev = NULL;
+ item->item.next = NULL;
+ item->item.type = UI_MENU_RADIOITEM_LIST;
+ item->callback = args.onselect;
+ item->userdata = args.onselectdata;
+ item->varname = nl_strdup(args.varname);
+
+ add_item((UiMenuItemI*)item);
+}
+
+
+void uic_add_menu_to_stack(UiMenu* menu) {
+ cxListInsert(current_builder->current, 0, menu);
+}
+
+UiMenu* uic_get_menu_list(void) {
+ return current_builder->menus_begin;
+}
+
+UIEXPORT void ui_menu_close(void) {
+ UiMenu* menu = cxListAt(current_builder->current, 0);
+ menu->end = 1;
+}
+
+UIEXPORT int ui_menu_is_open(void) {
+ UiMenu* menu = cxListAt(current_builder->current, 0);
+ if (menu->end) {
+ ui_menu_end();
+ return 0;
+ }
+ return 1;
+}
+
+
+void ui_contextmenu_builder(UiMenuBuilder **out_builder) {
+ UiMenuBuilder *builder = malloc(sizeof(UiMenuBuilder));
+ builder->menus_begin = NULL;
+ builder->menus_end = NULL;
+ builder->current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+ current_builder = builder;
+ *out_builder = builder;
+
+ ui_menu_create(NULL);
+}
+
+
+
+static void free_menuitem(UiMenuItemI *item) {
+ switch(item->type) {
+ default: break;
+ case UI_MENU: {
+ UiMenu *menu = (UiMenu*)item;
+ UiMenuItemI *m = menu->items_begin;
+ while(m) {
+ UiMenuItemI *next = m->next;
+ free_menuitem(m);
+ m = next;
+ }
+ break;
+ }
+ case UI_MENU_ITEM: {
+ UiMenuItem *i = (UiMenuItem*)item;
+ free(i->groups);
+ free(i->label);
+ free(i->stockid);
+ free(i->icon);
+ break;
+ }
+ case UI_MENU_CHECK_ITEM: {
+ UiMenuCheckItem *i = (UiMenuCheckItem*)item;
+ free(i->groups);
+ free(i->label);
+ free(i->stockid);
+ free(i->icon);
+ free(i->varname);
+ break;
+ }
+ case UI_MENU_RADIO_ITEM: {
+ UiMenuRadioItem *i = (UiMenuRadioItem*)item;
+ free(i->groups);
+ free(i->label);
+ free(i->stockid);
+ free(i->icon);
+ //free(i->varname);
+ break;
+ }
+ case UI_MENU_ITEM_LIST: {
+ break;
+ }
+ case UI_MENU_CHECKITEM_LIST: {
+ break;
+ }
+ case UI_MENU_RADIOITEM_LIST: {
+ break;
+ }
+ }
+
+ free(item);
+}
+
+void ui_menubuilder_free(UiMenuBuilder *builder) {
+ UiMenuItemI *m = &builder->menus_begin->item;
+ while(m) {
+ UiMenuItemI *next = m->next;
+ free_menuitem(m);
+ m = next;
+ }
+ cxListDestroy(builder->current);
+ free(builder);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_MENU_H
+#define UIC_MENU_H
+
+#include "../ui/menu.h"
+
+#include <cx/linked_list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiMenuItemI UiMenuItemI;
+typedef struct UiMenu UiMenu;
+typedef struct UiMenuItem UiMenuItem;
+typedef struct UiMenuCheckItem UiMenuCheckItem;
+typedef struct UiMenuRadioItem UiMenuRadioItem;
+typedef struct UiMenuItemList UiMenuItemList;
+
+enum UiMenuItemType {
+ UI_MENU = 0,
+ UI_MENU_ITEM,
+ UI_MENU_CHECK_ITEM,
+ UI_MENU_RADIO_ITEM,
+ UI_MENU_ITEM_LIST,
+ UI_MENU_CHECKITEM_LIST,
+ UI_MENU_RADIOITEM_LIST,
+ UI_MENU_SEPARATOR
+};
+
+typedef enum UiMenuItemType UiMenuItemType;
+
+struct UiMenuItemI {
+ UiMenuItemI *prev;
+ UiMenuItemI *next;
+ UiMenuItemType type;
+ char id[8];
+};
+
+struct UiMenu {
+ UiMenuItemI item;
+ const char *label;
+ UiMenuItemI *items_begin;
+ UiMenuItemI *items_end;
+ UiMenu *parent;
+ int end;
+};
+
+struct UiMenuItem {
+ UiMenuItemI item;
+ ui_callback callback;
+ char *label;
+ char *stockid;
+ char *icon;
+ void *userdata;
+ int *groups;
+ size_t ngroups;
+};
+
+struct UiMenuCheckItem {
+ UiMenuItemI item;
+ char *label;
+ char *stockid;
+ char *icon;
+ char *varname;
+ ui_callback callback;
+ void *userdata;
+ int *groups;
+ size_t ngroups;
+};
+
+struct UiMenuRadioItem {
+ UiMenuItemI item;
+ char *label;
+ char *stockid;
+ char *icon;
+ ui_callback callback;
+ void *userdata;
+ int *groups;
+ size_t ngroups;
+};
+
+struct UiMenuItemList {
+ UiMenuItemI item;
+ ui_getvaluefunc getvalue;
+ ui_callback callback;
+ void *userdata;
+ char *varname;
+};
+
+
+
+struct UiMenuBuilder {
+ UiMenu *menus_begin;
+ UiMenu *menus_end;
+ CxList *current;
+};
+
+void uic_menu_init(void);
+
+UiMenu* uic_get_menu_list(void);
+
+void uic_add_menu_to_stack(UiMenu* menu);
+
+int* uic_copy_groups(const int* groups, size_t *ngroups);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_MENU_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "object.h"
+#include "context.h"
+
+void ui_end(UiObject *obj) {
+ if(!obj->next) {
+ return;
+ }
+
+ UiObject *prev = NULL;
+ while(obj->next) {
+ prev = obj;
+ obj = obj->next;
+ }
+
+ if(prev) {
+ // TODO: free last obj
+ prev->next = NULL;
+ }
+}
+
+void ui_object_ref(UiObject *obj) {
+ obj->ref++;
+}
+
+void ui_object_unref(UiObject *obj) {
+ // it is possible to have 0 references, in case
+ // a window was created but ui_show was never called
+ if(obj->ref == 0 || --obj->ref == 0) {
+ if(obj->destroy) {
+ obj->destroy(obj);
+ } else {
+ uic_object_destroy(obj);
+ }
+ }
+}
+
+void uic_object_destroy(UiObject *obj) {
+ if(obj->ctx->close_callback) {
+ UiEvent ev;
+ ev.window = obj->window;
+ ev.document = obj->ctx->document;
+ ev.obj = obj;
+ ev.eventdata = NULL;
+ ev.intval = 0;
+ obj->ctx->close_callback(&ev, obj->ctx->close_data);
+ }
+ cxMempoolDestroy(obj->ctx->mp);
+}
+
+UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) {
+ return uic_ctx_object_new(toplevel->ctx, widget);
+}
+
+UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget) {
+ UiObject *newobj = cxCalloc(ctx->allocator, 1, sizeof(UiObject));
+ newobj->ctx = ctx;
+ newobj->widget = widget;
+
+ return newobj;
+}
+
+void uic_obj_add(UiObject *toplevel, UiObject *ctobj) {
+ UiObject *current = uic_current_obj(toplevel);
+ current->next = ctobj;
+}
+
+UiObject* uic_current_obj(UiObject *toplevel) {
+ if(!toplevel) {
+ return NULL;
+ }
+ UiObject *obj = toplevel;
+ while(obj->next) {
+ obj = obj->next;
+ }
+ return obj;
+}
+
+UiContainer* uic_get_current_container(UiObject *obj) {
+ return uic_current_obj(obj)->container;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_OBJECT_H
+#define UIC_OBJECT_H
+
+#include "../ui/toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void uic_object_destroy(UiObject *obj);
+
+UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget);
+UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget);
+void uic_obj_add(UiObject *toplevel, UiObject *ctobj);
+UiObject* uic_current_obj(UiObject *toplevel);
+
+UiContainer* uic_get_current_container(UiObject *obj);;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_OBJECT_H */
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2017 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+COMMON_SRC_DIR = ui/common/
+COMMON_OBJPRE = $(OBJ_DIR)$(COMMON_SRC_DIR)
+
+COMMON_OBJ = context.o
+COMMON_OBJ += document.o
+COMMON_OBJ += object.o
+COMMON_OBJ += types.o
+COMMON_OBJ += 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)
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include "../ui/toolkit.h"
+
+
+#include "properties.h"
+#include <cx/string.h>
+#include <cx/buffer.h>
+
+#include <cx/hash_map.h>
+#include "ucx_properties.h"
+
+static CxMap *application_properties;
+static CxMap *language;
+
+#ifndef UI_COCOA
+
+static char *locales_dir;
+static char *pixmaps_dir;
+
+#endif
+
+
+char* ui_getappdir(void) {
+ if(ui_appname() == NULL) {
+ return NULL;
+ }
+
+ return ui_configfile(NULL);
+}
+
+#ifndef _WIN32
+#define UI_PATH_SEPARATOR '/'
+#define UI_ENV_HOME "HOME"
+#else
+#define UI_PATH_SEPARATOR '\\'
+#define UI_ENV_HOME "USERPROFILE"
+#endif
+
+char* ui_configfile(char *name) {
+ const char *appname = ui_appname();
+ if(!appname) {
+ return NULL;
+ }
+
+ CxBuffer buf;
+ cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+
+ // add base dir
+ char *homeenv = getenv(UI_ENV_HOME);
+ if(homeenv == NULL) {
+ cxBufferDestroy(&buf);
+ return NULL;
+ }
+ cxstring home = cx_str(homeenv);
+
+ cxBufferWrite(home.ptr, 1, home.length, &buf);
+ if(home.ptr[home.length-1] != UI_PATH_SEPARATOR) {
+ cxBufferPut(&buf, UI_PATH_SEPARATOR);
+ }
+
+#ifdef UI_COCOA
+ // on OS X the app dir is $HOME/Library/Application Support/$APPNAME/
+ cxBufferPutString(&buf, "Library/Application Support/");
+#elif defined(_WIN32)
+ // on Windows the app dir is $USERPROFILE/AppData/Local/$APPNAME/
+ cxBufferPutString(&buf, "AppData\\Local\\");
+#else
+ // app dir is $HOME/.$APPNAME/
+ cxBufferPut(&buf, '.');
+#endif
+ cxBufferPutString(&buf, appname);
+ cxBufferPut(&buf, UI_PATH_SEPARATOR);
+
+ // add file name
+ if(name) {
+ cxBufferPutString(&buf, name);
+ }
+
+ cxBufferPut(&buf, 0);
+
+ return buf.space;
+}
+
+static int ui_mkdir(char *path) {
+#ifdef _WIN32
+ return mkdir(path);
+#else
+ return mkdir(path, S_IRWXU);
+#endif
+}
+
+void uic_load_app_properties() {
+ application_properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 128);
+
+ if(!ui_appname()) {
+ // applications without name cannot load app properties
+ return;
+ }
+
+ char *dir = ui_configfile(NULL);
+ if(!dir) {
+ return;
+ }
+ if(ui_mkdir(dir)) {
+ if(errno != EEXIST) {
+ fprintf(stderr, "Ui Error: Cannot create directory %s\n", dir);
+ free(dir);
+ return;
+ }
+ }
+ free(dir);
+
+ char *path = ui_configfile("application.properties");
+ if(!path) {
+ return;
+ }
+
+ FILE *file = fopen(path, "r");
+ if(!file) {
+ free(path);
+ return;
+ }
+
+ if(ucx_properties_load(application_properties, file)) {
+ fprintf(stderr, "Ui Error: Cannot load application properties.\n");
+ }
+
+ fclose(file);
+ free(path);
+}
+
+int uic_store_app_properties() {
+ char *path = ui_configfile("application.properties");
+ if(!path) {
+ return 1;
+ }
+
+ FILE *file = fopen(path, "w");
+ if(!file) {
+ fprintf(stderr, "Ui Error: Cannot open properties file: %s\n", path);
+ free(path);
+ return 1;
+ }
+
+ int ret = 0;
+ if(ucx_properties_store(application_properties, file)) {
+ fprintf(stderr, "Ui Error: Cannot store application properties.\n");
+ ret = 1;
+ }
+
+ fclose(file);
+ free(path);
+
+ return ret;
+}
+
+
+const char* ui_get_property(const char *name) {
+ return cxMapGet(application_properties, name);
+}
+
+void ui_set_property(const char *name, const char *value) {
+ cxMapPut(application_properties, name, (char*)value);
+}
+
+const char* ui_set_default_property(const char *name, const char *value) {
+ const char *v = cxMapGet(application_properties, name);
+ if(!v) {
+ cxMapPut(application_properties, name, (char*)value);
+ v = value;
+ }
+ return v;
+}
+
+int ui_properties_store(void) {
+ return uic_store_app_properties();
+}
+
+
+static char* uic_concat_path(const char *base, const char *p, const char *ext) {
+ size_t baselen = strlen(base);
+
+ CxBuffer *buf = cxBufferCreate(NULL, 32, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ if(baselen > 0) {
+ cxBufferWrite(base, 1, baselen, buf);
+ if(base[baselen - 1] != '/') {
+ cxBufferPut(buf, '/');
+ }
+ }
+ cxBufferWrite(p, 1, strlen(p), buf);
+ if(ext) {
+ cxBufferWrite(ext, 1, strlen(ext), buf);
+ }
+
+ char *str = buf->space;
+ free(buf);
+ return str;
+}
+
+#ifndef UI_COCOA
+
+void ui_locales_dir(char *path) {
+ locales_dir = path;
+}
+
+void ui_pixmaps_dir(char *path) {
+ pixmaps_dir = path;
+}
+
+char* uic_get_image_path(const char *imgfilename) {
+ if(pixmaps_dir) {
+ return uic_concat_path(pixmaps_dir, imgfilename, NULL);
+ } else {
+ return NULL;
+ }
+}
+
+void ui_load_lang(char *locale) {
+ if(!locale) {
+ locale = "en_EN";
+ }
+
+ char *path = uic_concat_path(locales_dir, locale, ".properties");
+
+ uic_load_language_file(path);
+ free(path);
+}
+
+void ui_load_lang_def(char *locale, char *default_locale) {
+ char tmp[6];
+ if(!locale) {
+ char *lang = getenv("LANG");
+ if(lang && strlen(lang) >= 5) {
+ memcpy(tmp, lang, 5);
+ tmp[5] = '\0';
+ locale = tmp;
+ } else {
+ locale = default_locale;
+ }
+ }
+
+ char *path = uic_concat_path(locales_dir, locale, ".properties");
+
+ if(uic_load_language_file(path)) {
+ if(default_locale) {
+ ui_load_lang_def(default_locale, NULL);
+ } else {
+ // cannot find any language file
+ fprintf(stderr, "Ui Error: Cannot load language.\n");
+ free(path);
+ exit(-1);
+ }
+ }
+ free(path);
+}
+
+#endif
+
+int uic_load_language_file(const char *path) {
+ CxMap *lang = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 256);
+
+ FILE *file = fopen(path, "r");
+ if(!file) {
+ return 1;
+ }
+
+ if(ucx_properties_load(lang, file)) {
+ fprintf(stderr, "Ui Error: Cannot parse language file: %s.\n", path);
+ }
+
+ fclose(file);
+
+ cxMapRehash(lang);
+
+ language = lang;
+
+ return 0;
+}
+
+char* uistr(char *name) {
+ char *value = uistr_n(name);
+ return value ? value : "missing string";
+}
+
+char* uistr_n(char *name) {
+ if(!language) {
+ return NULL;
+ }
+ return cxMapGet(language, name);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_PROPERTIES_H
+#define UIC_PROPERTIES_H
+
+#include "../ui/properties.h"
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _WIN32
+#define UI_HOME "USERPROFILE"
+#else
+#define UI_HOME "HOME"
+#endif
+
+void uic_load_app_properties();
+int uic_store_app_properties();
+
+int uic_load_language_file(const char *path);
+char* uic_get_image_path(const char *imgfilename);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_PROPERTIES_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "threadpool.h"
+#include "context.h"
+
+#include <pthread.h>
+
+#ifndef _WIN32
+
+
+static threadpool_job kill_job;
+
+UiThreadpool* threadpool_new(int min, int max) {
+ UiThreadpool *pool = malloc(sizeof(UiThreadpool));
+ pool->queue = NULL;
+ pool->queue_len = 0;
+ pool->num_idle = 0;
+ pool->min_threads = min;
+ pool->max_threads = max;
+
+ pthread_mutex_init(&pool->queue_lock, NULL);
+ pthread_mutex_init(&pool->avlbl_lock, NULL);
+ pthread_cond_init(&pool->available, NULL);
+
+ return pool;
+}
+
+int threadpool_start(UiThreadpool *pool) {
+ /* create pool threads */
+ for(int i=0;i<pool->min_threads;i++) {
+ pthread_t t;
+ if (pthread_create(&t, NULL, threadpool_func, pool) != 0) {
+ fprintf(stderr, "uic: threadpool_start: pthread_create failed: %s", strerror(errno));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void* threadpool_func(void *data) {
+ UiThreadpool *pool = (UiThreadpool*)data;
+
+ for(;;) {
+ threadpool_job *job = threadpool_get_job(pool);
+ if(job == &kill_job) {
+ break;
+ }
+
+ job->callback(job->data);
+
+ free(job);
+ }
+ return NULL;
+}
+
+threadpool_job* threadpool_get_job(UiThreadpool *pool) {
+ pthread_mutex_lock(&pool->queue_lock);
+
+ threadpool_job *job = NULL;
+ pool->num_idle++;
+ while(job == NULL) {
+ if(pool->queue_len == 0) {
+ pthread_cond_wait(&pool->available, &pool->queue_lock);
+ continue;
+ } else {
+ pool_queue_t *q = pool->queue;
+ job = q->job;
+ pool->queue = q->next;
+ pool->queue_len--;
+ free(q);
+ }
+ }
+ pool->num_idle--;
+
+ pthread_mutex_unlock(&pool->queue_lock);
+ return job;
+}
+
+void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data) {
+ // TODO: handle errors
+
+ threadpool_job *job = malloc(sizeof(threadpool_job));
+ job->callback = func;
+ job->data = data;
+
+ pthread_mutex_lock(&pool->queue_lock);
+ threadpool_enqueue_job(pool, job);
+
+ int create_thread = 0;
+ int destroy_thread = 0;
+ int diff = pool->queue_len - pool->num_idle;
+
+ //if(pool->queue_len == 1) {
+ pthread_cond_signal(&pool->available);
+ //}
+
+ pthread_mutex_unlock(&pool->queue_lock);
+}
+
+void threadpool_enqueue_job(UiThreadpool *pool, threadpool_job *job) {
+ pool_queue_t *q = malloc(sizeof(pool_queue_t));
+ q->job = job;
+ q->next = NULL;
+
+ if(pool->queue == NULL) {
+ pool->queue = q;
+ } else {
+ pool_queue_t *last_elem = pool->queue;
+ while(last_elem->next != NULL) {
+ last_elem = last_elem->next;
+ }
+ last_elem->next = q;
+ }
+ pool->queue_len++;
+}
+
+
+
+
+
+
+UiThreadpool* ui_threadpool_create(int nthreads) {
+ UiThreadpool *pool = threadpool_new(nthreads, nthreads);
+ threadpool_start(pool); // TODO: check return value
+ return pool;
+}
+
+void ui_threadpool_destroy(UiThreadpool* pool) {
+
+}
+
+static int ui_threadpool_job_finish(void *data) {
+ UiJob *job = data;
+ UiEvent event;
+ event.obj = job->obj;
+ event.window = job->obj->window;
+ event.document = job->obj->ctx->document;
+ event.intval = 0;
+ event.eventdata = NULL;
+ job->finish_callback(&event, job->finish_data);
+ free(job);
+ return 0;
+}
+
+static void* ui_threadpool_job_func(void *data) {
+ UiJob *job = data;
+ if (!job->job_func(job->job_data) && job->finish_callback) {
+ ui_call_mainthread(ui_threadpool_job_finish, job);
+ } else {
+ free(job);
+ }
+ return NULL;
+}
+
+void ui_threadpool_job(UiThreadpool* pool, UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd) {
+ UiJob* job = malloc(sizeof(UiJob));
+ job->obj = obj;
+ job->job_func = tf;
+ job->job_data = td;
+ job->finish_callback = f;
+ job->finish_data = fd;
+ threadpool_run(pool, ui_threadpool_job_func, job);
+}
+
+
+#endif
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_THREADPOOL_H
+#define UIC_THREADPOOL_H
+
+#include "../ui/toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+typedef struct UiJob {
+ UiObject *obj;
+ ui_threadfunc job_func;
+ void *job_data;
+ ui_callback finish_callback;
+ void *finish_data;
+} UiJob;
+
+
+typedef struct _threadpool_job threadpool_job;
+typedef void*(*job_callback_f)(void *data);
+
+typedef struct _pool_queue pool_queue_t;
+struct UiThreadpool {
+ pthread_mutex_t queue_lock;
+ pthread_mutex_t avlbl_lock;
+ pthread_cond_t available;
+ pool_queue_t *queue;
+ uint32_t queue_len;
+ uint32_t num_idle;
+ int min_threads;
+ int max_threads;
+};
+
+struct _threadpool_job {
+ job_callback_f callback;
+ void *data;
+};
+
+struct _pool_queue {
+ threadpool_job *job;
+ pool_queue_t *next;
+};
+
+UiThreadpool* threadpool_new(int min, int max);
+int threadpool_start(UiThreadpool *pool);
+void* threadpool_func(void *data);
+threadpool_job* threadpool_get_job(UiThreadpool *pool);
+void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data);
+void threadpool_enqueue_job(UiThreadpool *pool, threadpool_job *job);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_THREADPOOL_H */
+
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "toolbar.h"\r
+#include "menu.h"\r
+\r
+#include <string.h>\r
+\r
+static CxMap* toolbar_items;\r
+\r
+static CxList* toolbar_defaults[3]; // 0: left 1: center 2: right\r
+\r
+static UiToolbarMenuItem* ui_appmenu;\r
+\r
+\r
+void uic_toolbar_init(void) {\r
+ toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);\r
+ toolbar_defaults[0] = cxLinkedListCreateSimple(CX_STORE_POINTERS);\r
+ toolbar_defaults[1] = cxLinkedListCreateSimple(CX_STORE_POINTERS);\r
+ toolbar_defaults[2] = cxLinkedListCreateSimple(CX_STORE_POINTERS);\r
+}\r
+\r
+static char* nl_strdup(const char* str) {\r
+ return str ? strdup(str) : NULL;\r
+}\r
+\r
+static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs args, size_t *ngroups) {\r
+ UiToolbarItemArgs newargs;\r
+ newargs.label = nl_strdup(args.label);\r
+ newargs.stockid = nl_strdup(args.stockid);\r
+ newargs.icon = nl_strdup(args.icon);\r
+ newargs.onclick = args.onclick;\r
+ newargs.onclickdata = args.onclickdata;\r
+ newargs.groups = uic_copy_groups(args.groups, ngroups);\r
+ return newargs;\r
+}\r
+\r
+void ui_toolbar_item_create(const char* name, UiToolbarItemArgs args) {\r
+ UiToolbarItem* item = malloc(sizeof(UiToolbarItem));\r
+ item->item.type = UI_TOOLBAR_ITEM;\r
+ item->args = itemargs_copy(args, &item->ngroups);\r
+ cxMapPut(toolbar_items, name, item);\r
+}\r
+\r
+\r
+static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs args, size_t *ngroups) {\r
+ UiToolbarToggleItemArgs newargs;\r
+ newargs.label = nl_strdup(args.label);\r
+ newargs.stockid = nl_strdup(args.stockid);\r
+ newargs.icon = nl_strdup(args.icon);\r
+ newargs.varname = nl_strdup(args.varname);\r
+ newargs.onchange = args.onchange;\r
+ newargs.onchangedata = args.onchangedata;\r
+ newargs.groups = uic_copy_groups(args.groups, ngroups);\r
+ return newargs;\r
+}\r
+\r
+void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs args) {\r
+ UiToolbarToggleItem* item = malloc(sizeof(UiToolbarToggleItem));\r
+ item->item.type = UI_TOOLBAR_TOGGLEITEM;\r
+ item->args = toggleitemargs_copy(args, &item->ngroups);\r
+ cxMapPut(toolbar_items, name, item);\r
+}\r
+\r
+static UiToolbarMenuArgs menuargs_copy(UiToolbarMenuArgs args) {\r
+ UiToolbarMenuArgs newargs;\r
+ newargs.label = nl_strdup(args.label);\r
+ newargs.stockid = nl_strdup(args.stockid);\r
+ newargs.icon = nl_strdup(args.icon);\r
+ return newargs;\r
+}\r
+\r
+UIEXPORT void ui_toolbar_menu_create(const char* name, UiToolbarMenuArgs args) {\r
+ UiToolbarMenuItem* item = malloc(sizeof(UiToolbarMenuItem));\r
+ item->item.type = UI_TOOLBAR_MENU;\r
+ memset(&item->menu, 0, sizeof(UiMenu));\r
+ item->args = menuargs_copy(args);\r
+\r
+ item->end = 0;\r
+\r
+ if (!name) {\r
+ // special appmenu\r
+ ui_appmenu = item;\r
+ } else {\r
+ // toplevel menu\r
+ cxMapPut(toolbar_items, name, item);\r
+ }\r
+\r
+ uic_add_menu_to_stack(&item->menu);\r
+}\r
+\r
+\r
+CxMap* uic_get_toolbar_items(void) {\r
+ return toolbar_items;\r
+}\r
+\r
+CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos) {\r
+ if (pos >= 0 && pos < 3) {\r
+ return toolbar_defaults[pos];\r
+ }\r
+ return NULL;\r
+}\r
+\r
+void ui_toolbar_add_default(const char* name, enum UiToolbarPos pos) {\r
+ char* cp = strdup(name);\r
+ if (pos >= 0 && pos < 3) {\r
+ cxListAdd(toolbar_defaults[pos], cp);\r
+ } else {\r
+ // TODO: error\r
+ }\r
+}\r
+\r
+UiBool uic_toolbar_isenabled(void) {\r
+ return cxListSize(toolbar_defaults[0]) + cxListSize(toolbar_defaults[1]) + cxListSize(toolbar_defaults[2]) > 0;\r
+}\r
+\r
+UiToolbarItemI* uic_toolbar_get_item(const char* name) {\r
+ return cxMapGet(toolbar_items, name);\r
+}\r
+\r
+UiToolbarMenuItem* uic_get_appmenu(void) {\r
+ return ui_appmenu;\r
+}\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#ifndef UIC_TOOLBAR_H\r
+#define UIC_TOOLBAR_H\r
+\r
+#include "../ui/toolbar.h"\r
+\r
+#include <cx/linked_list.h>\r
+#include <cx/hash_map.h>\r
+\r
+#include "menu.h"\r
+\r
+#ifdef __cplusplus\r
+extern "C" {\r
+#endif\r
+\r
+typedef struct UiToolbarItemI UiToolbarItemI;\r
+\r
+typedef struct UiToolbarItem UiToolbarItem;\r
+typedef struct UiToolbarToggleItem UiToolbarToggleItem;\r
+\r
+typedef struct UiToolbarMenuItem UiToolbarMenuItem;\r
+\r
+enum UiToolbarItemType {\r
+ UI_TOOLBAR_ITEM = 0,\r
+ UI_TOOLBAR_TOGGLEITEM,\r
+ UI_TOOLBAR_MENU\r
+};\r
+\r
+typedef enum UiToolbarItemType UiToolbarItemType;\r
+\r
+struct UiToolbarItemI {\r
+ UiToolbarItemType type;\r
+};\r
+\r
+struct UiToolbarItem {\r
+ UiToolbarItemI item;\r
+ UiToolbarItemArgs args;\r
+ size_t ngroups;\r
+};\r
+\r
+struct UiToolbarToggleItem {\r
+ UiToolbarItemI item;\r
+ UiToolbarToggleItemArgs args;\r
+ size_t ngroups;\r
+};\r
+\r
+struct UiToolbarMenuItem {\r
+ UiToolbarItemI item;\r
+ UiMenu menu;\r
+ UiToolbarMenuArgs args;\r
+ int end;\r
+};\r
+\r
+\r
+void uic_toolbar_init(void);\r
+\r
+CxMap* uic_get_toolbar_items(void);\r
+CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos);\r
+\r
+UiBool uic_toolbar_isenabled(void);\r
+\r
+UiToolbarItemI* uic_toolbar_get_item(const char* name);\r
+\r
+UiToolbarMenuItem* uic_get_appmenu(void);\r
+\r
+#ifdef __cplusplus\r
+}\r
+#endif\r
+\r
+#endif /* UIC_TOOLBAR_H */\r
+\r
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <cx/list.h>
+#include <cx/array_list.h>
+#include "../ui/tree.h"
+#include "types.h"
+#include "context.h"
+
+
+
+UiObserver* ui_observer_new(ui_callback f, void *data) {
+ UiObserver *observer = malloc(sizeof(UiObserver));
+ observer->callback = f;
+ observer->data = data;
+ observer->next = NULL;
+ return observer;
+}
+
+UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer) {
+ if(!list) {
+ return observer;
+ } else {
+ UiObserver *l = list;
+ while(l->next) {
+ l = l->next;
+ }
+ l->next = observer;
+ return list;
+ }
+}
+
+UiObserver* ui_add_observer(UiObserver *list, ui_callback f, void *data) {
+ UiObserver *observer = ui_observer_new(f, data);
+ return ui_obsvlist_add(list, observer);
+}
+
+void ui_notify(UiObserver *observer, void *data) {
+ ui_notify_except(observer, NULL, data);
+}
+
+void ui_notify_except(UiObserver *observer, UiObserver *exc, void *data) {
+ UiEvent evt;
+ evt.obj = NULL;
+ evt.window = NULL;
+ evt.document = NULL;
+ evt.eventdata = data;
+ evt.intval = 0;
+
+ while(observer) {
+ if(observer != exc) {
+ observer->callback(&evt, observer->data);
+ }
+ observer = observer->next;
+ }
+}
+
+void ui_notify_evt(UiObserver *observer, UiEvent *event) {
+ while(observer) {
+ observer->callback(event, observer->data);
+ observer = observer->next;
+ }
+}
+
+/* --------------------------- UiList --------------------------- */
+
+UiList* ui_list_new(UiContext *ctx, char *name) {
+ UiList *list = malloc(sizeof(UiList));
+ list->first = ui_list_first;
+ list->next = ui_list_next;
+ list->get = ui_list_get;
+ list->count = ui_list_count;
+ list->observers = NULL;
+
+ list->data = cxArrayListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS, 32);
+ list->iter = NULL;
+
+ list->update = NULL;
+ list->getselection = NULL;
+ list->obj = NULL;
+
+ if(name) {
+ uic_reg_var(ctx, name, UI_VAR_LIST, list);
+ }
+
+ return list;
+}
+
+void ui_list_free(UiList *list) {
+ cxListDestroy(list->data);
+ free(list);
+}
+
+void* ui_list_first(UiList *list) {
+ list->iter = (void*)(intptr_t)0;
+ return cxListAt(list->data, 0);
+}
+
+void* ui_list_next(UiList *list) {
+ intptr_t iter = (intptr_t)list->iter;
+ iter++;
+ void *elm = cxListAt(list->data, iter);
+ if(elm) {
+ list->iter = (void*)iter;
+ }
+ return elm;
+}
+
+void* ui_list_get(UiList *list, int i) {
+ return cxListAt(list->data, i);
+}
+
+int ui_list_count(UiList *list) {
+ return cxListSize(list->data);
+}
+
+void ui_list_append(UiList *list, void *data) {
+ cxListAdd(list->data, data);
+}
+
+void ui_list_prepend(UiList *list, void *data) {
+ cxListInsert(list->data, 0, data);
+}
+
+void ui_list_remove(UiList *list, int i) {
+ cxListRemove(list->data, i);
+}
+
+void ui_list_clear(UiList *list) {
+ cxListClear(list->data);
+}
+
+UIEXPORT void ui_list_update(UiList *list) {
+ if(list->update) {
+ list->update(list, 0);
+ }
+}
+
+void ui_list_addobsv(UiList *list, ui_callback f, void *data) {
+ list->observers = ui_add_observer(list->observers, f, data);
+}
+
+void ui_list_notify(UiList *list) {
+ ui_notify(list->observers, list);
+}
+
+
+typedef struct {
+ int type;
+ char *name;
+} UiColumn;
+
+UiModel* ui_model(UiContext *ctx, ...) {
+ UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel));
+
+ va_list ap;
+ va_start(ap, ctx);
+
+ CxList *cols = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(UiColumn), 32);
+ int type;
+ while((type = va_arg(ap, int)) != -1) {
+ char *name = va_arg(ap, char*);
+
+ UiColumn column;
+ column.type = type;
+ column.name = name;
+
+ cxListAdd(cols, &column);
+ }
+
+ va_end(ap);
+
+ size_t len = cxListSize(cols);
+ info->columns = len;
+ info->types = ui_calloc(ctx, len, sizeof(UiModelType));
+ info->titles = ui_calloc(ctx, len, sizeof(char*));
+ info->columnsize = ui_calloc(ctx, len, sizeof(int));
+
+ int i = 0;
+ CxIterator iter = cxListIterator(cols);
+ cx_foreach(UiColumn*, c, iter) {
+ info->types[i] = c->type;
+ info->titles[i] = c->name;
+ i++;
+ }
+ cxListDestroy(cols);
+
+ return info;
+}
+
+UiModel* ui_model_copy(UiContext *ctx, UiModel* model) {
+ const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator;
+
+ UiModel* newmodel = cxMalloc(a, sizeof(UiModel));
+ *newmodel = *model;
+
+ newmodel->types = cxCalloc(a, model->columns, sizeof(UiModelType));
+ memcpy(newmodel->types, model->types, model->columns);
+
+ newmodel->titles = cxCalloc(a, model->columns, sizeof(char*));
+ for (int i = 0; i < model->columns; i++) {
+ newmodel->titles[i] = model->titles[i] ? cx_strdup_a(a, cx_str(model->titles[i])).ptr : NULL;
+ }
+
+ return newmodel;
+}
+
+void ui_model_free(UiContext *ctx, UiModel *mi) {
+ const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator;
+ cxFree(a, mi->types);
+ cxFree(a, mi->titles);
+ cxFree(a, mi);
+}
+
+// types
+
+// public functions
+UiInteger* ui_int_new(UiContext *ctx, char *name) {
+ UiInteger *i = ui_malloc(ctx, sizeof(UiInteger));
+ memset(i, 0, sizeof(UiInteger));
+ if(name) {
+ uic_reg_var(ctx, name, UI_VAR_INTEGER, i);
+ }
+ return i;
+}
+
+UiDouble* ui_double_new(UiContext *ctx, char *name) {
+ UiDouble *d = ui_malloc(ctx, sizeof(UiDouble));
+ memset(d, 0, sizeof(UiDouble));
+ if(name) {
+ uic_reg_var(ctx, name, UI_VAR_DOUBLE, d);
+ }
+ return d;
+}
+
+UiString* ui_string_new(UiContext *ctx, char *name) {
+ UiString *s = ui_malloc(ctx, sizeof(UiString));
+ memset(s, 0, sizeof(UiString));
+ if(name) {
+ uic_reg_var(ctx, name, UI_VAR_STRING, s);
+ }
+ return s;
+}
+
+UiText* ui_text_new(UiContext *ctx, char *name) {
+ UiText *t = ui_malloc(ctx, sizeof(UiText));
+ memset(t, 0, sizeof(UiText));
+ if(name) {
+ uic_reg_var(ctx, name, UI_VAR_TEXT, t);
+ }
+ return t;
+}
+
+UiRange* ui_range_new(UiContext *ctx, char *name) {
+ UiRange *r = ui_malloc(ctx, sizeof(UiRange));
+ memset(r, 0, sizeof(UiRange));
+ if(name) {
+ uic_reg_var(ctx, name, UI_VAR_RANGE, r);
+ }
+ return r;
+}
+
+UIEXPORT UiGeneric* ui_generic_new(UiContext *ctx, char *name) {
+ UiGeneric *g = ui_malloc(ctx, sizeof(UiGeneric));
+ memset(g, 0, sizeof(UiGeneric));
+ if(name) {
+ uic_reg_var(ctx, name, UI_VAR_GENERIC, g);
+ }
+ return g;
+}
+
+
+void ui_int_set(UiInteger* i, int64_t value) {
+ if (i && i->set) {
+ i->set(i, value);
+ }
+}
+
+int64_t ui_int_get(UiInteger* i) {
+ if (i) {
+ return i->get ? i->get(i) : i->value;
+ } else {
+ return 0;
+ }
+}
+
+void ui_double_set(UiDouble* d, double value) {
+ if (d && d->set) {
+ d->set(d, value);
+ }
+}
+
+double ui_double_get(UiDouble* d) {
+ if (d) {
+ return d->get ? d->get(d) : d->value;
+ }
+ else {
+ return 0;
+ }
+}
+
+void ui_string_set(UiString* s, const char* value) {
+ if (s && s->set) {
+ s->set(s, value);
+ }
+}
+
+char* ui_string_get(UiString* s) {
+ if (s) {
+ return s->get ? s->get(s) : s->value.ptr;
+ }
+ else {
+ return 0;
+ }
+}
+
+void ui_text_set(UiText* s, const char* value) {
+ if (s && s->set) {
+ s->set(s, value);
+ }
+}
+
+char* ui_text_get(UiText* s) {
+ if (s) {
+ return s->get ? s->get(s) : s->value.ptr;
+ }
+ else {
+ return 0;
+ }
+}
+
+
+// private functions
+void uic_int_copy(UiInteger *from, UiInteger *to) {
+ to->get = from->get;
+ to->set = from->set;
+ to->obj = from->obj;
+}
+
+void uic_double_copy(UiDouble *from, UiDouble *to) {
+ to->get = from->get;
+ to->set = from->set;
+ to->obj = from->obj;
+}
+
+void uic_string_copy(UiString *from, UiString *to) {
+ to->get = from->get;
+ to->set = from->set;
+ to->obj = from->obj;
+}
+
+void uic_text_copy(UiText *from, UiText *to) {
+ to->get = from->get;
+ to->set = from->set;
+ to->getsubstr = from->getsubstr;
+ to->insert = from->insert;
+ to->setposition = from->setposition;
+ to->position = from->position;
+ to->selection = from->selection;
+ to->length = from->length;
+ to->remove = from->remove;
+
+ to->obj = from->obj;
+ // do not copy the undo manager
+}
+
+void uic_range_copy(UiRange *from, UiRange *to) {
+ to->get = from->get;
+ to->set = from->set;
+ to->setrange = from->setrange;
+ to->setextent = from->setextent;
+ to->obj = from->obj;
+}
+
+void uic_list_copy(UiList *from, UiList *to) {
+ to->update = from->update;
+ to->obj = from->obj;
+}
+
+void uic_generic_copy(UiGeneric *from, UiGeneric *to) {
+ to->get = from->get;
+ to->get_type = from->get_type;
+ to->set = from->set;
+ to->obj = from->obj;
+}
+
+void uic_int_save(UiInteger *i) {
+ if(!i->obj) return;
+ i->value = i->get(i);
+}
+
+void uic_double_save(UiDouble *d) {
+ if(!d->obj) return;
+ d->value = d->get(d);
+}
+
+void uic_string_save(UiString *s) {
+ if(!s->obj) return;
+ s->get(s);
+}
+
+void uic_text_save(UiText *t) {
+ if(!t->obj) return;
+ t->get(t);
+ t->position(t);
+}
+
+void uic_range_save(UiRange *r) {
+ if(!r->obj) return;
+ r->get(r);
+}
+
+void uic_generic_save(UiGeneric *g) {
+ if(!g->obj) return;
+ g->get(g);
+}
+
+
+void uic_int_unbind(UiInteger *i) {
+ i->get = NULL;
+ i->set = NULL;
+ i->obj = NULL;
+}
+
+void uic_double_unbind(UiDouble *d) {
+ d->get = NULL;
+ d->set = NULL;
+ d->obj = NULL;
+}
+
+void uic_string_unbind(UiString *s) {
+ s->get = NULL;
+ s->set = NULL;
+ s->obj = NULL;
+}
+
+void uic_text_unbind(UiText *t) {
+ t->set = NULL;
+ t->get = NULL;
+ t->getsubstr = NULL;
+ t->insert = NULL;
+ t->setposition = NULL;
+ t->position = NULL;
+ t->selection = NULL;
+ t->length = NULL;
+ t->remove = NULL;
+ t->obj = NULL;
+ t->undomgr = NULL;
+}
+
+void uic_range_unbind(UiRange *r) {
+ r->get = NULL;
+ r->set = NULL;
+ r->setextent = NULL;
+ r->setrange = NULL;
+ r->obj = NULL;
+}
+
+void uic_list_unbind(UiList *l) {
+ l->update = NULL;
+ l->obj = NULL;
+}
+
+void uic_generic_unbind(UiGeneric *g) {
+ g->get = NULL;
+ g->get_type = NULL;
+ g->set = NULL;
+ g->obj = NULL;
+}
+
+
+UIEXPORT UiListSelection ui_list_getselection(UiList *list) {
+ if (list->getselection) {
+ return list->getselection(list);
+ }
+ return (UiListSelection){ 0, NULL };
+}
+
+UIEXPORT void ui_list_setselection(UiList *list, int index) {
+ if (list->setselection && index >= 0) {
+ UiListSelection sel;
+ sel.count = 1;
+ sel.rows = &index;
+ list->setselection(list, sel);
+ }
+}
+
+UIEXPORT void ui_listselection_free(UiListSelection selection) {
+ if (selection.rows) {
+ free(selection.rows);
+ }
+}
+
+UIEXPORT UiStr ui_str(char *cstr) {
+ return (UiStr) { cstr, NULL };
+}
+
+UIEXPORT UiStr ui_str_free(char *str, void (*freefunc)(void *v)) {
+ return (UiStr) { str, freefunc };
+}
+
+
+UIEXPORT UiFileList ui_filelist_copy(UiFileList list) {
+ char **newlist = calloc(sizeof(char*), list.nfiles);
+ for (int i = 0; i < list.nfiles; i++) {
+ newlist[i] = strdup(list.files[i]);
+ }
+ return (UiFileList) { newlist, list.nfiles };
+}
+
+UIEXPORT void ui_filelist_free(UiFileList list) {
+ for (int i = 0; i < list.nfiles; i++) {
+ free(list.files[i]);
+ }
+ free(list.files);
+}
+
+
+typedef struct UiObserverDestructor {
+ UiList *list;
+ UiObserver *observer;
+} UiObserverDestructor;
+
+static void observer_destructor(UiObserverDestructor *destr) {
+ UiObserver *remove_obs = destr->observer;
+ UiObserver *obs = destr->list->observers;
+ UiObserver *prev = NULL;
+ while(obs) {
+ if(obs == remove_obs) {
+ if(prev) {
+ prev->next = obs->next;
+ } else {
+ destr->list->observers = obs->next;
+ }
+ break;
+ }
+ prev = obs;
+ obs = obs->next;
+ }
+ free(remove_obs);
+}
+
+void uic_list_register_observer_destructor(UiContext *ctx, UiList *list, UiObserver *observer) {
+ CxMempool *mp = ctx->mp;
+ UiObserverDestructor *destr = cxMalloc(mp->allocator, sizeof(UiObserverDestructor));
+ destr->list = list;
+ destr->observer = observer;
+ cxMempoolSetDestructor(destr, (cx_destructor_func)observer_destructor);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_TYPES_H
+#define UIC_TYPES_H
+
+#include "../ui/toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+void uic_int_copy(UiInteger *from, UiInteger *to);
+void uic_double_copy(UiDouble *from, UiDouble *to);
+void uic_string_copy(UiString *from, UiString *to);
+void uic_text_copy(UiText *from, UiText *to);
+void uic_range_copy(UiRange *from, UiRange *to);
+void uic_list_copy(UiList *from, UiList *to);
+void uic_generic_copy(UiGeneric *from, UiGeneric *to);
+
+void uic_int_save(UiInteger *i);
+void uic_double_save(UiDouble *d);
+void uic_string_save(UiString *s);
+void uic_text_save(UiText *t);
+void uic_range_save(UiRange *r);
+void uic_generic_save(UiGeneric *g);
+
+void uic_int_unbind(UiInteger *i);
+void uic_double_unbind(UiDouble *d);
+void uic_string_unbind(UiString *s);
+void uic_text_unbind(UiText *t);
+void uic_range_unbind(UiRange *r);
+void uic_list_unbind(UiList *l);
+void uic_generic_unbind(UiGeneric *g);
+
+void uic_list_register_observer_destructor(UiContext *ctx, UiList *list, UiObserver *observer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_TYPES_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx_properties.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+UcxProperties *ucx_properties_new() {
+ UcxProperties *parser = (UcxProperties*)malloc(
+ sizeof(UcxProperties));
+ if(!parser) {
+ return NULL;
+ }
+
+ parser->buffer = NULL;
+ parser->buflen = 0;
+ parser->pos = 0;
+ parser->tmp = NULL;
+ parser->tmplen = 0;
+ parser->tmpcap = 0;
+ parser->error = 0;
+ parser->delimiter = '=';
+ parser->comment1 = '#';
+ parser->comment2 = 0;
+ parser->comment3 = 0;
+
+ return parser;
+}
+
+void ucx_properties_free(UcxProperties *parser) {
+ if(parser->tmp) {
+ free(parser->tmp);
+ }
+ free(parser);
+}
+
+void ucx_properties_fill(UcxProperties *parser, char *buf, size_t len) {
+ parser->buffer = buf;
+ parser->buflen = len;
+ parser->pos = 0;
+}
+
+static void parser_tmp_append(UcxProperties *parser, char *buf, size_t len) {
+ if(parser->tmpcap - parser->tmplen < len) {
+ size_t newcap = parser->tmpcap + len + 64;
+ parser->tmp = (char*)realloc(parser->tmp, newcap);
+ parser->tmpcap = newcap;
+ }
+ memcpy(parser->tmp + parser->tmplen, buf, len);
+ parser->tmplen += len;
+}
+
+int ucx_properties_next(UcxProperties *parser, cxstring *name, cxstring *value) {
+ if(parser->tmplen > 0) {
+ char *buf = parser->buffer + parser->pos;
+ size_t len = parser->buflen - parser->pos;
+ cxstring str = cx_strn(buf, len);
+ cxstring nl = cx_strchr(str, '\n');
+ if(nl.ptr) {
+ size_t newlen = (size_t)(nl.ptr - buf) + 1;
+ parser_tmp_append(parser, buf, newlen);
+ // the tmp buffer contains exactly one line now
+
+ char *orig_buf = parser->buffer;
+ size_t orig_len = parser->buflen;
+
+ parser->buffer = parser->tmp;
+ parser->buflen = parser->tmplen;
+ parser->pos = 0;
+ parser->tmp = NULL;
+ parser->tmpcap = 0;
+ parser->tmplen = 0;
+ // run ucx_properties_next with the tmp buffer as main buffer
+ int ret = ucx_properties_next(parser, name, value);
+
+ // restore original buffer
+ parser->tmp = parser->buffer;
+ parser->buffer = orig_buf;
+ parser->buflen = orig_len;
+ parser->pos = newlen;
+
+ /*
+ * if ret == 0 the tmp buffer contained just space or a comment
+ * we parse again with the original buffer to get a name/value
+ * or a new tmp buffer
+ */
+ return ret ? ret : ucx_properties_next(parser, name, value);
+ } else {
+ parser_tmp_append(parser, buf, len);
+ return 0;
+ }
+ } else if(parser->tmp) {
+ free(parser->tmp);
+ parser->tmp = NULL;
+ }
+
+ char comment1 = parser->comment1;
+ char comment2 = parser->comment2;
+ char comment3 = parser->comment3;
+ char delimiter = parser->delimiter;
+
+ // get one line and parse it
+ while(parser->pos < parser->buflen) {
+ char *buf = parser->buffer + parser->pos;
+ size_t len = parser->buflen - parser->pos;
+
+ /*
+ * First we check if we have at least one line. We also get indices of
+ * delimiter and comment chars
+ */
+ size_t delimiter_index = 0;
+ size_t comment_index = 0;
+ int has_comment = 0;
+
+ size_t i = 0;
+ char c = 0;
+ for(;i<len;i++) {
+ c = buf[i];
+ if(c == comment1 || c == comment2 || c == comment3) {
+ if(comment_index == 0) {
+ comment_index = i;
+ has_comment = 1;
+ }
+ } else if(c == delimiter) {
+ if(delimiter_index == 0 && !has_comment) {
+ delimiter_index = i;
+ }
+ } else if(c == '\n') {
+ break;
+ }
+ }
+
+ if(c != '\n') {
+ // we don't have enough data for a line
+ // store remaining bytes in temporary buffer for next round
+ parser->tmpcap = len + 128;
+ parser->tmp = (char*)malloc(parser->tmpcap);
+ parser->tmplen = len;
+ memcpy(parser->tmp, buf, len);
+ return 0;
+ }
+
+ cxstring line = has_comment ? cx_strn(buf, comment_index) : cx_strn(buf, i);
+ // check line
+ if(delimiter_index == 0) {
+ line = cx_strtrim(line);
+ if(line.length != 0) {
+ parser->error = 1;
+ }
+ } else {
+ cxstring n = cx_strn(buf, delimiter_index);
+ cxstring v = cx_strn(
+ buf + delimiter_index + 1,
+ line.length - delimiter_index - 1);
+ n = cx_strtrim(n);
+ v = cx_strtrim(v);
+ if(n.length != 0 || v.length != 0) {
+ *name = n;
+ *value = v;
+ parser->pos += i + 1;
+ return 1;
+ } else {
+ parser->error = 1;
+ }
+ }
+
+ parser->pos += i + 1;
+ }
+
+ return 0;
+}
+
+int ucx_properties2map(UcxProperties *parser, CxMap *map) {
+ cxstring name;
+ cxstring value;
+ while(ucx_properties_next(parser, &name, &value)) {
+ cxmutstr mutvalue = cx_strdup_a(map->collection.allocator, value);
+ if(!mutvalue.ptr) {
+ return 1;
+ }
+ if(cxMapPut(map, cx_hash_key_cxstr(name), mutvalue.ptr)) {
+ cxFree(map->collection.allocator, mutvalue.ptr);
+ return 1;
+ }
+ }
+ if (parser->error) {
+ return parser->error;
+ } else {
+ return 0;
+ }
+}
+
+// buffer size is documented - change doc, when you change bufsize!
+#define UCX_PROPLOAD_BUFSIZE 1024
+int ucx_properties_load(CxMap *map, FILE *file) {
+ UcxProperties *parser = ucx_properties_new();
+ if(!(parser && map && file)) {
+ return 1;
+ }
+
+ int error = 0;
+ size_t r;
+ char buf[UCX_PROPLOAD_BUFSIZE];
+ while((r = fread(buf, 1, UCX_PROPLOAD_BUFSIZE, file)) != 0) {
+ ucx_properties_fill(parser, buf, r);
+ error = ucx_properties2map(parser, map);
+ if (error) {
+ break;
+ }
+ }
+ ucx_properties_free(parser);
+ return error;
+}
+
+int ucx_properties_store(CxMap *map, FILE *file) {
+ CxIterator iter = cxMapIterator(map);
+ cxstring value;
+ size_t written;
+
+ cx_foreach(CxMapEntry *, v, iter) {
+ value = cx_str(v->value);
+
+ written = 0;
+ written += fwrite(v->key->data, 1, v->key->len, file);
+ written += fwrite(" = ", 1, 3, file);
+ written += fwrite(value.ptr, 1, value.length, file);
+ written += fwrite("\n", 1, 1, file);
+
+ if (written != v->key->len + value.length + 4) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * @file properties.h
+ *
+ * Load / store utilities for properties files.
+ *
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_PROPERTIES_H
+#define UCX_PROPERTIES_H
+
+#include <cx/hash_map.h>
+#include <cx/string.h>
+
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * UcxProperties object for parsing properties data.
+ * Most of the fields are for internal use only. You may configure the
+ * properties parser, e.g. by changing the used delimiter or specifying
+ * up to three different characters that shall introduce comments.
+ */
+typedef struct {
+ /**
+ * Input buffer (don't set manually).
+ * Automatically set by calls to ucx_properties_fill().
+ */
+ char *buffer;
+
+ /**
+ * Length of the input buffer (don't set manually).
+ * Automatically set by calls to ucx_properties_fill().
+ */
+ size_t buflen;
+
+ /**
+ * Current buffer position (don't set manually).
+ * Used by ucx_properties_next().
+ */
+ size_t pos;
+
+ /**
+ * Internal temporary buffer (don't set manually).
+ * Used by ucx_properties_next().
+ */
+ char *tmp;
+
+ /**
+ * Internal temporary buffer length (don't set manually).
+ * Used by ucx_properties_next().
+ */
+ size_t tmplen;
+
+ /**
+ * Internal temporary buffer capacity (don't set manually).
+ * Used by ucx_properties_next().
+ */
+ size_t tmpcap;
+
+ /**
+ * Parser error code.
+ * This is always 0 on success and a nonzero value on syntax errors.
+ * The value is set by ucx_properties_next().
+ */
+ int error;
+
+ /**
+ * The delimiter that shall be used.
+ * This is '=' by default.
+ */
+ char delimiter;
+
+ /**
+ * The first comment character.
+ * This is '#' by default.
+ */
+ char comment1;
+
+ /**
+ * The second comment character.
+ * This is not set by default.
+ */
+ char comment2;
+
+ /**
+ * The third comment character.
+ * This is not set by default.
+ */
+ char comment3;
+} UcxProperties;
+
+
+/**
+ * Constructs a new UcxProperties object.
+ * @return a pointer to the new UcxProperties object
+ */
+UcxProperties *ucx_properties_new();
+
+/**
+ * Destroys a UcxProperties object.
+ * @param prop the UcxProperties object to destroy
+ */
+void ucx_properties_free(UcxProperties *prop);
+
+/**
+ * Sets the input buffer for the properties parser.
+ *
+ * After calling this function, you may parse the data by calling
+ * ucx_properties_next() until it returns 0. The function ucx_properties2map()
+ * is a convenience function that reads as much data as possible by using this
+ * function.
+ *
+ *
+ * @param prop the UcxProperties object
+ * @param buf a pointer to the new buffer
+ * @param len the payload length of the buffer
+ * @see ucx_properties_next()
+ * @see ucx_properties2map()
+ */
+void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len);
+
+/**
+ * Retrieves the next key/value-pair.
+ *
+ * This function returns a nonzero value as long as there are key/value-pairs
+ * found. If no more key/value-pairs are found, you may refill the input buffer
+ * with ucx_properties_fill().
+ *
+ * <b>Attention:</b> the cxstring.ptr pointers of the output parameters point to
+ * memory within the input buffer of the parser and will get invalid some time.
+ * If you want long term copies of the key/value-pairs, use sstrdup() after
+ * calling this function.
+ *
+ * @param prop the UcxProperties object
+ * @param name a pointer to the cxstring that shall contain the property name
+ * @param value a pointer to the cxstring that shall contain the property value
+ * @return Nonzero, if a key/value-pair was successfully retrieved
+ * @see ucx_properties_fill()
+ */
+int ucx_properties_next(UcxProperties *prop, cxstring *name, cxstring *value);
+
+/**
+ * Retrieves all available key/value-pairs and puts them into a UcxMap.
+ *
+ * This is done by successive calls to ucx_properties_next() until no more
+ * key/value-pairs can be retrieved.
+ *
+ * The memory for the map values is allocated by the map's own allocator.
+ *
+ * @param prop the UcxProperties object
+ * @param map the target map
+ * @return The UcxProperties.error code (i.e. 0 on success).
+ * @see ucx_properties_fill()
+ * @see UcxMap.allocator
+ */
+int ucx_properties2map(UcxProperties *prop, CxMap *map);
+
+/**
+ * Loads a properties file to a UcxMap.
+ *
+ * This is a convenience function that reads data from an input
+ * stream until the end of the stream is reached.
+ *
+ * @param map the map object to write the key/value-pairs to
+ * @param file the <code>FILE*</code> stream to read from
+ * @return 0 on success, or a non-zero value on error
+ *
+ * @see ucx_properties_fill()
+ * @see ucx_properties2map()
+ */
+int ucx_properties_load(CxMap *map, FILE *file);
+
+/**
+ * Stores a UcxMap to a file.
+ *
+ * The key/value-pairs are written by using the following format:
+ *
+ * <code>[key] = [value]\\n</code>
+ *
+ * @param map the map to store
+ * @param file the <code>FILE*</code> stream to write to
+ * @return 0 on success, or a non-zero value on error
+ */
+int ucx_properties_store(CxMap *map, FILE *file);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_PROPERTIES_H */
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+$(GTK_OBJPRE)%.o: gtk/%.c
+ $(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $<
+
+$(UI_LIB): $(OBJ)
+ $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "button.h"
+#include "container.h"
+#include <cx/allocator.h>
+#include "../common/context.h"
+#include "../common/object.h"
+
+void ui_button_set_icon_name(GtkWidget *button, const char *icon) {
+ if(!icon) {
+ return;
+ }
+
+#ifdef UI_GTK4
+ gtk_button_set_icon_name(GTK_BUTTON(button), icon);
+#else
+#if GTK_CHECK_VERSION(2, 6, 0)
+ GtkWidget *image = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON);
+ if(image) {
+ gtk_button_set_image(GTK_BUTTON(button), image);
+ }
+#else
+ // TODO
+#endif
+#endif
+}
+
+GtkWidget* ui_create_button(
+ UiObject *obj,
+ const char *label,
+ const char *icon,
+ ui_callback onclick,
+ void *userdata,
+ int event_value,
+ bool activate_event)
+{
+ GtkWidget *button = gtk_button_new_with_label(label);
+ ui_button_set_icon_name(button, icon);
+
+ if(onclick) {
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = userdata;
+ event->callback = onclick;
+ event->value = event_value;
+ event->customdata = NULL;
+
+ g_signal_connect(
+ button,
+ "clicked",
+ G_CALLBACK(ui_button_clicked),
+ event);
+ g_signal_connect(
+ button,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+ if(activate_event) {
+ g_signal_connect(
+ button,
+ "activate",
+ G_CALLBACK(ui_button_clicked),
+ event);
+ }
+ }
+
+ return button;
+}
+
+UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs args) {
+ UiObject* current = uic_current_obj(obj);
+ GtkWidget *button = ui_create_button(obj, args.label, args.icon, args.onclick, args.onclickdata, 0, FALSE);
+ ui_set_name_and_style(button, args.name, args.style_class);
+ ui_set_widget_groups(obj->ctx, button, args.groups);
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, button, FALSE);
+ return button;
+}
+
+
+void ui_button_clicked(GtkWidget *widget, UiEventData *event) {
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = event->value;
+ event->callback(&e, event->userdata);
+}
+
+int64_t ui_toggle_button_get(UiInteger *integer) {
+ GtkToggleButton *button = integer->obj;
+ integer->value = (int)gtk_toggle_button_get_active(button);
+ return integer->value;
+}
+
+void ui_toggle_button_set(UiInteger *integer, int64_t value) {
+ GtkToggleButton *button = integer->obj;
+ integer->value = value;
+ gtk_toggle_button_set_active(button, value != 0 ? TRUE : FALSE);
+}
+
+void ui_toggled_obs(void *widget, UiVarEventData *event) {
+ UiInteger *i = event->var->value;
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = event->var->value;
+ e.intval = i->get(i);
+
+ ui_notify_evt(i->observers, &e);
+}
+
+static void ui_toggled_callback(GtkToggleButton *widget, UiEventData *event) {
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = gtk_toggle_button_get_active(widget);
+ event->callback(&e, event->userdata);
+}
+
+static void ui_togglebutton_enable_state_callback(GtkToggleButton *widget, UiEventData *event) {
+ if(gtk_toggle_button_get_active(widget)) {
+ ui_set_group(event->obj->ctx, event->value);
+ } else {
+ ui_unset_group(event->obj->ctx, event->value);
+ }
+}
+
+void ui_setup_togglebutton(
+ UiObject *obj,
+ GtkWidget *togglebutton,
+ const char *label,
+ const char *icon,
+ const char *varname,
+ UiInteger *value,
+ ui_callback onchange,
+ void *onchangedata,
+ int enable_state)
+{
+ if(label) {
+ gtk_button_set_label(GTK_BUTTON(togglebutton), label);
+ }
+ ui_button_set_icon_name(togglebutton, icon);
+
+ ui_bind_togglebutton(
+ obj,
+ togglebutton,
+ ui_toggle_button_get,
+ ui_toggle_button_set,
+ varname,
+ value,
+ (ui_toggled_func)ui_toggled_callback,
+ onchange,
+ onchangedata,
+ (ui_toggled_func)ui_togglebutton_enable_state_callback,
+ enable_state
+ );
+}
+
+void ui_bind_togglebutton(
+ UiObject *obj,
+ GtkWidget *widget,
+ int64_t (*getfunc)(UiInteger*),
+ void (*setfunc)(UiInteger*, int64_t),
+ const char *varname,
+ UiInteger *value,
+ void (*toggled_callback)(void*, void*),
+ ui_callback onchange,
+ void *onchangedata,
+ void (*enable_state_func)(void*, void*),
+ int enable_state)
+{
+ UiObject* current = uic_current_obj(obj);
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, value, varname, UI_VAR_INTEGER);
+ if (var) {
+ UiInteger* value = (UiInteger*)var->value;
+ value->obj = widget;
+ value->get = getfunc;
+ value->set = setfunc;
+
+ UiVarEventData *event = malloc(sizeof(UiVarEventData));
+ event->obj = obj;
+ event->var = var;
+ event->observers = NULL;
+ event->callback = NULL;
+ event->userdata = NULL;
+
+ g_signal_connect(
+ widget,
+ "toggled",
+ G_CALLBACK(ui_toggled_obs),
+ event);
+ g_signal_connect(
+ widget,
+ "destroy",
+ G_CALLBACK(ui_destroy_vardata),
+ event);
+ }
+
+ if(onchange) {
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = onchangedata;
+ event->callback = onchange;
+ event->value = 0;
+ event->customdata = NULL;
+
+ g_signal_connect(
+ widget,
+ "toggled",
+ G_CALLBACK(toggled_callback),
+ event);
+ g_signal_connect(
+ widget,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+ }
+
+ if(enable_state > 0) {
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = NULL;
+ event->callback = NULL;
+ event->value = enable_state;
+ event->customdata = NULL;
+
+ g_signal_connect(
+ widget,
+ "toggled",
+ G_CALLBACK(enable_state_func),
+ event);
+ g_signal_connect(
+ widget,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+ }
+}
+
+static UIWIDGET togglebutton_create(UiObject *obj, GtkWidget *widget, UiToggleArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ ui_setup_togglebutton(
+ current,
+ widget,
+ args.label,
+ args.icon,
+ args.varname,
+ args.value,
+ args.onchange,
+ args.onchangedata,
+ args.enable_group);
+ ui_set_name_and_style(widget, args.name, args.style_class);
+ ui_set_widget_groups(obj->ctx, widget, args.groups);
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, widget, FALSE);
+
+ return widget;
+}
+
+UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) {
+ return togglebutton_create(obj, gtk_toggle_button_new(), args);
+}
+
+#if GTK_MAJOR_VERSION >= 4
+
+int64_t ui_check_button_get(UiInteger *integer) {
+ GtkCheckButton *button = integer->obj;
+ integer->value = (int)gtk_check_button_get_active(button);
+ return integer->value;
+}
+
+void ui_check_button_set(UiInteger *integer, int64_t value) {
+ GtkCheckButton *button = integer->obj;
+ integer->value = value;
+ gtk_check_button_set_active(button, value != 0 ? TRUE : FALSE);
+}
+
+static void ui_checkbox_callback(GtkCheckButton *widget, UiEventData *event) {
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = gtk_check_button_get_active(widget);
+ event->callback(&e, event->userdata);
+}
+
+static void ui_checkbox_enable_state(GtkCheckButton *widget, UiEventData *event) {
+ if(gtk_check_button_get_active(widget)) {
+ ui_set_group(event->obj->ctx, event->value);
+ } else {
+ ui_unset_group(event->obj->ctx, event->value);
+ }
+}
+
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ GtkWidget *widget = gtk_check_button_new_with_label(args.label);
+ ui_bind_togglebutton(
+ obj,
+ widget,
+ ui_check_button_get,
+ ui_check_button_set,
+ args.varname,
+ args.value,
+ (ui_toggled_func)ui_checkbox_callback,
+ args.onchange,
+ args.onchangedata,
+ (ui_toggled_func)ui_checkbox_enable_state,
+ args.enable_group);
+
+ ui_set_name_and_style(widget, args.name, args.style_class);
+ ui_set_widget_groups(obj->ctx, widget, args.groups);
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, widget, FALSE);
+
+ return widget;
+}
+
+#else
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
+ return togglebutton_create(obj, gtk_check_button_new(), args);
+}
+#endif
+
+UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) {
+#ifdef UI_GTK3
+ return NULL; // TODO
+#else
+ return ui_checkbox_create(obj, args);
+#endif
+}
+
+#if GTK_MAJOR_VERSION >= 4
+#define RADIOBUTTON_NEW(group, label) gtk_check_button_new_with_label(label)
+#define RADIOBUTTON_SET_GROUP(button, group)
+#define RADIOBUTTON_GET_GROUP(button) GTK_CHECK_BUTTON(button)
+#define RADIOBUTTON_GET_ACTIVE(button) gtk_check_button_get_active(GTK_CHECK_BUTTON(button))
+#else
+#define RADIOBUTTON_NEW(group, label) gtk_radio_button_new_with_label(group, label)
+#define RADIOBUTTON_SET_GROUP(button, group) /* noop */
+#define RADIOBUTTON_GET_GROUP(button) gtk_radio_button_get_group(GTK_RADIO_BUTTON(button))
+#define RADIOBUTTON_GET_ACTIVE(button) gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))
+#endif
+
+static void radiobutton_toggled(void *widget, UiEventData *event) {
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = RADIOBUTTON_GET_ACTIVE(widget);
+ event->callback(&e, event->userdata);
+}
+
+typedef struct UiRadioButtonData {
+ UiInteger *value;
+ UiVarEventData *eventdata;
+ UiBool first;
+} UiRadioButtonData;
+
+static void destroy_radiobutton(GtkWidget *w, UiRadioButtonData *data) {
+ ui_destroy_vardata(w, data->eventdata);
+ if(data->first) {
+ g_slist_free(data->value->obj);
+ data->value->obj = NULL;
+ data->value->get = NULL;
+ data->value->set = NULL;
+ }
+ free(data);
+}
+
+UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ GSList *rg = NULL;
+ UiInteger *rgroup;
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+
+ UiBool first = FALSE;
+ if(var) {
+ rgroup = var->value;
+ rg = rgroup->obj;
+ if(!rg) {
+ first = TRUE;
+ }
+ }
+
+ GtkWidget *rbutton = RADIOBUTTON_NEW(rg, args.label);
+ ui_set_name_and_style(rbutton, args.name, args.style_class);
+ ui_set_widget_groups(obj->ctx, rbutton, args.groups);
+ if(rgroup) {
+#if GTK_MAJOR_VERSION >= 4
+ if(rg) {
+ gtk_check_button_set_group(GTK_CHECK_BUTTON(rbutton), rg->data);
+ }
+ rg = g_slist_prepend(rg, rbutton);
+#else
+ gtk_radio_button_set_group(GTK_RADIO_BUTTON(rbutton), rg);
+ rg = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rbutton));
+#endif
+
+ rgroup->obj = rg;
+ rgroup->get = ui_radiobutton_get;
+ rgroup->set = ui_radiobutton_set;
+
+ ui_radiobutton_set(rgroup, rgroup->value);
+
+ UiVarEventData *event = malloc(sizeof(UiVarEventData));
+ event->obj = obj;
+ event->var = var;
+ event->observers = NULL;
+ event->callback = NULL;
+ event->userdata = NULL;
+
+ UiRadioButtonData *rbdata = malloc(sizeof(UiRadioButtonData));
+ rbdata->value = rgroup;
+ rbdata->eventdata = event;
+ rbdata->first = first;
+
+ g_signal_connect(
+ rbutton,
+ "toggled",
+ G_CALLBACK(ui_radio_obs),
+ event);
+ g_signal_connect(
+ rbutton,
+ "destroy",
+ G_CALLBACK(destroy_radiobutton),
+ rbdata);
+ }
+
+ if(args.onchange) {
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = args.onchangedata;
+ event->callback = args.onchange;
+ event->value = 0;
+ event->customdata = NULL;
+
+ g_signal_connect(
+ rbutton,
+ "toggled",
+ G_CALLBACK(radiobutton_toggled),
+ event);
+ g_signal_connect(
+ rbutton,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+ }
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, rbutton, FALSE);
+
+ return rbutton;
+}
+
+void ui_radio_obs(GtkToggleButton *widget, UiVarEventData *event) {
+ UiInteger *i = event->var->value;
+
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = i->get(i);
+
+ ui_notify_evt(i->observers, &e);
+}
+
+#if GTK_MAJOR_VERSION >= 4
+int64_t ui_radiobutton_get(UiInteger *value) {
+ int selection = 0;
+ GSList *ls = value->obj;
+ int i = 0;
+ guint len = g_slist_length(ls);
+ while(ls) {
+ if(gtk_check_button_get_active(GTK_CHECK_BUTTON(ls->data))) {
+ selection = len - i - 1;
+ break;
+ }
+ ls = ls->next;
+ i++;
+ }
+
+ value->value = selection;
+ return selection;
+}
+
+void ui_radiobutton_set(UiInteger *value, int64_t i) {
+ GSList *ls = value->obj;
+ int s = g_slist_length(ls) - 1 - i;
+ int j = 0;
+ while(ls) {
+ if(j == s) {
+ gtk_check_button_set_active(GTK_CHECK_BUTTON(ls->data), TRUE);
+ break;
+ }
+ ls = ls->next;
+ j++;
+ }
+
+ value->value = i;
+}
+#else
+int64_t ui_radiobutton_get(UiInteger *value) {
+ int selection = 0;
+ GSList *ls = value->obj;
+ int i = 0;
+ guint len = g_slist_length(ls);
+ while(ls) {
+ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ls->data))) {
+ selection = len - i - 1;
+ break;
+ }
+ ls = ls->next;
+ i++;
+ }
+
+ value->value = selection;
+ return selection;
+}
+
+void ui_radiobutton_set(UiInteger *value, int64_t i) {
+ GSList *ls = value->obj;
+ int s = g_slist_length(ls) - 1 - i;
+ int j = 0;
+ while(ls) {
+ if(j == s) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ls->data), TRUE);
+ break;
+ }
+ ls = ls->next;
+ j++;
+ }
+
+ value->value = i;
+}
+#endif
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BUTTON_H
+#define BUTTON_H
+
+#include "../ui/toolkit.h"
+#include "../ui/button.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void ui_button_set_icon_name(GtkWidget *button, const char *icon_name);
+
+typedef void (*ui_toggled_func)(void*, void*);
+
+GtkWidget* ui_create_button(
+ UiObject *obj,
+ const char *label,
+ const char *icon,
+ ui_callback onclick,
+ void *userdata,
+ int event_value,
+ bool activate_event);
+
+void ui_setup_togglebutton(
+ UiObject *obj,
+ GtkWidget *togglebutton,
+ const char *label,
+ const char *icon,
+ const char *varname,
+ UiInteger *value,
+ ui_callback onchange,
+ void *onchangedata,
+ int enable_state);
+
+void ui_bind_togglebutton(
+ UiObject *obj,
+ GtkWidget *widget,
+ int64_t (*getfunc)(UiInteger*),
+ void (*setfunc)(UiInteger*, int64_t),
+ const char *varname,
+ UiInteger *value,
+ void (*toggled_callback)(void*, void*),
+ ui_callback onchange,
+ void *onchangedata,
+ void (*enable_state_func)(void*, void*),
+ int enable_state);
+
+// event wrapper
+void ui_button_clicked(GtkWidget *widget, UiEventData *event);
+
+
+void ui_toggled_obs(void *widget, UiVarEventData *event);
+
+UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var);
+
+UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var);
+
+void ui_radio_obs(GtkToggleButton *widget, UiVarEventData *event);
+
+int64_t ui_radiobutton_get(UiInteger *value);
+void ui_radiobutton_set(UiInteger *value, int64_t i);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BUTTON_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "container.h"
+#include "toolkit.h"
+#include "headerbar.h"
+
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+void ui_container_begin_close(UiObject *obj) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->close = 1;
+}
+
+int ui_container_finish(UiObject *obj) {
+ UiContainer *ct = uic_get_current_container(obj);
+ if(ct->close) {
+ ui_end(obj);
+ return 0;
+ }
+ return 1;
+}
+
+GtkWidget* ui_gtk_vbox_new(int spacing) {
+#if GTK_MAJOR_VERSION >= 3
+ return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing);
+#else
+ return gtk_vbox_new(FALSE, spacing);
+#endif
+}
+
+GtkWidget* ui_gtk_hbox_new(int spacing) {
+#if GTK_MAJOR_VERSION >= 3
+ return gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing);
+#else
+ return gtk_hbox_new(FALSE, spacing);
+#endif
+}
+
+GtkWidget* ui_subcontainer_create(
+ UiSubContainerType type,
+ UiObject *newobj,
+ int spacing,
+ int columnspacing,
+ int rowspacing,
+ int margin)
+{
+ GtkWidget *sub = NULL;
+ GtkWidget *add = NULL;
+ switch(type) {
+ default: {
+ sub = ui_gtk_vbox_new(spacing);
+ add = ui_box_set_margin(sub, margin);
+ newobj->container = ui_box_container(newobj, sub, type);
+ newobj->widget = sub;
+ break;
+ }
+ case UI_CONTAINER_HBOX: {
+ sub = ui_gtk_hbox_new(spacing);
+ add = ui_box_set_margin(sub, margin);
+ newobj->container = ui_box_container(newobj, sub, type);
+ newobj->widget = sub;
+ break;
+ }
+ case UI_CONTAINER_GRID: {
+ sub = ui_create_grid_widget(columnspacing, rowspacing);
+ add = ui_box_set_margin(sub, margin);
+ newobj->container = ui_grid_container(newobj, sub);
+ newobj->widget = sub;
+ break;
+ }
+ case UI_CONTAINER_NO_SUB: {
+ break;
+ }
+ }
+ return add;
+}
+
+
+/* -------------------- Box Container -------------------- */
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type) {
+ UiBoxContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiBoxContainer));
+ ct->container.widget = box;
+ ct->container.add = ui_box_container_add;
+ ct->type = type;
+ return (UiContainer*)ct;
+}
+
+void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+ UiBoxContainer *bc = (UiBoxContainer*)ct;
+ if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
+ fill = ui_lb2bool(ct->layout.fill);
+ }
+
+ if(bc->has_fill && fill) {
+ fprintf(stderr, "UiError: container has 2 filled widgets");
+ fill = FALSE;
+ }
+ if(fill) {
+ bc->has_fill = TRUE;
+ }
+
+ UiBool expand = fill;
+#if GTK_MAJOR_VERSION >= 4
+ gtk_box_append(GTK_BOX(ct->widget), widget);
+ GtkAlign align = expand ? GTK_ALIGN_FILL : GTK_ALIGN_START;
+ if(bc->type == UI_CONTAINER_VBOX) {
+ gtk_widget_set_valign(widget, align);
+ gtk_widget_set_vexpand(widget, expand);
+ gtk_widget_set_hexpand(widget, TRUE);
+ } else if(bc->type == UI_CONTAINER_HBOX) {
+ gtk_widget_set_halign(widget, align);
+ gtk_widget_set_hexpand(widget, expand);
+ gtk_widget_set_vexpand(widget, TRUE);
+ }
+
+#else
+ gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0);
+#endif
+
+ ui_reset_layout(ct->layout);
+ ct->current = widget;
+}
+
+UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid) {
+ UiGridContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiGridContainer));
+ ct->container.widget = grid;
+ ct->container.add = ui_grid_container_add;
+ UI_GTK_V2(ct->width = 0);
+ UI_GTK_V2(ct->height = 1);
+ return (UiContainer*)ct;
+}
+
+#if GTK_MAJOR_VERSION >= 3
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+ UiGridContainer *grid = (UiGridContainer*)ct;
+
+ if(ct->layout.newline) {
+ grid->x = 0;
+ grid->y++;
+ ct->layout.newline = FALSE;
+ }
+
+ int hexpand = FALSE;
+ int vexpand = FALSE;
+ int hfill = FALSE;
+ int vfill = FALSE;
+ if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
+ fill = ui_lb2bool(ct->layout.fill);
+ }
+ if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
+ hexpand = ct->layout.hexpand;
+ hfill = TRUE;
+ }
+ if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
+ vexpand = ct->layout.vexpand;
+ vfill = TRUE;
+ }
+ if(fill) {
+ hfill = TRUE;
+ vfill = TRUE;
+ }
+
+ if(!hfill) {
+ gtk_widget_set_halign(widget, GTK_ALIGN_START);
+ }
+ if(!vfill) {
+ gtk_widget_set_valign(widget, GTK_ALIGN_START);
+ }
+
+ gtk_widget_set_hexpand(widget, hexpand);
+ gtk_widget_set_vexpand(widget, vexpand);
+
+ int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1;
+ int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1;
+
+ gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, colspan, rowspan);
+ grid->x += colspan;
+
+ ui_reset_layout(ct->layout);
+ ct->current = widget;
+}
+#endif
+#ifdef UI_GTK2
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+ UiGridContainer *grid = (UiGridContainer*)ct;
+
+ if(ct->layout.newline) {
+ grid->x = 0;
+ grid->y++;
+ ct->layout.newline = FALSE;
+ }
+
+ int hexpand = FALSE;
+ int vexpand = FALSE;
+ if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
+ hexpand = ct->layout.hexpand;
+ }
+ if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
+ vexpand = ct->layout.vexpand;
+ }
+ GtkAttachOptions xoptions = hexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL;
+ GtkAttachOptions yoptions = vexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL;
+
+ int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1;
+ int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1;
+ // TODO: use colspan/rowspan
+
+ gtk_table_attach(GTK_TABLE(ct->widget), widget, grid->x, grid->x+1, grid->y, grid->y+1, xoptions, yoptions, 0, 0);
+ grid->x++;
+ int nw = grid->x > grid->width ? grid->x : grid->width;
+ if(grid->x > grid->width || grid->y > grid->height) {
+ grid->width = nw;
+ grid->height = grid->y + 1;
+ gtk_table_resize(GTK_TABLE(ct->widget), grid->width, grid->height);
+ }
+
+ ui_reset_layout(ct->layout);
+ ct->current = widget;
+}
+#endif
+
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame) {
+ UiContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiContainer));
+ ct->widget = frame;
+ ct->add = ui_frame_container_add;
+ return ct;
+}
+
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+ FRAME_SET_CHILD(ct->widget, widget);
+}
+
+UiContainer* ui_expander_container(UiObject *obj, GtkWidget *expander) {
+ UiContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiContainer));
+ ct->widget = expander;
+ ct->add = ui_expander_container_add;
+ return ct;
+}
+
+void ui_expander_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+ EXPANDER_SET_CHILD(ct->widget, widget);
+}
+
+void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+ // TODO: check if the widget implements GtkScrollable
+ SCROLLEDWINDOW_SET_CHILD(ct->widget, widget);
+ ui_reset_layout(ct->layout);
+ ct->current = widget;
+}
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) {
+ UiContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiContainer));
+ ct->widget = scrolledwindow;
+ ct->add = ui_scrolledwindow_container_add;
+ return ct;
+}
+
+UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview) {
+ UiTabViewContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiTabViewContainer));
+ ct->container.widget = tabview;
+ ct->container.add = ui_tabview_container_add;
+ return (UiContainer*)ct;
+}
+
+void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+ UiGtkTabView *data = ui_widget_get_tabview_data(ct->widget);
+ if(!data) {
+ fprintf(stderr, "UI Error: widget is not a tabview");
+ return;
+ }
+ data->add_tab(ct->widget, -1, ct->layout.label, widget);
+
+ ui_reset_layout(ct->layout);
+ ct->current = widget;
+}
+
+
+
+GtkWidget* ui_box_set_margin(GtkWidget *box, int margin) {
+ GtkWidget *ret = box;
+#if GTK_MAJOR_VERSION >= 3
+#if GTK_MAJOR_VERSION * 1000 + GTK_MINOR_VERSION >= 3012
+ gtk_widget_set_margin_start(box, margin);
+ gtk_widget_set_margin_end(box, margin);
+#else
+ gtk_widget_set_margin_left(box, margin);
+ gtk_widget_set_margin_right(box, margin);
+#endif
+ gtk_widget_set_margin_top(box, margin);
+ gtk_widget_set_margin_bottom(box, margin);
+#elif defined(UI_GTK2)
+ GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin);
+ gtk_container_add(GTK_CONTAINER(a), box);
+ ret = a;
+#endif
+ return ret;
+}
+
+UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, UiSubContainerType type) {
+ UiObject *current = uic_current_obj(obj);
+ UiContainer *ct = current->container;
+ UI_APPLY_LAYOUT1(current, args);
+
+ GtkWidget *box = type == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args.spacing) : ui_gtk_hbox_new(args.spacing);
+ ui_set_name_and_style(box, args.name, args.style_class);
+ GtkWidget *widget = args.margin > 0 ? ui_box_set_margin(box, args.margin) : box;
+ ct->add(ct, widget, TRUE);
+
+ UiObject *newobj = uic_object_new(obj, box);
+ newobj->container = ui_box_container(obj, box, type);
+ uic_obj_add(obj, newobj);
+
+ return widget;
+}
+
+UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args) {
+ return ui_box_create(obj, args, UI_CONTAINER_VBOX);
+}
+
+UIEXPORT UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args) {
+ return ui_box_create(obj, args, UI_CONTAINER_HBOX);
+}
+
+GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing) {
+#if GTK_MAJOR_VERSION >= 3
+ GtkWidget *grid = gtk_grid_new();
+ gtk_grid_set_column_spacing(GTK_GRID(grid), colspacing);
+ gtk_grid_set_row_spacing(GTK_GRID(grid), rowspacing);
+#else
+ GtkWidget *grid = gtk_table_new(1, 1, FALSE);
+ gtk_table_set_col_spacings(GTK_TABLE(grid), colspacing);
+ gtk_table_set_row_spacings(GTK_TABLE(grid), rowspacing);
+#endif
+ return grid;
+}
+
+UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) {
+ UiObject* current = uic_current_obj(obj);
+ UI_APPLY_LAYOUT1(current, args);
+ GtkWidget *widget;
+
+ GtkWidget *grid = ui_create_grid_widget(args.columnspacing, args.rowspacing);
+ ui_set_name_and_style(grid, args.name, args.style_class);
+ widget = ui_box_set_margin(grid, args.margin);
+ current->container->add(current->container, widget, TRUE);
+
+ UiObject *newobj = uic_object_new(obj, grid);
+ newobj->container = ui_grid_container(obj, grid);
+ uic_obj_add(obj, newobj);
+
+ return widget;
+}
+
+UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs args) {
+ UiObject* current = uic_current_obj(obj);
+ UI_APPLY_LAYOUT1(current, args);
+
+ GtkWidget *frame = gtk_frame_new(args.label);
+ UiObject *newobj = uic_object_new(obj, frame);
+ GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin);
+ if(sub) {
+ FRAME_SET_CHILD(frame, sub);
+ } else {
+ newobj->widget = frame;
+ newobj->container = ui_frame_container(obj, frame);
+ }
+ current->container->add(current->container, frame, FALSE);
+ uic_obj_add(obj, newobj);
+
+ return frame;
+}
+
+UIEXPORT UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs args) {
+ UiObject* current = uic_current_obj(obj);
+ UI_APPLY_LAYOUT1(current, args);
+
+ GtkWidget *expander = gtk_expander_new(args.label);
+ gtk_expander_set_expanded(GTK_EXPANDER(expander), args.isexpanded);
+ UiObject *newobj = uic_object_new(obj, expander);
+ GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin);
+ if(sub) {
+ EXPANDER_SET_CHILD(expander, sub);
+ } else {
+ newobj->widget = expander;
+ newobj->container = ui_expander_container(obj, expander);
+ }
+ current->container->add(current->container, expander, FALSE);
+ uic_obj_add(obj, newobj);
+
+ return expander;
+}
+
+
+UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args) {
+ UiObject* current = uic_current_obj(obj);
+ UI_APPLY_LAYOUT1(current, args);
+
+ GtkWidget *sw = SCROLLEDWINDOW_NEW();
+ ui_set_name_and_style(sw, args.name, args.style_class);
+ GtkWidget *widget = ui_box_set_margin(sw, args.margin);
+ current->container->add(current->container, widget, TRUE);
+
+ UiObject *newobj = uic_object_new(obj, sw);
+ GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin);
+ if(sub) {
+ SCROLLEDWINDOW_SET_CHILD(sw, sub);
+ } else {
+ newobj->widget = sw;
+ newobj->container = ui_scrolledwindow_container(obj, sw);
+ }
+
+ uic_obj_add(obj, newobj);
+
+ return sw;
+}
+
+
+void ui_notebook_tab_select(UIWIDGET tabview, int tab) {
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(tabview), tab);
+}
+
+void ui_notebook_tab_remove(UIWIDGET tabview, int tab) {
+ gtk_notebook_remove_page(GTK_NOTEBOOK(tabview), tab);
+}
+
+void ui_notebook_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) {
+ gtk_notebook_insert_page(
+ GTK_NOTEBOOK(widget),
+ child,
+ gtk_label_new(name),
+ index);
+}
+
+int64_t ui_notebook_get(UiInteger *i) {
+ GtkNotebook *nb = i->obj;
+ i->value = gtk_notebook_get_current_page(nb);
+ return i->value;
+}
+
+void ui_notebook_set(UiInteger *i, int64_t value) {
+ GtkNotebook *nb = i->obj;
+ gtk_notebook_set_current_page(nb, value);
+ i->value = gtk_notebook_get_current_page(nb);
+}
+
+
+#if GTK_MAJOR_VERSION >= 4
+static int stack_set_page(GtkWidget *stack, int index) {
+ GtkSelectionModel *pages = gtk_stack_get_pages(GTK_STACK(stack));
+ GListModel *list = G_LIST_MODEL(pages);
+ GtkStackPage *page = g_list_model_get_item(list, index);
+ if(page) {
+ gtk_stack_set_visible_child(GTK_STACK(stack), gtk_stack_page_get_child(page));
+ } else {
+ fprintf(stderr, "UI Error: ui_stack_set value out of bounds\n");
+ return -1;
+ }
+ return index;
+}
+
+void ui_stack_tab_select(UIWIDGET tabview, int tab) {
+ stack_set_page(tabview, tab);
+}
+
+void ui_stack_tab_remove(UIWIDGET tabview, int tab) {
+ GtkStack *stack = GTK_STACK(tabview);
+ GtkWidget *current = gtk_stack_get_visible_child(stack);
+ GtkSelectionModel *pages = gtk_stack_get_pages(stack);
+ GListModel *list = G_LIST_MODEL(pages);
+ GtkStackPage *page = g_list_model_get_item(list, tab);
+ if(page) {
+ gtk_stack_remove(stack, gtk_stack_page_get_child(page));
+ }
+}
+
+void ui_stack_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) {
+ (void)gtk_stack_add_titled(GTK_STACK(widget), child, name, name);
+}
+
+int64_t ui_stack_get(UiInteger *i) {
+ GtkStack *stack = GTK_STACK(i->obj);
+ GtkWidget *current = gtk_stack_get_visible_child(stack);
+ GtkSelectionModel *pages = gtk_stack_get_pages(stack);
+ GListModel *list = G_LIST_MODEL(pages);
+ int nitems = g_list_model_get_n_items(list);
+ for(int p=0;p<nitems;p++) {
+ GtkStackPage *page = g_list_model_get_item(list, p);
+ GtkWidget *child = gtk_stack_page_get_child(page);
+ if(child == current) {
+ i->value = p;
+ break;
+ }
+ }
+ return i->value;
+}
+
+void ui_stack_set(UiInteger *i, int64_t value) {
+ GtkWidget *widget = i->obj;
+ if(stack_set_page(widget, value) >= 0) {
+ i->value = value;
+ }
+}
+#elif GTK_MAJOR_VERSION >= 3
+static GtkWidget* stack_get_child(GtkWidget *stack, int index) {
+ GList *children = gtk_container_get_children(GTK_CONTAINER(stack));
+ if(children) {
+ return g_list_nth_data(children, index);
+ }
+ return NULL;
+}
+
+void ui_stack_tab_select(UIWIDGET tabview, int tab) {
+ GtkWidget *child = stack_get_child(tabview, tab);
+ if(child) {
+ gtk_stack_set_visible_child(GTK_STACK(tabview), child);
+ }
+}
+
+void ui_stack_tab_remove(UIWIDGET tabview, int tab) {
+ GtkWidget *child = stack_get_child(tabview, tab);
+ if(child) {
+ gtk_container_remove(GTK_CONTAINER(tabview), child);
+ }
+}
+
+void ui_stack_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) {
+ gtk_stack_add_titled(GTK_STACK(widget), child, name, name);
+}
+
+int64_t ui_stack_get(UiInteger *i) {
+ GtkWidget *visible = gtk_stack_get_visible_child(GTK_STACK(i->obj));
+ GList *children = gtk_container_get_children(GTK_CONTAINER(i->obj));
+ GList *elm = children;
+ int n = 0;
+ int64_t v = -1;
+ while(elm) {
+ GtkWidget *child = elm->data;
+ if(child == visible) {
+ v = n;
+ break;
+ }
+
+ elm = elm->next;
+ n++;
+ }
+ g_list_free(children);
+ i->value = v;
+ return v;
+}
+
+void ui_stack_set(UiInteger *i, int64_t value) {
+ GtkWidget *child = stack_get_child(i->obj, value);
+ if(child) {
+ gtk_stack_set_visible_child(GTK_STACK(i->obj), child);
+ i->value = value;
+ }
+}
+
+#endif
+
+
+
+
+UiGtkTabView* ui_widget_get_tabview_data(UIWIDGET tabview) {
+ return g_object_get_data(G_OBJECT(tabview), "ui_tabview");
+}
+
+typedef int64_t(*ui_tabview_get_func)(UiInteger*);
+typedef void (*ui_tabview_set_func)(UiInteger*, int64_t);
+
+UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs args) {
+ UiGtkTabView *data = malloc(sizeof(UiGtkTabView));
+ data->margin = args.margin;
+ data->spacing = args.spacing;
+ data->columnspacing = args.columnspacing;
+ data->rowspacing = args.rowspacing;
+
+ ui_tabview_get_func getfunc = NULL;
+ ui_tabview_set_func setfunc = NULL;
+
+ GtkWidget *widget = NULL;
+ GtkWidget *data_widget = NULL;
+ switch(args.tabview) {
+ case UI_TABVIEW_DOC: {
+ // TODO
+ break;
+ }
+ case UI_TABVIEW_NAVIGATION_SIDE: {
+#if GTK_CHECK_VERSION(3, 10, 0)
+ widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ GtkWidget *sidebar = gtk_stack_sidebar_new();
+ BOX_ADD(widget, sidebar);
+ GtkWidget *stack = gtk_stack_new();
+ gtk_stack_set_transition_type (GTK_STACK(stack), GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN);
+ gtk_stack_sidebar_set_stack(GTK_STACK_SIDEBAR(sidebar), GTK_STACK(stack));
+ BOX_ADD_EXPAND(widget, stack);
+ data->select_tab = ui_stack_tab_select;
+ data->remove_tab = ui_stack_tab_remove;
+ data->add_tab = ui_stack_tab_add;
+ getfunc = ui_stack_get;
+ setfunc = ui_stack_set;
+ data_widget = stack;
+#else
+ // TODO
+#endif
+ break;
+ }
+ case UI_TABVIEW_DEFAULT: /* fall through */
+ case UI_TABVIEW_NAVIGATION_TOP: /* fall through */
+ case UI_TABVIEW_INVISIBLE: /* fall through */
+ case UI_TABVIEW_NAVIGATION_TOP2: {
+ widget = gtk_notebook_new();
+ data_widget = widget;
+ data->select_tab = ui_notebook_tab_select;
+ data->remove_tab = ui_notebook_tab_remove;
+ data->add_tab = ui_notebook_tab_add;
+ getfunc = ui_notebook_get;
+ setfunc = ui_notebook_set;
+ if(args.tabview == UI_TABVIEW_INVISIBLE) {
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(widget), FALSE);
+ gtk_notebook_set_show_border(GTK_NOTEBOOK(widget), FALSE);
+ }
+ break;
+ }
+ }
+
+ UiObject* current = uic_current_obj(obj);
+ if(args.value || args.varname) {
+ UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+ UiInteger *i = var->value;
+ i->get = getfunc;
+ i->set = setfunc;
+ i->obj = data_widget;
+ }
+
+ g_object_set_data(G_OBJECT(widget), "ui_tabview", data);
+ data->widget = data_widget;
+ data->subcontainer = args.subcontainer;
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, widget, TRUE);
+
+ UiObject *newobj = uic_object_new(obj, widget);
+ newobj->container = ui_tabview_container(obj, widget);
+ uic_obj_add(obj, newobj);
+ data->obj = newobj;
+
+ return widget;
+}
+
+void ui_tab_create(UiObject* obj, const char* title) {
+ UiObject* current = uic_current_obj(obj);
+ UiGtkTabView *data = ui_widget_get_tabview_data(current->widget);
+ if(!data) {
+ fprintf(stderr, "UI Error: widget is not a tabview\n");
+ return;
+ }
+
+ UiObject *newobj = ui_tabview_add(current->widget, title, -1);
+ current->next = newobj;
+}
+
+
+
+void ui_tabview_select(UIWIDGET tabview, int tab) {
+ UiGtkTabView *data = ui_widget_get_tabview_data(tabview);
+ if(!data) {
+ fprintf(stderr, "UI Error: widget is not a tabview\n");
+ return;
+ }
+ data->select_tab(tabview, tab);
+}
+
+void ui_tabview_remove(UIWIDGET tabview, int tab) {
+ UiGtkTabView *data = ui_widget_get_tabview_data(tabview);
+ if(!data) {
+ fprintf(stderr, "UI Error: widget is not a tabview\n");
+ return;
+ }
+ data->remove_tab(tabview, tab);
+}
+
+UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) {
+ UiGtkTabView *data = ui_widget_get_tabview_data(tabview);
+ if(!data) {
+ fprintf(stderr, "UI Error: widget is not a tabview\n");
+ return NULL;
+ }
+
+ UiObject *newobj = cxCalloc(data->obj->ctx->allocator, 1, sizeof(UiObject));
+ newobj->ctx = data->obj->ctx;
+
+ GtkWidget *sub;
+ switch(data->subcontainer) {
+ default: {
+ sub = ui_gtk_vbox_new(data->spacing);
+ newobj->container = ui_box_container(newobj, sub, data->subcontainer);
+ break;
+ }
+ case UI_CONTAINER_HBOX: {
+ sub = ui_gtk_hbox_new(data->spacing);
+ newobj->container = ui_box_container(newobj, sub, data->subcontainer);
+ break;
+ }
+ case UI_CONTAINER_GRID: {
+ sub = ui_create_grid_widget(data->columnspacing, data->rowspacing);
+ newobj->container = ui_grid_container(newobj, sub);
+ break;
+ }
+ }
+ newobj->widget = sub;
+ GtkWidget *widget = ui_box_set_margin(sub, data->margin);
+
+ data->add_tab(data->widget, tab_index, name, widget);
+
+ return newobj;
+}
+
+
+/* -------------------- Headerbar -------------------- */
+
+static void hb_set_part(UiObject *obj, int part) {
+ UiObject* current = uic_current_obj(obj);
+ GtkWidget *headerbar = current->widget;
+
+ UiHeaderbarContainer *hb = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiHeaderbarContainer));
+ memcpy(hb, current->container, sizeof(UiHeaderbarContainer));
+
+ UiObject *newobj = uic_object_new(obj, headerbar);
+ newobj->container = (UiContainer*)hb;
+ uic_obj_add(obj, newobj);
+
+ hb->part = part;
+}
+
+void ui_headerbar_start_create(UiObject *obj) {
+ hb_set_part(obj, 0);
+}
+
+void ui_headerbar_center_create(UiObject *obj) {
+ hb_set_part(obj, 2);
+}
+
+void ui_headerbar_end_create(UiObject *obj) {
+ hb_set_part(obj, 1);
+}
+
+UIWIDGET ui_headerbar_fallback_create(UiObject *obj, UiHeaderbarArgs args) {
+ UiObject *current = uic_current_obj(obj);
+ UiContainer *ct = current->container;
+ UI_APPLY_LAYOUT1(current, args);
+
+ GtkWidget *box = ui_gtk_hbox_new(args.alt_spacing);
+ ui_set_name_and_style(box, args.name, args.style_class);
+ ct->add(ct, box, FALSE);
+
+ UiObject *newobj = uic_object_new(obj, box);
+ newobj->container = ui_headerbar_fallback_container(obj, box);
+ uic_obj_add(obj, newobj);
+
+ return box;
+}
+
+static void hb_fallback_set_part(UiObject *obj, int part) {
+ UiObject* current = uic_current_obj(obj);
+ GtkWidget *headerbar = current->widget;
+
+ UiObject *newobj = uic_object_new(obj, headerbar);
+ newobj->container = ui_headerbar_container(obj, headerbar);
+ uic_obj_add(obj, newobj);
+
+ UiHeaderbarContainer *hb = (UiHeaderbarContainer*)newobj->container;
+ hb->part = part;
+}
+
+UiContainer* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar) {
+ UiHeaderbarContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiHeaderbarContainer));
+ ct->container.widget = headerbar;
+ ct->container.add = ui_headerbar_fallback_container_add;
+ return (UiContainer*)ct;
+}
+
+void ui_headerbar_fallback_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+ UiHeaderbarContainer *hb = (UiHeaderbarContainer*)ct;
+ BOX_ADD(ct->widget, widget);
+}
+
+#if GTK_CHECK_VERSION(3, 10, 0)
+
+UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) {
+ GtkWidget *headerbar = g_object_get_data(G_OBJECT(obj->widget), "ui_headerbar");
+ if(!headerbar) {
+ return ui_headerbar_fallback_create(obj, args);
+ }
+
+ UiObject *newobj = uic_object_new(obj, headerbar);
+ newobj->container = ui_headerbar_container(obj, headerbar);
+ uic_obj_add(obj, newobj);
+
+ return headerbar;
+}
+
+UiContainer* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar) {
+ UiHeaderbarContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiHeaderbarContainer));
+ ct->container.widget = headerbar;
+ ct->container.add = ui_headerbar_container_add;
+ return (UiContainer*)ct;
+}
+
+void ui_headerbar_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
+ UiHeaderbarContainer *hb = (UiHeaderbarContainer*)ct;
+ if(hb->part == 0) {
+ UI_HEADERBAR_PACK_START(ct->widget, widget);
+ } else if(hb->part == 1) {
+ UI_HEADERBAR_PACK_END(ct->widget, widget);
+ } else if(hb->part == 2) {
+ if(!hb->centerbox) {
+ GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+ hb->centerbox = box;
+ UI_HEADERBAR_SET_TITLE_WIDGET(ct->widget, box);
+ }
+ BOX_ADD(hb->centerbox, widget);
+ }
+}
+
+#else
+
+UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) {
+ return ui_headerbar_fallback_create(obj, args);
+}
+
+#endif
+
+/* -------------------- Splitpane -------------------- */
+
+static GtkWidget* create_paned(UiOrientation orientation) {
+#if GTK_MAJOR_VERSION >= 3
+ switch(orientation) {
+ case UI_HORIZONTAL: return gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+ case UI_VERTICAL: return gtk_paned_new(GTK_ORIENTATION_VERTICAL);
+ }
+#else
+ switch(orientation) {
+ case UI_HORIZONTAL: return gtk_hpaned_new();
+ case UI_VERTICAL: return gtk_vpaned_new();
+ }
+#endif
+ return NULL;
+}
+
+
+
+
+
+
+/*
+ * -------------------- Layout Functions --------------------
+ *
+ * functions for setting layout attributes for the current container
+ *
+ */
+
+void ui_layout_fill(UiObject *obj, UiBool fill) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.fill = ui_bool2lb(fill);
+}
+
+void ui_layout_hexpand(UiObject *obj, UiBool expand) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.hexpand = expand;
+}
+
+void ui_layout_vexpand(UiObject *obj, UiBool expand) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.vexpand = expand;
+}
+
+void ui_layout_hfill(UiObject *obj, UiBool fill) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.hfill = fill;
+}
+
+void ui_layout_vfill(UiObject *obj, UiBool fill) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.vfill = fill;
+}
+
+void ui_layout_colspan(UiObject* obj, int cols) {
+ UiContainer* ct = uic_get_current_container(obj);
+ ct->layout.colspan = cols;
+}
+
+void ui_layout_rowspan(UiObject* obj, int rows) {
+ UiContainer* ct = uic_get_current_container(obj);
+ ct->layout.rowspan = rows;
+}
+
+void ui_newline(UiObject *obj) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.newline = TRUE;
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CONTAINER_H
+#define CONTAINER_H
+
+#include "../ui/toolkit.h"
+#include "../ui/container.h"
+#include <string.h>
+
+#include <cx/allocator.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
+#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)
+#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)
+
+typedef void (*ui_container_add_f)(UiContainer*, GtkWidget*, UiBool);
+
+typedef struct UiDocumentView UiDocumentView;
+
+typedef struct UiLayout UiLayout;
+typedef enum UiLayoutBool UiLayoutBool;
+
+enum UiLayoutBool {
+ UI_LAYOUT_UNDEFINED = 0,
+ UI_LAYOUT_TRUE,
+ UI_LAYOUT_FALSE,
+};
+
+struct UiLayout {
+ UiLayoutBool fill;
+ UiBool newline;
+ char *label;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int width;
+ int colspan;
+ int rowspan;
+};
+
+struct UiContainer {
+ GtkWidget *widget;
+ UIMENU menu;
+ GtkWidget *current;
+
+ void (*add)(UiContainer*, GtkWidget*, UiBool);
+ UiLayout layout;
+
+ int close;
+};
+
+typedef struct UiBoxContainer {
+ UiContainer container;
+ UiSubContainerType type;
+ UiBool has_fill;
+} UiBoxContainer;
+
+typedef struct UiGridContainer {
+ UiContainer container;
+ int x;
+ int y;
+#ifdef UI_GTK2
+ int width;
+ int height;
+#endif
+} UiGridContainer;
+
+/*
+typedef struct UiPanedContainer {
+ UiContainer container;
+ GtkWidget *current_pane;
+ int orientation;
+ int max;
+ int cur;
+} UiPanedContainer;
+*/
+
+typedef struct UiTabViewContainer {
+ UiContainer container;
+} UiTabViewContainer;
+
+typedef void (*ui_select_tab_func)(UIWIDGET widget, int tab);
+typedef void (*ui_add_tab_func)(UIWIDGET widget, int index, const char *name, UIWIDGET child);
+
+typedef struct UiGtkTabView {
+ UiObject *obj;
+ GtkWidget *widget;
+ ui_select_tab_func select_tab;
+ ui_select_tab_func remove_tab;
+ ui_add_tab_func add_tab;
+ UiSubContainerType subcontainer;
+ int margin;
+ int spacing;
+ int columnspacing;
+ int rowspacing;
+} UiGtkTabView;
+
+typedef struct UiHeaderbarContainer {
+ UiContainer container;
+ GtkWidget *centerbox;
+ int part;
+ UiHeaderbarAlternative alternative; /* only used by fallback headerbar */
+} UiHeaderbarContainer;
+
+GtkWidget* ui_gtk_vbox_new(int spacing);
+GtkWidget* ui_gtk_hbox_new(int spacing);
+
+GtkWidget* ui_subcontainer_create(
+ UiSubContainerType type,
+ UiObject *newobj,
+ int spacing,
+ int columnspacing,
+ int rowspacing,
+ int margin);
+
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame);
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+GtkWidget* ui_box_set_margin(GtkWidget *box, int margin);
+UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, UiSubContainerType type);
+
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type);
+void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing);
+UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid);
+void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame);
+void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_expander_container(UiObject *obj, GtkWidget *expander);
+void ui_expander_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow);
+void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview);
+void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill);
+void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+UiGtkTabView* ui_widget_get_tabview_data(UIWIDGET tabview);
+
+void ui_gtk_notebook_select_tab(GtkWidget *widget, int tab);
+
+#if GTK_CHECK_VERSION(3, 10, 0)
+UiContainer* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar);
+void ui_headerbar_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+#endif
+
+UiContainer* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar);
+void ui_headerbar_fallback_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CONTAINER_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "display.h"
+#include "container.h"
+#include "../common/context.h"
+#include "../common/object.h"
+#include "../ui/display.h"
+
+#include <cx/printf.h>
+
+static void set_alignment(GtkWidget *widget, float xalign, float yalign) {
+#if GTK_MAJOR_VERSION >= 4 || (GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16)
+ gtk_label_set_xalign(GTK_LABEL(widget), xalign);
+ gtk_label_set_yalign(GTK_LABEL(widget), yalign);
+#else
+ gtk_misc_set_alignment(GTK_MISC(widget), xalign, yalign);
+#endif
+}
+
+UIWIDGET ui_label_create(UiObject *obj, UiLabelArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ const char *css_class = NULL;
+ char *markup = NULL;
+ if(args.label) {
+ #if GTK_MAJOR_VERSION < 3
+ switch(args.style) {
+ case UI_LABEL_STYLE_DEFAULT: break;
+ case UI_LABEL_STYLE_TITLE: {
+ cxmutstr m = cx_asprintf("<b>%s</b>", args.label);
+ markup = m.ptr;
+ args.label = NULL;
+ }
+ case UI_LABEL_STYLE_SUBTITLE: {
+ break;
+ }
+ case UI_LABEL_STYLE_DIM: {
+ break;
+ }
+ }
+# else
+ switch(args.style) {
+ case UI_LABEL_STYLE_DEFAULT: break;
+ case UI_LABEL_STYLE_TITLE: {
+ css_class = "ui_label_title";
+ break;
+ }
+ case UI_LABEL_STYLE_SUBTITLE: {
+ css_class = "subtitle";
+ break;
+ }
+ case UI_LABEL_STYLE_DIM: {
+ css_class = "dim-label";
+ break;
+ }
+ }
+# endif
+ }
+
+
+ GtkWidget *widget = gtk_label_new(args.label);
+ if(markup) {
+ gtk_label_set_markup(GTK_LABEL(widget), markup);
+ free(markup);
+ }
+
+ if(css_class) {
+ WIDGET_ADD_CSS_CLASS(widget, css_class);
+ }
+
+ switch(args.align) {
+ case UI_ALIGN_DEFAULT: break;
+ case UI_ALIGN_LEFT: set_alignment(widget, 0, .5); break;
+ case UI_ALIGN_RIGHT: set_alignment(widget, 1, .5); break;
+ case UI_ALIGN_CENTER: break; // TODO
+ }
+
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+ if(var) {
+ UiString* value = (UiString*)var->value;
+ value->obj = widget;
+ value->get = ui_label_get;
+ value->set = ui_label_set;
+ }
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, widget, FALSE);
+
+ return widget;
+}
+
+UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args) {
+ args.align = UI_ALIGN_LEFT;
+ return ui_label_create(obj, args);
+}
+
+UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args) {
+ args.align = UI_ALIGN_RIGHT;
+ return ui_label_create(obj, args);
+}
+
+char* ui_label_get(UiString *s) {
+ if(s->value.ptr) {
+ s->value.free(s->value.ptr);
+ }
+ s->value.ptr = g_strdup(gtk_label_get_text(GTK_LABEL(s->obj)));
+ s->value.free = (ui_freefunc)g_free;
+ return s->value.ptr;
+}
+
+void ui_label_set(UiString *s, const char *value) {
+ gtk_label_set_text(GTK_LABEL(s->obj), value);
+ if(s->value.ptr) {
+ s->value.free(s->value.ptr);
+ s->value.ptr = NULL;
+ s->value.free = NULL;
+ }
+}
+
+UIWIDGET ui_space_deprecated(UiObject *obj) {
+ GtkWidget *widget = gtk_label_new("");
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(ct, widget, TRUE);
+
+ return widget;
+}
+
+UIWIDGET ui_separator_deprecated(UiObject *obj) {
+#if GTK_MAJOR_VERSION >= 3
+ GtkWidget *widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+#else
+ GtkWidget *widget = gtk_hseparator_new();
+#endif
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(ct, widget, FALSE);
+
+ return widget;
+}
+
+/* ------------------------- progress bar ------------------------- */
+
+typedef struct UiProgressBarRange {
+ double min;
+ double max;
+} UiProgressBarRange;
+
+UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ GtkWidget *progressbar = gtk_progress_bar_new();
+ if(args.max > args.min) {
+ UiProgressBarRange *range = malloc(sizeof(UiProgressBarRange));
+ range->min = args.min;
+ range->max = args.max;
+ g_signal_connect(
+ progressbar,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ range);
+ g_object_set_data(G_OBJECT(progressbar), "ui_range", range);
+ }
+
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE);
+ if(var && var->value) {
+ UiDouble *value = var->value;
+ value->get = ui_progressbar_get;
+ value->set = ui_progressbar_set;
+ value->obj = progressbar;
+ ui_progressbar_set(value, value->value);
+ }
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, progressbar, FALSE);
+
+ return progressbar;
+}
+
+double ui_progressbar_get(UiDouble *d) {
+ UiProgressBarRange *range = g_object_get_data(d->obj, "ui_range");
+ double fraction = gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(d->obj));
+ if(range) {
+ fraction = range->min + (range->max - range->min) * fraction;
+ }
+ d->value = fraction;
+ return d->value;
+}
+
+void ui_progressbar_set(UiDouble *d, double value) {
+ d->value = value;
+ UiProgressBarRange *range = g_object_get_data(d->obj, "ui_range");
+ if(range) {
+ value = (value - range->min) / (range->max - range->min);
+ }
+ gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(d->obj), value);
+}
+
+
+/* ------------------------- progress spinner ------------------------- */
+
+UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ GtkWidget *spinner = gtk_spinner_new();
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+ if(var && var->value) {
+ UiInteger *value = var->value;
+ value->get = ui_spinner_get;
+ value->set = ui_spinner_set;
+ value->obj = spinner;
+ ui_spinner_set(value, value->value);
+ }
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, spinner, FALSE);
+
+ return spinner;
+}
+
+int64_t ui_spinner_get(UiInteger *i) {
+ return i->value;
+}
+
+void ui_spinner_set(UiInteger *i, int64_t value) {
+ i->value = value;
+ if(i->obj) {
+ GtkSpinner *spinner = GTK_SPINNER(i->obj);
+ if(value != 0) {
+ gtk_spinner_start(spinner);
+ } else {
+ gtk_spinner_stop(spinner);
+ }
+ }
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LABEL_H
+#define LABEL_H
+
+#include "../ui/toolkit.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+char* ui_label_get(UiString *s);
+void ui_label_set(UiString *s, const char *value);
+
+UIWIDGET ui_progressbar_var(UiObject *obj, UiVar *var);
+double ui_progressbar_get(UiDouble *d);
+void ui_progressbar_set(UiDouble *d, double value);
+int64_t ui_spinner_get(UiInteger *i);
+void ui_spinner_set(UiInteger *i, int64_t value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LABEL_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dnd.h"
+#include <cx/buffer.h>
+#include <cx/array_list.h>
+
+#ifdef UI_GTK2LEGACY
+static gboolean selection_data_set_uris(GtkSelectionData *selection_data, char **uris) {
+ CxBuffer *buf = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+ char *uri;
+ int i = 0;
+ while((uri = uris[i]) != NULL) {
+ cxBufferPutString(buf, uri);
+ cxBufferPutString(buf, "\r\n");
+ }
+ GdkAtom type = gdk_atom_intern("text/uri-list", FALSE);
+ gtk_selection_data_set(selection_data, type, 8, (guchar*)buf->space, buf->pos);
+ cxBufferFree(buf);
+ return TRUE;
+}
+static char** selection_data_get_uris(GtkSelectionData *selection_data) {
+ // TODO: implement
+ return NULL;
+}
+#define gtk_selection_data_set_uris selection_data_set_uris
+#define gtk_selection_data_get_uris selection_data_get_uris
+#endif
+
+/*
+void ui_selection_settext(UiSelection *sel, char *str, int len) {
+ // TODO: handle error?
+ gtk_selection_data_set_text(sel->data, str, len);
+}
+
+void ui_selection_seturis(UiSelection *sel, char **uris, int nelm) {
+ char **uriarray = calloc(nelm+1, sizeof(char*));
+ for(int i=0;i<nelm;i++) {
+ uriarray[i] = uris[i];
+ }
+ uriarray[nelm] = NULL;
+ gtk_selection_data_set_uris(sel->data, uriarray);
+}
+
+char* ui_selection_gettext(UiSelection *sel) {
+ guchar *text = gtk_selection_data_get_text(sel->data);
+ if(text) {
+ char *textcp = strdup((char*)text);
+ g_free(text);
+ return textcp;
+ }
+ return NULL;
+}
+
+char** ui_selection_geturis(UiSelection *sel, size_t *nelm) {
+ gchar **uris = gtk_selection_data_get_uris(sel->data);
+ if(uris) {
+ size_t al = 32;
+ char **array = malloc(al * sizeof(char*));
+ size_t i = 0;
+ while(uris[i] != NULL) {
+ if(i >= al) {
+ al *= 2;
+ array = realloc(array, al * sizeof(char*));
+ }
+ array[i] = strdup((char*)uris[i]);
+ i++;
+ }
+ *nelm = i;
+ g_strfreev(uris);
+ return array;
+ }
+ return NULL;
+}
+*/
+
+#if GTK_MAJOR_VERSION >= 4
+
+void ui_selection_settext(UiDnD *sel, char *str, int len) {
+ if(!sel->providers) {
+ return;
+ }
+
+ if(len == -1) {
+ len = strlen(str);
+ }
+ GBytes *bytes = g_bytes_new(str, len);
+ GdkContentProvider *provider = gdk_content_provider_new_for_bytes("text/plain;charset=utf-8", bytes);
+ g_bytes_unref(bytes);
+
+ cxListAdd(sel->providers, &provider);
+}
+
+void ui_selection_seturis(UiDnD *sel, char **uris, int nelm) {
+ if(!sel->providers) {
+ return;
+ }
+
+ GFile **files = calloc(nelm, sizeof(GFile*));
+ for(int i=0;i<nelm;i++) {
+ GFile *file = uris[i][0] == '/' ? g_file_new_for_path(uris[i]) : g_file_new_for_uri(uris[i]);
+ files[i] = file;
+ }
+ GdkFileList *list = gdk_file_list_new_from_array(files, nelm);
+
+ GdkContentProvider *provider = gdk_content_provider_new_typed(GDK_TYPE_FILE_LIST, list);
+ cxListAdd(sel->providers, &provider);
+
+ g_slist_free_full ((GSList*)list, g_object_unref);
+ free(files);
+}
+
+char* ui_selection_gettext(UiDnD *sel) {
+ if(!sel->value) {
+ return NULL;
+ }
+
+ if(G_VALUE_HOLDS(sel->value, G_TYPE_STRING)) {
+ const char *str = g_value_get_string(sel->value);
+ return str ? strdup(str) : NULL;
+ }
+
+ return NULL;
+}
+
+UiFileList ui_selection_geturis(UiDnD *sel) {
+ if(!sel->value) {
+ return (UiFileList){NULL,0};
+ }
+
+ if(G_VALUE_HOLDS(sel->value, GDK_TYPE_FILE_LIST)) {
+ GSList *list = g_value_get_boxed(sel->value);
+ if(!list) {
+ return (UiFileList){NULL,0};
+ }
+ guint size = g_slist_length(list);
+
+ UiFileList flist;
+ flist.nfiles = size;
+ flist.files = calloc(size, sizeof(char*));
+ int i=0;
+ while(list) {
+ GFile *file = list->data;
+ char *uri = g_file_get_uri(file);
+ flist.files[i++] = strdup(uri);
+ g_free(uri);
+ list = list->next;
+ }
+ return flist;
+ }
+ return (UiFileList){NULL,0};
+}
+
+
+UiDnD* ui_create_dnd(void) {
+ UiDnD *dnd = malloc(sizeof(UiDnD));
+ memset(dnd, 0, sizeof(UiDnD));
+ dnd->providers = cxArrayListCreateSimple(sizeof(void*), 16);
+ dnd->selected_action = 0;
+ dnd->delete = FALSE;
+ return dnd;
+}
+
+void ui_dnd_free(UiDnD *dnd) {
+ cxListDestroy(dnd->providers);
+ free(dnd);
+}
+
+UiDnDAction ui_dnd_result(UiDnD *dnd) {
+ switch(dnd->selected_action) {
+ case 0: return UI_DND_ACTION_NONE;
+ case GDK_ACTION_COPY: return UI_DND_ACTION_COPY;
+ case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE;
+ case GDK_ACTION_LINK: return UI_DND_ACTION_LINK;
+ default: break;
+ }
+ return UI_DND_ACTION_CUSTOM;
+}
+
+#else
+
+void ui_selection_settext(UiDnD *sel, char *str, int len) {
+ gtk_selection_data_set_text(sel->data, str, len);
+}
+
+void ui_selection_seturis(UiDnD *sel, char **uris, int nelm) {
+ char **uriarray = calloc(nelm+1, sizeof(char*));
+ for(int i=0;i<nelm;i++) {
+ uriarray[i] = uris[i];
+ }
+ uriarray[nelm] = NULL;
+ gtk_selection_data_set_uris(sel->data, uriarray);
+ free(uriarray);
+}
+
+char* ui_selection_gettext(UiDnD *sel) {
+ if(!sel->data) {
+ return NULL;
+ }
+
+ guchar *text = gtk_selection_data_get_text(sel->data);
+ if(text) {
+ char *textcp = strdup((char*)text);
+ g_free(text);
+ return textcp;
+ }
+ return NULL;
+}
+
+UiFileList ui_selection_geturis(UiDnD *sel) {
+ if(!sel->data) {
+ return (UiFileList){NULL,0};
+ }
+
+ gchar **uris = gtk_selection_data_get_uris(sel->data);
+ if(uris) {
+ size_t al = 32;
+ char **array = malloc(al * sizeof(char*));
+ size_t i = 0;
+ while(uris[i] != NULL) {
+ if(i >= al) {
+ al *= 2;
+ array = realloc(array, al * sizeof(char*));
+ }
+ array[i] = strdup((char*)uris[i]);
+ i++;
+ }
+ g_strfreev(uris);
+ return (UiFileList){array,i};
+ }
+
+ return (UiFileList){NULL,0};
+}
+
+UiDnDAction ui_dnd_result(UiDnD *dnd) {
+ switch(dnd->selected_action) {
+ case 0: return UI_DND_ACTION_NONE;
+ case GDK_ACTION_COPY: return UI_DND_ACTION_COPY;
+ case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE;
+ case GDK_ACTION_LINK: return UI_DND_ACTION_LINK;
+ default: break;
+ }
+ return UI_DND_ACTION_CUSTOM;
+}
+
+
+UiDnD* ui_create_dnd(void) {
+ UiDnD *dnd = malloc(sizeof(UiDnD));
+ memset(dnd, 0, sizeof(UiDnD));
+ return dnd;
+}
+
+void ui_dnd_free(UiDnD *dnd) {
+ free(dnd);
+}
+
+#endif
+
+UiBool ui_dnd_need_delete(UiDnD *dnd) {
+ return dnd->delete;
+}
+
+void ui_dnd_accept(UiDnD *dnd, UiBool accept) {
+ dnd->accept = accept;
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DND_H
+#define DND_H
+
+#include "../ui/dnd.h"
+#include "toolkit.h"
+
+#include <cx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if GTK_MAJOR_VERSION >= 4
+
+struct UiDnD {
+ GtkDropTarget *target;
+ const GValue *value;
+ CxList *providers;
+ GdkDragAction selected_action;
+ gboolean delete;
+ gboolean accept;
+};
+
+#else
+
+struct UiDnD {
+ GdkDragContext *context;
+ GtkSelectionData *data;
+ GdkDragAction selected_action;
+ gboolean delete;
+ gboolean accept;
+};
+
+#endif
+
+UiDnD* ui_create_dnd(void);
+void ui_dnd_free(UiDnD *dnd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DND_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "container.h"
+
+#include "draw_cairo.h"
+
+
+#if GTK_MAJOR_VERSION >= 3
+static void ui_drawingarea_draw(
+ GtkDrawingArea *area,
+ cairo_t *cr,
+ int width,
+ int height,
+ gpointer data)
+{
+ UiCairoGraphics g;
+ g.g.width = width;
+ g.g.height = height;
+ g.widget = GTK_WIDGET(area);
+ g.cr = cr;
+
+ UiDrawEvent *event = data;
+ UiEvent ev;
+ ev.obj = event->obj;
+ ev.window = event->obj->window;
+ ev.document = event->obj->ctx->document;
+
+ event->callback(&ev, &g.g, event->userdata);
+}
+#endif
+
+#if UI_GTK3
+gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) {
+ int width = gtk_widget_get_allocated_width(w);
+ int height = gtk_widget_get_allocated_height(w);
+ ui_drawingarea_draw(GTK_DRAWING_AREA(w), cr, width, height, data);
+ return FALSE;
+}
+#endif
+#ifdef UI_GTK2
+gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data) {
+ UiCairoGraphics g;
+ g.g.width = w->allocation.width;
+ g.g.height = w->allocation.height;
+ g.widget = w;
+ g.cr = gdk_cairo_create(w->window);
+
+ UiDrawEvent *event = data;
+ UiEvent ev;
+ ev.obj = event->obj;
+ ev.window = event->obj->window;
+ ev.document = event->obj->ctx->document;
+
+ event->callback(&ev, &g.g, event->userdata);
+
+ return FALSE;
+}
+#endif
+
+// function from graphics.h
+
+void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) {
+#if GTK_MAJOR_VERSION >= 4
+ gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(widget), ui_drawingarea_draw, event, NULL);
+#elif GTK_MAJOR_VERSION == 3
+ g_signal_connect(G_OBJECT(widget),
+ "draw",
+ G_CALLBACK(ui_drawingarea_expose),
+ event);
+#else
+ g_signal_connect(G_OBJECT(widget),
+ "expose_event",
+ G_CALLBACK(ui_canvas_expose),
+ event);
+#endif
+}
+
+
+PangoContext *ui_get_pango_context(UiGraphics *g) {
+ UiCairoGraphics *gr = (UiCairoGraphics*)g;
+ //return gtk_widget_get_pango_context(gr->widget);
+ return pango_cairo_create_context(gr->cr);
+}
+
+
+// drawing functions
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
+ UiCairoGraphics *gr = (UiCairoGraphics*)g;
+ double dred = (double)red / (double)255;
+ double dgreen = (double)green / (double)255;
+ double dblue = (double)blue / (double)255;
+ cairo_set_source_rgb(gr->cr, dred, dgreen, dblue);
+}
+
+
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
+ UiCairoGraphics *gr = (UiCairoGraphics*)g;
+ cairo_set_line_width(gr->cr, 1);
+ cairo_move_to(gr->cr, (double)x1 + 0.5, (double)y1 + 0.5);
+ cairo_line_to(gr->cr, (double)x2 + 0.5, (double)y2 + 0.5);
+ cairo_stroke(gr->cr);
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+ UiCairoGraphics *gr = (UiCairoGraphics*)g;
+ cairo_set_line_width(gr->cr, 1);
+ cairo_rectangle(gr->cr, x + 0.5, y + 0.5 , w, h);
+ if(fill) {
+ cairo_fill(gr->cr);
+ } else {
+ cairo_stroke(gr->cr);
+ }
+}
+
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
+ UiCairoGraphics *gr = (UiCairoGraphics*)g;
+ cairo_move_to(gr->cr, x, y);
+ pango_cairo_show_layout(gr->cr, text->layout);
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DRAW_CAIRO_H
+#define DRAW_CAIRO_H
+
+#include "graphics.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiCairoGraphics {
+ UiGraphics g;
+ GtkWidget *widget;
+ cairo_t *cr;
+} UiCairoGraphics;
+
+// ui_canvas_expose
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DRAW_CAIRO_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "container.h"
+
+#include "draw_gdk.h"
+
+
+gboolean ui_drawingarea_expose(GtkWidget *w, GdkEventExpose *e, void *data) {
+ UiGdkGraphics g;
+ g.g.width = w->allocation.width;
+ g.g.height = w->allocation.height;
+ g.widget = w;
+ g.gc = gdk_gc_new(w->window);
+
+ UiDrawEvent *event = data;
+ UiEvent ev;
+ ev.obj = event->obj;
+ ev.window = event->obj->window;
+ ev.document = event->obj->ctx->document;
+
+ event->callback(&ev, &g.g, event->userdata);
+
+ return FALSE;
+}
+
+// function from graphics.h
+
+void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) {
+ g_signal_connect(G_OBJECT(widget),
+ "expose_event",
+ G_CALLBACK(ui_drawingarea_expose),
+ event);
+}
+
+PangoContext *ui_get_pango_context(UiGraphics *g) {
+ UiGdkGraphics *gr = (UiGdkGraphics*)g;
+ return gtk_widget_get_pango_context(gr->widget);
+}
+
+// drawing functions
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
+ UiGdkGraphics *gr = (UiGdkGraphics*)g;
+ GdkColor color;
+ color.red = red * 257;
+ color.green = green * 257;
+ color.blue = blue * 257;
+ gdk_gc_set_rgb_fg_color(gr->gc, &color);
+ //gdk_gc_set_rgb_bg_color(g->gc, &color);
+}
+
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
+ UiGdkGraphics *gr = (UiGdkGraphics*)g;
+ gdk_draw_line(gr->widget->window, gr->gc, x1, y1, x2, y2);
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+ UiGdkGraphics *gr = (UiGdkGraphics*)g;
+ gdk_draw_rectangle(gr->widget->window, gr->gc, fill, x, y, w, h);
+}
+
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
+ UiGdkGraphics *gr = (UiGdkGraphics*)g;
+ gdk_draw_layout(gr->widget->window, gr->gc, x, y, text->layout);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DRAW_GDK_H
+#define DRAW_GDK_H
+
+#include "graphics.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiGdkGraphics {
+ UiGraphics g;
+ GtkWidget *widget;
+ GdkGC *gc;
+} UiGdkGraphics;
+
+gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DRAW_GDK_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../common/context.h"
+#include "../common/object.h"
+#include "container.h"
+#include "entry.h"
+
+
+UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs args) {
+ double min = 0;
+ double max = 1000;
+
+ UiObject* current = uic_current_obj(obj);
+
+ UiVar *var = NULL;
+ if(args.varname) {
+ var = uic_get_var(obj->ctx, args.varname);
+ }
+
+ if(!var) {
+ if(args.intvalue) {
+ var = uic_widget_var(obj->ctx, current->ctx, args.intvalue, NULL, UI_VAR_INTEGER);
+ } else if(args.doublevalue) {
+ var = uic_widget_var(obj->ctx, current->ctx, args.doublevalue, NULL, UI_VAR_DOUBLE);
+ } else if(args.rangevalue) {
+ var = uic_widget_var(obj->ctx, current->ctx, args.rangevalue, NULL, UI_VAR_RANGE);
+ }
+ }
+
+ if(var && var->type == UI_VAR_RANGE) {
+ UiRange *r = var->value;
+ min = r->min;
+ max = r->max;
+ }
+ if(args.step == 0) {
+ args.step = 1;
+ }
+#ifdef UI_GTK2LEGACY
+ if(min == max) {
+ max = min + 1;
+ }
+#endif
+ GtkWidget *spin = gtk_spin_button_new_with_range(min, max, args.step);
+ ui_set_name_and_style(spin, args.name, args.style_class);
+ ui_set_widget_groups(obj->ctx, spin, args.groups);
+ gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), args.digits);
+ UiObserver **obs = NULL;
+ if(var) {
+ double value = 0;
+ switch(var->type) {
+ default: break;
+ case UI_VAR_INTEGER: {
+ UiInteger *i = var->value;
+ i->get = ui_spinbutton_getint;
+ i->set = ui_spinbutton_setint;
+ i->obj = spin;
+ value = (double)i->value;
+ obs = &i->observers;
+ break;
+ }
+ case UI_VAR_DOUBLE: {
+ UiDouble *d = var->value;
+ d->get = ui_spinbutton_getdouble;
+ d->set = ui_spinbutton_setdouble;
+ d->obj = spin;
+ value = d->value;
+ obs = &d->observers;
+ break;
+ }
+ case UI_VAR_RANGE: {
+ UiRange *r = var->value;
+ r->get = ui_spinbutton_getrangeval;
+ r->set = ui_spinbutton_setrangeval;
+ r->setrange = ui_spinbutton_setrange;
+ r->setextent = ui_spinbutton_setextent;
+ r->obj = spin;
+ value = r->value;
+ obs = &r->observers;
+ break;
+ }
+ }
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
+ }
+
+ UiVarEventData *event = malloc(sizeof(UiVarEventData));
+ event->obj = obj;
+ event->var = var;
+ event->observers = obs;
+ event->callback = args.onchange;
+ event->userdata = args.onchangedata;
+
+ g_signal_connect(
+ spin,
+ "value-changed",
+ G_CALLBACK(ui_spinner_changed),
+ event);
+ g_signal_connect(
+ spin,
+ "destroy",
+ G_CALLBACK(ui_destroy_vardata),
+ event);
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, spin, FALSE);
+
+ return spin;
+}
+
+void ui_spinner_setrange(UIWIDGET spinner, double min, double max) {
+ gtk_spin_button_set_range(GTK_SPIN_BUTTON(spinner), min, max);
+}
+
+void ui_spinner_setdigits(UIWIDGET spinner, int digits) {
+ gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spinner), digits);
+}
+
+
+void ui_spinner_changed(GtkSpinButton *spinner, UiVarEventData *event) {
+ gdouble value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spinner));
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = &value;
+ e.intval = (int64_t)value;
+
+ if(event->callback) {
+ event->callback(&e, event->userdata);
+ }
+
+ if(event->observers) {
+ UiObserver *observer = *event->observers;
+ ui_notify_evt(observer, &e);
+ }
+}
+
+
+int64_t ui_spinbutton_getint(UiInteger *i) {
+ i->value = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(i->obj));
+ return i->value;
+}
+
+void ui_spinbutton_setint(UiInteger *i, int64_t val) {
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(i->obj), (double)val);
+ i->value = val;
+}
+
+double ui_spinbutton_getdouble(UiDouble *d) {
+ d->value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->obj));
+ return d->value;
+}
+
+void ui_spinbutton_setdouble(UiDouble *d, double val) {
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->obj), val);
+ d->value = val;
+}
+
+double ui_spinbutton_getrangeval(UiRange *r) {
+ r->value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(r->obj));
+ return r->value;
+}
+
+void ui_spinbutton_setrangeval(UiRange *r, double val) {
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(r->obj), val);
+ r->value = val;
+}
+void ui_spinbutton_setrange(UiRange *r, double min, double max) {
+ gtk_spin_button_set_range(GTK_SPIN_BUTTON(r->obj), min, max);
+ r->min = min;
+ r->max = max;
+}
+
+void ui_spinbutton_setextent(UiRange *r, double extent) {
+ gtk_spin_button_set_increments(GTK_SPIN_BUTTON(r->obj), extent, extent*10);
+ r->extent = extent;
+}
--- /dev/null
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+/*
+ * File: entry.h
+ * Author: olaf
+ *
+ * Created on 11. November 2017, 13:38
+ */
+
+#ifndef ENTRY_H
+#define ENTRY_H
+
+#include "toolkit.h"
+#include "../ui/entry.h"
+#include "../common/context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void ui_spinner_changed(GtkSpinButton *spinner, UiVarEventData *event);
+
+int64_t ui_spinbutton_getint(UiInteger *i);
+void ui_spinbutton_setint(UiInteger *i, int64_t val);
+
+double ui_spinbutton_getdouble(UiDouble *d);
+void ui_spinbutton_setdouble(UiDouble *d, double val);
+
+double ui_spinbutton_getrangeval(UiRange *r);
+void ui_spinbutton_setrangeval(UiRange *r, double val);
+void ui_spinbutton_setrange(UiRange *r, double min, double max);
+void ui_spinbutton_setextent(UiRange *r, double extent);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ENTRY_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "graphics.h"
+#include "container.h"
+#include "../common/object.h"
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
+ GtkWidget *widget = gtk_drawing_area_new();
+
+ if(f) {
+ UiDrawEvent *event = malloc(sizeof(UiDrawEvent));
+ event->obj = obj;
+ event->callback = f;
+ event->userdata = userdata;
+ ui_connect_draw_handler(widget, event);
+ }
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(ct, widget, TRUE);
+
+ return widget;
+}
+
+
+#if GTK_MAJOR_VERSION <= 3
+static gboolean widget_button_pressed(
+ GtkWidget *widget,
+ GdkEvent *event,
+ gpointer userdata)
+{
+ UiEventData *eventdata = userdata;
+
+ UiMouseEvent me;
+ me.x = (int)event->button.x;
+ me.y = (int)event->button.y;
+
+ int exec = 0;
+ if(event->button.type == GDK_BUTTON_PRESS) {
+ exec = 1;
+ me.type = UI_PRESS;
+ } else if(event->button.type == GDK_2BUTTON_PRESS) {
+ exec = 1;
+ me.type = UI_PRESS2;
+ }
+
+ if(exec) {
+ UiEvent e;
+ e.obj = eventdata->obj;
+ e.window = eventdata->obj->window;
+ e.document = eventdata->obj->ctx->document;
+ e.eventdata = &me;
+ e.intval = 0;
+ eventdata->callback(&e, eventdata->userdata);
+ }
+ return TRUE;
+}
+#endif
+
+void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
+#if GTK_MAJOR_VERSION >= 4
+ *width = gtk_widget_get_width(drawingarea);
+ *height = gtk_widget_get_height(drawingarea);
+#elif GTK_MAJOR_VERSION == 3
+ *width = gtk_widget_get_allocated_width(drawingarea);
+ *height = gtk_widget_get_allocated_height(drawingarea);
+#else
+ *width = drawingarea->allocation.width;
+ *height = drawingarea->allocation.height;
+#endif
+}
+
+void ui_drawingarea_redraw(UIWIDGET drawingarea) {
+ gtk_widget_queue_draw(drawingarea);
+}
+
+void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) {
+#if GTK_MAJOR_VERSION >= 4
+ // TODO
+#else
+ gtk_widget_set_events(widget, GDK_BUTTON_PRESS_MASK);
+ if(f) {
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->callback = f;
+ event->userdata = u;
+ event->customdata = NULL;
+ event->value = 0;
+
+ g_signal_connect(G_OBJECT(widget),
+ "button-press-event",
+ G_CALLBACK(widget_button_pressed),
+ event);
+ } else {
+ // TODO: warning
+ }
+#endif
+}
+
+
+// text layout
+UiTextLayout* ui_text(UiGraphics *g) {
+ UiTextLayout *layout = malloc(sizeof(UiTextLayout));
+ PangoContext *pc = ui_get_pango_context(g);
+ layout->layout = pango_layout_new(pc);
+ return layout;
+}
+
+void ui_text_setstring(UiTextLayout *layout, char *str) {
+ pango_layout_set_text(layout->layout, str, -1);
+}
+
+void ui_text_setstringl(UiTextLayout *layout, char *str, int len) {
+ pango_layout_set_text(layout->layout, str, len);
+}
+
+void ui_text_setfont(UiTextLayout *layout, char *font, int size) {
+ PangoFontDescription *fontDesc;
+ fontDesc = pango_font_description_from_string(font);
+ pango_font_description_set_size(fontDesc, size * PANGO_SCALE);
+ pango_layout_set_font_description(layout->layout, fontDesc);
+ pango_font_description_free(fontDesc);
+}
+
+void ui_text_getsize(UiTextLayout *layout, int *width, int *height) {
+ pango_layout_get_size(layout->layout, width, height);
+ *width = *width / PANGO_SCALE;
+ *height = *height / PANGO_SCALE;
+}
+
+void ui_text_setwidth(UiTextLayout *layout, int width) {
+ pango_layout_set_width(layout->layout, width * PANGO_SCALE);
+ pango_layout_set_ellipsize(layout->layout, PANGO_ELLIPSIZE_END);
+ //pango_layout_set_wrap(layout->layout, PANGO_WRAP_WORD_CHAR);
+}
+
+void ui_text_free(UiTextLayout *text) {
+ g_object_unref(text->layout);
+ free(text);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DRAWINGAREA_H
+#define DRAWINGAREA_H
+
+#include "../ui/graphics.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiDrawEvent {
+ ui_drawfunc callback;
+ UiObject *obj;
+ void *userdata;
+} UiDrawEvent;
+
+struct UiTextLayout {
+ PangoLayout *layout;
+};
+
+// implemented in draw_*.h
+void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event);
+PangoContext *ui_get_pango_context(UiGraphics *g);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DRAWINGAREA_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 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)
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HEADERBAR_H
+#define HEADERBAR_H
+
+#include "toolkit.h"
+#include "../ui/toolbar.h"
+#include "../common/toolbar.h"
+#include <cx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if GTK_CHECK_VERSION(3, 10, 0)
+
+#ifdef UI_LIBADWAITA
+#define UI_HEADERBAR AdwHeaderBar*
+#define UI_HEADERBAR_CAST(h) ADW_HEADER_BAR(h)
+#define UI_HEADERBAR_PACK_START(h, w) adw_header_bar_pack_start(ADW_HEADER_BAR(h), w)
+#define UI_HEADERBAR_PACK_END(h, w) adw_header_bar_pack_end(ADW_HEADER_BAR(h), w)
+#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) adw_header_bar_set_title_widget(ADW_HEADER_BAR(h), w)
+#else
+#define UI_HEADERBAR GtkHeaderBar*
+#define UI_HEADERBAR_CAST(h) GTK_HEADER_BAR(h)
+#define UI_HEADERBAR_PACK_START(h, w) gtk_header_bar_pack_start(GTK_HEADER_BAR(h), w)
+#define UI_HEADERBAR_PACK_END(h, w) gtk_header_bar_pack_end(GTK_HEADER_BAR(h), w)
+#if GTK_MAJOR_VERSION >= 4
+#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_title_widget(GTK_HEADER_BAR(h), w)
+#else
+#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_custom_title(GTK_HEADER_BAR(h), w)
+#endif
+#endif
+
+void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar);
+
+void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos);
+
+void ui_add_headerbar_item(
+ GtkWidget *headerbar,
+ GtkWidget *box,
+ UiToolbarItem *item,
+ UiObject *obj,
+ enum UiToolbarPos pos);
+
+void ui_add_headerbar_toggleitem(
+ GtkWidget *headerbar,
+ GtkWidget *box,
+ UiToolbarToggleItem *item,
+ UiObject *obj,
+ enum UiToolbarPos pos);
+
+void ui_add_headerbar_menu(
+ GtkWidget *headerbar,
+ GtkWidget *box,
+ UiToolbarMenuItem *item,
+ UiObject *obj,
+ enum UiToolbarPos pos);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* HEADERBAR_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cx/map.h>
+
+#include "toolkit.h"
+#include "icon.h"
+#include "../common/properties.h"
+
+static CxMap *image_map;
+
+static GtkIconTheme *icon_theme;
+
+#if GTK_MAJOR_VERSION >= 4
+#define ICONTHEME_GET_DEFAULT() gtk_icon_theme_get_for_display(gdk_display_get_default())
+#else
+#define ICONTHEME_GET_DEFAULT() gtk_icon_theme_get_default()
+#endif
+
+void ui_image_init(void) {
+ image_map = cxHashMapCreateSimple(CX_STORE_POINTERS);
+
+ icon_theme = ICONTHEME_GET_DEFAULT();
+}
+
+// **** deprecated functions ****
+
+GdkPixbuf* ui_get_image(const char *name) {
+ UiImage *img = cxMapGet(image_map, name);
+ if(img) {
+ return img->pixbuf;
+ } else {
+ //ui_add_image(name, name);
+ //return ucx_map_cstr_get(image_map, name);
+ // TODO
+ return NULL;
+ }
+}
+
+// **** deprecated2****
+
+static UiIcon* get_icon(const char *name, int size, int scale) {
+#if GTK_MAJOR_VERSION >= 4
+ GtkIconPaintable *info = gtk_icon_theme_lookup_icon(icon_theme, name, NULL, size, scale, GTK_TEXT_DIR_LTR, GTK_ICON_LOOKUP_FORCE_REGULAR);
+#elif defined(UI_SUPPORTS_SCALE)
+ GtkIconInfo *info = gtk_icon_theme_lookup_icon_for_scale(icon_theme, name, size, scale, 0);
+#else
+ GtkIconInfo *info = gtk_icon_theme_lookup_icon(icon_theme, name, size, 0);
+#endif
+ if(info) {
+ UiIcon *icon = malloc(sizeof(UiIcon));
+ icon->info = info;
+ icon->pixbuf = NULL;
+ return icon;
+ }
+ return NULL;
+}
+
+UiIcon* ui_icon(const char* name, size_t size) {
+ return get_icon(name, size, ui_get_scalefactor());
+}
+
+UiIcon* ui_imageicon(const char* file) {
+ GError *error = NULL;
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(file, &error);
+ if(!pixbuf) {
+ fprintf(stderr, "UiError: Cannot load image: %s\n", file);
+ return NULL;
+ }
+
+ UiIcon *icon = malloc(sizeof(UiIcon));
+ icon->info = NULL;
+ icon->pixbuf = pixbuf;
+ return icon;
+}
+
+void ui_icon_free(UiIcon* icon) {
+ if(icon->info) {
+ g_object_unref(icon->info);
+ }
+ if(icon->pixbuf) {
+ g_object_unref(icon->pixbuf);
+ }
+ free(icon);
+}
+
+UiIcon* ui_foldericon(size_t size) {
+ return ui_icon("folder", size);
+}
+
+UiIcon* ui_fileicon(size_t size) {
+ UiIcon *icon = ui_icon("file", size);
+#if GTK_MAJOR_VERSION >= 4
+ GFile *file = gtk_icon_paintable_get_file(icon->info);
+ char *path = g_file_get_path(file);
+ if(!path) {
+ icon = ui_icon("application-x-generic", size);
+ }
+#else
+ if(!icon) {
+ icon = ui_icon("application-x-generic", size);
+ }
+#endif
+ return icon;
+}
+
+UiIcon* ui_icon_unscaled(const char *name, int size) {
+ return get_icon(name, size, 1);
+}
+
+#if GTK_MAJOR_VERSION >= 4
+GdkPixbuf* ui_icon_pixbuf(UiIcon *icon) {
+ if(!icon->pixbuf) {
+ GFile *file = gtk_icon_paintable_get_file(icon->info);
+ GError *error = NULL;
+ char *path = g_file_get_path(file);
+ icon->pixbuf = gdk_pixbuf_new_from_file(path, &error);
+ }
+ return icon->pixbuf;
+}
+#else
+GdkPixbuf* ui_icon_pixbuf(UiIcon *icon) {
+ if(!icon->pixbuf) {
+ GError *error = NULL;
+ icon->pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+ }
+ return icon->pixbuf;
+}
+#endif
+
+/*
+UiImage* ui_icon_image(UiIcon *icon) {
+ GError *error = NULL;
+ GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+ if(pixbuf) {
+ UiImage *img = malloc(sizeof(UiImage));
+ img->pixbuf = pixbuf;
+ return img;
+ }
+ return NULL;
+}
+*/
+
+/*
+UiImage* ui_image(const char *filename) {
+ return ui_named_image(filename, NULL);
+}
+
+UiImage* ui_named_image(const char *filename, const char *name) {
+ char *path = uic_get_image_path(filename);
+ if(!path) {
+ fprintf(stderr, "UiError: pixmaps directory not set\n");
+ return NULL;
+ }
+ UiImage *img = ui_load_image_from_path(path, name);
+ free(path);
+ return img;
+}
+
+UiImage* ui_load_image_from_path(const char *path, const char *name) {
+ GError *error = NULL;
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error);
+ if(!pixbuf) {
+ fprintf(stderr, "UiError: Cannot load image: %s\n", path);
+ return NULL;
+ }
+
+ UiImage *img = malloc(sizeof(UiImage));
+ img->pixbuf = pixbuf;
+ if(name) {
+ cxMapPut(image_map, name, img);
+ }
+ return img;
+}
+*/
+
+void ui_free_image(UiImage *img) {
+ g_object_unref(img->pixbuf);
+ free(img);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ICON_H
+#define ICON_H
+
+#include "../ui/icons.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 10
+#define UI_SUPPORTS_SCALE
+#endif
+
+
+struct UiIcon {
+#if GTK_MAJOR_VERSION >= 4
+ GtkIconPaintable *info;
+#else
+ GtkIconInfo *info;
+#endif
+ GdkPixbuf *pixbuf;
+};
+
+struct UiImage {
+ GdkPixbuf *pixbuf;
+};
+
+void ui_image_init(void);
+
+GdkPixbuf* ui_get_image(const char *name);
+
+GdkPixbuf* ui_icon_pixbuf(UiIcon *icon);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ICON_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "image.h"
+
+#include "container.h"
+#include "menu.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) {
+ UiObject *current = uic_current_obj(obj);
+
+ GtkWidget *scrolledwindow = SCROLLEDWINDOW_NEW();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *image = gtk_picture_new();
+#else
+ GtkWidget *image = gtk_image_new();
+#endif
+
+ ui_set_name_and_style(image, args.name, args.style_class);
+
+#if GTK_MAJOR_VERSION < 4
+ GtkWidget *eventbox = gtk_event_box_new();
+ SCROLLEDWINDOW_SET_CHILD(scrolledwindow, eventbox);
+ gtk_container_add(GTK_CONTAINER(eventbox), image);
+#else
+ SCROLLEDWINDOW_SET_CHILD(scrolledwindow, image);
+ GtkWidget *eventbox = image;
+#endif
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, scrolledwindow, TRUE);
+
+ UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_GENERIC);
+ if(var) {
+ UiGeneric *value = var->value;
+ value->get = ui_imageviewer_get;
+ value->get_type = ui_imageviewer_get_type;
+ value->set = ui_imageviewer_set;
+ value->obj = image;
+ if(value->value && value->type && !strcmp(value->type, UI_IMAGE_OBJECT_TYPE)) {
+ GdkPixbuf *pixbuf = value->value;
+ value->value = NULL;
+ ui_imageviewer_set(value, pixbuf, UI_IMAGE_OBJECT_TYPE);
+ }
+ }
+
+ if(args.contextmenu) {
+ UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, eventbox);
+ ui_widget_set_contextmenu(eventbox, menu);
+ }
+
+ return scrolledwindow;
+}
+
+void* ui_imageviewer_get(UiGeneric *g) {
+ return g->value;
+}
+
+const char* ui_imageviewer_get_type(UiGeneric *g) {
+
+}
+
+int ui_imageviewer_set(UiGeneric *g, void *value, const char *type) {
+ if(!type || strcmp(type, UI_IMAGE_OBJECT_TYPE)) {
+ return 1;
+ }
+
+ // TODO: do we need to free the previous value here?
+
+ g->value = value;
+ g->type = type;
+ GdkPixbuf *pixbuf = value;
+
+ if(pixbuf) {
+ int width = gdk_pixbuf_get_width(pixbuf);
+ int height = gdk_pixbuf_get_height(pixbuf);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GdkTexture *texture = gdk_texture_new_for_pixbuf(pixbuf);
+ gtk_picture_set_paintable(GTK_PICTURE(g->obj), GDK_PAINTABLE(texture));
+#else
+ gtk_image_set_from_pixbuf(GTK_IMAGE(g->obj), pixbuf);
+#endif
+ gtk_widget_set_size_request(g->obj, width, height);
+ }
+
+
+ return 0;
+}
+
+
+
+int ui_image_load_file(UiGeneric *obj, const char *path) {
+ GError *error = NULL;
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error);
+ if(!pixbuf) {
+ return 1;
+ }
+
+ if(obj->set) {
+ obj->set(obj, pixbuf, UI_IMAGE_OBJECT_TYPE);
+ } else {
+ obj->value = pixbuf;
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef IMAGE_H
+#define IMAGE_H
+
+#include "../ui/image.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+void* ui_imageviewer_get(UiGeneric *g);
+const char* ui_imageviewer_get_type(UiGeneric *g);
+int ui_imageviewer_set(UiGeneric *g, void *value, const char *type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IMAGE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "../common/context.h"
+#include "../common/object.h"
+#include "container.h"
+
+#include <cx/array_list.h>
+
+#include "list.h"
+#include "icon.h"
+#include "menu.h"
+#include "dnd.h"
+
+
+void* ui_strmodel_getvalue(void *elm, int column) {
+ return column == 0 ? elm : NULL;
+}
+
+static GtkListStore* create_list_store(UiList *list, UiModel *model) {
+ int columns = model->columns;
+ GType types[2*columns];
+ int c = 0;
+ for(int i=0;i<columns;i++,c++) {
+ switch(model->types[i]) {
+ case UI_STRING:
+ case UI_STRING_FREE: types[c] = G_TYPE_STRING; break;
+ case UI_INTEGER: types[c] = G_TYPE_INT; break;
+ case UI_ICON: types[c] = G_TYPE_OBJECT; break;
+ case UI_ICON_TEXT:
+ case UI_ICON_TEXT_FREE: {
+ types[c] = G_TYPE_OBJECT;
+ types[++c] = G_TYPE_STRING;
+ }
+ }
+ }
+
+ GtkListStore *store = gtk_list_store_newv(c, types);
+
+ if(list) {
+ void *elm = list->first(list);
+ while(elm) {
+ // insert new row
+ GtkTreeIter iter;
+ gtk_list_store_insert (store, &iter, -1);
+
+ // set column values
+ int c = 0;
+ for(int i=0;i<columns;i++,c++) {
+ void *data = model->getvalue(elm, c);
+
+ GValue value = G_VALUE_INIT;
+ switch(model->types[i]) {
+ case UI_STRING:
+ case UI_STRING_FREE: {
+ g_value_init(&value, G_TYPE_STRING);
+ g_value_set_string(&value, data);
+ if(model->types[i] == UI_STRING_FREE) {
+ free(data);
+ }
+ break;
+ }
+ case UI_INTEGER: {
+ g_value_init(&value, G_TYPE_INT);
+ int *intptr = data;
+ g_value_set_int(&value, *intptr);
+ break;
+ }
+ case UI_ICON: {
+ g_value_init(&value, G_TYPE_OBJECT);
+ UiIcon *icon = data;
+#if GTK_MAJOR_VERSION >= 4
+ g_value_set_object(&value, icon->info); // TODO: does this work?
+#else
+ if(!icon->pixbuf && icon->info) {
+ GError *error = NULL;
+ GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+ icon->pixbuf = pixbuf;
+ }
+
+ if(icon->pixbuf) {
+ g_value_set_object(&value, icon->pixbuf);
+ }
+#endif
+ break;
+ }
+ case UI_ICON_TEXT:
+ case UI_ICON_TEXT_FREE: {
+ UiIcon *icon = data;
+#if GTK_MAJOR_VERSION >= 4
+ if(icon) {
+ GValue iconvalue = G_VALUE_INIT;
+ g_value_init(&iconvalue, G_TYPE_OBJECT);
+ g_value_set_object(&iconvalue, ui_icon_pixbuf(icon));
+ gtk_list_store_set_value(store, &iter, c, &iconvalue);
+ }
+#else
+ GValue pixbufvalue = G_VALUE_INIT;
+ if(icon) {
+ if(!icon->pixbuf && icon->info) {
+ GError *error = NULL;
+ GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
+ icon->pixbuf = pixbuf;
+ }
+ g_value_init(&pixbufvalue, G_TYPE_OBJECT);
+ g_value_set_object(&pixbufvalue, icon->pixbuf);
+ gtk_list_store_set_value(store, &iter, c, &pixbufvalue);
+ }
+#endif
+ c++;
+
+ char *str = model->getvalue(elm, c);
+ g_value_init(&value, G_TYPE_STRING);
+ g_value_set_string(&value, str);
+ if(model->types[i] == UI_ICON_TEXT_FREE) {
+ free(str);
+ }
+ break;
+ }
+ }
+
+ gtk_list_store_set_value(store, &iter, c, &value);
+ }
+
+ // next row
+ elm = list->next(list);
+ }
+ }
+
+ return store;
+}
+
+
+UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ // create treeview
+ GtkWidget *view = gtk_tree_view_new();
+ ui_set_name_and_style(view, args.name, args.style_class);
+ ui_set_widget_groups(obj->ctx, view, args.groups);
+ GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+ GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
+
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
+#ifdef UI_GTK3
+#if GTK_MINOR_VERSION >= 8
+ //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE);
+#else
+ // TODO: implement for older gtk3
+#endif
+#else
+ // TODO: implement for gtk2
+#endif
+
+ UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+ model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+
+ UiList *list = var ? var->value : NULL;
+ GtkListStore *listmodel = create_list_store(list, model);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+ g_object_unref(listmodel);
+
+ UiListView *listview = malloc(sizeof(UiListView));
+ listview->obj = obj;
+ listview->widget = view;
+ listview->var = var;
+ listview->model = model;
+ g_signal_connect(
+ view,
+ "destroy",
+ G_CALLBACK(ui_listview_destroy),
+ listview);
+
+ // bind var
+ list->update = ui_listview_update;
+ list->getselection = ui_listview_getselection;
+ list->setselection = ui_listview_setselection;
+ list->obj = listview;
+
+ // add callback
+ UiTreeEventData *event = malloc(sizeof(UiTreeEventData));
+ event->obj = obj;
+ event->activate = args.onactivate;
+ event->activatedata = args.onactivatedata;
+ event->selection = args.onselection;
+ event->selectiondata = args.onselectiondata;
+ g_signal_connect(
+ view,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+
+ if(args.onactivate) {
+ g_signal_connect(
+ view,
+ "row-activated",
+ G_CALLBACK(ui_listview_activate_event),
+ event);
+ }
+ if(args.onselection) {
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(
+ GTK_TREE_VIEW(view));
+ g_signal_connect(
+ selection,
+ "changed",
+ G_CALLBACK(ui_listview_selection_event),
+ event);
+ }
+ if(args.contextmenu) {
+ UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, view);
+ ui_widget_set_contextmenu(view, menu);
+ }
+
+
+ // add widget to the current container
+ GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
+ gtk_scrolled_window_set_policy(
+ GTK_SCROLLED_WINDOW(scroll_area),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS
+ SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, scroll_area, FALSE);
+
+ // ct->current should point to view, not scroll_area, to make it possible
+ // to add a context menu
+ current->container->current = view;
+
+ return scroll_area;
+}
+
+/*
+static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) {
+ printf("drag begin\n");
+
+}
+
+static void drag_end(
+ GtkWidget *widget,
+ GdkDragContext *context,
+ guint time,
+ gpointer udata)
+{
+ printf("drag end\n");
+
+}
+*/
+
+/*
+static GtkTargetEntry targetentries[] =
+ {
+ { "STRING", 0, 0 },
+ { "text/plain", 0, 1 },
+ { "text/uri-list", 0, 2 },
+ };
+*/
+
+UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ // create treeview
+ GtkWidget *view = gtk_tree_view_new();
+
+ UiModel *model = args.model;
+ int columns = model ? model->columns : 0;
+
+ int addi = 0;
+ for(int i=0;i<columns;i++) {
+ GtkTreeViewColumn *column = NULL;
+ if(model->types[i] == UI_ICON_TEXT) {
+ column = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(column, model->titles[i]);
+
+ GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new();
+ GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new();
+
+ gtk_tree_view_column_pack_end(column, textrenderer, TRUE);
+ gtk_tree_view_column_pack_start(column, iconrenderer, FALSE);
+
+
+ gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i);
+ gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1);
+
+ addi++;
+ } else if (model->types[i] == UI_ICON) {
+ GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new();
+ column = gtk_tree_view_column_new_with_attributes(
+ model->titles[i],
+ iconrenderer,
+ "pixbuf",
+ i + addi,
+ NULL);
+ } else {
+ GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(
+ model->titles[i],
+ renderer,
+ "text",
+ i + addi,
+ NULL);
+ }
+
+ int colsz = model->columnsize[i];
+ if(colsz > 0) {
+ gtk_tree_view_column_set_fixed_width(column, colsz);
+ } else if(colsz < 0) {
+ gtk_tree_view_column_set_expand(column, TRUE);
+ }
+
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
+ }
+
+ //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
+#ifdef UI_GTK3
+ //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE);
+#else
+
+#endif
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+
+ UiList *list = var ? var->value : NULL;
+ GtkListStore *listmodel = create_list_store(list, model);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+ g_object_unref(listmodel);
+
+ //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL);
+ //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL);
+
+ // add TreeView as observer to the UiList to update the TreeView if the
+ // data changes
+ UiListView *tableview = malloc(sizeof(UiListView));
+ tableview->obj = obj;
+ tableview->widget = view;
+ tableview->var = var;
+ tableview->model = model;
+ tableview->ondragstart = args.ondragstart;
+ tableview->ondragstartdata = args.ondragstartdata;
+ tableview->ondragcomplete = args.ondragcomplete;
+ tableview->ondragcompletedata = args.ondragcompletedata;
+ tableview->ondrop = args.ondrop;
+ tableview->ondropdata = args.ondropsdata;
+ g_signal_connect(
+ view,
+ "destroy",
+ G_CALLBACK(ui_listview_destroy),
+ tableview);
+
+ // bind var
+ list->update = ui_listview_update;
+ list->getselection = ui_listview_getselection;
+ list->setselection = ui_listview_setselection;
+ list->obj = tableview;
+
+ // add callback
+ UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
+ event->obj = obj;
+ event->activate = args.onactivate;
+ event->selection = args.onselection;
+ event->activatedata = args.onactivatedata;
+ event->selectiondata = args.onselectiondata;
+ if(args.onactivate) {
+ g_signal_connect(
+ view,
+ "row-activated",
+ G_CALLBACK(ui_listview_activate_event),
+ event);
+ }
+ if(args.onselection) {
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(
+ GTK_TREE_VIEW(view));
+ g_signal_connect(
+ selection,
+ "changed",
+ G_CALLBACK(ui_listview_selection_event),
+ event);
+ }
+ // TODO: destroy callback
+
+
+ if(args.ondragstart) {
+ ui_listview_add_dnd(tableview, &args);
+ }
+ if(args.ondrop) {
+ ui_listview_enable_drop(tableview, &args);
+ }
+
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
+ if(args.multiselection) {
+ gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
+ }
+
+ // add widget to the current container
+ GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
+ gtk_scrolled_window_set_policy(
+ GTK_SCROLLED_WINDOW(scroll_area),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS
+ SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
+
+ if(args.contextmenu) {
+ UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, scroll_area);
+#if GTK_MAJOR_VERSION >= 4
+ ui_widget_set_contextmenu(scroll_area, menu);
+#else
+ ui_widget_set_contextmenu(view, menu);
+#endif
+ }
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, scroll_area, FALSE);
+
+ // ct->current should point to view, not scroll_area, to make it possible
+ // to add a context menu
+ current->container->current = view;
+
+ return scroll_area;
+}
+
+#if GTK_MAJOR_VERSION >= 4
+
+static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) {
+ //printf("drag prepare\n");
+ UiListView *listview = data;
+
+ UiDnD *dnd = ui_create_dnd();
+ GdkContentProvider *provider = NULL;
+
+
+ if(listview->ondragstart) {
+ UiEvent event;
+ event.obj = listview->obj;
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ event.eventdata = dnd;
+ event.intval = 0;
+ listview->ondragstart(&event, listview->ondragstartdata);
+ }
+
+ size_t numproviders = cxListSize(dnd->providers);
+ if(numproviders > 0) {
+ GdkContentProvider **providers = (GdkContentProvider**)cxListAt(dnd->providers, 0);
+ provider = gdk_content_provider_new_union(providers, numproviders);
+ }
+ ui_dnd_free(dnd);
+
+ return provider;
+}
+
+static void ui_listview_drag_begin(GtkDragSource *self, GdkDrag *drag, gpointer userdata) {
+ //printf("drag begin\n");
+}
+
+static void ui_listview_drag_end(GtkDragSource *self, GdkDrag *drag, gboolean delete_data, gpointer user_data) {
+ //printf("drag end\n");
+ UiListView *listview = user_data;
+ if(listview->ondragcomplete) {
+ UiDnD dnd;
+ dnd.target = NULL;
+ dnd.value = NULL;
+ dnd.providers = NULL;
+ dnd.selected_action = gdk_drag_get_selected_action(drag);
+ dnd.delete = delete_data;
+ dnd.accept = FALSE;
+
+ UiEvent event;
+ event.obj = listview->obj;
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ event.eventdata = &dnd;
+ event.intval = 0;
+ listview->ondragcomplete(&event, listview->ondragcompletedata);
+ }
+}
+
+static gboolean ui_listview_drop(
+ GtkDropTarget *target,
+ const GValue* value,
+ gdouble x,
+ gdouble y,
+ gpointer user_data)
+{
+ UiListView *listview = user_data;
+ UiDnD dnd;
+ dnd.providers = NULL;
+ dnd.target = target;
+ dnd.value = value;
+ dnd.selected_action = 0;
+ dnd.delete = FALSE;
+ dnd.accept = FALSE;
+
+ if(listview->ondrop) {
+ dnd.accept = TRUE;
+ UiEvent event;
+ event.obj = listview->obj;
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ event.eventdata = &dnd;
+ event.intval = 0;
+ listview->ondrop(&event, listview->ondropdata);
+ }
+
+ return dnd.accept;
+}
+
+void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) {
+ GtkDragSource *dragsource = gtk_drag_source_new();
+ gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(dragsource));
+ g_signal_connect (dragsource, "prepare", G_CALLBACK (ui_listview_dnd_prepare), listview);
+ g_signal_connect(
+ dragsource,
+ "drag-begin",
+ G_CALLBACK(ui_listview_drag_begin),
+ listview);
+ g_signal_connect(
+ dragsource,
+ "drag-end",
+ G_CALLBACK(ui_listview_drag_end),
+ listview);
+}
+
+void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) {
+ GtkDropTarget *target = gtk_drop_target_new(G_TYPE_INVALID, GDK_ACTION_COPY);
+ gtk_widget_add_controller(listview->widget, GTK_EVENT_CONTROLLER(target));
+ GType default_types[2] = { GDK_TYPE_FILE_LIST, G_TYPE_STRING };
+ gtk_drop_target_set_gtypes(target, default_types, 2);
+ g_signal_connect(target, "drop", G_CALLBACK(ui_listview_drop), listview);
+}
+
+#else
+
+static GtkTargetEntry targetentries[] =
+{
+ { "STRING", 0, 0 },
+ { "text/plain", 0, 1 },
+ { "text/uri-list", 0, 2 },
+};
+
+static void ui_listview_drag_getdata(
+ GtkWidget* self,
+ GdkDragContext* context,
+ GtkSelectionData* data,
+ guint info,
+ guint time,
+ gpointer user_data)
+{
+ UiListView *listview = user_data;
+ UiDnD dnd;
+ dnd.context = context;
+ dnd.data = data;
+ dnd.selected_action = 0;
+ dnd.delete = FALSE;
+ dnd.accept = FALSE;
+
+ if(listview->ondragstart) {
+ UiEvent event;
+ event.obj = listview->obj;
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ event.eventdata = &dnd;
+ event.intval = 0;
+ listview->ondragstart(&event, listview->ondragstartdata);
+ }
+}
+
+static void ui_listview_drag_end(
+ GtkWidget *widget,
+ GdkDragContext *context,
+ guint time,
+ gpointer user_data)
+{
+ UiListView *listview = user_data;
+ UiDnD dnd;
+ dnd.context = context;
+ dnd.data = NULL;
+ dnd.selected_action = gdk_drag_context_get_selected_action(context);
+ dnd.delete = dnd.selected_action == UI_DND_ACTION_MOVE ? TRUE : FALSE;
+ dnd.accept = FALSE;
+ if(listview->ondragcomplete) {
+ UiEvent event;
+ event.obj = listview->obj;
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ event.eventdata = &dnd;
+ event.intval = 0;
+ listview->ondragcomplete(&event, listview->ondragcompletedata);
+ }
+}
+
+void ui_listview_add_dnd(UiListView *listview, UiListArgs *args) {
+ gtk_tree_view_enable_model_drag_source(
+ GTK_TREE_VIEW(listview->widget),
+ GDK_BUTTON1_MASK,
+ targetentries,
+ 2,
+ GDK_ACTION_COPY);
+
+ g_signal_connect(listview->widget, "drag-data-get", G_CALLBACK(ui_listview_drag_getdata), listview);
+ g_signal_connect(listview->widget, "drag-end", G_CALLBACK(ui_listview_drag_end), listview);
+}
+
+
+
+
+static void ui_listview_drag_data_received(
+ GtkWidget *self,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *data,
+ guint info,
+ guint time,
+ gpointer user_data)
+{
+ UiListView *listview = user_data;
+ UiDnD dnd;
+ dnd.context = context;
+ dnd.data = data;
+ dnd.selected_action = 0;
+ dnd.delete = FALSE;
+ dnd.accept = FALSE;
+
+ if(listview->ondrop) {
+ dnd.accept = TRUE;
+ UiEvent event;
+ event.obj = listview->obj;
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ event.eventdata = &dnd;
+ event.intval = 0;
+ listview->ondrop(&event, listview->ondropdata);
+ }
+}
+
+void ui_listview_enable_drop(UiListView *listview, UiListArgs *args) {
+ gtk_tree_view_enable_model_drag_dest(
+ GTK_TREE_VIEW(listview->widget),
+ targetentries,
+ 3,
+ GDK_ACTION_COPY);
+ if(listview->ondrop) {
+ g_signal_connect(listview->widget, "drag_data_received", G_CALLBACK(ui_listview_drag_data_received), listview);
+ }
+}
+
+#endif
+
+
+GtkWidget* ui_get_tree_widget(UIWIDGET widget) {
+ return SCROLLEDWINDOW_GET_CHILD(widget);
+}
+
+static char** targets2array(char *target0, va_list ap, int *nelm) {
+ int al = 16;
+ char **targets = calloc(16, sizeof(char*));
+ targets[0] = target0;
+
+ int i = 1;
+ char *target;
+ while((target = va_arg(ap, char*)) != NULL) {
+ if(i >= al) {
+ al *= 2;
+ targets = realloc(targets, al*sizeof(char*));
+ }
+ targets[i] = target;
+ i++;
+ }
+
+ *nelm = i;
+ return targets;
+}
+
+/*
+static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) {
+ GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry));
+ for(int i=0;i<nelm;i++) {
+ targets[i].target = str[i];
+ }
+ return targets;
+}
+*/
+
+void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) {
+ va_list ap;
+ va_start(ap, target0);
+ int nelm;
+ char **targets = targets2array(target0, ap, &nelm);
+ va_end(ap);
+
+ // disabled
+ //ui_table_dragsource_a(tablewidget, actions, targets, nelm);
+
+ free(targets);
+}
+
+/*
+void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
+ GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
+ gtk_tree_view_enable_model_drag_source(
+ GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
+ GDK_BUTTON1_MASK,
+ t,
+ nelm,
+ GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+ free(t);
+}
+
+
+void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) {
+ va_list ap;
+ va_start(ap, target0);
+ int nelm;
+ char **targets = targets2array(target0, ap, &nelm);
+ va_end(ap);
+ ui_table_dragdest_a(tablewidget, actions, targets, nelm);
+ free(targets);
+}
+
+void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) {
+ GtkTargetEntry* t = targetstr2gtktargets(targets, nelm);
+ gtk_tree_view_enable_model_drag_dest(
+ GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)),
+ t,
+ nelm,
+ GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);
+ free(t);
+}
+*/
+
+void ui_listview_update(UiList *list, int i) {
+ UiListView *view = list->obj;
+ GtkListStore *store = create_list_store(list, view->model);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store));
+ g_object_unref(G_OBJECT(store));
+}
+
+UiListSelection ui_listview_getselection(UiList *list) {
+ UiListView *view = list->obj;
+ UiListSelection selection = ui_listview_selection(
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)),
+ NULL);
+ return selection;
+}
+
+void ui_listview_setselection(UiList *list, UiListSelection selection) {
+ UiListView *view = list->obj;
+ GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget));
+ GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count);
+ gtk_tree_selection_select_path(sel, path);
+ //g_object_unref(path);
+}
+
+void ui_listview_destroy(GtkWidget *w, UiListView *v) {
+ //gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL);
+ ui_destroy_boundvar(v->obj->ctx, v->var);
+ free(v);
+}
+
+void ui_combobox_destroy(GtkWidget *w, UiListView *v) {
+ ui_destroy_boundvar(v->obj->ctx, v->var);
+ free(v);
+}
+
+
+void ui_listview_activate_event(
+ GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ UiTreeEventData *event)
+{
+ UiListSelection selection = ui_listview_selection(
+ gtk_tree_view_get_selection(treeview),
+ event);
+
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = &selection;
+ e.intval = selection.count > 0 ? selection.rows[0] : -1;
+ event->activate(&e, event->activatedata);
+
+ if(selection.count > 0) {
+ free(selection.rows);
+ }
+}
+
+void ui_listview_selection_event(
+ GtkTreeSelection *treeselection,
+ UiTreeEventData *event)
+{
+ UiListSelection selection = ui_listview_selection(treeselection, event);
+
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = &selection;
+ e.intval = selection.count > 0 ? selection.rows[0] : -1;
+ event->selection(&e, event->selectiondata);
+
+ if(selection.count > 0) {
+ free(selection.rows);
+ }
+}
+
+UiListSelection ui_listview_selection(
+ GtkTreeSelection *selection,
+ UiTreeEventData *event)
+{
+ GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
+
+ UiListSelection ls;
+ ls.count = g_list_length(rows);
+ ls.rows = calloc(ls.count, sizeof(int));
+ GList *r = rows;
+ int i = 0;
+ while(r) {
+ GtkTreePath *path = r->data;
+ ls.rows[i] = ui_tree_path_list_index(path);
+ r = r->next;
+ i++;
+ }
+ return ls;
+}
+
+int ui_tree_path_list_index(GtkTreePath *path) {
+ int depth = gtk_tree_path_get_depth(path);
+ if(depth == 0) {
+ fprintf(stderr, "UiError: treeview selection: depth == 0\n");
+ return -1;
+ }
+ int *indices = gtk_tree_path_get_indices(path);
+ return indices[depth - 1];
+}
+
+
+/* --------------------------- ComboBox --------------------------- */
+
+UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
+ model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+
+ GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata);
+ ui_set_name_and_style(combobox, args.name, args.style_class);
+ ui_set_widget_groups(obj->ctx, combobox, args.groups);
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, combobox, FALSE);
+ current->container->current = combobox;
+ return combobox;
+}
+
+GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) {
+ GtkWidget *combobox = gtk_combo_box_new();
+
+ UiListView *uicbox = malloc(sizeof(UiListView));
+ uicbox->obj = obj;
+ uicbox->widget = combobox;
+
+ UiList *list = var ? var->value : NULL;
+ GtkListStore *listmodel = create_list_store(list, model);
+
+ if(listmodel) {
+ gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel));
+ g_object_unref(listmodel);
+ }
+
+ uicbox->var = var;
+ uicbox->model = model;
+
+ g_signal_connect(
+ combobox,
+ "destroy",
+ G_CALLBACK(ui_combobox_destroy),
+ uicbox);
+
+ // bind var
+ if(list) {
+ list->update = ui_combobox_modelupdate;
+ list->getselection = ui_combobox_getselection;
+ list->setselection = ui_combobox_setselection;
+ list->obj = uicbox;
+ }
+
+ GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE);
+ gtk_cell_layout_set_attributes(
+ GTK_CELL_LAYOUT(combobox),
+ renderer,
+ "text",
+ 0,
+ NULL);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0);
+
+ // add callback
+ if(f) {
+ UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = udata;
+ event->callback = f;
+ event->value = 0;
+ event->customdata = NULL;
+
+ g_signal_connect(
+ combobox,
+ "changed",
+ G_CALLBACK(ui_combobox_change_event),
+ event);
+ }
+
+ return combobox;
+}
+
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) {
+ UiEvent event;
+ event.obj = e->obj;
+ event.window = event.obj->window;
+ event.document = event.obj->ctx->document;
+ event.eventdata = NULL;
+ event.intval = gtk_combo_box_get_active(widget);
+ e->callback(&event, e->userdata);
+}
+
+void ui_combobox_modelupdate(UiList *list, int i) {
+ UiListView *view = list->obj;
+ GtkListStore *store = create_list_store(view->var->value, view->model);
+ gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store));
+ g_object_unref(store);
+}
+
+UiListSelection ui_combobox_getselection(UiList *list) {
+ UiListView *combobox = list->obj;
+ UiListSelection ret;
+ ret.rows = malloc(sizeof(int*));
+ ret.count = 1;
+ ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget));
+ return ret;
+}
+
+void ui_combobox_setselection(UiList *list, UiListSelection selection) {
+ UiListView *combobox = list->obj;
+ if(selection.count > 0) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]);
+ }
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TREE_H
+#define TREE_H
+
+#include "../ui/tree.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiListView {
+ UiObject *obj;
+ GtkWidget *widget;
+ UiVar *var;
+ UiModel *model;
+ ui_callback ondragstart;
+ void *ondragstartdata;
+ ui_callback ondragcomplete;
+ void *ondragcompletedata;
+ ui_callback ondrop;
+ void *ondropdata;
+
+} UiListView;
+
+typedef struct UiTreeEventData {
+ UiObject *obj;
+ ui_callback activate;
+ ui_callback selection;
+ void *activatedata;
+ void *selectiondata;
+} UiTreeEventData;
+
+void* ui_strmodel_getvalue(void *elm, int column);
+
+UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb);
+
+GtkWidget* ui_get_tree_widget(UIWIDGET widget);
+
+void ui_listview_update(UiList *list, int i);
+UiListSelection ui_listview_getselection(UiList *list);
+void ui_listview_setselection(UiList *list, UiListSelection selection);
+
+void ui_combobox_destroy(GtkWidget *w, UiListView *v);
+void ui_listview_destroy(GtkWidget *w, UiListView *v);
+
+void ui_listview_activate_event(
+ GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ UiTreeEventData *event);
+void ui_listview_selection_event(
+ GtkTreeSelection *treeselection,
+ UiTreeEventData *event);
+UiListSelection ui_listview_selection(
+ GtkTreeSelection *selection,
+ UiTreeEventData *event);
+int ui_tree_path_list_index(GtkTreePath *path);
+
+void ui_listview_add_dnd(UiListView *listview, UiListArgs *args);
+void ui_listview_enable_drop(UiListView *listview, UiListArgs *args);
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata);
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e);
+void ui_combobox_modelupdate(UiList *list, int i);
+UiListSelection ui_combobox_getselection(UiList *list);
+void ui_combobox_setselection(UiList *list, UiListSelection selection);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TREE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdarg.h>
+
+#include "menu.h"
+#include "toolkit.h"
+#include "../common/context.h"
+#include "../common/menu.h"
+#include "../common/types.h"
+#include "../ui/properties.h"
+#include "../ui/window.h"
+#include "container.h"
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+#if GTK_MAJOR_VERSION <= 3
+
+static ui_menu_add_f createMenuItem[] = {
+ /* UI_MENU */ add_menu_widget,
+ /* UI_MENU_ITEM */ add_menuitem_widget,
+ /* UI_MENU_CHECK_ITEM */ add_checkitem_widget,
+ /* UI_MENU_RADIO_ITEM */ add_radioitem_widget,
+ /* UI_MENU_ITEM_LIST */ add_menuitem_list_widget,
+ /* UI_MENU_CHECKITEM_LIST */ add_menuitem_list_widget,
+ /* UI_MENU_RADIOITEM_LIST */ add_menuitem_list_widget,
+ /* UI_MENU_SEPARATOR */ add_menuseparator_widget
+};
+
+// private menu functions
+GtkWidget *ui_create_menubar(UiObject *obj) {
+ UiMenu *menus_begin = uic_get_menu_list();
+ if(menus_begin == NULL) {
+ return NULL;
+ }
+
+ GtkWidget *mb = gtk_menu_bar_new();
+
+ UiMenu *ls = menus_begin;
+ while(ls) {
+ UiMenu *menu = ls;
+ add_menu_widget(mb, 0, &menu->item, obj);
+
+ ls = (UiMenu*)ls->item.next;
+ }
+
+ return mb;
+}
+
+void ui_add_menu_items(GtkWidget *parent, int i, UiMenu *menu, UiObject *obj) {
+ UiMenuItemI *it = menu->items_begin;
+ int index = 0;
+ while(it) {
+ createMenuItem[it->type](parent, index, it, obj);
+ it = it->next;
+ index++;
+ }
+}
+
+void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj) {
+ UiMenu *menu = (UiMenu*)item;
+
+ GtkWidget *menu_widget = gtk_menu_new();
+ GtkWidget *menu_item = gtk_menu_item_new_with_mnemonic(menu->label);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu_widget);
+
+ ui_add_menu_items(menu_widget, i, menu, obj);
+
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(parent), menu_item);
+}
+
+void add_menuitem_widget(GtkWidget *parent, int index, UiMenuItemI *item, UiObject *obj) {
+ UiMenuItem *i = (UiMenuItem*)item;
+
+ //GtkWidget *widget = gtk_menu_item_new_with_label(i->title);
+ GtkWidget *widget = gtk_menu_item_new_with_mnemonic(i->label);
+
+ if(i->callback != NULL) {
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = i->userdata;
+ event->callback = i->callback;
+ event->value = 0;
+ event->customdata = NULL;
+
+ g_signal_connect(
+ widget,
+ "activate",
+ G_CALLBACK(ui_menu_event_wrapper),
+ event);
+ g_signal_connect(
+ widget,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+ }
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget);
+
+ if(i->groups) {
+ CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups);
+ cxListAddArray(groups, i->groups, i->ngroups);
+ uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
+ cxListDestroy(groups);
+ }
+}
+
+/*
+void add_menuitem_st_widget(
+ GtkWidget *parent,
+ int index,
+ UiMenuItemI *item,
+ UiObject *obj)
+{
+ UiStMenuItem *i = (UiStMenuItem*)item;
+
+ GtkWidget *widget = gtk_image_menu_item_new_from_stock(i->stockid, obj->ctx->accel_group);
+
+ if(i->callback != NULL) {
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = i->userdata;
+ event->callback = i->callback;
+ event->value = 0;
+
+ g_signal_connect(
+ widget,
+ "activate",
+ G_CALLBACK(ui_menu_event_wrapper),
+ event);
+ g_signal_connect(
+ widget,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+ }
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget);
+
+ if(i->groups) {
+ uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups);
+ }
+}
+*/
+
+void add_menuseparator_widget(
+ GtkWidget *parent,
+ int index,
+ UiMenuItemI *item,
+ UiObject *obj)
+{
+ gtk_menu_shell_append(
+ GTK_MENU_SHELL(parent),
+ gtk_separator_menu_item_new());
+}
+
+void add_checkitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
+ UiMenuCheckItem *ci = (UiMenuCheckItem*)item;
+ GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label);
+ gtk_menu_shell_append(GTK_MENU_SHELL(p), widget);
+
+ if(ci->callback) {
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = ci->userdata;
+ event->callback = ci->callback;
+ event->value = 0;
+ event->customdata = NULL;
+
+ g_signal_connect(
+ widget,
+ "toggled",
+ G_CALLBACK(ui_menu_event_toggled),
+ event);
+ g_signal_connect(
+ widget,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+ }
+}
+
+void add_radioitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
+ // TODO
+}
+
+/*
+void add_checkitemnv_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
+ UiCheckItemNV *ci = (UiCheckItemNV*)item;
+ GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label);
+ gtk_menu_shell_append(GTK_MENU_SHELL(p), widget);
+
+ UiVar *var = uic_create_var(obj->ctx, ci->varname, UI_VAR_INTEGER);
+ if(var) {
+ UiInteger *value = var->value;
+ value->obj = widget;
+ value->get = ui_checkitem_get;
+ value->set = ui_checkitem_set;
+ value = 0;
+ } else {
+ // TODO: error
+ }
+}
+*/
+
+void add_menuitem_list_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
+ UiMenuItemList *il = (UiMenuItemList*)item;
+ const CxAllocator *a = obj->ctx->allocator;
+
+ UiActiveMenuItemList *ls = cxMalloc(
+ a,
+ sizeof(UiActiveMenuItemList));
+
+ ls->object = obj;
+ ls->menu = GTK_MENU_SHELL(p);
+ ls->index = index;
+ ls->oldcount = 0;
+ ls->getvalue = il->getvalue;
+
+ UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+ ls->list = var->value;
+
+ ls->callback = il->callback;
+ ls->userdata = il->userdata;
+
+ UiObserver *observer = ui_observer_new((ui_callback)ui_update_menuitem_list, ls);
+ ls->list->observers = ui_obsvlist_add(ls->list->observers, observer);
+ uic_list_register_observer_destructor(obj->ctx, ls->list, observer);
+
+ ui_update_menuitem_list(NULL, ls);
+}
+
+
+void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) {
+ // remove old items
+ if(list->oldcount > 0) {
+ int i = 0;
+ GList *mi = gtk_container_get_children(GTK_CONTAINER(list->menu));
+ while(mi) {
+ if(i >= list->index && i < list->index + list->oldcount) {
+ //gtk_container_remove(GTK_CONTAINER(list->menu), mi->data);
+ gtk_widget_destroy(mi->data);
+ }
+ mi = mi->next;
+ i++;
+ }
+ }
+
+ void* elm = ui_list_first(list->list);
+ if(elm) {
+ GtkWidget *widget = gtk_separator_menu_item_new();
+ gtk_menu_shell_insert(list->menu, widget, list->index);
+ gtk_widget_show(widget);
+ }
+
+ ui_getvaluefunc getvalue = list->getvalue;
+ int i = 1;
+ while(elm) {
+ char *label = (char*) (getvalue ? getvalue(elm, 0) : elm);
+
+ GtkWidget *widget = gtk_menu_item_new_with_label(label);
+ gtk_menu_shell_insert(list->menu, widget, list->index + i);
+ gtk_widget_show(widget);
+
+ if(list->callback) {
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = list->object;
+ event->userdata = list->userdata;
+ event->callback = list->callback;
+ event->value = i - 1;
+ event->customdata = elm;
+
+ g_signal_connect(
+ widget,
+ "activate",
+ G_CALLBACK(ui_menu_event_wrapper),
+ event);
+ g_signal_connect(
+ widget,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+ }
+
+ elm = ui_list_next(list->list);
+ i++;
+ }
+
+ list->oldcount = i;
+}
+
+void ui_menu_event_wrapper(GtkMenuItem *item, UiEventData *event) {
+ UiEvent evt;
+ evt.obj = event->obj;
+ evt.window = event->obj->window;
+ evt.document = event->obj->ctx->document;
+ evt.eventdata = event->customdata;
+ evt.intval = event->value;
+ event->callback(&evt, event->userdata);
+}
+
+void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event) {
+ UiEvent evt;
+ evt.obj = event->obj;
+ evt.window = event->obj->window;
+ evt.document = event->obj->ctx->document;
+ evt.eventdata = NULL;
+ evt.intval = gtk_check_menu_item_get_active(ci);
+ event->callback(&evt, event->userdata);
+}
+
+int64_t ui_checkitem_get(UiInteger *i) {
+ int state = gtk_check_menu_item_get_active(i->obj);
+ i->value = state;
+ return state;
+}
+
+void ui_checkitem_set(UiInteger *i, int64_t value) {
+ i->value = value;
+ gtk_check_menu_item_set_active(i->obj, value);
+}
+
+
+/*
+ * widget menu functions
+ */
+
+UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget) {
+ GtkWidget *menu_widget = gtk_menu_new();
+ ui_add_menu_items(menu_widget, 0, builder->menus_begin, obj);
+ return GTK_MENU(menu_widget);
+}
+
+static gboolean ui_button_press_event(GtkWidget *widget, GdkEvent *event, GtkMenu *menu) {
+ if(event->type == GDK_BUTTON_PRESS) {
+ GdkEventButton *e = (GdkEventButton*)event;
+ if(e->button == 3) {
+ gtk_widget_show_all(GTK_WIDGET(menu));
+ ui_contextmenu_popup(menu, widget, 0, 0);
+ }
+ }
+ return FALSE;
+}
+
+void ui_widget_set_contextmenu(GtkWidget *widget, GtkMenu *menu) {
+ g_signal_connect(widget, "button-press-event", (GCallback) ui_button_press_event, menu);
+}
+
+void ui_contextmenu_popup(UIMENU menu, GtkWidget *widget, int x, int y) {
+#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16
+ gtk_menu_popup_at_pointer(menu, NULL);
+#else
+ gtk_menu_popup(menu, NULL, NULL, 0, 0, 0, gtk_get_current_event_time());
+#endif
+}
+
+#endif /* GTK_MAJOR_VERSION <= 3 */
+
+
+
+#if GTK_MAJOR_VERSION >= 4
+
+
+
+static ui_gmenu_add_f createMenuItem[] = {
+ /* UI_MENU */ ui_gmenu_add_menu,
+ /* UI_MENU_ITEM */ ui_gmenu_add_menuitem,
+ /* UI_MENU_CHECK_ITEM */ ui_gmenu_add_checkitem,
+ /* UI_MENU_RADIO_ITEM */ ui_gmenu_add_radioitem,
+ /* UI_MENU_ITEM_LIST */ ui_gmenu_add_menuitem_list,
+ /* UI_MENU_CHECKITEM_LIST */ ui_gmenu_add_menuitem_list,
+ /* UI_MENU_RADIOITEM_LIST */ ui_gmenu_add_menuitem_list,
+ /* UI_MENU_SEPARATOR */ ui_gmenu_add_menuseparator
+};
+
+void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj) {
+ UiMenuItemI *it = menu->items_begin;
+ int index = 0;
+ int index_section = 0;
+ GMenu *section = NULL;
+ while(it) {
+ if(it->type == UI_MENU_SEPARATOR) {
+ section = g_menu_new();
+ g_menu_append_section(parent, NULL, G_MENU_MODEL(section));
+ index++;
+ index_section = 0;
+ } else {
+ if(section) {
+ createMenuItem[it->type](section, index_section++, it, obj);
+ } else {
+ createMenuItem[it->type](parent, index++, it, obj);
+ }
+ }
+ it = it->next;
+ }
+}
+
+void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) {
+ UiMenu *mi = (UiMenu*)item;
+ GMenu *menu = g_menu_new();
+ ui_gmenu_add_menu_items(menu, 0, mi, obj);
+ g_menu_append_submenu(parent, mi->label, G_MENU_MODEL(menu));
+}
+
+static void action_enable(GSimpleAction *action, int enabled) {
+ g_simple_action_set_enabled(action, enabled);
+}
+
+void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) {
+ UiMenuItem *i = (UiMenuItem*)item;
+
+ GSimpleAction *action = g_simple_action_new(item->id, NULL);
+ g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
+
+ if(i->groups) {
+ CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups);
+ cxListAddArray(groups, i->groups, i->ngroups);
+ uic_add_group_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups);
+ cxListDestroy(groups);
+ }
+
+ if(i->callback != NULL) {
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = i->userdata;
+ event->callback = i->callback;
+ event->value = 0;
+ event->customdata = NULL;
+
+ g_signal_connect(
+ action,
+ "activate",
+ G_CALLBACK(ui_activate_event_wrapper),
+ event);
+ g_signal_connect(
+ obj->widget,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+ }
+
+ char action_name[32];
+ snprintf(action_name, 32, "win.%s", item->id);
+ g_menu_append(parent, i->label, action_name);
+}
+
+void ui_gmenu_add_menuseparator(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+
+}
+
+void ui_gmenu_add_checkitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+ UiMenuCheckItem *checkitem = (UiMenuCheckItem*)item;
+
+ // TODO
+}
+
+void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+
+}
+
+void ui_gmenu_add_menuitem_list(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+ UiMenuItemList *il = (UiMenuItemList*)item;
+
+ const CxAllocator *a = obj->ctx->allocator;
+
+ UiActiveGMenuItemList *ls = cxMalloc(
+ a,
+ sizeof(UiActiveGMenuItemList));
+
+ ls->object = obj;
+ ls->menu = p;
+ ls->index = index;
+ ls->oldcount = 0;
+ ls->getvalue = il->getvalue;
+
+ UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+ ls->var = var;
+ UiList *list = var->value;
+
+ ls->callback = il->callback;
+ ls->userdata = il->userdata;
+
+ UiObserver *observer = ui_observer_new((ui_callback)ui_update_gmenu_item_list, ls);
+ list->observers = ui_obsvlist_add(list->observers, observer);
+ uic_list_register_observer_destructor(obj->ctx, list, observer);
+
+ GSimpleAction *action = g_simple_action_new(item->id, g_variant_type_new("i"));
+ g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
+ snprintf(ls->action, 32, "win.%s", item->id);
+
+
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = il->userdata;
+ event->callback = il->callback;
+ event->customdata = var;
+ event->value = 0;
+
+ g_signal_connect(
+ action,
+ "activate",
+ G_CALLBACK(ui_menu_list_item_activate_event_wrapper),
+ event);
+ g_signal_connect(
+ obj->widget,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+
+ ui_update_gmenu_item_list(NULL, ls);
+}
+
+void ui_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event) {
+ int intval = event->value;
+ if(parameter && g_variant_is_of_type(parameter, G_VARIANT_TYPE_INT32)) {
+ intval = g_variant_get_int32(parameter);
+ }
+
+ UiEvent evt;
+ evt.obj = event->obj;
+ evt.window = event->obj->window;
+ evt.document = event->obj->ctx->document;
+ evt.eventdata = event->customdata;
+ evt.intval = intval;
+ event->callback(&evt, event->userdata);
+}
+
+void ui_menu_list_item_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event) {
+ int index = g_variant_get_int32(parameter);
+ UiVar *var = event->customdata;
+ UiList *list = var->value;
+
+ UiEvent evt;
+ evt.obj = event->obj;
+ evt.window = event->obj->window;
+ evt.document = event->obj->ctx->document;
+ evt.eventdata = ui_list_get(list, index);
+ evt.intval = index;
+ event->callback(&evt, event->userdata);
+
+}
+
+void ui_update_gmenu_item_list(UiEvent *event, UiActiveGMenuItemList *list) {
+ // remove old items
+ for(int i=0;i<list->oldcount;i++) {
+ g_menu_remove(list->menu, list->index);
+ }
+ UiList *ls = list->var->value;
+
+ // add list items
+ ui_getvaluefunc getvalue = list->getvalue;
+ int i = 0;
+ void* elm = ui_list_first(ls);
+ while(elm) {
+ char *label = (char*) (getvalue ? getvalue(elm, 0) : elm);
+
+ GMenuItem *item = g_menu_item_new(label, NULL);
+ GVariant *v = g_variant_new("i", i);
+ g_menu_item_set_action_and_target_value(item, list->action, v);
+ g_menu_insert_item(list->menu, list->index+i, item);
+
+ elm = ui_list_next(ls);
+ i++;
+ }
+
+ list->oldcount = i;
+}
+
+
+/* --------------------- context menu / menubuilder --------------------- */
+
+static void remove_popover(GtkWidget *object, GtkPopoverMenu *menu) {
+ gtk_widget_unparent(GTK_WIDGET(menu));
+}
+
+UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, GtkWidget *widget) {
+ GMenu *menu = g_menu_new();
+ ui_gmenu_add_menu_items(menu, 0, builder->menus_begin, obj);
+ GtkWidget *contextmenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu));
+ gtk_popover_set_has_arrow(GTK_POPOVER(contextmenu), FALSE);
+ gtk_widget_set_halign(contextmenu, GTK_ALIGN_START);
+ gtk_widget_set_parent(GTK_WIDGET(contextmenu), widget);
+ g_signal_connect(
+ widget,
+ "destroy",
+ G_CALLBACK(remove_popover),
+ contextmenu);
+ return GTK_POPOVER_MENU(contextmenu);
+}
+
+static void gesture_button_press(GtkGestureClick *gesture, gint n_press, gdouble x, gdouble y, gpointer user_data) {
+ gtk_popover_set_pointing_to(GTK_POPOVER(user_data), &(GdkRectangle){ x, y, 0, 0 });
+ gtk_popover_popup(GTK_POPOVER(user_data));
+}
+
+void ui_widget_set_contextmenu(GtkWidget *widget, GtkPopoverMenu *menu) {
+ GtkGesture *gesture = gtk_gesture_click_new();
+ gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 3);
+ gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(gesture));
+ g_signal_connect(gesture, "pressed", G_CALLBACK(gesture_button_press), menu);
+}
+
+void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y) {
+ gtk_popover_set_pointing_to(GTK_POPOVER(menu), &(GdkRectangle){ x, y, 0, 0 });
+ gtk_popover_popup(GTK_POPOVER(menu));
+}
+
+#endif
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MENU_H
+#define MENU_H
+
+#include "../ui/menu.h"
+#include "../common/menu.h"
+#include <cx/list.h>
+#include "toolkit.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UIMENU ui_create_menu(UiMenuBuilder *builder, UiObject *obj);
+void ui_widget_set_contextmenu(GtkWidget *widget, UIMENU menu);
+
+#if GTK_MAJOR_VERSION <= 3
+
+typedef struct UiActiveMenuItemList UiActiveMenuItemList;
+
+typedef void(*ui_menu_add_f)(GtkWidget *, int, UiMenuItemI*, UiObject*);
+
+struct UiActiveMenuItemList {
+ UiObject *object;
+ GtkMenuShell *menu;
+ int index;
+ int oldcount;
+ UiList *list;
+ ui_getvaluefunc getvalue;
+ ui_callback callback;
+ void *userdata;
+};
+
+GtkWidget *ui_create_menubar(UiObject *obj);
+
+void ui_add_menu_items(GtkWidget *parent, int i, UiMenu *menu, UiObject *obj);
+
+void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_st_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuseparator_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_checkitem_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_radioitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj);
+void add_checkitemnv_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+void add_menuitem_list_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
+
+void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list);
+void ui_menu_event_wrapper(GtkMenuItem *item, UiEventData *event);
+void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event);
+int64_t ui_checkitem_get(UiInteger *i);
+void ui_checkitem_set(UiInteger *i, int64_t value);
+
+#endif /* GTK_MAJOR_VERSION <= 3 */
+
+#if GTK_MAJOR_VERSION >= 4
+
+typedef void(*ui_gmenu_add_f)(GMenu *, int, UiMenuItemI*, UiObject*);
+
+typedef struct UiActiveGMenuItemList UiActiveGMenuItemList;
+struct UiActiveGMenuItemList {
+ UiObject *object;
+ GMenu *menu;
+ char action[32];
+ int index;
+ int oldcount;
+ UiVar *var;
+ ui_getvaluefunc getvalue;
+ ui_callback callback;
+ void *userdata;
+};
+
+void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj);
+
+void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_menuseparator(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_checkitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_menuitem_list(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+
+void ui_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event);
+void ui_menu_list_item_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event);
+void ui_update_gmenu_item_list(UiEvent *event, UiActiveGMenuItemList *list);
+
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MENU_H */
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2017 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+GTK_SRC_DIR = ui/gtk/
+GTK_OBJPRE = $(OBJ_DIR)$(GTK_SRC_DIR)
+
+# some objects are defined in config.mk
+GTKOBJ += toolkit.o
+GTKOBJ += window.o
+GTKOBJ += container.o
+GTKOBJ += menu.o
+GTKOBJ += toolbar.o
+GTKOBJ += button.o
+GTKOBJ += display.o
+GTKOBJ += text.o
+GTKOBJ += 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)
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "range.h"
+#include "container.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+static UIWIDGET ui_scrollbar(UiObject *obj, UiOrientation orientation, UiRange *range, ui_callback f, void *userdata) {
+#if GTK_MAJOR_VERSION >= 3
+ GtkWidget *scrollbar = gtk_scrollbar_new(orientation == UI_HORIZONTAL ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, NULL);
+#else
+ GtkWidget *scrollbar;
+ if(orientation == UI_HORIZONTAL) {
+ scrollbar = gtk_hscrollbar_new(NULL);
+ } else {
+ scrollbar = gtk_hscrollbar_new(NULL);
+ }
+#endif
+
+ if(range) {
+ range->get = ui_scrollbar_get;
+ range->set = ui_scrollbar_set;
+ range->setrange = ui_scrollbar_setrange;
+ range->setextent = ui_scrollbar_setextent;
+ range->obj = scrollbar;
+ }
+
+ if(f) {
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = userdata;
+ event->callback = f;
+ event->value = 0;
+ event->customdata = NULL;
+
+ g_signal_connect(
+ G_OBJECT(scrollbar),
+ "value-changed",
+ G_CALLBACK(ui_scrollbar_value_changed),
+ event);
+ g_signal_connect(
+ scrollbar,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+ }
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(ct, scrollbar, FALSE);
+
+ return scrollbar;
+}
+
+UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
+ return ui_scrollbar(obj, UI_HORIZONTAL, range, f, userdata);
+}
+
+UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
+ return ui_scrollbar(obj, UI_VERTICAL, range, f, userdata);
+}
+
+gboolean ui_scrollbar_value_changed(GtkRange *range, UiEventData *event) {
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = event->value;
+ event->callback(&e, event->userdata);
+ return TRUE;
+}
+
+double ui_scrollbar_get(UiRange *range) {
+ double value = gtk_range_get_value(GTK_RANGE(range->obj));
+ range->value = value;
+ return value;
+}
+
+void ui_scrollbar_set(UiRange *range, double value) {
+ gtk_range_set_value(GTK_RANGE(range->obj), value);
+ range->value = value;
+}
+
+void ui_scrollbar_setrange(UiRange *range, double min, double max) {
+ gtk_range_set_range(GTK_RANGE(range->obj), min, max);
+ range->min = min;
+ range->max = max;
+}
+
+void ui_scrollbar_setextent(UiRange *range, double extent) {
+ GtkAdjustment *a = gtk_range_get_adjustment(GTK_RANGE(range->obj));
+#ifdef UI_GTK2LEGACY
+ a->page_size = extent;
+#else
+ gtk_adjustment_set_page_size(a, extent);
+#endif
+#if GTK_MAJOR_VERSION * 100 + GTK_MIMOR_VERSION < 318
+ gtk_adjustment_changed(a);
+#endif
+ range->extent = extent;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef RANGE_H
+#define RANGE_H
+
+#include "toolkit.h"
+#include "../ui/range.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+gboolean ui_scrollbar_value_changed(GtkRange *range, UiEventData *event);
+
+double ui_scrollbar_get(UiRange *range);
+void ui_scrollbar_set(UiRange *range, double value);
+void ui_scrollbar_setrange(UiRange *range, double min, double max);
+void ui_scrollbar_setextent(UiRange *range, double extent);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RANGE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "text.h"
+#include "container.h"
+
+#include <cx/printf.h>
+
+#include <gdk/gdkkeysyms.h>
+
+
+#include "../common/types.h"
+
+static void selection_handler(
+ GtkTextBuffer *buf,
+ GtkTextIter *location,
+ GtkTextMark *mark,
+ UiTextArea *textview)
+{
+ const char *mname = gtk_text_mark_get_name(mark);
+ if(mname) {
+ GtkTextIter begin;
+ GtkTextIter end;
+ int sel = gtk_text_buffer_get_selection_bounds (buf, &begin, &end);
+ if(sel != textview->last_selection_state) {
+ if(sel) {
+ ui_set_group(textview->ctx, UI_GROUP_SELECTION);
+ } else {
+ ui_unset_group(textview->ctx, UI_GROUP_SELECTION);
+ }
+ }
+ textview->last_selection_state = sel;
+ }
+}
+
+UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) {
+ UiObject* current = uic_current_obj(obj);
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_TEXT);
+
+ GtkWidget *text_area = gtk_text_view_new();
+ ui_set_name_and_style(text_area, args.name, args.style_class);
+ ui_set_widget_groups(obj->ctx, text_area, args.groups);
+
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR);
+ g_signal_connect(
+ text_area,
+ "realize",
+ G_CALLBACK(ui_textarea_realize_event),
+ NULL);
+
+ UiTextArea *uitext = malloc(sizeof(UiTextArea));
+ uitext->obj = obj;
+ uitext->ctx = obj->ctx;
+ uitext->var = var;
+ uitext->last_selection_state = 0;
+ uitext->onchange = args.onchange;
+ uitext->onchangedata = args.onchangedata;
+
+ g_signal_connect(
+ text_area,
+ "destroy",
+ G_CALLBACK(ui_textarea_destroy),
+ uitext);
+
+ GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
+ gtk_scrolled_window_set_policy(
+ GTK_SCROLLED_WINDOW(scroll_area),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS
+ SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area);
+
+ // font and padding
+ //PangoFontDescription *font;
+ //font = pango_font_description_from_string("Monospace");
+ //gtk_widget_modify_font(text_area, font); // TODO
+ //pango_font_description_free(font);
+
+ gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_area), 2);
+ gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_area), 2);
+
+ // add
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, scroll_area, TRUE);
+
+ // bind value
+ if(var) {
+ UiText *value = var->value;
+ GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area));
+
+ if(value->value.ptr) {
+ gtk_text_buffer_set_text(buf, value->value.ptr, -1);
+ value->value.free(value->value.ptr);
+ }
+
+ value->get = ui_textarea_get;
+ value->set = ui_textarea_set;
+ value->getsubstr = ui_textarea_getsubstr;
+ value->insert = ui_textarea_insert;
+ value->setposition = ui_textarea_setposition;
+ value->position = ui_textarea_position;
+ value->selection = ui_textarea_selection;
+ value->length = ui_textarea_length;
+ value->remove = ui_textarea_remove;
+ value->value.ptr = NULL;
+ value->value.free = NULL;
+ value->obj = buf;
+ if(!value->undomgr) {
+ value->undomgr = ui_create_undomgr();
+ }
+
+ g_signal_connect(
+ buf,
+ "changed",
+ G_CALLBACK(ui_textbuf_changed),
+ uitext);
+
+ // register undo manager
+ g_signal_connect(
+ buf,
+ "insert-text",
+ G_CALLBACK(ui_textbuf_insert),
+ var);
+ g_signal_connect(
+ buf,
+ "delete-range",
+ G_CALLBACK(ui_textbuf_delete),
+ var);
+ g_signal_connect(
+ buf,
+ "mark-set",
+ G_CALLBACK(selection_handler),
+ uitext);
+ }
+
+ return scroll_area;
+}
+
+void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) {
+ if(textarea->var) {
+ ui_destroy_boundvar(textarea->ctx, textarea->var);
+ }
+ free(textarea);
+}
+
+UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) {
+ return SCROLLEDWINDOW_GET_CHILD(textarea);
+}
+
+char* ui_textarea_get(UiText *text) {
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+ GtkTextBuffer *buf = text->obj;
+ GtkTextIter start;
+ GtkTextIter end;
+ gtk_text_buffer_get_bounds(buf, &start, &end);
+ char *str = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
+ text->value.ptr = g_strdup(str);
+ text->value.free = (ui_freefunc)g_free;
+ return str;
+}
+
+void ui_textarea_set(UiText *text, const char *str) {
+ gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -1);
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+ text->value.ptr = NULL;
+ text->value.free = NULL;
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+ GtkTextBuffer *buf = text->obj;
+ GtkTextIter ib;
+ GtkTextIter ie;
+ gtk_text_buffer_get_iter_at_offset(text->obj, &ib, begin);
+ gtk_text_buffer_get_iter_at_offset(text->obj, &ie, end);
+ char *str = gtk_text_buffer_get_text(buf, &ib, &ie, FALSE);
+ text->value.ptr = g_strdup(str);
+ text->value.free = (ui_freefunc)g_free;
+ return str;
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+ GtkTextIter offset;
+ gtk_text_buffer_get_iter_at_offset(text->obj, &offset, pos);
+ gtk_text_buffer_insert(text->obj, &offset, str, -1);
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+ text->value.ptr = NULL;
+ text->value.free = NULL;
+}
+
+void ui_textarea_setposition(UiText *text, int pos) {
+ GtkTextIter iter;
+ gtk_text_buffer_get_iter_at_offset(text->obj, &iter, pos);
+ gtk_text_buffer_place_cursor(text->obj, &iter);
+}
+
+int ui_textarea_position(UiText *text) {
+ GtkTextIter begin;
+ GtkTextIter end;
+ gtk_text_buffer_get_selection_bounds(text->obj, &begin, &end);
+ text->pos = gtk_text_iter_get_offset(&begin);
+ return text->pos;
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+ GtkTextIter b;
+ GtkTextIter e;
+ gtk_text_buffer_get_selection_bounds(text->obj, &b, &e);
+ *begin = gtk_text_iter_get_offset(&b);
+ *end = gtk_text_iter_get_offset(&e);
+}
+
+int ui_textarea_length(UiText *text) {
+ GtkTextBuffer *buf = text->obj;
+ GtkTextIter start;
+ GtkTextIter end;
+ gtk_text_buffer_get_bounds(buf, &start, &end);
+ return gtk_text_iter_get_offset(&end);
+}
+
+void ui_textarea_remove(UiText *text, int begin, int end) {
+ GtkTextBuffer *buf = text->obj;
+ GtkTextIter ib;
+ GtkTextIter ie;
+ gtk_text_buffer_get_iter_at_offset(buf, &ib, begin);
+ gtk_text_buffer_get_iter_at_offset(buf, &ie, end);
+ gtk_text_buffer_delete(buf, &ib, &ie);
+}
+
+void ui_textarea_realize_event(GtkWidget *widget, gpointer data) {
+ gtk_widget_grab_focus(widget);
+}
+
+
+
+void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) {
+ UiText *value = textarea->var->value;
+
+ UiEvent e;
+ e.obj = textarea->obj;
+ e.window = e.obj->window;
+ e.document = textarea->ctx->document;
+ e.eventdata = value;
+ e.intval = 0;
+
+ if(textarea->onchange) {
+ textarea->onchange(&e, textarea->onchangedata);
+ }
+
+ if(value->observers) {
+ ui_notify_evt(value->observers, &e);
+ }
+}
+
+// undo manager functions
+
+void ui_textbuf_insert(
+ GtkTextBuffer *textbuffer,
+ GtkTextIter *location,
+ char *text,
+ int length,
+ void *data)
+{
+ UiVar *var = data;
+ UiText *value = var->value;
+ if(!value->undomgr) {
+ value->undomgr = ui_create_undomgr();
+ }
+ UiUndoMgr *mgr = value->undomgr;
+ if(!mgr->event) {
+ return;
+ }
+
+ if(mgr->cur) {
+ UiTextBufOp *elm = mgr->cur->next;
+ if(elm) {
+ mgr->cur->next = NULL;
+ mgr->end = mgr->cur;
+ while(elm) {
+ elm->prev = NULL;
+ UiTextBufOp *next = elm->next;
+ ui_free_textbuf_op(elm);
+ elm = next;
+ }
+ }
+
+ UiTextBufOp *last_op = mgr->cur;
+ if(
+ last_op->type == UI_TEXTBUF_INSERT &&
+ ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
+ {
+ // append text to last op
+ int ln = last_op->len;
+ char *newtext = malloc(ln + length + 1);
+ memcpy(newtext, last_op->text, ln);
+ memcpy(newtext+ln, text, length);
+ newtext[ln+length] = '\0';
+
+ last_op->text = newtext;
+ last_op->len = ln + length;
+ last_op->end += length;
+
+ return;
+ }
+ }
+
+ char *dpstr = malloc(length + 1);
+ memcpy(dpstr, text, length);
+ dpstr[length] = 0;
+
+ UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+ op->prev = NULL;
+ op->next = NULL;
+ op->type = UI_TEXTBUF_INSERT;
+ op->start = gtk_text_iter_get_offset(location);
+ op->end = op->start+length;
+ op->len = length;
+ op->text = dpstr;
+
+ cx_linked_list_add(
+ (void**)&mgr->begin,
+ (void**)&mgr->end,
+ offsetof(UiTextBufOp, prev),
+ offsetof(UiTextBufOp, next),
+ op);
+
+ mgr->cur = op;
+}
+
+void ui_textbuf_delete(
+ GtkTextBuffer *textbuffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ void *data)
+{
+ UiVar *var = data;
+ UiText *value = var->value;
+ if(!value->undomgr) {
+ value->undomgr = ui_create_undomgr();
+ }
+ UiUndoMgr *mgr = value->undomgr;
+ if(!mgr->event) {
+ return;
+ }
+
+ if(mgr->cur) {
+ UiTextBufOp *elm = mgr->cur->next;
+ if(elm) {
+ mgr->cur->next = NULL;
+ mgr->end = mgr->cur;
+ while(elm) {
+ elm->prev = NULL;
+ UiTextBufOp *next = elm->next;
+ ui_free_textbuf_op(elm);
+ elm = next;
+ }
+ }
+ }
+
+ char *text = gtk_text_buffer_get_text(value->obj, start, end, FALSE);
+
+ UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+ op->prev = NULL;
+ op->next = NULL;
+ op->type = UI_TEXTBUF_DELETE;
+ op->start = gtk_text_iter_get_offset(start);
+ op->end = gtk_text_iter_get_offset(end);
+ op->len = op->end - op->start;
+
+ char *dpstr = malloc(op->len + 1);
+ memcpy(dpstr, text, op->len);
+ dpstr[op->len] = 0;
+ op->text = dpstr;
+
+ cx_linked_list_add(
+ (void**)&mgr->begin,
+ (void**)&mgr->end,
+ offsetof(UiTextBufOp, prev),
+ offsetof(UiTextBufOp, next),
+ op);
+
+ mgr->cur = op;
+}
+
+UiUndoMgr* ui_create_undomgr() {
+ UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
+ mgr->begin = NULL;
+ mgr->end = NULL;
+ mgr->cur = NULL;
+ mgr->length = 0;
+ mgr->event = 1;
+ return mgr;
+}
+
+void ui_destroy_undomgr(UiUndoMgr *mgr) {
+ UiTextBufOp *op = mgr->begin;
+ while(op) {
+ UiTextBufOp *nextOp = op->next;
+ if(op->text) {
+ free(op->text);
+ }
+ free(op);
+ op = nextOp;
+ }
+ free(mgr);
+}
+
+void ui_free_textbuf_op(UiTextBufOp *op) {
+ if(op->text) {
+ free(op->text);
+ }
+ free(op);
+}
+
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) {
+ // return 1 if oldstr + newstr are one word
+
+ int has_space = 0;
+ for(int i=0;i<oldlen;i++) {
+ if(oldstr[i] < 33) {
+ has_space = 1;
+ break;
+ }
+ }
+
+ for(int i=0;i<newlen;i++) {
+ if(has_space && newstr[i] > 32) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void ui_text_undo(UiText *value) {
+ UiUndoMgr *mgr = value->undomgr;
+
+ if(mgr->cur) {
+ UiTextBufOp *op = mgr->cur;
+ mgr->event = 0;
+ switch(op->type) {
+ case UI_TEXTBUF_INSERT: {
+ GtkTextIter begin;
+ GtkTextIter end;
+ gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+ gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+ gtk_text_buffer_delete(value->obj, &begin, &end);
+ break;
+ }
+ case UI_TEXTBUF_DELETE: {
+ GtkTextIter begin;
+ GtkTextIter end;
+ gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+ gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+ gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
+ break;
+ }
+ }
+ mgr->event = 1;
+ mgr->cur = mgr->cur->prev;
+ }
+}
+
+void ui_text_redo(UiText *value) {
+ UiUndoMgr *mgr = value->undomgr;
+
+ UiTextBufOp *elm = NULL;
+ if(mgr->cur) {
+ if(mgr->cur->next) {
+ elm = mgr->cur->next;
+ }
+ } else if(mgr->begin) {
+ elm = mgr->begin;
+ }
+
+ if(elm) {
+ UiTextBufOp *op = elm;
+ mgr->event = 0;
+ switch(op->type) {
+ case UI_TEXTBUF_INSERT: {
+ GtkTextIter begin;
+ GtkTextIter end;
+ gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+ gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+ gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
+ break;
+ }
+ case UI_TEXTBUF_DELETE: {
+ GtkTextIter begin;
+ GtkTextIter end;
+ gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
+ gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
+ gtk_text_buffer_delete(value->obj, &begin, &end);
+ break;
+ }
+ }
+ mgr->event = 1;
+ mgr->cur = elm;
+ }
+}
+
+
+
+
+static UIWIDGET create_textfield(UiObject *obj, UiBool frameless, UiBool password, UiTextFieldArgs args) {
+ GtkWidget *textfield = gtk_entry_new();
+ ui_set_name_and_style(textfield, args.name, args.style_class);
+ ui_set_widget_groups(obj->ctx, textfield, args.groups);
+
+ UiObject* current = uic_current_obj(obj);
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+
+ UiTextField *uitext = malloc(sizeof(UiTextField));
+ uitext->obj = obj;
+ uitext->var = var;
+ uitext->onchange = args.onchange;
+ uitext->onchangedata = args.onchangedata;
+
+ g_signal_connect(
+ textfield,
+ "destroy",
+ G_CALLBACK(ui_textfield_destroy),
+ uitext);
+
+ if(args.width > 0) {
+ // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
+ gtk_entry_set_width_chars(GTK_ENTRY(textfield), args.width);
+#endif
+ }
+ if(frameless) {
+ // TODO: gtk2legacy workaroud
+ gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE);
+ }
+ if(password) {
+ gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE);
+ }
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, textfield, FALSE);
+
+ if(var) {
+ UiString *value = var->value;
+ if(value->value.ptr) {
+ ENTRY_SET_TEXT(textfield, value->value.ptr);
+ value->value.free(value->value.ptr);
+ value->value.ptr = NULL;
+ value->value.free = NULL;
+ }
+
+ value->get = ui_textfield_get;
+ value->set = ui_textfield_set;
+ value->value.ptr = NULL;
+ value->value.free = NULL;
+ value->obj = GTK_ENTRY(textfield);
+ }
+
+ if(args.onchange || var) {
+ g_signal_connect(
+ textfield,
+ "changed",
+ G_CALLBACK(ui_textfield_changed),
+ uitext);
+ }
+
+ return textfield;
+}
+
+UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) {
+ return create_textfield(obj, FALSE, FALSE, args);
+}
+
+UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {
+ return create_textfield(obj, TRUE, FALSE, args);
+}
+
+UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {
+ return create_textfield(obj, FALSE, TRUE, args);
+}
+
+
+void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
+ free(textfield);
+}
+
+void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
+ UiString *value = textfield->var->value;
+
+ UiEvent e;
+ e.obj = textfield->obj;
+ e.window = e.obj->window;
+ e.document = textfield->obj->ctx->document;
+ e.eventdata = value;
+ e.intval = 0;
+
+ if(textfield->onchange) {
+ textfield->onchange(&e, textfield->onchangedata);
+ }
+
+ if(textfield->var) {
+ ui_notify_evt(value->observers, &e);
+ }
+}
+
+
+char* ui_textfield_get(UiString *str) {
+ if(str->value.ptr) {
+ str->value.free(str->value.ptr);
+ }
+ str->value.ptr = g_strdup(ENTRY_GET_TEXT(str->obj));
+ str->value.free = (ui_freefunc)g_free;
+ return str->value.ptr;
+}
+
+void ui_textfield_set(UiString *str, const char *value) {
+ ENTRY_SET_TEXT(str->obj, value);
+ if(str->value.ptr) {
+ str->value.free(str->value.ptr);
+ str->value.ptr = NULL;
+ str->value.free = NULL;
+ }
+}
+
+// ----------------------- path textfield -----------------------
+
+// TODO: move to common
+static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) {
+ cxstring *pathelms;
+ size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms);
+
+ if (nelm == 0) {
+ *ret_nelm = 0;
+ return NULL;
+ }
+
+ UiPathElm* elms = (UiPathElm*)calloc(nelm, sizeof(UiPathElm));
+ size_t n = nelm;
+ int j = 0;
+ for (int i = 0; i < nelm; i++) {
+ cxstring c = pathelms[i];
+ if (c.length == 0) {
+ if (i == 0) {
+ c.length = 1;
+ }
+ else {
+ n--;
+ continue;
+ }
+ }
+
+ cxmutstr m = cx_strdup(c);
+ elms[j].name = m.ptr;
+ elms[j].name_len = m.length;
+
+ size_t elm_path_len = c.ptr + c.length - full_path;
+ cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len));
+ elms[j].path = elm_path.ptr;
+ elms[j].path_len = elm_path.length;
+
+ j++;
+ }
+ *ret_nelm = n;
+
+ return elms;
+}
+
+static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) {
+ for(int i=0;i<nelm;i++) {
+ free(elms[i].name);
+ free(elms[i].path);
+ }
+ free(elms);
+}
+
+static void ui_path_textfield_destroy(GtkWidget *object, UiPathTextField *pathtf) {
+ g_object_unref(pathtf->entry);
+ free(pathtf);
+}
+
+void ui_path_button_clicked(GtkWidget *widget, UiEventDataExt *event) {
+ UiPathTextField *pathtf = event->customdata1;
+ for(int i=0;i<event->value1;i++) {
+ if(i <= event->value0) {
+ WIDGET_REMOVE_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive");
+ } else {
+ WIDGET_ADD_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive");
+ }
+ }
+
+ UiPathElm *elm = event->customdata0;
+ cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len));
+ UiEvent evt;
+ evt.obj = event->obj;
+ evt.window = evt.obj->window;
+ evt.document = evt.obj->ctx->document;
+ evt.eventdata = elm->path;
+ evt.intval = event->value0;
+ event->callback(&evt, event->userdata);
+ free(path.ptr);
+}
+
+int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path) {
+ size_t full_path_len = strlen(full_path);
+ if(full_path_len == 0) {
+ return 1;
+ }
+
+ size_t nelm = 0;
+ UiPathElm* path_elm = pathtf->getpathelm(full_path, full_path_len, &nelm, pathtf->getpathelmdata);
+ if (!path_elm) {
+ return 1;
+ }
+
+ free(pathtf->current_path);
+ pathtf->current_path = strdup(full_path);
+
+ ui_pathelm_destroy(pathtf->current_pathelms, pathtf->current_nelm);
+ free(pathtf->current_path_buttons);
+ pathtf->current_path_buttons = calloc(nelm, sizeof(GtkWidget*));
+ pathtf->current_pathelms = path_elm;
+ pathtf->current_nelm = nelm;
+
+ return ui_pathtextfield_update_widget(pathtf);
+}
+
+static GtkWidget* ui_path_elm_button(UiPathTextField *pathtf, UiPathElm *elm, int i) {
+ cxmutstr name = cx_strdup(cx_strn(elm->name, elm->name_len));
+ GtkWidget *button = gtk_button_new_with_label(name.ptr);
+ pathtf->current_path_buttons[i] = button;
+ free(name.ptr);
+
+ if(pathtf->onactivate) {
+ UiEventDataExt *eventdata = malloc(sizeof(UiEventDataExt));
+ memset(eventdata, 0, sizeof(UiEventDataExt));
+ eventdata->callback = pathtf->onactivate;
+ eventdata->userdata = pathtf->onactivatedata;
+ eventdata->obj = pathtf->obj;
+ eventdata->customdata0 = elm;
+ eventdata->customdata1 = pathtf;
+ eventdata->value0 = i;
+ eventdata->value1 = pathtf->current_nelm;
+
+ g_signal_connect(
+ button,
+ "clicked",
+ G_CALLBACK(ui_path_button_clicked),
+ eventdata);
+
+ g_signal_connect(
+ button,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ eventdata);
+ }
+
+ return button;
+}
+
+static void ui_path_textfield_activate(GtkWidget *entry, UiPathTextField *pathtf) {
+ const gchar *text = ENTRY_GET_TEXT(pathtf->entry);
+ if(strlen(text) == 0) {
+ return;
+ }
+
+ UiObject *obj = pathtf->obj;
+
+ if(ui_pathtextfield_update(pathtf, text)) {
+ return;
+ }
+
+ if(pathtf->onactivate) {
+ UiEvent evt;
+ evt.obj = obj;
+ evt.window = obj->window;
+ evt.document = obj->ctx->document;
+ evt.eventdata = (char*)text;
+ evt.intval = -1;
+ pathtf->onactivate(&evt, pathtf->onactivatedata);
+ }
+}
+
+#if GTK_MAJOR_VERSION >= 4
+
+static void pathbar_show_hbox(GtkWidget *widget, UiPathTextField *pathtf) {
+ if(pathtf->current_path) {
+ gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+ ENTRY_SET_TEXT(pathtf->entry, pathtf->current_path);
+ }
+}
+
+static gboolean ui_path_textfield_key_controller(
+ GtkEventControllerKey* self,
+ guint keyval,
+ guint keycode,
+ GdkModifierType state,
+ UiPathTextField *pathtf)
+{
+ if(keyval == GDK_KEY_Escape) {
+ pathbar_show_hbox(NULL, pathtf);
+ }
+ return FALSE;
+}
+
+UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ UiPathTextField *pathtf = malloc(sizeof(UiPathTextField));
+ memset(pathtf, 0, sizeof(UiPathTextField));
+ pathtf->obj = obj;
+ pathtf->getpathelm = args.getpathelm;
+ pathtf->getpathelmdata = args.getpathelmdata;
+ pathtf->onactivate = args.onactivate;
+ pathtf->onactivatedata = args.onactivatedata;
+ pathtf->ondragcomplete = args.ondragcomplete;
+ pathtf->ondragcompletedata = args.ondragcompletedata;
+ pathtf->ondragstart = args.ondragstart;
+ pathtf->ondragstartdata = args.ondragstartdata;
+ pathtf->ondrop = args.ondrop;
+ pathtf->ondropdata = args.ondropsdata;
+
+ if(!pathtf->getpathelm) {
+ pathtf->getpathelm = default_pathelm_func;
+ pathtf->getpathelmdata = NULL;
+ }
+
+ pathtf->stack = gtk_stack_new();
+ gtk_widget_set_name(pathtf->stack, "path-textfield-box");
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, pathtf->stack, FALSE);
+
+ pathtf->entry_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ pathtf->entry = gtk_entry_new();
+ gtk_box_append(GTK_BOX(pathtf->entry_box), pathtf->entry);
+ gtk_widget_set_hexpand(pathtf->entry, TRUE);
+
+ GtkWidget *cancel_button = gtk_button_new_from_icon_name("window-close-symbolic");
+ gtk_widget_add_css_class(cancel_button, "flat");
+ gtk_widget_add_css_class(cancel_button, "pathbar-extra-button");
+ gtk_box_append(GTK_BOX(pathtf->entry_box), cancel_button);
+ g_signal_connect(
+ cancel_button,
+ "clicked",
+ G_CALLBACK(pathbar_show_hbox),
+ pathtf);
+
+ gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+ g_object_ref(pathtf->entry); // for compatibility with older pathbar version
+ g_signal_connect(
+ pathtf->entry,
+ "activate",
+ G_CALLBACK(ui_path_textfield_activate),
+ pathtf);
+
+ GtkEventController *entry_cancel = gtk_event_controller_key_new();
+ gtk_widget_add_controller(pathtf->entry, entry_cancel);
+ g_signal_connect(entry_cancel, "key-pressed", G_CALLBACK(ui_path_textfield_key_controller), pathtf);
+
+ gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+ if (var) {
+ UiString* value = (UiString*)var->value;
+ value->obj = pathtf;
+ value->get = ui_path_textfield_get;
+ value->set = ui_path_textfield_set;
+
+ if(value->value.ptr) {
+ char *str = strdup(value->value.ptr);
+ ui_string_set(value, str);
+ free(str);
+ }
+ }
+
+ return pathtf->stack;
+}
+
+static void pathbar_pressed(
+ GtkGestureClick* self,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ UiPathTextField *pathtf)
+{
+ gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+ gtk_widget_grab_focus(pathtf->entry);
+}
+
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf) {
+ // recreate button hbox
+ if(pathtf->hbox) {
+ gtk_stack_remove(GTK_STACK(pathtf->stack), pathtf->hbox);
+ }
+ pathtf->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_set_homogeneous(GTK_BOX(pathtf->hbox), FALSE);
+ gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+ gtk_widget_set_name(pathtf->hbox, "pathbar");
+
+ // add buttons for path elements
+ for (int i=0;i<pathtf->current_nelm;i++) {
+ UiPathElm *elm = &pathtf->current_pathelms[i];
+
+ GtkWidget *button = ui_path_elm_button(pathtf, elm, i);
+ gtk_widget_add_css_class(button, "flat");
+
+ gtk_box_append(GTK_BOX(pathtf->hbox), button);
+
+ if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), CX_STR("/"))) {
+ GtkWidget *path_separator = gtk_label_new("/");
+ gtk_widget_add_css_class(path_separator, "pathbar-button-inactive");
+ gtk_box_append(GTK_BOX(pathtf->hbox), path_separator);
+ }
+ }
+ gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+
+ // create a widget for receiving button press events
+ GtkWidget *event_area = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ GtkGesture *handler = gtk_gesture_click_new();
+ gtk_widget_add_controller(event_area, GTK_EVENT_CONTROLLER(handler));
+ g_signal_connect(
+ handler,
+ "pressed",
+ G_CALLBACK(pathbar_pressed),
+ pathtf);
+ gtk_widget_set_hexpand(event_area, TRUE);
+ gtk_widget_set_vexpand(event_area, TRUE);
+ gtk_box_append(GTK_BOX(pathtf->hbox), event_area);
+
+ return 0;
+}
+
+#else
+
+static gboolean path_textfield_btn_pressed(GtkWidget *widget, GdkEventButton *event, UiPathTextField *pathtf) {
+ gtk_box_pack_start(GTK_BOX(pathtf->hbox), pathtf->entry, TRUE, TRUE, 0);
+ gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox);
+ pathtf->buttonbox = NULL;
+
+ gtk_widget_show(pathtf->entry);
+ gtk_widget_grab_focus(pathtf->entry);
+
+ return TRUE;
+}
+
+static gboolean ui_path_textfield_key_press(GtkWidget *self, GdkEventKey *event, UiPathTextField *pathtf) {
+ if (event->keyval == GDK_KEY_Escape) {
+ // reset GtkEntry value
+ gtk_entry_set_text(GTK_ENTRY(self), pathtf->current_path);
+ const gchar *text = gtk_entry_get_text(GTK_ENTRY(self));
+ ui_pathtextfield_update(pathtf, text);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static GtkWidget* create_path_button_box() {
+ GtkWidget *bb = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(bb), GTK_BUTTONBOX_EXPAND); // linked style
+ gtk_box_set_homogeneous(GTK_BOX(bb), FALSE);
+ gtk_box_set_spacing(GTK_BOX(bb), 0);
+ return bb;
+}
+
+UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
+ UiObject* current = uic_current_obj(obj);
+
+ UiPathTextField *pathtf = malloc(sizeof(UiPathTextField));
+ memset(pathtf, 0, sizeof(UiPathTextField));
+ pathtf->obj = obj;
+ pathtf->getpathelm = args.getpathelm;
+ pathtf->getpathelmdata = args.getpathelmdata;
+ pathtf->onactivate = args.onactivate;
+ pathtf->onactivatedata = args.onactivatedata;
+ pathtf->ondragcomplete = args.ondragcomplete;
+ pathtf->ondragcompletedata = args.ondragcompletedata;
+ pathtf->ondragstart = args.ondragstart;
+ pathtf->ondragstartdata = args.ondragstartdata;
+ pathtf->ondrop = args.ondrop;
+ pathtf->ondropdata = args.ondropsdata;
+
+ if(!pathtf->getpathelm) {
+ pathtf->getpathelm = default_pathelm_func;
+ pathtf->getpathelmdata = NULL;
+ }
+
+ // top level container for the path textfield is a GtkEventBox
+ // the event box is needed to handle background button presses
+ GtkWidget *eventbox = gtk_event_box_new();
+ g_signal_connect(
+ eventbox,
+ "button-press-event",
+ G_CALLBACK(path_textfield_btn_pressed),
+ pathtf);
+ g_signal_connect(
+ eventbox,
+ "destroy",
+ G_CALLBACK(ui_path_textfield_destroy),
+ pathtf);
+
+ UI_APPLY_LAYOUT1(current, args);
+ current->container->add(current->container, eventbox, FALSE);
+
+ // hbox as parent for the GtkEntry and GtkButtonBox
+ GtkWidget *hbox = ui_gtk_hbox_new(0);
+ pathtf->hbox = hbox;
+ gtk_container_add(GTK_CONTAINER(eventbox), hbox);
+ gtk_widget_set_name(hbox, "path-textfield-box");
+
+ // create GtkEntry, that is also visible by default (with input yet)
+ pathtf->entry = gtk_entry_new();
+ g_object_ref(G_OBJECT(pathtf->entry));
+ gtk_box_pack_start(GTK_BOX(hbox), pathtf->entry, TRUE, TRUE, 0);
+
+ g_signal_connect(
+ pathtf->entry,
+ "activate",
+ G_CALLBACK(ui_path_textfield_activate),
+ pathtf);
+ g_signal_connect(
+ pathtf->entry,
+ "key-press-event",
+ G_CALLBACK(ui_path_textfield_key_press),
+ pathtf);
+
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+ if (var) {
+ UiString* value = (UiString*)var->value;
+ value->obj = pathtf;
+ value->get = ui_path_textfield_get;
+ value->set = ui_path_textfield_set;
+
+ if(value->value.ptr) {
+ char *str = strdup(value->value.ptr);
+ ui_string_set(value, str);
+ free(str);
+ }
+ }
+
+ return hbox;
+}
+
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf) {
+ GtkWidget *buttonbox = create_path_button_box();
+
+ // switch from entry to buttonbox or remove current buttonbox
+ if(pathtf->buttonbox) {
+ gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox);
+ } else {
+ gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->entry);
+ }
+ gtk_box_pack_start(GTK_BOX(pathtf->hbox), buttonbox, FALSE, FALSE, 0);
+ pathtf->buttonbox = buttonbox;
+
+ for (int i=0;i<pathtf->current_nelm;i++) {
+ UiPathElm *elm = &pathtf->current_pathelms[i];
+ GtkWidget *button = ui_path_elm_button(pathtf, elm, i);
+ gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 0);
+ }
+
+ gtk_widget_show_all(buttonbox);
+
+ return 0;
+}
+
+#endif
+
+char* ui_path_textfield_get(UiString *str) {
+ if(str->value.ptr) {
+ str->value.free(str->value.ptr);
+ }
+ UiPathTextField *tf = str->obj;
+ str->value.ptr = g_strdup(ENTRY_GET_TEXT(tf->entry));
+ str->value.free = (ui_freefunc)g_free;
+ return str->value.ptr;
+}
+
+void ui_path_textfield_set(UiString *str, const char *value) {
+ UiPathTextField *tf = str->obj;
+ ENTRY_SET_TEXT(tf->entry, value);
+ ui_pathtextfield_update(tf, value);
+ if(str->value.ptr) {
+ str->value.free(str->value.ptr);
+ str->value.ptr = NULL;
+ str->value.free = NULL;
+ }
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TEXT_H
+#define TEXT_H
+
+#include "../ui/text.h"
+#include "toolkit.h"
+#include <cx/linked_list.h>
+#include "../common/context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UI_TEXTBUF_INSERT 0
+#define UI_TEXTBUF_DELETE 1
+
+typedef struct UiTextBufOp UiTextBufOp;
+struct UiTextBufOp {
+ UiTextBufOp *prev;
+ UiTextBufOp *next;
+ int type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE
+ int start;
+ int end;
+ int len;
+ char *text;
+};
+
+typedef struct UiUndoMgr {
+ UiTextBufOp *begin;
+ UiTextBufOp *end;
+ UiTextBufOp *cur;
+ int length;
+ int event;
+} UiUndoMgr;
+
+typedef struct UiTextArea {
+ UiObject *obj;
+ UiContext *ctx;
+ UiVar *var;
+ int last_selection_state;
+ ui_callback onchange;
+ void *onchangedata;
+} UiTextArea;
+
+typedef struct UiTextField {
+ UiObject *obj;
+ UiVar *var;
+ ui_callback onchange;
+ void *onchangedata;
+} UiTextField;
+
+typedef struct UiPathTextField {
+ UiObject *obj;
+
+ GtkWidget *stack;
+ GtkWidget *hbox;
+ GtkWidget *entry_box;
+ GtkWidget *entry;
+#if GTK_MAJOR_VERSION == 3
+ GtkWidget *buttonbox;
+#endif
+
+ char *current_path;
+ UiPathElm *current_pathelms;
+ GtkWidget **current_path_buttons;
+ size_t current_nelm;
+
+ ui_pathelm_func getpathelm;
+ void* getpathelmdata;
+
+ ui_callback onactivate;
+ void* onactivatedata;
+
+ ui_callback ondragstart;
+ void* ondragstartdata;
+ ui_callback ondragcomplete;
+ void* ondragcompletedata;
+ ui_callback ondrop;
+ void* ondropdata;
+} UiPathTextField;
+
+UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var);
+void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea);
+
+char* ui_textarea_get(UiText *text);
+void ui_textarea_set(UiText *text, const char *str);
+char* ui_textarea_getsubstr(UiText *text, int begin, int end);
+void ui_textarea_insert(UiText *text, int pos, char *str);
+void ui_textarea_setposition(UiText *text, int pos);
+int ui_textarea_position(UiText *text);
+void ui_textarea_selection(UiText *text, int *begin, int *end);
+int ui_textarea_length(UiText *text);
+void ui_textarea_remove(UiText *text, int begin, int end);
+
+void ui_textarea_realize_event(GtkWidget *widget, gpointer data);
+void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea);
+
+void ui_textbuf_insert(
+ GtkTextBuffer *textbuffer,
+ GtkTextIter *location,
+ char *text,
+ int len,
+ void *data);
+void ui_textbuf_delete(
+ GtkTextBuffer *textbuffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ void *data);
+UiUndoMgr* ui_create_undomgr();
+void ui_destroy_undomgr(UiUndoMgr *mgr);
+void ui_free_textbuf_op(UiTextBufOp *op);
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen);
+
+void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield);
+void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield);
+
+char* ui_textfield_get(UiString *str);
+void ui_textfield_set(UiString *str, const char *value);
+
+int ui_pathtextfield_update(UiPathTextField* pathtf, const char *full_path);
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf);
+char* ui_path_textfield_get(UiString *str);
+void ui_path_textfield_set(UiString *str, const char *value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEXT_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "toolbar.h"
+#include "menu.h"
+#include "button.h"
+#include "icon.h"
+#include "list.h"
+#include <cx/mempool.h>
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+#include "../common/context.h"
+
+
+#if UI_GTK2 || UI_GTK3
+
+GtkWidget* ui_create_toolbar(UiObject *obj) {
+ GtkWidget *toolbar = gtk_toolbar_new();
+#ifdef UI_GTK3
+ gtk_style_context_add_class(
+ gtk_widget_get_style_context(toolbar),
+ GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
+#endif
+
+ CxMap *items = uic_get_toolbar_items();
+ CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
+ CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
+ CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
+
+ ui_toolbar_add_items(obj, toolbar, items, left_defaults);
+ ui_toolbar_add_items(obj, toolbar, items, center_defaults);
+ ui_toolbar_add_items(obj, toolbar, items, right_defaults);
+
+ /*
+ GtkToolbar *tb = GTK_TOOLBAR(toolbar);
+ CxIterator i = cxListIterator(defaults);
+ cx_foreach(char *, def, i) {
+ UiToolItemI *item = cxMapGet(toolbar_items, def);
+ if(item) {
+ item->add_to(tb, item, obj);
+ } else if(!strcmp(def, "@separator")) {
+ gtk_toolbar_insert(tb, gtk_separator_tool_item_new(), -1);
+ } else {
+ fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", def);
+ }
+ }
+ */
+
+ return toolbar;
+}
+
+static void create_item(UiObject *obj, GtkWidget *toolbar, UiToolbarItemI *i) {
+ GtkToolbar *tb = GTK_TOOLBAR(toolbar);
+ switch(i->type) {
+ case UI_TOOLBAR_ITEM: {
+ add_toolitem_widget(tb, (UiToolbarItem*)i, obj);
+ break;
+ }
+ case UI_TOOLBAR_TOGGLEITEM: {
+ add_toolitem_toggle_widget(tb, (UiToolbarToggleItem*)i, obj);
+ break;
+ }
+ case UI_TOOLBAR_MENU: {
+ add_toolitem_menu_widget(tb, (UiToolbarMenuItem*)i, obj);
+ break;
+ }
+ default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type);
+ }
+}
+
+void ui_toolbar_add_items(UiObject *obj, GtkWidget *toolbar, CxMap *items, CxList *defaults) {
+ // add pre-configured items
+ CxIterator i = cxListIterator(defaults);
+ cx_foreach(char*, def, i) {
+ UiToolbarItemI* item = uic_toolbar_get_item(def);
+ if (!item) {
+ fprintf(stderr, "unknown toolbar item: %s\n", def);
+ continue;
+ }
+ create_item(obj, toolbar, item);
+ }
+}
+
+static void set_toolbutton_icon(GtkToolItem *item, const char *icon_name) {
+#if GTK_MAJOR_VERSION >= 3
+ gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), icon_name);
+#else
+ UiIcon *icon = ui_icon(icon_name, 24);
+ if(icon) {
+ GdkPixbuf *pixbuf = ui_icon_pixbuf(icon);
+ if(pixbuf) {
+ GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
+ gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(item), image);
+ }
+ }
+#endif
+}
+
+void add_toolitem_widget(GtkToolbar *tb, UiToolbarItem *item, UiObject *obj) {
+ GtkToolItem *button;
+ if(item->args.stockid) {
+#ifdef UI_GTK2
+ button = gtk_tool_button_new_from_stock(item->args.stockid);
+#else
+ // TODO: gtk3 stock
+ button = gtk_tool_button_new(NULL, item->args.label);
+#endif
+ } else {
+ button = gtk_tool_button_new(NULL, item->args.label);
+ }
+
+ gtk_tool_item_set_homogeneous(button, FALSE);
+ if(item->args.icon) {
+ set_toolbutton_icon(button, item->args.icon);
+ }
+ gtk_tool_item_set_is_important(button, TRUE);
+
+ ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups);
+
+ if(item->args.onclick) {
+ UiEventData *event = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiEventData));
+ event->obj = obj;
+ event->callback = item->args.onclick;
+ event->userdata = item->args.onclickdata;
+ event->customdata = NULL;
+ event->value = 0;
+
+ g_signal_connect(
+ button,
+ "clicked",
+ G_CALLBACK(ui_button_clicked),
+ event);
+ }
+
+ gtk_toolbar_insert(tb, button, -1);
+
+ /*
+ if(item->groups) {
+ uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups);
+ }
+ */
+}
+
+void add_toolitem_toggle_widget(GtkToolbar *tb, UiToolbarToggleItem *item, UiObject *obj) {
+ GtkToolItem *button;
+ if(item->args.stockid) {
+#ifdef UI_GTK2
+ button = gtk_toggle_tool_button_new_from_stock(item->args.stockid);
+#else
+ button = gtk_toggle_tool_button_new_from_stock(item->args.stockid); // TODO: gtk3 stock
+#endif
+ } else {
+ button = gtk_toggle_tool_button_new();
+ gtk_tool_item_set_homogeneous(button, FALSE);
+ if(item->args.label) {
+ gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), item->args.label);
+ }
+ if(item->args.icon) {
+ set_toolbutton_icon(button, item->args.icon);
+ }
+ }
+ ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups);
+
+ UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, item->args.varname, UI_VAR_INTEGER);
+ if(var) {
+ UiInteger *i = (UiInteger*)var->value;
+ if(i) {
+ i->get = ui_tool_toggle_button_get;
+ i->set = ui_tool_toggle_button_set;
+ i->obj = button;
+
+ if(i->value != 0) {
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(button), TRUE);
+ }
+ }
+ }
+
+ UiVarEventData *event = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiVarEventData));
+ event->obj = obj;
+ event->callback = item->args.onchange;
+ event->userdata = item->args.onchangedata;
+ event->var = var;
+
+ g_signal_connect(
+ button,
+ "toggled",
+ G_CALLBACK(ui_tool_button_toggled),
+ event);
+
+ // add item to toolbar
+ gtk_toolbar_insert(tb, button, -1);
+
+ /*
+ if(item->groups) {
+ uic_add_group_widget(obj->ctx, button, (ui_enablefunc)ui_set_enabled, item->groups);
+ }
+ */
+}
+
+void ui_tool_button_toggled(GtkToggleToolButton *widget, UiVarEventData *event) {
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = gtk_toggle_tool_button_get_active(widget);
+
+ if(event->callback) {
+ event->callback(&e, event->userdata);
+ }
+
+ UiVar *var = event->var;
+ UiInteger *i = var ? var->value : NULL;
+
+ if(i) {
+ ui_notify_evt(i->observers, &e);
+ }
+}
+
+int64_t ui_tool_toggle_button_get(UiInteger *integer) {
+ integer->value = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(integer->obj));
+ return integer->value;
+}
+
+void ui_tool_toggle_button_set(UiInteger *integer, int64_t value) {
+ gboolean s = value != 0 ? TRUE : FALSE;
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(integer->obj), s);
+ integer->value = s;
+}
+
+
+
+typedef struct UiToolbarMenuWidget {
+ GtkWidget *button;
+ GtkMenu *menu;
+} UiToolbarMenuWidget;
+
+static void ui_toolbar_menubutton_clicked(GtkWidget *widget, UiToolbarMenuWidget *menu) {
+ int x;
+ gtk_menu_popup_at_widget(menu->menu, menu->button, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
+}
+
+static void ui_toolbar_menubutton_destroy(GtkWidget *widget, UiToolbarMenuWidget *menu) {
+ free(menu);
+}
+
+void add_toolitem_menu_widget(GtkToolbar *tb, UiToolbarMenuItem *item, UiObject *obj) {
+ GtkToolItem *button;
+ if(item->args.stockid) {
+#ifdef UI_GTK2
+ button = gtk_tool_button_new_from_stock(item->args.stockid);
+#else
+ // TODO: gtk3 stock
+ button = gtk_tool_button_new(NULL, item->args.label);
+#endif
+ } else {
+ button = gtk_tool_button_new(NULL, item->args.label);
+ }
+
+ gtk_tool_item_set_homogeneous(button, FALSE);
+ if(item->args.icon) {
+ set_toolbutton_icon(button, item->args.icon);
+ }
+ gtk_tool_item_set_is_important(button, TRUE);
+
+ gtk_toolbar_insert(tb, button, -1);
+
+ // menu
+ GtkWidget *menu_widget = gtk_menu_new();
+ ui_add_menu_items(menu_widget, 0, &item->menu, obj);
+ gtk_widget_show_all(menu_widget);
+
+ UiToolbarMenuWidget *tbmenu = malloc(sizeof(UiToolbarMenuWidget));
+ tbmenu->button = GTK_WIDGET(button);
+ tbmenu->menu = GTK_MENU(menu_widget);
+
+ g_signal_connect(
+ button,
+ "clicked",
+ G_CALLBACK(ui_toolbar_menubutton_clicked),
+ tbmenu);
+
+ g_signal_connect(
+ button,
+ "destroy",
+ G_CALLBACK(ui_toolbar_menubutton_destroy),
+ tbmenu);
+}
+
+
+
+
+// deprecated / unsupported
+/*
+void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj) {
+ UiModel *modelinfo = ui_model(obj->ctx, UI_STRING, "", -1);
+ modelinfo->getvalue = cb->getvalue;
+ UiListModel *model = ui_list_model_new(obj, cb->var, modelinfo);
+
+ GtkWidget *combobox = ui_create_combobox(obj, model, cb->callback, cb->userdata);
+ GtkToolItem *item = gtk_tool_item_new();
+ gtk_container_add(GTK_CONTAINER(item), combobox);
+ gtk_toolbar_insert(tb, item, -1);
+}
+
+void add_toolbar_combobox_nv(GtkToolbar *tb, UiToolbarComboBoxNV *cb, UiObject *obj) {
+ UiVar *var = uic_create_var(obj->ctx, cb->listname, UI_VAR_LIST);
+ if(var) {
+ UiModel *modelinfo = ui_model(obj->ctx, UI_STRING, "", -1);
+ modelinfo->getvalue = cb->getvalue;
+ UiListModel *model = ui_list_model_new(obj, var, modelinfo);
+
+ GtkWidget *combobox = ui_create_combobox(obj, model, cb->callback, cb->userdata);
+ GtkToolItem *item = gtk_tool_item_new();
+ gtk_container_add(GTK_CONTAINER(item), combobox);
+ gtk_toolbar_insert(tb, item, -1);
+ }
+}
+*/
+
+
+
+#ifdef UI_GTK3
+
+GtkWidget* ui_create_headerbar(UiObject *obj) {
+ GtkWidget *headerbar = gtk_header_bar_new();
+
+ CxMap *items = uic_get_toolbar_items();
+ CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
+ CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
+ CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
+
+ ui_toolbar_headerbar_add_items(obj, headerbar, items, left_defaults);
+ ui_toolbar_headerbar_add_items(obj, headerbar, items, center_defaults);
+ ui_toolbar_headerbar_add_items(obj, headerbar, items, right_defaults);
+
+ return headerbar;
+}
+
+static void hb_create_item(UiObject *obj, GtkWidget *toolbar, UiToolbarItemI *i) {
+ GtkHeaderBar *tb = GTK_HEADER_BAR(toolbar);
+ switch(i->type) {
+ case UI_TOOLBAR_ITEM: {
+ add_headerbar_item_widget(tb, (UiToolbarItem*)i, obj);
+ break;
+ }
+ case UI_TOOLBAR_TOGGLEITEM: {
+ add_headerbar_item_toggle_widget(tb, (UiToolbarToggleItem*)i, obj);
+ break;
+ }
+ case UI_TOOLBAR_MENU: {
+ add_headerbar_item_menu_widget(tb, (UiToolbarMenuItem*)i, obj);
+ break;
+ }
+ default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type);
+ }
+}
+
+
+void ui_toolbar_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults) {
+ // add pre-configured items
+ CxIterator i = cxListIterator(defaults);
+ cx_foreach(char*, def, i) {
+ UiToolbarItemI* item = uic_toolbar_get_item(def);
+ if (!item) {
+ fprintf(stderr, "unknown toolbar item: %s\n", def);
+ continue;
+ }
+ hb_create_item(obj, headerbar, item);
+ }
+}
+
+void add_headerbar_item_widget(GtkHeaderBar *hb, UiToolbarItem *item, UiObject *obj) {
+ GtkWidget *button = gtk_button_new_with_label(item->args.label);
+ if(item->args.icon) {
+ ui_button_set_icon_name(button, item->args.icon);
+ }
+ ui_set_widget_groups(obj->ctx, button, item->args.groups);
+
+ gtk_header_bar_pack_start(hb, button);
+
+}
+
+void add_headerbar_item_toggle_widget(GtkHeaderBar *hb, UiToolbarToggleItem *item, UiObject *obj) {
+
+}
+
+void add_headerbar_item_menu_widget(GtkHeaderBar *hb, UiToolbarMenuItem *item, UiObject *obj) {
+
+}
+
+#endif /* UI_GTK3 */
+
+#endif /* UI_GTK2 || UI_GTK3 */
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TOOLBAR_H
+#define TOOLBAR_H
+
+#include "../ui/toolbar.h"
+#include "../common/toolbar.h"
+#include <cx/map.h>
+#include <cx/list.h>
+
+#include "list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if UI_GTK2 || UI_GTK3
+
+typedef struct UiToolItemI UiToolItemI;
+typedef struct UiToolItem UiToolItem;
+typedef struct UiStToolItem UiStToolItem;
+typedef struct UiToggleToolItem UiToggleToolItem;
+
+typedef struct UiToolbarComboBox UiToolbarComboBox;
+typedef struct UiToolbarComboBoxNV UiToolbarComboBoxNV;
+
+typedef void(*ui_toolbar_add_f)(GtkToolbar*, UiToolItemI*, UiObject*);
+
+struct UiToolItemI {
+ ui_toolbar_add_f add_to;
+};
+
+struct UiToolItem {
+ UiToolItemI item;
+ const char *label;
+ const char *image;
+ ui_callback callback;
+ void *userdata;
+ const char *varname;
+ CxList *groups;
+ int isimportant;
+};
+
+struct UiStToolItem {
+ UiToolItemI item;
+ const char *stockid;
+ ui_callback callback;
+ void *userdata;
+ const char *varname;
+ CxList *groups;
+ int isimportant;
+};
+
+struct UiToggleToolItem {
+ UiToolItemI item;
+ const char *label;
+ const char *image;
+ const char *stockid;
+ UiInteger *value;
+ const char *var;
+ CxList *groups;
+ int isimportant;
+};
+
+struct UiToolbarComboBox {
+ UiToolItemI item;
+ UiVar *var;
+ ui_getvaluefunc getvalue;
+ ui_callback callback;
+ void *userdata;
+};
+
+struct UiToolbarComboBoxNV {
+ UiToolItemI item;
+ char *listname;
+ ui_getvaluefunc getvalue;
+ ui_callback callback;
+ void *userdata;
+};
+
+
+void ui_toolitem_vstgr(
+ char *name,
+ char *stockid,
+ int isimportant,
+ ui_callback f,
+ void *userdata,
+ va_list ap);
+
+GtkWidget* ui_create_toolbar(UiObject *obj);
+
+void ui_toolbar_add_items(UiObject *obj, GtkWidget *toolbar, CxMap *items, CxList *defaults);
+
+void add_toolitem_widget(GtkToolbar *tb, UiToolbarItem *item, UiObject *obj);
+void add_toolitem_toggle_widget(GtkToolbar *tb, UiToolbarToggleItem *item, UiObject *obj);
+void add_toolitem_menu_widget(GtkToolbar *tb, UiToolbarMenuItem *item, UiObject *obj);
+
+void ui_tool_button_toggled(GtkToggleToolButton *widget, UiVarEventData *event);
+int64_t ui_tool_toggle_button_get(UiInteger *integer);
+void ui_tool_toggle_button_set(UiInteger *integer, int64_t value);
+
+GtkWidget* ui_create_headerbar(UiObject *obj);
+
+void ui_toolbar_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults);
+
+void add_headerbar_item_widget(GtkHeaderBar *hb, UiToolbarItem *item, UiObject *obj);
+void add_headerbar_item_toggle_widget(GtkHeaderBar *hb, UiToolbarToggleItem *item, UiObject *obj);
+void add_headerbar_item_menu_widget(GtkHeaderBar *hb, UiToolbarMenuItem *item, UiObject *obj);
+
+
+/*
+void add_toolbar_combobox(GtkToolbar *tb, UiToolbarComboBox *cb, UiObject *obj);
+void add_toolbar_combobox_nv(GtkToolbar *tb, UiToolbarComboBoxNV *cb, UiObject *obj);
+void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e);
+void ui_combobox_update(UiEvent *event, void *combobox);
+*/
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TOOLBAR_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "toolkit.h"
+#include "toolbar.h"
+#include "icon.h"
+#include "../common/document.h"
+#include "../common/properties.h"
+#include "../common/menu.h"
+#include "../common/toolbar.h"
+#include "../common/threadpool.h"
+
+#include <cx/utils.h>
+#include <cx/string.h>
+#include <cx/printf.h>
+
+#include <pthread.h>
+
+#ifdef UI_APPLICATION
+UI_APPLICATION app;
+#endif
+
+static const char *application_name;
+
+static ui_callback startup_func;
+static void *startup_data;
+static ui_callback open_func;
+void *open_data;
+static ui_callback exit_func;
+void *exit_data;
+
+static ui_callback appclose_fnc;
+static void *appclose_udata;
+
+static UiObject *active_window;
+
+static int scale_factor = 1;
+
+UIEXPORT void ui_init(const char *appname, int argc, char **argv) {
+ application_name = appname;
+ uic_init_global_context();
+
+#if GTK_MAJOR_VERSION >= 4
+ gtk_init();
+#else
+ gtk_init(&argc, &argv);
+#endif
+
+ ui_css_init();
+ uic_docmgr_init();
+ uic_menu_init();
+ uic_toolbar_init();
+ ui_image_init();
+ uic_load_app_properties();
+
+#if GTK_MAJOR_VERSION >= 4
+ scale_factor = 1; // TODO
+#elif defined(UI_SUPPORTS_SCALE)
+ scale_factor = gdk_monitor_get_scale_factor(
+ gdk_display_get_primary_monitor(gdk_display_get_default()));
+#endif
+}
+
+const char* ui_appname() {
+ return application_name;
+}
+
+void ui_onstartup(ui_callback f, void *userdata) {
+ startup_func = f;
+ startup_data = userdata;
+}
+
+void ui_onopen(ui_callback f, void *userdata) {
+ open_func = f;
+ open_data = userdata;
+}
+
+void ui_onexit(ui_callback f, void *userdata) {
+ exit_func = f;
+ exit_data = userdata;
+}
+
+
+#ifndef UI_GTK2
+static void app_startup(GtkApplication* app, gpointer userdata) {
+ if(startup_func) {
+ startup_func(NULL, startup_data);
+ }
+}
+
+static void app_activate(GtkApplication* app, gpointer userdata) {
+ printf("activate\n");
+}
+#endif
+
+void ui_main() {
+#ifdef UI_APPLICATION
+ cxmutstr appid = cx_asprintf(
+ "ui.%s",
+ application_name ? application_name : "application1");
+ app = UI_APPLICATION_NEW(appid.ptr);
+ g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
+ g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
+ g_application_run(G_APPLICATION (app), 0, NULL);
+ g_object_unref (app);
+
+ free(appid.ptr);
+#else
+ if(startup_func) {
+ startup_func(NULL, startup_data);
+ }
+ gtk_main();
+#endif
+ if(exit_func) {
+ exit_func(NULL, exit_data);
+ }
+ uic_store_app_properties();
+}
+
+#ifndef UI_GTK2
+void ui_app_quit() {
+ g_application_quit(G_APPLICATION(app));
+}
+
+GtkApplication* ui_get_application() {
+ return GTK_APPLICATION(app);
+}
+#endif
+
+void ui_show(UiObject *obj) {
+ gboolean visible = gtk_widget_is_visible(obj->widget);
+
+ uic_check_group_widgets(obj->ctx);
+#if GTK_MAJOR_VERSION >= 4
+ gtk_window_present(GTK_WINDOW(obj->widget));
+#elif GTK_MAJOR_VERSION <= 3
+ gtk_widget_show_all(obj->widget);
+#endif
+
+ if(!visible) {
+ obj->ref++;
+ }
+}
+
+void ui_close(UiObject *obj) {
+ uic_context_prepare_close(obj->ctx);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_close(GTK_WINDOW(obj->widget));
+#else
+ gtk_widget_destroy(obj->widget);
+#endif
+}
+
+
+static gboolean ui_job_finished(void *data) {
+ UiJob *job = data;
+
+ UiEvent event;
+ event.obj = job->obj;
+ event.window = job->obj->window;
+ event.document = job->obj->ctx->document;
+ event.intval = 0;
+ event.eventdata = NULL;
+
+ job->finish_callback(&event, job->finish_data);
+ free(job);
+ return FALSE;
+}
+
+static void* ui_jobthread(void *data) {
+ UiJob *job = data;
+ int result = job->job_func(job->job_data);
+ if(!result && job->finish_callback) {
+ g_idle_add(ui_job_finished, job);
+ } else {
+ free(job);
+ }
+ return NULL;
+}
+
+static gboolean ui_idle_func(void *data) {
+ UiJob *job = data;
+ job->job_func(job->job_data);
+ free(job);
+ return FALSE;
+}
+
+void ui_call_mainthread(ui_threadfunc tf, void* td) {
+ UiJob *job = malloc(sizeof(UiJob));
+ job->job_func = tf;
+ job->job_data = td;
+ job->finish_callback = NULL;
+ job->finish_data = NULL;
+ job->obj = NULL;
+ g_idle_add(ui_idle_func, job);
+}
+
+void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
+ UiJob *job = malloc(sizeof(UiJob));
+ job->obj = obj;
+ job->job_func = tf;
+ job->job_data = td;
+ job->finish_callback = f;
+ job->finish_data = fd;
+ pthread_t pid;
+ pthread_create(&pid, NULL, ui_jobthread, job);
+}
+
+void ui_set_enabled(UIWIDGET widget, int enabled) {
+ gtk_widget_set_sensitive(widget, enabled);
+}
+
+void ui_set_show_all(UIWIDGET widget, int value) {
+ // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
+ gtk_widget_set_no_show_all(widget, !value);
+#endif
+}
+
+void ui_set_visible(UIWIDGET widget, int visible) {
+ // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
+ if(visible) {
+ gtk_widget_set_no_show_all(widget, FALSE);
+ gtk_widget_show_all(widget);
+ } else {
+ gtk_widget_hide(widget);
+ }
+#endif
+}
+
+void ui_clipboard_set(char *str) {
+#if GTK_MAJOR_VERSION >= 4
+ // TODO: gtk4: needs widget
+#else
+ GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+ gtk_clipboard_set_text(cb, str, strlen(str));
+#endif
+}
+
+char* ui_clipboard_get() {
+#if GTK_MAJOR_VERSION >= 4
+ // TODO: gtk4: needs widget
+ return NULL;
+#else
+ GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+ char *str = gtk_clipboard_wait_for_text(cb);
+ if(str) {
+ char *copy = strdup(str);
+ g_free(str);
+ return copy;
+ } else {
+ return NULL;
+ }
+#endif
+}
+
+int ui_get_scalefactor() {
+ return scale_factor;
+}
+
+void ui_destroy_userdata(GtkWidget *object, void *userdata) {
+ free(userdata);
+}
+
+void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data) {
+ if(data->var) {
+ ui_destroy_boundvar(data->obj->ctx, data->var);
+ }
+ free(data);
+}
+
+void ui_destroy_boundvar(UiContext *ctx, UiVar *var) {
+ uic_unbind_var(var);
+
+ if(var->type == UI_VAR_SPECIAL) {
+ ui_free(var->from_ctx, var);
+ } else {
+ ui_free(var->from_ctx, var);
+ // TODO: free or unbound
+ //uic_remove_bound_var(ctx, var);
+ }
+}
+
+void ui_set_active_window(UiObject *obj) {
+ active_window = obj;
+}
+
+UiObject *ui_get_active_window() {
+ return active_window;
+}
+
+
+#if GTK_MAJOR_VERSION >= 3
+
+static GtkCssProvider* ui_gtk_css_provider;
+
+#if GTK_MAJOR_VERSION == 4
+static const char *ui_gtk_css =
+"#path-textfield-box {\n"
+" background-color: alpha(currentColor, 0.1);"
+" border-radius: 6px;"
+" padding: 0px;"
+"}\n"
+".pathbar-extra-button {\n"
+" border-top-right-radius: 6px;"
+" border-bottom-right-radius: 6px;"
+" border-top-left-radius: 0px;"
+" border-bottom-left-radius: 0px;"
+"}\n"
+"#pathbar button {\n"
+" margin: 3px;"
+" border-radius: 4px;"
+" padding-top: 0px;"
+" padding-bottom: 0px;"
+" padding-left: 8px;"
+" padding-right: 8px;"
+"}\n"
+"#path-textfield-box entry {\n"
+" background-color: #00000000;"
+" border-top-left-radius: 6px;"
+" border-bottom-left-radius: 6px;"
+" border-top-right-radius: 0px;"
+" border-bottom-right-radius: 0px;"
+"}\n"
+".pathbar-button-inactive {\n"
+" color: alpha(currentColor, 0.5);"
+"}\n"
+".ui_test {\n"
+" background-color: red;\n"
+"}\n"
+".ui_label_title {\n"
+" font-weight: bold;\n"
+"}\n"
+;
+
+#elif GTK_MAJOR_VERSION == 3
+static const char *ui_gtk_css =
+"#path-textfield-box {\n"
+" background-color: @theme_base_color;\n"
+" border-radius: 5px;\n"
+" padding: 0px;\n"
+"}\n"
+".pathbar-button-inactive {\n"
+" color: alpha(currentColor, 0.5);"
+"}\n"
+".ui_test {\n"
+" background-color: red;\n"
+"}\n"
+".ui_label_title {\n"
+" font-weight: bold;\n"
+"}\n"
+;
+#endif
+
+void ui_css_init(void) {
+ ui_gtk_css_provider = gtk_css_provider_new();
+
+#ifdef UI_GTK3
+ gtk_css_provider_load_from_data(ui_gtk_css_provider, ui_gtk_css, -1, NULL);
+
+ GdkScreen *screen = gdk_screen_get_default();
+ gtk_style_context_add_provider_for_screen(
+ screen,
+ GTK_STYLE_PROVIDER(ui_gtk_css_provider),
+ GTK_STYLE_PROVIDER_PRIORITY_USER);
+#endif /* UI_GTK3 */
+
+#ifdef UI_GTK4
+
+
+#if GTK_MINOR_VERSION < 12
+ gtk_css_provider_load_from_data(ui_gtk_css_provider, ui_gtk_css, -1);
+#else
+ gtk_css_provider_load_from_string(ui_gtk_css_provider, ui_gtk_css);
+#endif /* GTK_MINOR_VERSION < 12 */
+
+ GdkDisplay *display = gdk_display_get_default();
+ gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(ui_gtk_css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+#endif /* UI_GTK4 */
+}
+
+
+
+#endif
+
+void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *style_classes) {
+ if(name) {
+ gtk_widget_set_name(widget, name);
+ }
+ if(style_classes) {
+ cxstring *cls = NULL;
+ size_t numClasses = cx_strsplit_a(cxDefaultAllocator, cx_str(style_classes), CX_STR(" "), 128, &cls);
+ for(int i=0;i<numClasses;i++) {
+ cxmutstr m = cx_strdup(cls[i]);
+#if GTK_MAJOR_VERSION >= 4
+ gtk_widget_add_css_class(widget, m.ptr);
+#elif GTK_MAJOR_VERSION >= 3
+ GtkStyleContext *ctx = gtk_widget_get_style_context(widget);
+ gtk_style_context_add_class(ctx, m.ptr);
+#endif
+ free(m.ptr);
+ }
+ free(cls);
+
+ }
+}
+
+void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups) {
+ if(!groups) {
+ return;
+ }
+ size_t ngroups = uic_group_array_size(groups);
+ ui_set_widget_ngroups(ctx, widget, groups, ngroups);
+}
+
+void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups) {
+ if(ngroups > 0) {
+ uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups);
+ ui_set_enabled(widget, FALSE);
+ }
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TOOLKIT_H
+#define TOOLKIT_H
+
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+
+#if GLIB_MAJOR_VERSION * 1000 + GLIB_MINOR_VERSION > 74
+#define UI_G_APPLICATION_FLAGS G_APPLICATION_DEFAULT_FLAGS
+#else
+#define UI_G_APPLICATION_FLAGS G_APPLICATION_FLAGS_NONE
+#endif
+
+#ifdef UI_LIBADWAITA
+#define UI_APPLICATION AdwApplication*
+#define UI_APPLICATION_NEW(id) adw_application_new(id, UI_G_APPLICATION_FLAGS)
+#elif GTK_MAJOR_VERSION >= 3
+#define UI_APPLICATION GtkApplication*
+#define UI_APPLICATION_NEW(id) gtk_application_new(id, UI_G_APPLICATION_FLAGS)
+#endif
+
+#if GTK_MAJOR_VERSION >= 4
+#define WINDOW_SHOW(window) gtk_window_present(GTK_WINDOW(window))
+#define WINDOW_DESTROY(window) gtk_window_destroy(GTK_WINDOW(window))
+#define WINDOW_SET_CONTENT(window, child) gtk_window_set_child(GTK_WINDOW(window), child)
+#define BOX_ADD(box, child) gtk_box_append(GTK_BOX(box), child)
+#define BOX_ADD_EXPAND(box, child) gtk_widget_set_hexpand(child, TRUE); gtk_widget_set_vexpand(child, TRUE); gtk_box_append(GTK_BOX(box), child)
+#define BOX_ADD_NO_EXPAND(box, child) gtk_box_append(GTK_BOX(box), child)
+#define ENTRY_SET_TEXT(entry, text) gtk_editable_set_text(GTK_EDITABLE(entry), text)
+#define ENTRY_GET_TEXT(entry) gtk_editable_get_text(GTK_EDITABLE(entry))
+#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new()
+#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw), child)
+#define SCROLLEDWINDOW_GET_CHILD(sw) gtk_scrolled_window_get_child(GTK_SCROLLED_WINDOW(sw))
+#define FRAME_SET_CHILD(frame, child) gtk_frame_set_child(GTK_FRAME(frame), child)
+#define EXPANDER_SET_CHILD(expander, child) gtk_expander_set_child(GTK_EXPANDER(expander), child)
+#define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_widget_add_css_class(w, cssclass)
+#define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_widget_remove_css_class(w, cssclass)
+#else
+#define WINDOW_SHOW(window) gtk_widget_show_all(window)
+#define WINDOW_DESTROY(window) gtk_widget_destroy(window)
+#define WINDOW_SET_CONTENT(window, child) gtk_container_add(GTK_CONTAINER(window), child)
+#define BOX_ADD(box, child) gtk_container_add(GTK_CONTAINER(box), child)
+#define BOX_ADD_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, TRUE, 0)
+#define BOX_ADD_NO_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, FALSE, 0)
+#define ENTRY_SET_TEXT(entry, text) gtk_entry_set_text(GTK_ENTRY(entry), text)
+#define ENTRY_GET_TEXT(entry) gtk_entry_get_text(GTK_ENTRY(entry))
+#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new(NULL, NULL)
+#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_container_add(GTK_CONTAINER(sw), child)
+#define SCROLLEDWINDOW_GET_CHILD(sw) gtk_bin_get_child(GTK_BIN(sw))
+#define FRAME_SET_CHILD(frame, child) gtk_container_add(GTK_CONTAINER(frame), child)
+#define EXPANDER_SET_CHILD(expander, child) gtk_container_add(GTK_CONTAINER(expander), child)
+#define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_style_context_add_class(gtk_widget_get_style_context(w), cssclass)
+#define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_style_context_remove_class(gtk_widget_get_style_context(w), cssclass)
+#endif
+
+#ifdef UI_GTK2
+#undef SCROLLEDWINDOW_SET_CHILD
+#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), child)
+#endif
+
+#if GTK_MAJOR_VERSION >= 4
+#define UI_GTK_SINCE_V4(st) st
+#define UI_GTK_SINCE_V3(st)
+#define UI_GTK_V2(st)
+#define UI_GTK_V3(st)
+#define UI_GTK_V4(st) st
+#elif GTK_MAJOR_VERSION >= 3
+#define UI_GTK_SINCE_V4(st) st
+#define UI_GTK_SINCE_V3(st) st
+#define UI_GTK_V2(st)
+#define UI_GTK_V3(st) st
+#define UI_GTK_V4(st)
+#else
+#define UI_GTK_SINCE_V4(st)
+#define UI_GTK_SINCE_V3(st)
+#define UI_GTK_V2(st) st
+#define UI_GTK_V3(st)
+#define UI_GTK_V4(st)
+#endif
+
+
+typedef struct UiEventData {
+ UiObject *obj;
+ ui_callback callback;
+ void *userdata;
+ int value;
+ void *customdata;
+} UiEventData;
+
+typedef struct UiEventDataExt {
+ UiObject *obj;
+ ui_callback callback;
+ void *userdata;
+ ui_callback callback2;
+ void *userdata2;
+ int value0;
+ int value1;
+ int value2;
+ int value3;
+ void *customdata0;
+ void *customdata1;
+ void *customdata2;
+ void *customdata3;
+} UiEventDataExt;
+
+typedef struct UiVarEventData {
+ UiObject *obj;
+ UiVar *var;
+ UiObserver **observers;
+ ui_callback callback;
+ void *userdata;
+} UiVarEventData;
+
+#ifndef UI_GTK4
+struct UiSelection {
+ GtkSelectionData *data;
+};
+#endif
+
+typedef enum UiOrientation UiOrientation;
+enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
+
+#ifdef UI_APPLICATION
+void ui_app_quit();
+GtkApplication* ui_get_application();
+#endif
+
+int ui_get_scalefactor();
+
+void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *style);
+void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups);
+void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups);
+
+void ui_destroy_userdata(GtkWidget *object, void *userdata);
+void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data);
+void ui_destroy_boundvar(UiContext *ctx, UiVar *var);
+
+void ui_set_active_window(UiObject *obj);
+UiObject *ui_get_active_window();
+
+#if GTK_MAJOR_VERSION >= 3
+void ui_css_init(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TOOLKIT_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../ui/window.h"
+#include "../ui/properties.h"
+#include "../common/context.h"
+#include "../common/menu.h"
+#include "../common/toolbar.h"
+
+#include <cx/mempool.h>
+
+#include "menu.h"
+#include "toolbar.h"
+#include "container.h"
+#include "headerbar.h"
+#include "button.h"
+
+static int nwindows = 0;
+
+static int window_default_width = 650;
+static int window_default_height = 550;
+
+static gboolean ui_window_destroy(void *data) {
+ UiObject *obj = data;
+ uic_object_destroy(obj);
+
+ nwindows--;
+#ifdef UI_GTK2
+ if(nwindows == 0) {
+ gtk_main_quit();
+ }
+#endif
+
+ return FALSE;
+}
+
+void ui_window_widget_destroy(UiObject *obj) {
+#if GTK_MAJOR_VERSION >= 4
+ gtk_window_destroy(GTK_WINDOW(obj->widget));
+#else
+ gtk_widget_destroy(obj->widget);
+#endif
+}
+
+void ui_exit_event(GtkWidget *widget, gpointer data) {
+ // delay exit handler
+ g_idle_add(ui_window_destroy, data);
+}
+
+static gboolean ui_window_close_request(UiObject *obj) {
+ uic_context_prepare_close(obj->ctx);
+ obj->ref--;
+ if(obj->ref > 0) {
+#if GTK_CHECK_VERSION(2, 18, 0)
+ gtk_widget_set_visible(obj->widget, FALSE);
+#else
+ gtk_widget_hide(obj->widget);
+#endif
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+#if GTK_MAJOR_VERSION >= 4
+static gboolean close_request(GtkWindow* self, UiObject *obj) {
+ return ui_window_close_request(obj);
+}
+#else
+static gboolean close_request(GtkWidget* self, GdkEvent* event, UiObject *obj) {
+ return ui_window_close_request(obj);
+}
+#endif
+
+static UiObject* create_window(const char *title, void *window_data, UiBool simple) {
+ CxMempool *mp = cxBasicMempoolCreate(256);
+ UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject));
+ obj->ref = 0;
+
+#ifdef UI_LIBADWAITA
+ obj->widget = adw_application_window_new(ui_get_application());
+#elif !defined(UI_GTK2)
+ obj->widget = gtk_application_window_new(ui_get_application());
+#else
+ obj->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+#endif
+
+
+ obj->ctx = uic_context(obj, mp);
+ obj->window = window_data;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ obj->ctx->action_map = G_ACTION_MAP(obj->widget);
+#endif
+
+ if(title != NULL) {
+ gtk_window_set_title(GTK_WINDOW(obj->widget), title);
+ }
+
+ const char *width = ui_get_property("ui.window.width");
+ const char *height = ui_get_property("ui.window.height");
+ if(width && height) {
+ gtk_window_set_default_size(
+ GTK_WINDOW(obj->widget),
+ atoi(width),
+ atoi(height));
+ } else {
+ gtk_window_set_default_size(
+ GTK_WINDOW(obj->widget),
+ window_default_width,
+ window_default_height);
+ }
+
+ obj->destroy = ui_window_widget_destroy;
+ g_signal_connect(
+ obj->widget,
+ "destroy",
+ G_CALLBACK(ui_exit_event),
+ obj);
+#if GTK_MAJOR_VERSION >= 4
+ g_signal_connect(
+ obj->widget,
+ "close-request",
+ G_CALLBACK(close_request),
+ obj);
+#else
+ g_signal_connect(
+ obj->widget,
+ "delete-event",
+ G_CALLBACK(close_request),
+ obj);
+#endif
+
+ GtkWidget *vbox = ui_gtk_vbox_new(0);
+#ifdef UI_LIBADWAITA
+ GtkWidget *toolbar_view = adw_toolbar_view_new();
+ adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), toolbar_view);
+ adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(toolbar_view), vbox);
+
+ GtkWidget *headerbar = adw_header_bar_new();
+ adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar);
+ g_object_set_data(G_OBJECT(obj->widget), "ui_headerbar", headerbar);
+
+ if(!simple) {
+ ui_fill_headerbar(obj, headerbar);
+ }
+#elif GTK_MAJOR_VERSION >= 4
+ WINDOW_SET_CONTENT(obj->widget, vbox);
+#else
+ gtk_container_add(GTK_CONTAINER(obj->widget), vbox);
+
+ if(!simple) {
+ // menu
+ if(uic_get_menu_list()) {
+ GtkWidget *mb = ui_create_menubar(obj);
+ if(mb) {
+ gtk_box_pack_start(GTK_BOX(vbox), mb, FALSE, FALSE, 0);
+ }
+ }
+
+ // toolbar
+ if(uic_toolbar_isenabled()) {
+ GtkWidget *tb = ui_create_toolbar(obj);
+ if(tb) {
+ gtk_box_pack_start(GTK_BOX(vbox), tb, FALSE, FALSE, 0);
+ }
+ }
+
+ //GtkWidget *hb = ui_create_headerbar(obj);
+ //gtk_window_set_titlebar(GTK_WINDOW(obj->widget), hb);
+ }
+#endif
+
+ // window content
+ // the content has a (TODO: not yet) configurable frame
+ // TODO: really? why
+ /*
+ GtkWidget *frame = gtk_frame_new(NULL);
+ gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
+ gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
+
+ // content vbox
+ GtkWidget *content_box = ui_gtk_vbox_new(0);
+ gtk_container_add(GTK_CONTAINER(frame), content_box);
+ obj->container = ui_box_container(obj, content_box);
+ */
+ GtkWidget *content_box = ui_gtk_vbox_new(0);
+ BOX_ADD_EXPAND(GTK_BOX(vbox), content_box);
+ obj->container = ui_box_container(obj, content_box, UI_CONTAINER_VBOX);
+
+ nwindows++;
+ return obj;
+}
+
+
+UiObject* ui_window(const char *title, void *window_data) {
+ return create_window(title, window_data, FALSE);
+}
+
+UiObject* ui_simple_window(const char *title, void *window_data) {
+ return create_window(title, window_data, TRUE);
+}
+
+void ui_window_size(UiObject *obj, int width, int height) {
+ gtk_window_set_default_size(
+ GTK_WINDOW(obj->widget),
+ width,
+ height);
+}
+
+#ifdef UI_LIBADWAITA
+
+static void dialog_response(AdwAlertDialog *self, gchar *response, UiEventData *data) {
+ UiEvent evt;
+ evt.obj = data->obj;
+ evt.document = evt.obj->ctx->document;
+ evt.window = evt.obj->window;
+ evt.eventdata = NULL;
+ evt.intval = 0;
+
+ if(!strcmp(response, "btn1")) {
+ evt.intval = 1;
+ } else if(!strcmp(response, "btn2")) {
+ evt.intval = 2;
+ }
+
+ if(data->customdata) {
+ GtkWidget *entry = data->customdata;
+ evt.eventdata = (void*)ENTRY_GET_TEXT(GTK_ENTRY(entry));
+ }
+
+ if(data->callback) {
+ data->callback(&evt, data->userdata);
+ }
+}
+
+void ui_dialog_create(UiObject *parent, UiDialogArgs args) {
+ AdwDialog *dialog = adw_alert_dialog_new(args.title, args.content);
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->callback = args.result;
+ event->userdata = args.resultdata;
+ event->customdata = NULL;
+ event->value = 0;
+ event->obj = parent;
+
+ if(args.button1_label) {
+ adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "btn1", args.button1_label);
+ }
+ if(args.button2_label) {
+ adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "btn2", args.button2_label);
+ }
+ if(args.closebutton_label) {
+ adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "close", args.closebutton_label);
+ adw_alert_dialog_set_close_response(ADW_ALERT_DIALOG(dialog), "close");
+ }
+
+ GtkWidget *entry = NULL;
+ if(args.input || args.password) {
+ entry = gtk_entry_new();
+ if(args.password) {
+ gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+ }
+ if(args.input_value) {
+ ENTRY_SET_TEXT(entry, args.input_value);
+ }
+ adw_alert_dialog_set_extra_child(ADW_ALERT_DIALOG(dialog), entry);
+ event->customdata = entry;
+ }
+
+ g_signal_connect(
+ dialog,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+
+ g_signal_connect(dialog, "response", G_CALLBACK(dialog_response), event);
+ adw_dialog_present(dialog, parent->widget);
+
+ if(entry) {
+ gtk_entry_grab_focus_without_selecting(GTK_ENTRY(entry));
+ }
+}
+#else
+
+static void ui_dialog_response (GtkDialog* self, gint response_id, gpointer user_data) {
+ UiEventData *data = user_data;
+ UiEvent evt;
+ evt.obj = data->obj;
+ evt.document = evt.obj->ctx->document;
+ evt.window = evt.obj->window;
+ evt.eventdata = NULL;
+ evt.intval = 0;
+
+ if(data->customdata) {
+ GtkWidget *entry = data->customdata;
+ evt.eventdata = (void*)ENTRY_GET_TEXT(GTK_ENTRY(entry));
+
+ }
+
+ if(response_id == 1 || response_id == 2) {
+ evt.intval = response_id;
+ }
+
+
+ if(data->callback) {
+ data->callback(&evt, data->userdata);
+ }
+
+ WINDOW_DESTROY(GTK_WIDGET(self));
+}
+
+void ui_dialog_create(UiObject *parent, UiDialogArgs args) {
+ GtkDialog *dialog = GTK_DIALOG(gtk_dialog_new());
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget));
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+
+ GtkWidget *dialog_w = GTK_WIDGET(dialog);
+ if(args.title) {
+ gtk_window_set_title(GTK_WINDOW(dialog), args.title);
+ }
+ if(args.button1_label) {
+ gtk_dialog_add_button(dialog, args.button1_label, 1);
+ }
+ if(args.button2_label) {
+ gtk_dialog_add_button(dialog, args.button2_label, 2);
+ }
+ if(args.closebutton_label) {
+ gtk_dialog_add_button(dialog, args.closebutton_label, 0);
+ }
+
+ GtkWidget *content_area = gtk_dialog_get_content_area(dialog);
+ if(args.content) {
+ GtkWidget *label = gtk_label_new(args.content);
+ BOX_ADD(content_area, label);
+ }
+
+ GtkWidget *textfield = NULL;
+ if(args.input || args.password) {
+ textfield = gtk_entry_new();
+ if(args.password) {
+ gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE);
+ }
+ if(args.input_value) {
+ ENTRY_SET_TEXT(textfield, args.input_value);
+ }
+ BOX_ADD(content_area, textfield);
+ }
+
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = parent;
+ event->callback = args.result;
+ event->userdata = args.resultdata;
+ event->value = 0;
+ event->customdata = textfield;
+
+ g_signal_connect(dialog_w,
+ "response",
+ G_CALLBACK(ui_dialog_response),
+ event);
+
+ WINDOW_SHOW(GTK_WIDGET(dialog_w));
+}
+#endif
+
+
+#if GTK_MAJOR_VERSION >= 3
+UiFileList listmodel2filelist(GListModel *selection) {
+ UiFileList flist;
+ flist.files = NULL;
+ flist.nfiles = 0;
+ flist.nfiles = g_list_model_get_n_items(selection);
+ flist.files = calloc(flist.nfiles, sizeof(char*));
+ for(int i=0;i<flist.nfiles;i++) {
+ GFile *file = g_list_model_get_item(selection, i);
+ char *path = g_file_get_path(file);
+ flist.files[i] = path ? strdup(path) : NULL;
+ g_object_unref(file);
+ }
+ return flist;
+}
+#endif
+
+
+#if GTK_CHECK_VERSION(4, 10, 0)
+
+#define UI_GTK_FILEDIALOG_OPEN 16
+#define UI_GTK_FILEDIALOG_SAVE 32
+
+static void filechooser_opened(GObject *source, GAsyncResult *result, void *data) {
+ UiEventData *event = data;
+
+ GFile *file = NULL;
+ GListModel *selection = NULL;
+ GError *error = NULL;
+
+ int mode = event->value;
+ int multi = mode & UI_FILEDIALOG_SELECT_MULTI;
+ if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {
+ if(multi) {
+ selection = gtk_file_dialog_select_multiple_folders_finish(GTK_FILE_DIALOG(source), result, &error);
+ } else {
+ file = gtk_file_dialog_select_folder_finish(GTK_FILE_DIALOG(source), result, &error);
+ }
+ } else if((mode & UI_GTK_FILEDIALOG_OPEN) == UI_GTK_FILEDIALOG_OPEN) {
+ if(multi) {
+ selection = gtk_file_dialog_open_multiple_finish(GTK_FILE_DIALOG(source), result, &error);
+ } else {
+ file = gtk_file_dialog_open_finish(GTK_FILE_DIALOG(source), result, &error);
+ }
+ } else {
+ file = gtk_file_dialog_save_finish(GTK_FILE_DIALOG(source), result, &error);
+ }
+
+ UiEvent evt;
+ evt.obj = event->obj;
+ evt.document = evt.obj->ctx->document;
+ evt.window = evt.obj->window;
+ evt.intval = 0;
+
+ UiFileList flist;
+ flist.files = NULL;
+ flist.nfiles = 0;
+ evt.eventdata = &flist;
+
+ if(selection) {
+ flist = listmodel2filelist(selection);
+ g_object_unref(selection);
+ } else if(file) {
+ char *path = g_file_get_path(file);
+ if(path) {
+ flist.nfiles = 1;
+ flist.files = calloc(flist.nfiles, sizeof(char*));
+ flist.files[0] = strdup(path);
+ }
+ g_object_unref(file);
+ }
+
+ if(event->callback) {
+ event->callback(&evt, event->userdata);
+ }
+
+ for(int i=0;i<flist.nfiles;i++) {
+ free(flist.files[i]);
+ }
+}
+
+static void ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action, unsigned int mode, const char *name, ui_callback file_selected_callback, void *cbdata) {
+ if(action == GTK_FILE_CHOOSER_ACTION_OPEN) {
+ mode |= UI_GTK_FILEDIALOG_OPEN;
+ } else {
+ mode |= UI_GTK_FILEDIALOG_SAVE;
+ }
+
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->callback = file_selected_callback;
+ event->userdata = cbdata;
+ event->customdata = NULL;
+ event->value = mode;
+ event->obj = obj;
+
+ GtkWindow *parent = GTK_WINDOW(gtk_widget_get_root(obj->widget));
+ GtkFileDialog *dialog = gtk_file_dialog_new();
+ if(name) {
+ gtk_file_dialog_set_initial_name(dialog, name);
+ }
+
+ int multi = mode & UI_FILEDIALOG_SELECT_MULTI;
+ if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {
+ if(multi) {
+ gtk_file_dialog_select_multiple_folders(dialog, parent, NULL, filechooser_opened, event);
+ } else {
+ gtk_file_dialog_select_folder(dialog, parent, NULL, filechooser_opened, event);
+ }
+ } else if(action == GTK_FILE_CHOOSER_ACTION_OPEN) {
+ if(multi) {
+ gtk_file_dialog_open_multiple(dialog, parent, NULL, filechooser_opened, event);
+ } else {
+ gtk_file_dialog_open(dialog, parent, NULL, filechooser_opened, event);
+ }
+ } else {
+ gtk_file_dialog_save(dialog, parent, NULL, filechooser_opened, event);
+ }
+
+ g_object_unref(dialog);
+}
+#else
+
+
+
+static void filechooser_response(GtkDialog* self, gint response_id, UiEventData *data) {
+ UiEvent evt;
+ evt.obj = data->obj;
+ evt.document = evt.obj->ctx->document;
+ evt.window = evt.obj->window;
+ evt.intval = 0;
+
+ UiFileList flist;
+ flist.files = NULL;
+ flist.nfiles = 0;
+ evt.eventdata = &flist;
+
+ if(response_id == GTK_RESPONSE_ACCEPT) {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GListModel *selection = gtk_file_chooser_get_files(GTK_FILE_CHOOSER(self));
+ flist = flist = listmodel2filelist(selection);
+ g_object_unref(selection);
+#else
+ GSList *selection = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(self));
+ flist.nfiles = g_slist_length(selection);
+ flist.files = calloc(flist.nfiles, sizeof(char*));
+ int i = 0;
+ while(selection) {
+ char *file = selection->data;
+ flist.files[i] = strdup(file);
+ g_free(file);
+ selection = selection->next;
+ i++;
+ }
+ g_slist_free(selection);
+#endif
+ }
+
+
+ if(data->callback) {
+ data->callback(&evt, data->userdata);
+ }
+
+ for(int i=0;i<flist.nfiles;i++) {
+ free(flist.files[i]);
+ }
+
+ WINDOW_DESTROY(GTK_WIDGET(self));
+}
+
+static void ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action, unsigned int mode, const char *name, ui_callback file_selected_callback, void *cbdata) {
+ char *button;
+ char *title;
+
+ GtkWidget *dialog;
+ if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {
+ dialog = gtk_file_chooser_dialog_new (
+ "Open Folder",
+ GTK_WINDOW(obj->widget),
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ "Cancel",
+ GTK_RESPONSE_CANCEL,
+ "Select Folder",
+ GTK_RESPONSE_ACCEPT,
+ NULL);
+ } else if(action == GTK_FILE_CHOOSER_ACTION_OPEN) {
+ dialog = gtk_file_chooser_dialog_new (
+ "Select Folder",
+ GTK_WINDOW(obj->widget),
+ action,
+ "Cancel",
+ GTK_RESPONSE_CANCEL,
+ "Open File",
+ GTK_RESPONSE_ACCEPT,
+ NULL);
+ } else {
+ dialog = gtk_file_chooser_dialog_new (
+ "Save File",
+ GTK_WINDOW(obj->widget),
+ action,
+ "Cancel",
+ GTK_RESPONSE_CANCEL,
+ "Save File",
+ GTK_RESPONSE_ACCEPT,
+ NULL);
+ }
+
+ if((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) {
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+ }
+
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = cbdata;
+ event->callback = file_selected_callback;
+ event->value = 0;
+ event->customdata = NULL;
+
+ g_signal_connect(
+ dialog,
+ "response",
+ G_CALLBACK(filechooser_response),
+ event);
+ g_signal_connect(
+ dialog,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+
+
+ UiEvent evt;
+ evt.obj = obj;
+ evt.document = evt.obj->ctx->document;
+ evt.window = evt.obj->window;
+ evt.intval = 0;
+
+ UiFileList flist;
+ flist.files = NULL;
+ flist.nfiles = 0;
+ evt.eventdata = &flist;
+
+ gtk_widget_show(dialog);
+}
+#endif
+
+void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {
+ ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_OPEN, mode, NULL, file_selected_callback, cbdata);
+}
+
+void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) {
+ ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_SAVE, 0, name, file_selected_callback, cbdata);
+}
+
+#if GTK_CHECK_VERSION(4, 10, 0)
+#define DIALOG_NEW() gtk_window_new()
+#else
+#define DIALOG_NEW() gtk_dialog_new()
+
+static void ui_dialogwindow_response(GtkDialog* self, gint response_id, gpointer user_data) {
+ UiEventData *event = user_data;
+ // TODO: do we need to check if response_id == GTK_RESPONSE_DELETE_EVENT?
+ if(event->callback) {
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = event->value;
+ event->callback(&e, event->userdata);
+ }
+}
+
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+#define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(headerbar), set)
+#define DEFAULT_BUTTON(window, button) gtk_window_set_default_widget(GTK_WINDOW(window), button)
+#else
+#define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(headerbar), set)
+#define DEFAULT_BUTTON(window, button) gtk_widget_set_can_default(button, TRUE); gtk_window_set_default(GTK_WINDOW(window), button)
+#endif
+
+
+
+UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) {
+ GtkWidget *dialog = DIALOG_NEW();
+ if(args.width > 0 || args.height > 0) {
+ gtk_window_set_default_size(
+ GTK_WINDOW(dialog),
+ args.width,
+ args.height);
+ }
+
+
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget));
+ if(args.modal != UI_OFF) {
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ }
+
+ CxMempool *mp = cxBasicMempoolCreate(256);
+ UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject));
+ obj->ctx = uic_context(obj, mp);
+ obj->widget = dialog;
+ obj->ref = 0;
+ obj->destroy = ui_window_widget_destroy;
+ nwindows++;
+
+ if(args.title != NULL) {
+ gtk_window_set_title(GTK_WINDOW(dialog), args.title);
+ }
+
+#if ! GTK_CHECK_VERSION(4, 10, 0)
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = args.onclickdata;
+ event->callback = args.onclick;
+ event->value = 0;
+ event->customdata = NULL;
+
+ g_signal_connect(dialog, "response", G_CALLBACK(ui_dialogwindow_response), event);
+ g_signal_connect(
+ dialog,
+ "destroy",
+ G_CALLBACK(ui_destroy_userdata),
+ event);
+#endif
+
+ g_signal_connect(
+ dialog,
+ "destroy",
+ G_CALLBACK(ui_exit_event),
+ obj);
+#if GTK_MAJOR_VERSION >= 4
+ g_signal_connect(
+ obj->widget,
+ "close-request",
+ G_CALLBACK(close_request),
+ obj);
+#else
+ g_signal_connect(
+ obj->widget,
+ "delete-event",
+ G_CALLBACK(close_request),
+ obj);
+#endif
+
+#if GTK_MAJOR_VERSION < 4
+ GtkWidget *c = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+ gtk_container_remove(GTK_CONTAINER(dialog), c);
+#endif
+
+ GtkWidget *content_vbox = ui_gtk_vbox_new(0);
+ obj->container = ui_box_container(obj, content_vbox, UI_CONTAINER_VBOX);
+ if(args.lbutton1 || args.lbutton2 || args.rbutton3 || args.rbutton4) {
+#if GTK_CHECK_VERSION(3, 10, 0)
+ if(args.titlebar_buttons != UI_OFF) {
+ GtkWidget *headerbar = gtk_header_bar_new();
+ gtk_window_set_titlebar(GTK_WINDOW(dialog), headerbar);
+ if(args.show_closebutton == UI_OFF) {
+ HEADERBAR_SHOW_CLOSEBUTTON(headerbar, FALSE);
+ }
+
+ if(args.lbutton1) {
+ GtkWidget *button = ui_create_button(obj, args.lbutton1, NULL, args.onclick, args.onclickdata, 1, args.default_button == 1);
+ gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button);
+ if(args.default_button == 1) {
+ WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+ DEFAULT_BUTTON(dialog, button);
+ }
+ }
+ if(args.lbutton2) {
+ GtkWidget *button = ui_create_button(obj, args.lbutton2, NULL, args.onclick, args.onclickdata, 2, args.default_button == 2);
+ gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button);
+ if(args.default_button == 2) {
+ WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+ DEFAULT_BUTTON(dialog, button);
+ }
+ }
+
+ if(args.rbutton4) {
+ GtkWidget *button = ui_create_button(obj, args.rbutton4, NULL, args.onclick, args.onclickdata, 4, args.default_button == 4);
+ gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button);
+ if(args.default_button == 4) {
+ WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+ DEFAULT_BUTTON(dialog, button);
+ }
+ }
+ if(args.rbutton3) {
+ GtkWidget *button = ui_create_button(obj, args.rbutton3, NULL, args.onclick, args.onclickdata, 3, args.default_button == 3);
+ gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button);
+ if(args.default_button == 3) {
+ WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+ DEFAULT_BUTTON(dialog, button);
+ }
+ }
+ WINDOW_SET_CONTENT(obj->widget, content_vbox);
+ return obj;
+ }
+#endif
+ GtkWidget *vbox = ui_gtk_vbox_new(0);
+ WINDOW_SET_CONTENT(obj->widget, vbox);
+
+ GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+
+ GtkWidget *grid = ui_create_grid_widget(10, 10);
+ GtkWidget *widget = ui_box_set_margin(grid, 16);
+ gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE);
+
+ if(args.lbutton1) {
+ GtkWidget *button = ui_create_button(obj, args.lbutton1, NULL, args.onclick, args.onclickdata, 1, args.default_button == 1);
+ gtk_grid_attach(GTK_GRID(grid), button, 0, 0, 1, 1);
+ if(args.default_button == 1) {
+ WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+ DEFAULT_BUTTON(dialog, button);
+ }
+ }
+ if(args.lbutton2) {
+ GtkWidget *button = ui_create_button(obj, args.lbutton2, NULL, args.onclick, args.onclickdata, 2, args.default_button == 2);
+ gtk_grid_attach(GTK_GRID(grid), button, 1, 0, 1, 1);
+ if(args.default_button == 2) {
+ WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+ DEFAULT_BUTTON(dialog, button);
+ }
+ }
+ GtkWidget *space = gtk_label_new(NULL);
+ gtk_widget_set_hexpand(space, TRUE);
+ gtk_grid_attach(GTK_GRID(grid), space, 2, 0, 1, 1);
+ if(args.rbutton3) {
+ GtkWidget *button = ui_create_button(obj, args.rbutton3, NULL, args.onclick, args.onclickdata, 3, args.default_button == 3);
+ gtk_grid_attach(GTK_GRID(grid), button, 3, 0, 1, 1);
+ if(args.default_button == 3) {
+ WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+ DEFAULT_BUTTON(dialog, button);
+ }
+ }
+ if(args.rbutton4) {
+ GtkWidget *button = ui_create_button(obj, args.rbutton4, NULL, args.onclick, args.onclickdata, 4, args.default_button == 4);
+ gtk_grid_attach(GTK_GRID(grid), button, 4, 0, 1, 1);
+ if(args.default_button == 4) {
+ WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+ DEFAULT_BUTTON(dialog, button);
+ }
+ }
+
+ BOX_ADD_EXPAND(vbox, content_vbox);
+ BOX_ADD_NO_EXPAND(vbox, separator);
+ BOX_ADD_NO_EXPAND(vbox, widget);
+ } else {
+ WINDOW_SET_CONTENT(obj->widget, content_vbox);
+ }
+
+ return obj;
+}
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+$(MOTIF_OBJPRE)%.o: motif/%.c
+ $(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $<
+
+$(UI_LIB): $(OBJ)
+ $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "button.h"
+#include "container.h"
+#include "../common/context.h"
+#include <cx/mempool.h>
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+#include <cx/compare.h>
+
+
+UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) {
+ UiContainer *ct = uic_get_current_container(obj);
+ XmString str = XmStringCreateLocalized(label);
+
+ int n = 0;
+ Arg args[16];
+
+ XtSetArg(args[n], XmNlabelString, str);
+ n++;
+
+ Widget parent = ct->prepare(ct, args, &n, FALSE);
+ Widget button = XmCreatePushButton(parent, "button", args, n);
+ ct->add(ct, button);
+
+ if(f) {
+ UiEventData *event = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = data;
+ event->callback = f;
+ event->value = 0;
+ XtAddCallback(
+ button,
+ XmNactivateCallback,
+ (XtCallbackProc)ui_push_button_callback,
+ event);
+ }
+
+ XtManageChild(button);
+
+ return button;
+}
+
+// wrapper
+int64_t ui_toggle_button_get(UiInteger *i) {
+ int state = 0;
+ XtVaGetValues(i->obj, XmNset, &state, NULL);
+ i->value = state;
+ return state;
+}
+
+void ui_toggle_button_set(UiInteger *i, int64_t value) {
+ Arg arg;
+ XtSetArg(arg, XmNset, value);
+ XtSetValues(i->obj, &arg, 1);
+ i->value = value;
+}
+
+void ui_toggle_button_callback(
+ Widget widget,
+ UiEventData *event,
+ XmToggleButtonCallbackStruct *tb)
+{
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ // TODO: e.document
+ e.intval = tb->set;
+ event->callback(&e, event->userdata);
+}
+
+void ui_push_button_callback(Widget widget, UiEventData *event, XtPointer d) {
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.intval = event->value;
+ event->callback(&e, event->userdata);
+}
+
+
+static void radio_callback(
+ Widget widget,
+ RadioEventData *event,
+ XmToggleButtonCallbackStruct *tb)
+{
+ if(tb->set) {
+ RadioButtonGroup *group = event->group;
+ if(group->current) {
+ Arg arg;
+ XtSetArg(arg, XmNset, FALSE);
+ XtSetValues(group->current, &arg, 1);
+ }
+ group->current = widget;
+ }
+}
+
+UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) {
+ UiContainer *ct = uic_get_current_container(obj);
+ XmString str = XmStringCreateLocalized(label);
+
+ int n = 0;
+ Arg args[16];
+
+ XtSetArg(args[n], XmNlabelString, str);
+ n++;
+ XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY_ROUND);
+ n++;
+
+ Widget parent = ct->prepare(ct, args, &n, FALSE);
+ Widget button = XmCreateToggleButton(parent, "radiobutton", args, n);
+ ct->add(ct, button);
+
+ if(rgroup) {
+ RadioButtonGroup *group;
+ if(rgroup->obj) {
+ group = rgroup->obj;
+ if(!group->buttons) {
+ group->buttons = cxArrayListCreate(cxDefaultAllocator, cx_cmp_uintptr, CX_STORE_POINTERS, 8);
+ }
+ cxListAdd(group->buttons, button);
+ group->ref++;
+ } else {
+ group = malloc(sizeof(RadioButtonGroup));
+ group->buttons = cxArrayListCreate(cxDefaultAllocator, cx_cmp_uintptr, CX_STORE_POINTERS, 8);
+ cxListAdd(group->buttons, button);
+ group->current = button;
+ // this is the first button in the radiobutton group
+ // so we should enable it
+ Arg arg;
+ XtSetArg(arg, XmNset, TRUE);
+ XtSetValues(button, &arg, 1);
+ rgroup->obj = group;
+
+ group->current = button;
+ }
+
+ RadioEventData *event = malloc(sizeof(RadioEventData));
+ event->obj = obj;
+ event->callback = NULL;
+ event->userdata = NULL;
+ event->group = group;
+ XtAddCallback(
+ button,
+ XmNvalueChangedCallback,
+ (XtCallbackProc)radio_callback,
+ event);
+
+ rgroup->get = ui_radiobutton_get;
+ rgroup->set = ui_radiobutton_set;
+ }
+
+ XtManageChild(button);
+ return button;
+}
+
+int64_t ui_radiobutton_get(UiInteger *value) {
+ RadioButtonGroup *group = value->obj;
+
+ int i = cxListFind(group->buttons, group->current);
+ if (i >= 0) {
+ value->value = i;
+ return i;
+ } else {
+ return 0;
+ }
+}
+
+void ui_radiobutton_set(UiInteger *value, int64_t i) {
+ RadioButtonGroup *group = value->obj;
+ Arg arg;
+
+ XtSetArg(arg, XmNset, FALSE);
+ XtSetValues(group->current, &arg, 1);
+
+ Widget button = cxListAt(group->buttons, i);
+ if(button) {
+ XtSetArg(arg, XmNset, TRUE);
+ XtSetValues(button, &arg, 1);
+ group->current = button;
+ }
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BUTTON_H
+#define BUTTON_H
+
+#include "../ui/button.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+ CxList *buttons;
+ Widget current;
+ int ref;
+} RadioButtonGroup;
+
+typedef struct {
+ UiObject *obj;
+ ui_callback callback;
+ void *userdata;
+ RadioButtonGroup *group;
+} RadioEventData;
+
+// wrapper
+int64_t ui_toggle_button_get(UiInteger *i);
+void ui_toggle_button_set(UiInteger *i, int64_t value);
+void ui_toggle_button_callback(
+ Widget widget,
+ UiEventData *data,
+ XmToggleButtonCallbackStruct *e);
+void ui_push_button_callback(Widget widget, UiEventData *event, XtPointer d);
+
+int64_t ui_radiobutton_get(UiInteger *value);
+void ui_radiobutton_set(UiInteger *value, int64_t i);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BUTTON_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include "container.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+#include <cx/array_list.h>
+#include <cx/linked_list.h>
+#include <cx/compare.h>
+
+#define UI_GRID_MAX_COLUMNS 512
+
+static UiBool ui_lb2bool(UiLayoutBool b) {
+ return b == UI_LAYOUT_TRUE ? TRUE : FALSE;
+}
+
+static UiLayoutBool ui_bool2lb(UiBool b) {
+ return b ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE;
+}
+
+
+UiContainer* ui_frame_container(UiObject *obj, Widget frame) {
+ UiContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiContainer));
+ ct->widget = frame;
+ ct->prepare = ui_frame_container_prepare;
+ ct->add = ui_frame_container_add;
+ return ct;
+}
+
+Widget ui_frame_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
+ return ct->widget;
+}
+
+void ui_frame_container_add(UiContainer *ct, Widget widget) {
+ ui_reset_layout(ct->layout);
+ ct->current = widget;
+}
+
+
+UiContainer* ui_box_container(UiObject *obj, Widget box, int margin, int spacing, UiBoxOrientation orientation) {
+ UiBoxContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiBoxContainer));
+ ct->container.widget = box;
+ ct->container.prepare = ui_box_container_prepare;
+ ct->container.add = ui_box_container_add;
+ ct->orientation = orientation;
+ ct->margin = margin;
+ ct->spacing = spacing;
+ return (UiContainer*)ct;
+}
+
+Widget ui_box_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
+ UiBoxContainer *bc = (UiBoxContainer*)ct;
+ if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
+ fill = ui_lb2bool(ct->layout.fill);
+ }
+
+ if(bc->has_fill && fill) {
+ fprintf(stderr, "UiError: container has 2 filled widgets");
+ fill = FALSE;
+ }
+ if(fill) {
+ bc->has_fill = TRUE;
+ }
+
+ int a = *n;
+ // determine fixed and dynamic attachments
+ void *f1;
+ void *f2;
+ void *d1;
+ void *d2;
+ void *w1;
+ void *w2;
+ if(bc->orientation == UI_BOX_VERTICAL) {
+ f1 = XmNleftAttachment;
+ f2 = XmNrightAttachment;
+ d1 = XmNtopAttachment;
+ d2 = XmNbottomAttachment;
+ w1 = XmNtopWidget;
+ w2 = XmNbottomWidget;
+
+ // margin/spacing
+ XtSetArg(args[a], XmNleftOffset, bc->margin); a++;
+ XtSetArg(args[a], XmNrightOffset, bc->margin); a++;
+
+ XtSetArg(args[a], XmNtopOffset, bc->prev_widget ? bc->spacing : bc->margin); a++;
+ } else {
+ f1 = XmNtopAttachment;
+ f2 = XmNbottomAttachment;
+ d1 = XmNleftAttachment;
+ d2 = XmNrightAttachment;
+ w1 = XmNleftWidget;
+ w2 = XmNrightWidget;
+
+ // margin/spacing
+ XtSetArg(args[a], XmNtopOffset, bc->margin); a++;
+ XtSetArg(args[a], XmNbottomOffset, bc->margin); a++;
+
+ XtSetArg(args[a], XmNleftOffset, bc->prev_widget ? bc->spacing : bc->margin); a++;
+ }
+ XtSetArg(args[a], f1, XmATTACH_FORM); a++;
+ XtSetArg(args[a], f2, XmATTACH_FORM); a++;
+
+ if(fill) {
+ XtSetArg(args[a], d2, XmATTACH_FORM); a++;
+ }
+ if(bc->prev_widget) {
+ XtSetArg(args[a], d1, XmATTACH_WIDGET); a++;
+ XtSetArg(args[a], w1, bc->prev_widget); a++;
+ } else {
+ XtSetArg(args[a], d1, XmATTACH_FORM); a++;
+ }
+
+ *n = a;
+ return ct->widget;
+}
+
+void ui_box_container_add(UiContainer *ct, Widget widget) {
+ UiBoxContainer *bc = (UiBoxContainer*)ct;
+ // determine dynamic attachments
+ void *d1;
+ void *d2;
+ void *w1;
+ void *w2;
+ if(bc->orientation == UI_BOX_VERTICAL) {
+ d1 = XmNtopAttachment;
+ d2 = XmNbottomAttachment;
+ w1 = XmNtopWidget;
+ w2 = XmNbottomWidget;
+
+ } else {
+ d1 = XmNleftAttachment;
+ d2 = XmNrightAttachment;
+ w1 = XmNleftWidget;
+ w2 = XmNrightWidget;
+ }
+
+ if(bc->prev_widget) {
+ int v = 0;
+ XtVaGetValues(bc->prev_widget, d2, &v, NULL);
+ if(v == XmATTACH_FORM) {
+ XtVaSetValues(
+ bc->prev_widget,
+ d2,
+ XmATTACH_WIDGET,
+ w2,
+ widget,
+ NULL);
+ XtVaSetValues(
+ widget,
+ d1,
+ XmATTACH_NONE,
+ d2,
+ XmATTACH_FORM,
+ NULL);
+ }
+ }
+ bc->prev_widget = widget;
+
+ ui_reset_layout(ct->layout);
+ ct->current = widget;
+}
+
+UiContainer* ui_grid_container(UiObject *obj, Widget form, int columnspacing, int rowspacing) {
+ UiGridContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiGridContainer));
+ ct->container.widget = form;
+ ct->container.prepare = ui_grid_container_prepare;
+ ct->container.add = ui_grid_container_add;
+ ct->columnspacing = columnspacing;
+ ct->rowspacing = rowspacing;
+ ct->lines = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ return (UiContainer*)ct;
+}
+
+void ui_grid_newline(UiGridContainer *grid) {
+ if(grid->current) {
+ grid->current = NULL;
+ }
+ grid->container.layout.newline = FALSE;
+}
+
+Widget ui_grid_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
+ UiGridContainer *grid = (UiGridContainer*)ct;
+ if(ct->layout.newline) {
+ ui_grid_newline(grid);
+ }
+ return ct->widget;
+}
+
+void ui_grid_container_add(UiContainer *ct, Widget widget) {
+ UiGridContainer *grid = (UiGridContainer*)ct;
+
+ if(grid->current) {
+ cxListAdd(grid->current, widget);
+ } else {
+ grid->current = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+ cxListAdd(grid->current, widget);
+ cxListAdd(grid->lines, grid->current);
+ }
+
+ ui_reset_layout(ct->layout);
+ ct->current = widget;
+}
+
+static void ui_grid_resize(Widget widget, XtPointer udata, XtPointer cdata) {
+ UiGridContainer *grid = udata;
+
+ CxList *rowdim = cxArrayListCreateSimple(sizeof(int), grid->lines->size);
+ int coldim[UI_GRID_MAX_COLUMNS];
+ memset(coldim, 0, UI_GRID_MAX_COLUMNS*sizeof(int));
+ int numcol = 0;
+
+ // get the minimum size of the columns and rows
+ int sumw = 0;
+ int sumh = 0;
+ CxIterator lineIterator = cxListIterator(grid->lines);
+ cx_foreach(CxList *, row, lineIterator) {
+ int rheight = 0;
+ int i=0;
+ int sum_width = 0;
+ CxIterator colIterator = cxListIterator(row);
+ cx_foreach(Widget, w, colIterator) {
+ int widget_width = 0;
+ int widget_height = 0;
+ XtVaGetValues(
+ w,
+ XmNwidth,
+ &widget_width,
+ XmNheight,
+ &widget_height,
+ NULL);
+
+ // get the maximum height in this row
+ if(widget_height > rheight) {
+ rheight = widget_height;
+ }
+
+ // get the maximum width in this column
+ if(widget_width > coldim[i]) {
+ coldim[i] = widget_width;
+ }
+ sum_width += widget_width;
+ if(sum_width > sumw) {
+ sumw = sum_width;
+ }
+
+ i++;
+ if(i > numcol) {
+ numcol = i;
+ }
+ }
+ cxListAdd(rowdim, &rheight);
+ sumh += rheight;
+ }
+
+ // check container size
+ int gwidth = 0;
+ int gheight = 0;
+ XtVaGetValues(widget, XmNwidth, &gwidth, XmNheight, &gheight, NULL);
+ if(gwidth < sumw || gheight < sumh) {
+ XtVaSetValues(widget, XmNwidth, sumw, XmNheight, sumh, NULL);
+ cxListDestroy(rowdim);
+ return;
+ }
+
+
+ // adjust the positions of all children
+ int y = 0;
+ lineIterator = cxListIterator(grid->lines);
+ cx_foreach(CxList *, row, lineIterator) {
+ int x = 0;
+ int i=0;
+ int *rowheight = cxListAt(rowdim, lineIterator.index);
+ CxIterator colIterator = cxListIterator(row);
+ cx_foreach(Widget, w, colIterator) {
+ XtVaSetValues(
+ w,
+ XmNx, x,
+ XmNy, y,
+ XmNwidth, coldim[i],
+ XmNheight, *rowheight,
+ NULL);
+
+ x += coldim[i];
+ i++;
+ }
+ y += *rowheight;
+ }
+
+ cxListDestroy(rowdim);
+}
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow) {
+ UiContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiContainer));
+ ct->widget = scrolledwindow;
+ ct->prepare = ui_scrolledwindow_container_prepare;
+ ct->add = ui_scrolledwindow_container_add;
+ return ct;
+}
+
+Widget ui_scrolledwindow_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
+ return ct->widget;
+}
+
+void ui_scrolledwindow_container_add(UiContainer *ct, Widget widget) {
+ ui_reset_layout(ct->layout);
+ ct->current = widget;
+}
+
+
+UiContainer* ui_tabview_container(UiObject *obj, Widget frame) {
+ UiTabViewContainer *ct = cxCalloc(
+ obj->ctx->allocator,
+ 1,
+ sizeof(UiTabViewContainer));
+ ct->context = obj->ctx;
+ ct->container.widget = frame;
+ ct->container.prepare = ui_tabview_container_prepare;
+ ct->container.add = ui_tabview_container_add;
+ ct->tabs = cxArrayListCreate(cxDefaultAllocator, cx_cmp_uintptr, CX_STORE_POINTERS, 16);
+ return (UiContainer*)ct;
+}
+
+Widget ui_tabview_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill) {
+ int a = *n;
+ XtSetArg(args[a], XmNleftAttachment, XmATTACH_FORM); a++;
+ XtSetArg(args[a], XmNrightAttachment, XmATTACH_FORM); a++;
+ XtSetArg(args[a], XmNtopAttachment, XmATTACH_FORM); a++;
+ XtSetArg(args[a], XmNbottomAttachment, XmATTACH_FORM); a++;
+ *n = a;
+ return ct->widget;
+}
+
+void ui_tabview_container_add(UiContainer *ct, Widget widget) {
+ UiTabViewContainer *tabview = (UiTabViewContainer*)ct;
+
+ if(tabview->current) {
+ XtUnmanageChild(tabview->current);
+ }
+
+ tabview->current = widget;
+ cxListAdd(tabview->tabs, widget);
+
+ ui_select_tab(ct->widget, 0);
+ ui_reset_layout(ct->layout);
+ ct->current = widget;
+}
+
+UIWIDGET ui_box(UiObject *obj, int margin, int spacing, UiBoxOrientation orientation) {
+ UiContainer *ct = uic_get_current_container(obj);
+
+ Arg args[16];
+ int n = 0;
+ Widget parent = ct->prepare(ct, args, &n, TRUE);
+ Widget form = XmCreateForm(parent, "vbox", args, n);
+ ct->add(ct, form);
+ XtManageChild(form);
+
+ UiObject *newobj = uic_object_new(obj, form);
+ newobj->container = ui_box_container(obj, form, margin, spacing, orientation);
+ uic_obj_add(obj, newobj);
+
+ return form;
+}
+
+UIWIDGET ui_vbox(UiObject *obj) {
+ return ui_box(obj, 0, 0, UI_BOX_VERTICAL);
+}
+
+UIWIDGET ui_hbox(UiObject *obj) {
+ return ui_box(obj, 0, 0, UI_BOX_HORIZONTAL);
+}
+
+UIWIDGET ui_vbox_sp(UiObject *obj, int margin, int spacing) {
+ return ui_box(obj, margin, spacing, UI_BOX_VERTICAL);
+}
+
+UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing) {
+ return ui_box(obj, margin, spacing, UI_BOX_HORIZONTAL);
+}
+
+UIWIDGET ui_grid(UiObject *obj) {
+ return ui_grid_sp(obj, 0, 0, 0);
+}
+
+UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) {
+ UiContainer *ct = uic_get_current_container(obj);
+
+ Arg args[16];
+ int n = 0;
+ Widget parent = ct->prepare(ct, args, &n, TRUE);
+ Widget grid = XmCreateDrawingArea(parent, "grid", args, n);
+ ct->add(ct, grid);
+ XtManageChild(grid);
+
+ UiObject *newobj = uic_object_new(obj, grid);
+ newobj->container = ui_grid_container(obj, grid, columnspacing, rowspacing);
+ uic_obj_add(obj, newobj);
+
+ XtAddCallback (grid, XmNresizeCallback , ui_grid_resize, newobj->container);
+
+ return grid;
+}
+
+UIWIDGET ui_scrolledwindow(UiObject *obj) {
+ UiContainer *ct = uic_get_current_container(obj);
+
+ Arg args[16];
+ int n = 0;
+ XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC); // TODO: dosn't work, use XmAPPLICATION_DEFINED
+ n++;
+ Widget parent = ct->prepare(ct, args, &n, TRUE);
+ Widget scrolledwindow = XmCreateScrolledWindow(parent, "scrolledwindow", args, n);
+ ct->add(ct, scrolledwindow);
+ XtManageChild(scrolledwindow);
+
+ UiObject *newobj = uic_object_new(obj, scrolledwindow);
+ newobj->container = ui_scrolledwindow_container(obj, scrolledwindow);
+ uic_obj_add(obj, newobj);
+
+ return scrolledwindow;
+}
+
+UIWIDGET ui_sidebar(UiObject *obj) {
+ UiContainer *ct = uic_get_current_container(obj);
+
+ Arg args[16];
+ int n = 0;
+
+ XtSetArg(args[n], XmNorientation, XmHORIZONTAL);
+ n++;
+
+ Widget parent = ct->prepare(ct, args, &n, TRUE);
+ Widget pane = XmCreatePanedWindow(parent, "pane", args, n);
+ ct->add(ct, pane);
+ XtManageChild(pane);
+
+ // add sidebar widget
+ Widget sidebar = XmCreateForm(pane, "sidebar", args, 0);
+ XtManageChild(sidebar);
+
+ UiObject *left = uic_object_new(obj, sidebar);
+ left->container = ui_box_container(left, sidebar, 0, 0, UI_BOX_VERTICAL);
+
+ // add content widget
+ XtSetArg (args[0], XmNpaneMaximum, 8000);
+ Widget content = XmCreateForm(pane, "content_area", args, 1);
+ XtManageChild(content);
+
+ UiObject *right = uic_object_new(obj, content);
+ right->container = ui_box_container(right, content, 0, 0, UI_BOX_VERTICAL);
+
+ uic_obj_add(obj, right);
+ uic_obj_add(obj, left);
+
+ return sidebar;
+}
+
+UIWIDGET ui_tabview(UiObject *obj) {
+ UiContainer *ct = uic_get_current_container(obj);
+
+ // create a simple frame as container widget
+ // when tabs are selected, the current child will be replaced by the
+ // the new tab widget
+ Arg args[16];
+ int n = 0;
+ XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_OUT);
+ n++;
+ XtSetArg(args[n], XmNshadowThickness, 0);
+ n++;
+ Widget parent = ct->prepare(ct, args, &n, TRUE);
+ Widget form = XmCreateForm(parent, "tabview", args, n);
+ ct->add(ct, form);
+ XtManageChild(form);
+
+ UiObject *tabviewobj = uic_object_new(obj, form);
+ tabviewobj->container = ui_tabview_container(obj, form);
+ uic_obj_add(obj, tabviewobj);
+
+ XtVaSetValues(form, XmNuserData, tabviewobj->container, NULL);
+
+ return form;
+}
+
+void ui_tab(UiObject *obj, char *title) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.label = title;
+
+ ui_vbox(obj);
+}
+
+void ui_select_tab(UIWIDGET tabview, int tab) {
+ UiTabViewContainer *ct = NULL;
+ XtVaGetValues(tabview, XmNuserData, &ct, NULL);
+ if(ct) {
+ XtUnmanageChild(ct->current);
+ Widget w = cxListAt(ct->tabs, tab);
+ if(w) {
+ XtManageChild(w);
+ ct->current = w;
+ } else {
+ fprintf(stderr, "UiError: front tab index: %d\n", tab);
+ }
+ } else {
+ fprintf(stderr, "UiError: widget is not a tabview\n");
+ }
+}
+
+
+/* document tabview */
+
+static void ui_tabbar_resize(Widget widget, XtPointer udata, XtPointer cdata) {
+ MotifTabbedPane *v = (MotifTabbedPane*)udata;
+
+ int width = 0;
+ int height = 0;
+ XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL);
+ int button_width = width / 4;
+ int x = 0;
+ CxIterator tabIterator = cxListIterator(v->tabs);
+ cx_foreach(UiTab*, tab, tabIterator) {
+ XtVaSetValues(
+ tab->tab_button,
+ XmNx, x,
+ XmNy, 0,
+ XmNwidth,
+ button_width,
+
+ NULL);
+ x += button_width;
+ }
+
+ if(height <= v->height) {
+ XtVaSetValues(widget, XmNheight, v->height + 4, NULL);
+ }
+}
+
+static void ui_tabbar_expose(Widget widget, XtPointer udata, XtPointer cdata) {
+ MotifTabbedPane *v = (MotifTabbedPane*)udata;
+ XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)cdata;
+ XEvent *event = cbs->event;
+ Display *dpy = XtDisplay(widget);
+
+ XGCValues gcvals;
+ GC gc;
+ Pixel fgpix;
+
+ int tab_x;
+ int tab_width;
+ XtVaGetValues(v->current->tab_button, XmNx, &tab_x, XmNwidth, &tab_width, XmNhighlightColor, &fgpix, NULL);
+
+ gcvals.foreground = v->bg1;
+ gc = XCreateGC( dpy, XtWindow(widget), (GCForeground), &gcvals);
+
+ int width = 0;
+ int height = 0;
+ XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL);
+ XFillRectangle(dpy, XtWindow(widget), gc, 0, 0, width, height);
+
+ gcvals.foreground = fgpix;
+ gc = XCreateGC( dpy, XtWindow(widget), (GCForeground), &gcvals);
+
+ XFillRectangle(dpy, XtWindow(widget), gc, tab_x, 0, tab_width, height);
+
+}
+
+UiTabbedPane* ui_tabbed_document_view(UiObject *obj) {
+ int n = 0;
+ Arg args[16];
+
+ UiContainer *ct = uic_get_current_container(obj);
+ Widget parent = ct->prepare(ct, args, &n, TRUE);
+
+ Widget tabview = XmCreateForm(parent, "tabview_form", args, n);
+ XtManageChild(tabview);
+
+ XtSetArg(args[0], XmNorientation, XmHORIZONTAL);
+ XtSetArg(args[1], XmNpacking, XmPACK_TIGHT);
+ XtSetArg(args[2], XmNspacing, 1);
+ XtSetArg(args[3], XmNleftAttachment, XmATTACH_FORM);
+ XtSetArg(args[4], XmNrightAttachment, XmATTACH_FORM);
+ XtSetArg(args[5], XmNtopAttachment, XmATTACH_FORM);
+ XtSetArg(args[6], XmNmarginWidth, 0);
+ XtSetArg(args[7], XmNmarginHeight, 0);
+ Widget tabbar = XmCreateDrawingArea(tabview, "tabbar", args, 8);
+ XtManageChild(tabbar);
+
+ XtSetArg(args[0], XmNleftAttachment, XmATTACH_FORM);
+ XtSetArg(args[1], XmNrightAttachment, XmATTACH_FORM);
+ XtSetArg(args[2], XmNtopAttachment, XmATTACH_WIDGET);
+ XtSetArg(args[3], XmNtopWidget, tabbar);
+ XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM);
+ XtSetArg(args[5], XmNshadowThickness, 0);
+ Widget tabct = XmCreateForm(tabview, "tabview", args, 6);
+ XtManageChild(tabct);
+
+ MotifTabbedPane *tabbedpane = ui_malloc(obj->ctx, sizeof(MotifTabbedPane));
+ tabbedpane->view.ctx = uic_current_obj(obj)->ctx;
+ tabbedpane->view.widget = tabct;
+ tabbedpane->view.document = NULL;
+ tabbedpane->tabbar = tabbar;
+ tabbedpane->tabs = cxArrayListCreate(obj->ctx->allocator, cx_cmp_uintptr, CX_STORE_POINTERS, 16);
+ tabbedpane->current = NULL;
+ tabbedpane->height = 0;
+
+ XtAddCallback(tabbar, XmNresizeCallback , ui_tabbar_resize, tabbedpane);
+ XtAddCallback(tabbar, XmNexposeCallback, ui_tabbar_expose, tabbedpane);
+
+ return &tabbedpane->view;
+}
+
+UiObject* ui_document_tab(UiTabbedPane *view) {
+ MotifTabbedPane *v = (MotifTabbedPane*)view;
+ int n = 0;
+ Arg args[16];
+
+ // hide the current tab content
+ if(v->current) {
+ XtUnmanageChild(v->current->content->widget);
+ }
+
+ UiTab *tab = ui_malloc(view->ctx, sizeof(UiTab));
+
+ // create the new tab content
+ XtSetArg(args[0], XmNshadowThickness, 0);
+ XtSetArg(args[1], XmNleftAttachment, XmATTACH_FORM);
+ XtSetArg(args[2], XmNrightAttachment, XmATTACH_FORM);
+ XtSetArg(args[3], XmNtopAttachment, XmATTACH_FORM);
+ XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM);
+ XtSetArg(args[5], XmNuserData, tab);
+ Widget frame = XmCreateFrame(view->widget, "tab", args, 6);
+ XtManageChild(frame);
+
+ UiObject *content = ui_malloc(view->ctx, sizeof(UiObject));
+ content->widget = NULL; // initialization for uic_context()
+ content->ctx = uic_context(content, view->ctx->mp);
+ content->ctx->parent = view->ctx;
+ content->ctx->attach_document = uic_context_attach_document;
+ content->ctx->detach_document2 = uic_context_detach_document2;
+ content->widget = frame;
+ content->window = view->ctx->obj->window;
+ content->container = ui_frame_container(content, frame);
+ content->next = NULL;
+
+ // add tab button
+ cxListAdd(v->tabs, tab);
+
+ XmString label = XmStringCreateLocalized("tab");
+ XtSetArg(args[0], XmNlabelString, label);
+ XtSetArg(args[1], XmNshadowThickness, 0);
+ XtSetArg(args[2], XmNhighlightThickness, 0);
+
+ Widget button = XmCreatePushButton(v->tabbar, "tab_button", args, 3);
+ tab->tabbedpane = v;
+ tab->content = content;
+ tab->tab_button = button;
+ XtManageChild(button);
+ XtAddCallback(
+ button,
+ XmNactivateCallback,
+ (XtCallbackProc)ui_tab_button_callback,
+ tab);
+
+ if(v->height == 0) {
+ XtVaGetValues(
+ button,
+ XmNarmColor,
+ &v->bg1,
+ XmNbackground,
+ &v->bg2,
+ XmNheight,
+ &v->height,
+ NULL);
+ v->height += 2; // border
+ }
+
+ ui_change_tab(v, tab);
+ ui_tabbar_resize(v->tabbar, v, NULL);
+
+ return content;
+}
+
+void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d) {
+ MotifTabbedPane *t = tab->tabbedpane;
+ if(t->current) {
+ XtUnmanageChild(t->current->content->widget);
+ XtVaSetValues(t->current->tab_button, XmNset, 0, NULL);
+ }
+ XtManageChild(tab->content->widget);
+
+ ui_change_tab(t, tab);
+
+}
+
+void ui_change_tab(MotifTabbedPane *pane, UiTab *tab) {
+ UiContext *ctx = tab->content->ctx;
+ ctx->parent->detach_document2(ctx->parent, pane->current->content->ctx->document);
+ ctx->parent->attach_document(ctx->parent, ctx->document);
+
+ if(pane->current) {
+ XtVaSetValues(pane->current->tab_button, XmNshadowThickness, 0, XmNbackground, pane->bg1, NULL);
+ }
+ XtVaSetValues(tab->tab_button, XmNshadowThickness, 1, XmNbackground, pane->bg2, NULL);
+
+ pane->current = tab;
+ pane->index = cxListFind(pane->tabs, tab);
+ printf("index: %d\n", pane->index);
+
+ // redraw tabbar
+ Display *dpy = XtDisplay(pane->tabbar);
+ Window window = XtWindow(pane->tabbar);
+ if(dpy && window) {
+ XClearArea(dpy, XtWindow(pane->tabbar), 0, 0, 0, 0, TRUE);
+ XFlush(dpy);
+ }
+}
+
+void ui_tab_set_document(UiContext *ctx, void *document) {
+ if(ctx->parent->document) {
+ //ctx->parent->detach_document(ctx->parent, ctx->parent->document);
+ }
+ uic_context_attach_document(ctx, document);
+ //uic_context_set_document(ctx->parent, document);
+ //ctx->parent->document = document;
+
+ UiTab *tab = NULL;
+ XtVaGetValues(
+ ctx->obj->widget,
+ XmNuserData,
+ &tab,
+ NULL);
+ if(tab) {
+ if(tab->tabbedpane->current == tab) {
+ ctx->parent->attach_document(ctx->parent, ctx->document);
+ }
+ } else {
+ fprintf(stderr, "UiError: ui_bar_set_document: Cannot set document");
+ }
+}
+
+
+
+/*
+ * -------------------- Layout Functions --------------------
+ *
+ * functions for setting layout attributes for the current container
+ *
+ */
+
+void ui_layout_fill(UiObject *obj, UiBool fill) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.fill = ui_bool2lb(fill);
+}
+
+void ui_layout_hexpand(UiObject *obj, UiBool expand) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.hexpand = expand;
+}
+
+void ui_layout_vexpand(UiObject *obj, UiBool expand) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.vexpand = expand;
+}
+
+void ui_layout_gridwidth(UiObject *obj, int width) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.gridwidth = width;
+}
+
+void ui_newline(UiObject *obj) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.newline = TRUE;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CONTAINER_H
+#define CONTAINER_H
+
+#include "../ui/toolkit.h"
+#include "../ui/container.h"
+#include <cx/list.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
+
+typedef struct MotifTabbedPane MotifTabbedPane;
+typedef struct UiTab UiTab;
+typedef struct UiBoxContainer UiBoxContainer;
+typedef struct UiGridContainer UiGridContainer;
+typedef struct UiTabViewContainer UiTabViewContainer;
+typedef struct UiLayout UiLayout;
+
+typedef Widget (*ui_container_add_f)(UiContainer*, Arg*, int*, UiBool);
+
+typedef enum UiLayoutBool UiLayoutBool;
+typedef enum UiBoxOrientation UiBoxOrientation;
+
+
+enum UiLayoutBool {
+ UI_LAYOUT_UNDEFINED = 0,
+ UI_LAYOUT_TRUE,
+ UI_LAYOUT_FALSE,
+};
+
+enum UiBoxOrientation {
+ UI_BOX_VERTICAL = 0,
+ UI_BOX_HORIZONTAL
+};
+
+struct UiLayout {
+ UiLayoutBool fill;
+ UiBool newline;
+ char *label;
+ UiBool hexpand;
+ UiBool vexpand;
+ int gridwidth;
+};
+
+struct UiContainer {
+ Widget widget;
+ Widget (*prepare)(UiContainer*, Arg *, int*, UiBool);
+ void (*add)(UiContainer*, Widget);
+ UiLayout layout;
+ Widget current;
+ Widget menu;
+};
+
+struct UiBoxContainer {
+ UiContainer container;
+ Widget prev_widget;
+ UiBool has_fill;
+ UiBoxOrientation orientation;
+ int margin;
+ int spacing;
+};
+
+struct UiGridContainer {
+ UiContainer container;
+ CxList *lines;
+ CxList *current;
+ int columnspacing;
+ int rowspacing;
+};
+
+struct UiTabViewContainer {
+ UiContainer container;
+ UiContext *context;
+ Widget widget;
+ CxList *tabs;
+ Widget current;
+};
+
+struct MotifTabbedPane {
+ UiTabbedPane view;
+ Widget tabbar;
+ CxList *tabs;
+ UiTab *current;
+ int index;
+ Pixel bg1;
+ Pixel bg2;
+ int height;
+};
+
+struct UiTab {
+ MotifTabbedPane *tabbedpane;
+ UiObject *content;
+ Widget tab_button;
+};
+
+
+UiContainer* ui_frame_container(UiObject *obj, Widget frame);
+Widget ui_frame_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
+void ui_frame_container_add(UiContainer *ct, Widget widget);
+
+UiContainer* ui_box_container(UiObject *obj, Widget box, int margin, int spacing, UiBoxOrientation orientation);
+Widget ui_box_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
+void ui_box_container_add(UiContainer *ct, Widget widget);
+
+UiContainer* ui_grid_container(UiObject *obj, Widget form, int columnspacing, int rowspacing);
+Widget ui_grid_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
+void ui_grid_container_add(UiContainer *ct, Widget widget);
+
+UiContainer* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow);
+Widget ui_scrolledwindow_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
+void ui_scrolledwindow_container_add(UiContainer *ct, Widget widget);
+
+UiContainer* ui_tabview_container(UiObject *obj, Widget rowcolumn);
+Widget ui_tabview_container_prepare(UiContainer *ct, Arg *args, int *n, UiBool fill);
+void ui_tabview_container_add(UiContainer *ct, Widget widget);
+
+void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d);
+void ui_change_tab(MotifTabbedPane *pane, UiTab *tab);
+
+void ui_tab_set_document(UiContext *ctx, void *document);
+void ui_tab_detach_document(UiContext *ctx);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CONTAINER_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dnd.h"
+
+void ui_selection_settext(UiSelection *sel, char *str, int len) {
+
+}
+
+void ui_selection_seturis(UiSelection *sel, char **uris, int nelm) {
+
+}
+
+char* ui_selection_gettext(UiSelection *sel) {
+ return NULL;
+}
+
+char** ui_selection_geturis(UiSelection *sel, size_t *nelm) {
+ return NULL;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DND_H
+#define DND_H
+
+#include "../ui/dnd.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DND_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <X11/Intrinsic.h>
+#include <X11/IntrinsicP.h>
+
+#include "graphics.h"
+
+#include "container.h"
+
+static void ui_drawingarea_expose(Widget widget, XtPointer u, XtPointer c) {
+ UiDrawEvent *drawevent = u;
+ //XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)c;
+ //XEvent *event = cbs->event;
+ Display *dpy = XtDisplay(widget);
+
+ UiEvent ev;
+ ev.obj = drawevent->obj;
+ ev.window = drawevent->obj->window;
+ ev.document = drawevent->obj->ctx->document;
+ ev.eventdata = NULL;
+ ev.intval = 0;
+
+ XtVaGetValues(
+ widget,
+ XmNwidth,
+ &drawevent->gr.g.width,
+ XmNheight,
+ &drawevent->gr.g.height,
+ NULL);
+
+ XGCValues gcvals;
+ gcvals.foreground = BlackPixelOfScreen(XtScreen(widget));
+ drawevent->gr.gc = XCreateGC(dpy, XtWindow(widget), (GCForeground), &gcvals);
+
+ drawevent->callback(&ev, &drawevent->gr.g, drawevent->userdata);
+}
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
+ UiContainer *ct = uic_get_current_container(obj);
+
+ int n = 0;
+ Arg args[16];
+
+ Widget parent = ct->prepare(ct, args, &n, TRUE);
+ Widget drawingarea = XmCreateDrawingArea(parent, "drawingarea", args, n);
+
+ if(f) {
+ UiDrawEvent *event = malloc(sizeof(UiDrawEvent));
+ event->obj = obj;
+ event->callback = f;
+ event->userdata = userdata;
+
+ event->gr.display = XtDisplay(drawingarea);
+ event->gr.widget = drawingarea;
+
+ Colormap colormap;
+ XtVaGetValues(drawingarea, XmNcolormap, &colormap, NULL);
+ event->gr.colormap = colormap;
+
+ XtAddCallback(
+ drawingarea,
+ XmNexposeCallback,
+ ui_drawingarea_expose,
+ event);
+
+ XtVaSetValues(drawingarea, XmNuserData, event, NULL);
+ }
+
+ XtManageChild(drawingarea);
+ return drawingarea;
+}
+
+static void ui_drawingarea_input(Widget widget, XtPointer u, XtPointer c) {
+ XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c;
+ XEvent *xevent = cbs->event;
+ UiMouseEventData *event = u;
+
+ if (cbs->reason == XmCR_INPUT) {
+ if (xevent->xany.type == ButtonPress) {
+ UiMouseEvent me;
+ me.x = xevent->xbutton.x;
+ me.y = xevent->xbutton.y;
+ // TODO: configurable double click time
+ me.type = xevent->xbutton.time - event->last_event > 300 ? UI_PRESS : UI_PRESS2;
+
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = &me;
+ e.intval = 0;
+ event->callback(&e, event->userdata);
+
+
+ event->last_event = me.type == UI_PRESS2 ? 0 : xevent->xbutton.time;
+ }
+ }
+
+}
+
+void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) {
+ if(f) {
+ UiMouseEventData *event = malloc(sizeof(UiMouseEventData));
+ event->obj = obj;
+ event->callback = f;
+ event->userdata = u;
+ event->last_event = 0;
+
+ XtAddCallback(widget, XmNinputCallback, ui_drawingarea_input, event);
+ }
+}
+
+void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
+ XtVaGetValues(
+ drawingarea,
+ XmNwidth,
+ width,
+ XmNheight,
+ height,
+ NULL);
+}
+
+void ui_drawingarea_redraw(UIWIDGET drawingarea) {
+ //XClearArea(XtDisplay(drawingarea), drawingarea->core.window, 0, 0, drawingarea->core.width, drawingarea->core.height, True);
+ UiDrawEvent *event;
+ XtVaGetValues(drawingarea, XmNuserData, &event, NULL);
+ ui_drawingarea_expose(drawingarea, event, NULL);
+}
+
+
+/* -------------------- text layout functions -------------------- */
+UiTextLayout* ui_text(UiGraphics *g) {
+ UiTextLayout *text = malloc(sizeof(UiTextLayout));
+ memset(text, 0, sizeof(UiTextLayout));
+ text->text = NULL;
+ text->length = 0;
+ text->widget = ((UiXlibGraphics*)g)->widget;
+ text->fontset = NULL;
+ return text;
+}
+
+static void create_default_fontset(UiTextLayout *layout) {
+ char **missing = NULL;
+ int num_missing = 0;
+ char *def = NULL;
+ Display *dpy = XtDisplay(layout->widget);
+ XFontSet fs = XCreateFontSet(
+ dpy,
+ "-dt-interface system-medium-r-normal-s*utf*:,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-1,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-10,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-15,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-2,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-3,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-4,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-5,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-9,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-e,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-r,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-ru,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-u,"
+ "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-uni,"
+ "-misc-fixed-medium-r-normal--14-130-75-75-c-140-jisx0208",
+ &missing, &num_missing, &def);
+ layout->fontset = fs;
+}
+
+void ui_text_free(UiTextLayout *text) {
+ // TODO
+}
+
+void ui_text_setstring(UiTextLayout *layout, char *str) {
+ ui_text_setstringl(layout, str, strlen(str));
+}
+
+void ui_text_setstringl(UiTextLayout *layout, char *str, int len) {
+ layout->text = str;
+ layout->length = len;
+ layout->changed = 1;
+}
+
+void ui_text_setfont(UiTextLayout *layout, char *font, int size) {
+ create_default_fontset(layout);//TODO
+ layout->changed = 1;
+}
+
+void ui_text_getsize(UiTextLayout *layout, int *width, int *height) {
+ if(layout->changed) {
+ XRectangle ext, lext;
+ XmbTextExtents(layout->fontset, layout->text, layout->length, &ext, &lext);
+ layout->width = ext.width;
+ layout->height = ext.height;
+ layout->changed = 0;
+ }
+ *width = layout->width;
+ *height = layout->height;
+}
+
+void ui_text_setwidth(UiTextLayout *layout, int width) {
+ layout->maxwidth = width;
+}
+
+
+/* -------------------- drawing functions -------------------- */
+
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
+ UiXlibGraphics *gr = (UiXlibGraphics*)g;
+ XColor color;
+ color.flags= DoRed | DoGreen | DoBlue;
+ color.red = red * 257;
+ color.green = green * 257;
+ color.blue = blue * 257;
+ XAllocColor(gr->display, gr->colormap, &color);
+ XSetForeground(gr->display, gr->gc, color.pixel);
+}
+
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
+ UiXlibGraphics *gr = (UiXlibGraphics*)g;
+ XDrawLine(gr->display, XtWindow(gr->widget), gr->gc, x1, y1, x2, y2);
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+ UiXlibGraphics *gr = (UiXlibGraphics*)g;
+ if(fill) {
+ XFillRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h);
+ } else {
+ XDrawRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h);
+ }
+}
+
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
+ UiXlibGraphics *gr = (UiXlibGraphics*)g;
+ int width, height;
+ ui_text_getsize(text, &width, &height);
+ if(text->maxwidth > 0) {
+ XRectangle clip;
+ clip.x = x;
+ clip.y = y;
+ clip.width = text->maxwidth;
+ clip.height = height;
+ XSetClipRectangles(gr->display, gr->gc, 0, 0, &clip, 1, Unsorted);
+ }
+
+ XmbDrawString(
+ gr->display,
+ XtWindow(gr->widget),
+ text->fontset,
+ gr->gc,
+ x,
+ y + height,
+ text->text,
+ text->length);
+
+ XSetClipMask(gr->display, gr->gc, None);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GRAPHICS_H
+#define GRAPHICS_H
+
+#include "../ui/graphics.h"
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiXlibGraphics {
+ UiGraphics g;
+ Display *display;
+ Widget widget;
+ Colormap colormap;
+ GC gc;
+} UiXlibGraphics;
+
+typedef struct UiDrawEvent {
+ ui_drawfunc callback;
+ UiObject *obj;
+ void *userdata;
+ UiXlibGraphics gr;
+} UiDrawEvent;
+
+typedef struct UiMouseEventData {
+ UiObject *obj;
+ ui_callback callback;
+ void *userdata;
+ Time last_event;
+} UiMouseEventData;
+
+struct UiTextLayout {
+ char *text;
+ size_t length;
+ Widget widget;
+ XFontSet fontset;
+ int maxwidth;
+ int width;
+ int height;
+ int changed;
+};
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GRAPHICS_H */
+
--- /dev/null
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+#include "image.h"
+
+UiIcon* ui_icon(const char *name, int size) {
+ return NULL;
+}
+
+UiIcon* ui_icon_unscaled(const char *name, int size) {
+ return NULL;
+}
+
+void ui_free_icon(UiIcon *icon) {
+
+}
+
+UiImage* ui_icon_image(UiIcon *icon) {
+ return NULL;
+}
+
+UiImage* ui_image(const char *filename) {
+ return NULL;
+}
+
+UiImage* ui_named_image(const char *filename, const char *name) {
+ return NULL;
+}
+
+UiImage* ui_load_image_from_path(const char *path, const char *name) {
+ return NULL;
+}
+
+void ui_free_image(UiImage *img) {
+
+}
+
--- /dev/null
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+/*
+ * File: image.h
+ * Author: olaf
+ *
+ * Created on 1. Juli 2018, 19:01
+ */
+
+#ifndef IMAGE_H
+#define IMAGE_H
+
+#include "../ui/image.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IMAGE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "label.h"
+#include "container.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+UIWIDGET ui_label(UiObject *obj, char *label) {
+ UiContainer *ct = uic_get_current_container(obj);
+ XmString str = XmStringCreateLocalized(label);
+
+ int n = 0;
+ Arg args[16];
+ XtSetArg(args[n], XmNlabelString, str);
+ n++;
+
+ Widget parent = ct->prepare(ct, args, &n, FALSE);
+ Widget widget = XmCreateLabel(parent, "label", args, n);
+ ct->add(ct, widget);
+ XtManageChild(widget);
+
+ return widget;
+}
+
+UIWIDGET ui_space(UiObject *obj) {
+ UiContainer *ct = uic_get_current_container(obj);
+ XmString str = XmStringCreateLocalized("");
+
+ int n = 0;
+ Arg args[16];
+ XtSetArg(args[n], XmNlabelString, str);
+ n++;
+
+ Widget parent = ct->prepare(ct, args, &n, TRUE);
+ Widget widget = XmCreateLabel(parent, "space_label", args, n);
+ ct->add(ct, widget);
+ XtManageChild(widget);
+
+ return widget;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LABEL_H
+#define LABEL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LABEL_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "container.h"
+
+#include "list.h"
+#include "../common/object.h"
+
+
+void* ui_strmodel_getvalue(void *elm, int column) {
+ return column == 0 ? elm : NULL;
+}
+
+
+UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
+ return ui_listview(obj, list, ui_strmodel_getvalue, f, udata);
+}
+
+UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+ int count;
+ XmStringTable items = ui_create_stringlist(var->value, getvalue, &count);
+
+ Arg args[8];
+ int n = 0;
+ XtSetArg(args[n], XmNitemCount, count);
+ n++;
+ XtSetArg(args[n], XmNitems, count == 0 ? NULL : items);
+ n++;
+
+ UiContainer *ct = uic_get_current_container(obj);
+ Widget parent = ct->prepare(ct, args, &n, TRUE);
+ Widget widget = XmCreateScrolledList(parent, "listview", args, n);
+ ct->add(ct, XtParent(widget));
+ XtManageChild(widget);
+
+ UiListView *listview = cxMalloc(obj->ctx->allocator, sizeof(UiListView));
+ listview->widget = widget;
+ listview->list = var;
+ listview->getvalue = getvalue;
+
+ for (int i=0;i<count;i++) {
+ XmStringFree(items[i]);
+ }
+ XtFree((char *)items);
+
+ if(f) {
+ UiListViewEventData *event = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiListViewEventData));
+ event->event.obj = obj;
+ event->event.userdata = udata;
+ event->event.callback = f;
+ event->event.value = 0;
+ event->var = var;
+ XtAddCallback(
+ widget,
+ XmNdefaultActionCallback,
+ (XtCallbackProc)ui_list_selection_callback,
+ event);
+ }
+
+ return widget;
+}
+
+UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+ UiVar *var = malloc(sizeof(UiVar));
+ var->value = list;
+ var->type = UI_VAR_SPECIAL;
+ return ui_listview_var(obj, var, getvalue, f, udata);
+}
+
+UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+ UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+ if(var) {
+ UiListVar *value = var->value;
+ return ui_listview_var(obj, var, getvalue, f, udata);
+ } else {
+ // TODO: error
+ }
+ return NULL;
+}
+
+
+XmStringTable ui_create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count) {
+ int num = list->count(list);
+ XmStringTable items = (XmStringTable)XtMalloc(num * sizeof(XmString));
+ void *data = list->first(list);
+ for(int i=0;i<num;i++) {
+ items[i] = XmStringCreateLocalized(getvalue(data, 0));
+ data = list->next(list);
+ }
+
+ *count = num;
+ return items;
+}
+
+
+void ui_listview_update(UiEvent *event, UiListView *view) {
+ int count;
+ XmStringTable items = ui_create_stringlist(
+ view->list->value,
+ view->getvalue,
+ &count);
+
+ XtVaSetValues(
+ view->widget,
+ XmNitems, count == 0 ? NULL : items,
+ XmNitemCount,
+ count,
+ NULL);
+
+ for (int i=0;i<count;i++) {
+ XmStringFree(items[i]);
+ }
+ XtFree((char *)items);
+}
+
+void ui_list_selection_callback (Widget widget, UiListViewEventData *event, XtPointer data) {
+ XmListCallbackStruct *cbs = (XmListCallbackStruct *)data;
+
+ UiEvent e;
+ e.obj = event->event.obj;
+ e.window = event->event.obj->window;
+ e.document = event->event.obj->ctx->document;
+ UiList *list = event->var->value;
+ e.eventdata = list->get(list, cbs->item_position - 1);
+ e.intval = cbs->item_position - 1;
+ event->event.callback(&e, event->event.userdata);
+}
+
+
+/* --------------------------- ComboBox --------------------------- */
+
+UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
+ return ui_combobox(obj, list, ui_strmodel_getvalue, f, udata);
+}
+
+UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+ UiVar *var = malloc(sizeof(UiVar));
+ var->value = list;
+ var->type = UI_VAR_SPECIAL;
+ return ui_combobox_var(obj, var, getvalue, f, udata);
+}
+
+UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+ UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST);
+ if(var) {
+ UiListVar *value = var->value;
+ return ui_combobox_var(obj, var, getvalue, f, udata);
+ } else {
+ // TODO: error
+ }
+ return NULL;
+}
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) {
+ UiListView *listview = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiListView));
+
+ UiContainer *ct = uic_get_current_container(obj);
+ Arg args[16];
+ int n = 0;
+ XtSetArg(args[n], XmNindicatorOn, XmINDICATOR_NONE);
+ n++;
+ XtSetArg(args[n], XmNtraversalOn, FALSE);
+ n++;
+ XtSetArg(args[n], XmNwidth, 160);
+ n++;
+ Widget parent = ct->prepare(ct, args, &n, FALSE);
+ Widget combobox = XmCreateDropDownList(parent, "combobox", args, n);
+ XtManageChild(combobox);
+ listview->widget = combobox;
+ listview->list = var;
+ listview->getvalue = getvalue;
+
+ ui_listview_update(NULL, listview);
+
+ return parent;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIST_H
+#define LIST_H
+
+#include "toolkit.h"
+#include "../ui/tree.h"
+#include "../common/context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiListView {
+ Widget widget;
+ UiVar *list;
+ ui_getvaluefunc getvalue;
+} UiListView;
+
+typedef struct UiListViewEventData {
+ UiEventData event;
+ UiVar *var;
+} UiListViewEventData;
+
+void* ui_strmodel_getvalue(void *elm, int column);
+
+XmStringTable ui_create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count);
+void ui_listview_update(UiEvent *event, UiListView *view);
+void ui_list_selection_callback (Widget widget, UiListViewEventData *event, XtPointer data);
+
+UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIST_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "menu.h"
+#include "button.h"
+#include "toolkit.h"
+#include "stock.h"
+#include "container.h"
+#include "../common/context.h"
+#include "../ui/window.h"
+
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+static ui_menu_add_f createMenuItem[] = {
+ /* UI_MENU */ add_menu_widget,
+ /* UI_MENU_SUBMENU */ add_menu_widget,
+ /* UI_MENU_ITEM */ add_menuitem_widget,
+ /* UI_MENU_STOCK_ITEM */ add_menuitem_st_widget,
+ /* UI_MENU_CHECK_ITEM */ add_checkitem_widget,
+ /* UI_MENU_CHECK_ITEM_NV */ add_checkitemnv_widget,
+ /* UI_MENU_ITEM_LIST */ add_menuitem_list_widget,
+ /* UI_MENU_ITEM_LIST_NV */ NULL, // TODO
+ /* UI_MENU_SEPARATOR */ add_menuseparator_widget
+};
+
+// private menu functions
+void ui_create_menubar(UiObject *obj) {
+ UiMenu *menus = uic_get_menu_list();
+ if(!menus) {
+ return;
+ }
+
+ Widget menubar = XmCreateMenuBar(obj->widget, "main_list", NULL, 0);
+ XtManageChild(menubar);
+
+ UiMenu *menu = menus;
+ int menu_index = 0;
+ while(menu) {
+ menu_index += add_menu_widget(menubar, menu_index, &menu->item, obj);
+
+ menu = (UiMenu*)menu->item.next;
+ }
+}
+
+int add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) {
+ UiMenu *menu = (UiMenu*)item;
+
+ Widget menuItem = XtVaCreateManagedWidget(
+ menu->label,
+ xmCascadeButtonWidgetClass,
+ parent,
+ NULL);
+ Widget m = XmVaCreateSimplePulldownMenu(parent, menu->label, i, NULL, NULL);
+
+ UiMenuItemI *mi = menu->items_begin;
+ int menu_index = 0;
+ while(mi) {
+ menu_index += createMenuItem[mi->type](m, menu_index, mi, obj);
+ mi = mi->next;
+ }
+
+ return 1;
+}
+
+int add_menuitem_widget(
+ Widget parent,
+ int i,
+ UiMenuItemI *item,
+ UiObject *obj)
+{
+ UiMenuItem *mi = (UiMenuItem*)item;
+
+ Arg args[1];
+ XmString label = XmStringCreateLocalized(mi->label);
+ XtSetArg(args[0], XmNlabelString, label);
+
+ Widget mitem = XtCreateManagedWidget(
+ "menubutton",
+ xmPushButtonWidgetClass,
+ parent,
+ args,
+ 1);
+ XmStringFree(label);
+
+ if(mi->callback != NULL) {
+ UiEventData *event = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = mi->userdata;
+ event->callback = mi->callback;
+ event->value = 0;
+ XtAddCallback(
+ mitem,
+ XmNactivateCallback,
+ (XtCallbackProc)ui_push_button_callback,
+ event);
+ }
+
+ if(mi->groups) {
+ uic_add_group_widget(obj->ctx, mitem, (ui_enablefunc)XtSetSensitive, mi->groups);
+ }
+
+ return 1;
+}
+
+int add_menuitem_st_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) {
+ UiStMenuItem *mi = (UiStMenuItem*)item;
+
+ UiStockItem *si = ui_get_stock_item(mi->stockid);
+ if(!si) {
+ fprintf(stderr, "UI Error: unknown stock id: %s\n", mi->stockid);
+ return 0;
+ }
+
+ int n = 0;
+ Arg args[4];
+ XmString label = XmStringCreateLocalized(si->label);
+ XmString at = NULL;
+
+ XtSetArg(args[n], XmNlabelString, label);
+ n++;
+ if(si->accelerator) {
+ XtSetArg(args[n], XmNaccelerator, si->accelerator);
+ n++;
+ }
+ if(si->accelerator_label) {
+ at = XmStringCreateLocalized(si->accelerator_label);
+ XtSetArg(args[n], XmNacceleratorText, at);
+ n++;
+ }
+
+ Widget mitem = XtCreateManagedWidget(
+ "menubutton",
+ xmPushButtonWidgetClass,
+ parent,
+ args,
+ n);
+ XmStringFree(label);
+ if(at) {
+ XmStringFree(at);
+ }
+
+ if(mi->callback != NULL) {
+ UiEventData *event = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = mi->userdata;
+ event->callback = mi->callback;
+ event->value = 0;
+ XtAddCallback(
+ mitem,
+ XmNactivateCallback,
+ (XtCallbackProc)ui_push_button_callback,
+ event);
+ }
+
+ if(mi->groups) {
+ uic_add_group_widget(obj->ctx, mitem, (ui_enablefunc)XtSetSensitive, mi->groups);
+ }
+
+ return 1;
+}
+
+int add_menuseparator_widget(
+ Widget parent,
+ int i,
+ UiMenuItemI *item,
+ UiObject *obj)
+{
+ Widget s = XmCreateSeparatorGadget (parent, "menu_separator", NULL, 0);
+ XtManageChild(s);
+ return 1;
+}
+
+int add_checkitem_widget(
+ Widget parent,
+ int i,
+ UiMenuItemI *item,
+ UiObject *obj)
+{
+ UiCheckItem *ci = (UiCheckItem*)item;
+
+ Arg args[3];
+ XmString label = XmStringCreateLocalized(ci->label);
+ XtSetArg(args[0], XmNlabelString, label);
+ XtSetArg(args[1], XmNvisibleWhenOff, 1);
+ Widget checkbox = XtCreateManagedWidget(
+ "menutogglebutton",
+ xmToggleButtonWidgetClass,
+ parent,
+ args,
+ 2);
+ XmStringFree(label);
+
+ if(ci->callback) {
+ UiEventData *event = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = ci->userdata;
+ event->callback = ci->callback;
+ XtAddCallback(
+ checkbox,
+ XmNvalueChangedCallback,
+ (XtCallbackProc)ui_toggle_button_callback,
+ event);
+ }
+
+ return 1;
+}
+
+int add_checkitemnv_widget(
+ Widget parent,
+ int i,
+ UiMenuItemI *item,
+ UiObject *obj)
+{
+ UiCheckItemNV *ci = (UiCheckItemNV*)item;
+
+ Arg args[3];
+ XmString label = XmStringCreateLocalized(ci->label);
+ XtSetArg(args[0], XmNlabelString, label);
+ XtSetArg(args[1], XmNvisibleWhenOff, 1);
+ Widget checkbox = XtCreateManagedWidget(
+ "menutogglebutton",
+ xmToggleButtonWidgetClass,
+ parent,
+ args,
+ 2);
+ XmStringFree(label);
+
+ UiVar *var = uic_create_var(obj->ctx, ci->varname, UI_VAR_INTEGER);
+ if(var) {
+ UiInteger *value = var->value;
+ value->obj = checkbox;
+ value->get = ui_toggle_button_get;
+ value->set = ui_toggle_button_set;
+ value = 0;
+ } else {
+ // TODO: error
+ }
+
+ return 1;
+}
+
+int add_menuitem_list_widget(
+ Widget parent,
+ int i,
+ UiMenuItemI *item,
+ UiObject *obj)
+{
+ UiMenuItemList *il = (UiMenuItemList*)item;
+
+ UiActiveMenuItemList *ls = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiActiveMenuItemList));
+
+ ls->object = obj;
+ ls->menu = parent;
+ ls->index = i;
+ ls->oldcount = 0;
+ ls->list = il->list;
+ ls->callback = il->callback;
+ ls->userdata = il->userdata;
+
+ ls->list->observers = ui_add_observer(
+ ls->list->observers,
+ (ui_callback)ui_update_menuitem_list,
+ ls);
+
+ ui_update_menuitem_list(NULL, ls);
+
+ return 0;
+}
+
+void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) {
+ Arg args[4];
+
+ // remove old items
+ if(list->oldcount > 0) {
+ Widget *children;
+ int nc;
+
+ XtVaGetValues(
+ list->menu,
+ XmNchildren,
+ &children,
+ XmNnumChildren,
+ &nc,
+ NULL);
+
+ for(int i=0;i<list->oldcount;i++) {
+ XtDestroyWidget(children[list->index + i]);
+ }
+ }
+
+ char *str = ui_list_first(list->list);
+ if(str) {
+ // add separator
+ XtSetArg(args[0], XmNpositionIndex, list->index);
+ Widget s = XmCreateSeparatorGadget (list->menu, "menu_separator", args, 1);
+ XtManageChild(s);
+ }
+ int i = 1;
+ while(str) {
+ XmString label = XmStringCreateLocalized(str);
+ XtSetArg(args[0], XmNlabelString, label);
+ XtSetArg(args[1], XmNpositionIndex, list->index + i);
+
+ Widget mitem = XtCreateManagedWidget(
+ "menubutton",
+ xmPushButtonWidgetClass,
+ list->menu,
+ args,
+ 2);
+ XmStringFree(label);
+
+ if(list->callback) {
+ // TODO: use mempool
+ UiEventData *event = malloc(sizeof(UiEventData));
+ event->obj = list->object;
+ event->userdata = list->userdata;
+ event->callback = list->callback;
+ event->value = i - 1;
+
+ XtAddCallback(
+ mitem,
+ XmNactivateCallback,
+ (XtCallbackProc)ui_push_button_callback,
+ event);
+ }
+
+ str = ui_list_next(list->list);
+ i++;
+ }
+
+ list->oldcount = i;
+}
+
+void ui_menu_event_wrapper(Widget widget, XtPointer udata, XtPointer cdata) {
+ UiEventData *event = udata;
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.intval = 0;
+ event->callback(&e, event->userdata);
+}
+
+
+/*
+ * widget menu functions
+ */
+
+static void ui_popup_handler(Widget widget, XtPointer data, XEvent *event, Boolean *c) {
+ Widget menu = data;
+ XmMenuPosition(menu, (XButtonPressedEvent *)event);
+ XtManageChild(menu);
+
+ *c = FALSE;
+}
+
+UIMENU ui_contextmenu(UiObject *obj) {
+ UiContainer *ct = uic_get_current_container(obj);
+ if(ct->current) {
+ return ui_contextmenu_w(obj, ct->current);
+ } else {
+ return NULL; // TODO: warn
+ }
+}
+
+UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) {
+ UiContainer *ct = uic_get_current_container(obj);
+
+ Widget menu = XmCreatePopupMenu(widget, "popup_menu", NULL, 0);
+ ct->menu = menu;
+
+ XtAddEventHandler(widget, ButtonPressMask, FALSE, ui_popup_handler, menu);
+
+ return menu;
+}
+
+void ui_contextmenu_popup(UIMENU menu) {
+
+}
+
+void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) {
+ ui_widget_menuitem_gr(obj, label, f, userdata, -1);
+}
+
+void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) {
+ UiContainer *ct = uic_get_current_container(obj);
+ if(!ct->menu) {
+ return;
+ }
+
+ // add groups
+ CxList *groups = NULL;
+ va_list ap;
+ va_start(ap, userdata);
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ if(!groups) {
+ groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
+ }
+ cxListAdd(groups, &group);
+ }
+ va_end(ap);
+
+ // create menuitem
+ Arg args[4];
+ XmString labelstr = XmStringCreateLocalized(label);
+ XtSetArg(args[0], XmNlabelString, labelstr);
+
+ Widget item = XmCreatePushButton(ct->menu, "menu_button", args, 1);
+ XtManageChild(item);
+ XmStringFree(labelstr);
+}
+
+void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata) {
+ ui_widget_menuitem_stgr(obj, stockid, f, userdata, -1);
+}
+
+void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) {
+ UiContainer *ct = uic_get_current_container(obj);
+ if(!ct->menu) {
+ return;
+ }
+
+ // add groups
+ CxList *groups = NULL;
+ va_list ap;
+ va_start(ap, userdata);
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ if(!groups) {
+ groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
+ }
+ cxListAdd(groups, &group);
+ }
+ va_end(ap);
+
+ // create menuitem
+ UiStockItem *stockItem = ui_get_stock_item(stockid);
+ Arg args[4];
+ XmString labelstr = XmStringCreateLocalized(stockItem->label);
+ XtSetArg(args[0], XmNlabelString, labelstr);
+
+ Widget item = XmCreatePushButton(ct->menu, "menu_button", args, 1);
+ XtManageChild(item);
+ XmStringFree(labelstr);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MENU_H
+#define MENU_H
+
+#include "../ui/menu.h"
+#include "../common/menu.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+typedef struct UiActiveMenuItemList UiActiveMenuItemList;
+
+typedef int(*ui_menu_add_f)(Widget, int, UiMenuItemI*, UiObject*);
+
+struct UiActiveMenuItemList {
+ UiObject *object;
+ Widget menu;
+ int index;
+ int oldcount;
+ UiList *list;
+ ui_callback callback;
+ void *userdata;
+};
+
+void ui_create_menubar(UiObject *obj);
+
+int add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+int add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+int add_menuitem_st_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+int add_menuseparator_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+int add_checkitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+int add_checkitemnv_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+int add_menuitem_list_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj);
+
+void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list);
+void ui_menu_event_wrapper(Widget widget, XtPointer udata, XtPointer cdata);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MENU_H */
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+MOTIF_SRC_DIR = ui/motif/
+MOTIF_OBJPRE = $(OBJ_DIR)$(MOTIF_SRC_DIR)
+
+MOTIFOBJ = toolkit.o
+MOTIFOBJ += stock.o
+MOTIFOBJ += window.o
+MOTIFOBJ += container.o
+MOTIFOBJ += menu.o
+MOTIFOBJ += toolbar.o
+MOTIFOBJ += button.o
+MOTIFOBJ += label.o
+MOTIFOBJ += text.o
+MOTIFOBJ += list.o
+MOTIFOBJ += tree.o
+MOTIFOBJ += graphics.o
+MOTIFOBJ += range.o
+MOTIFOBJ += dnd.o
+MOTIFOBJ += image.o
+
+TOOLKITOBJS += $(MOTIFOBJ:%=$(MOTIF_OBJPRE)%)
+TOOLKITSOURCE += $(MOTIFOBJ:%.o=motif/%.c)
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "range.h"
+#include "container.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+
+static UIWIDGET ui_scrollbar(UiObject *obj, UiOrientation orientation, UiRange *range, ui_callback f, void *userdata) {
+ UiContainer *ct = uic_get_current_container(obj);
+
+ int n = 0;
+ Arg args[16];
+ XtSetArg(args[n], XmNorientation, orientation == UI_HORIZONTAL ? XmHORIZONTAL : XmVERTICAL);
+ n++;
+ XtSetArg (args[n], XmNmaximum, 10);
+ n++;
+ XtSetArg (args[n], XmNsliderSize, 1);
+ n++;
+ Widget parent = ct->prepare(ct, args, &n, FALSE);
+ Widget scrollbar = XmCreateScrollBar(parent, "scrollbar", args, n);
+ XtManageChild(scrollbar);
+ ct->add(ct, scrollbar);
+
+ if(range) {
+ range->get = ui_scrollbar_get;
+ range->set = ui_scrollbar_set;
+ range->setrange = ui_scrollbar_setrange;
+ range->setextent = ui_scrollbar_setextent;
+ range->obj = scrollbar;
+ }
+
+ if(f) {
+ UiEventData *event = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = userdata;
+ event->callback = f;
+ event->value = 0;
+ XtAddCallback(
+ scrollbar,
+ XmNvalueChangedCallback,
+ (XtCallbackProc)ui_scrollbar_callback,
+ event);
+ }
+
+ return scrollbar;
+}
+
+UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
+ return ui_scrollbar(obj, UI_HORIZONTAL, range, f, userdata);
+}
+
+UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata) {
+ return ui_scrollbar(obj, UI_VERTICAL, range, f, userdata);
+}
+
+void ui_scrollbar_callback(Widget scrollbar, UiEventData *event, XtPointer cdata) {
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.intval = event->value;
+ event->callback(&e, event->userdata);
+}
+
+double ui_scrollbar_get(UiRange *range) {
+ int intval;
+ XtVaGetValues(
+ range->obj,
+ XmNvalue,
+ &intval,
+ NULL);
+ double value = (double)intval / 10;
+ range->value = value;
+ return value;
+}
+
+void ui_scrollbar_set(UiRange *range, double value) {
+ XtVaSetValues(
+ range->obj,
+ XmNvalue,
+ (int)(value * 10),
+ NULL);
+ range->value = value;
+}
+
+void ui_scrollbar_setrange(UiRange *range, double min, double max) {
+ XtVaSetValues(
+ range->obj,
+ XmNminimum,
+ (int)(min * 10),
+ XmNmaximum,
+ (int)(max * 10),
+ NULL);
+ range->min = min;
+ range->max = max;
+}
+
+void ui_scrollbar_setextent(UiRange *range, double extent) {
+ XtVaSetValues(
+ range->obj,
+ XmNsliderSize,
+ (int)(extent * 10),
+ NULL);
+ range->extent = extent;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef RANGE_H
+#define RANGE_H
+
+#include "toolkit.h"
+#include "../ui/range.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void ui_scrollbar_callback(Widget scrollbar, UiEventData *event, XtPointer cdata);
+double ui_scrollbar_get(UiRange *range);
+void ui_scrollbar_set(UiRange *range, double value);
+void ui_scrollbar_setrange(UiRange *range, double min, double max);
+void ui_scrollbar_setextent(UiRange *range, double extent);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RANGE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "stock.h"
+#include "../ui/properties.h"
+#include <cx/hash_map.h>
+
+static CxMap *stock_items;
+
+void ui_stock_init() {
+ stock_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 64);
+
+ ui_add_stock_item(UI_STOCK_NEW, "New", "Ctrl<Key>N", "Ctrl+N", NULL);
+ ui_add_stock_item(UI_STOCK_OPEN, "Open", "Ctrl<Key>O", "Ctrl+O", NULL);
+ ui_add_stock_item(UI_STOCK_SAVE, "Save", "Ctrl<Key>S", "Ctrl+S", NULL);
+ ui_add_stock_item(UI_STOCK_SAVE_AS, "Save as ...", NULL, NULL, NULL);
+ ui_add_stock_item(UI_STOCK_REVERT_TO_SAVED, "Revert to saved", NULL, NULL, NULL);
+ ui_add_stock_item(UI_STOCK_CLOSE, "Close", "Ctrl<Key>W", "Ctrl+W", NULL);
+ ui_add_stock_item(UI_STOCK_UNDO, "Undo", "Ctrl<Key>Z", "Ctrl+Z", NULL);
+ ui_add_stock_item(UI_STOCK_REDO, "Redo", NULL, NULL, NULL);
+ ui_add_stock_item(UI_STOCK_GO_BACK, "Back", NULL, NULL, NULL);
+ ui_add_stock_item(UI_STOCK_GO_FORWARD, "Forward", NULL, NULL, NULL);
+ ui_add_stock_item(UI_STOCK_CUT, "Cut", "Ctrl<Key>X", "Ctrl+X", NULL);
+ ui_add_stock_item(UI_STOCK_COPY, "Copy", "Ctrl<Key>C", "Ctrl+C", NULL);
+ ui_add_stock_item(UI_STOCK_PASTE, "Paste", "Ctrl<Key>V", "Ctrl+V", NULL);
+ ui_add_stock_item(UI_STOCK_DELETE, "Delete", NULL, NULL, NULL);
+}
+
+void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon) {
+ UiStockItem *i = malloc(sizeof(UiStockItem));
+ i->label = label;
+ i->accelerator = accelerator;
+ i->accelerator_label = accelerator_label;
+ // TODO: icon
+
+ cxMapPut(stock_items, id, i);
+}
+
+UiStockItem* ui_get_stock_item(char *id) {
+ UiStockItem *item = cxMapGet(stock_items, id);
+ if(item) {
+ char *label = uistr_n(id);
+ if(label) {
+ item->label = label;
+ }
+ }
+ return item;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef STOCK_H
+#define STOCK_H
+
+#include "../ui/stock.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiStockItem {
+ char *label;
+ char *accelerator;
+ char *accelerator_label;
+ // TODO: icon
+} UiStockItem;
+
+void ui_stock_init();
+
+void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon);
+
+UiStockItem* ui_get_stock_item(char *id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* STOCK_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "text.h"
+#include "container.h"
+
+
+UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) {
+ UiContainer *ct = uic_get_current_container(obj);
+ int n = 0;
+ Arg args[16];
+
+ //XtSetArg(args[n], XmNeditable, TRUE);
+ //n++;
+ XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT);
+ n++;
+
+ Widget parent = ct->prepare(ct, args, &n, TRUE);
+ Widget text_area = XmCreateScrolledText(parent, "text_area", args, n);
+ ct->add(ct, XtParent(text_area));
+ XtManageChild(text_area);
+
+ UiTextArea *uitext = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiTextArea));
+ uitext->ctx = obj->ctx;
+ uitext->last_selection_state = 0;
+ XtAddCallback(
+ text_area,
+ XmNmotionVerifyCallback,
+ (XtCallbackProc)ui_text_selection_callback,
+ uitext);
+
+ // bind value
+ if(var->value) {
+ UiText *value = var->value;
+ if(value->value.ptr) {
+ XmTextSetString(text_area, value->value.ptr);
+ value->value.free(value->value.ptr);
+ }
+
+ value->set = ui_textarea_set;
+ value->get = ui_textarea_get;
+ value->getsubstr = ui_textarea_getsubstr;
+ value->insert = ui_textarea_insert;
+ value->setposition = ui_textarea_setposition;
+ value->position = ui_textarea_position;
+ value->selection = ui_textarea_selection;
+ value->length = ui_textarea_length;
+ value->value.ptr = NULL;
+ value->obj = text_area;
+
+ if(!value->undomgr) {
+ value->undomgr = ui_create_undomgr();
+ }
+
+ XtAddCallback(
+ text_area,
+ XmNmodifyVerifyCallback,
+ (XtCallbackProc)ui_text_modify_callback,
+ var);
+ }
+
+ return text_area;
+}
+
+UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
+ UiVar *var = malloc(sizeof(UiVar));
+ var->value = value;
+ var->type = UI_VAR_SPECIAL;
+ return ui_textarea_var(obj, var);
+}
+
+UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
+ UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_TEXT);
+ if(var) {
+ return ui_textarea_var(obj, var);
+ } else {
+ // TODO: error
+ }
+ return NULL;
+}
+
+char* ui_textarea_get(UiText *text) {
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+ char *str = XmTextGetString(text->obj);
+ text->value.ptr = str;
+ text->value.free = (ui_freefunc)XtFree;
+ return str;
+}
+
+void ui_textarea_set(UiText *text, const char *str) {
+ XmTextSetString(text->obj, str);
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+ text->value.ptr = NULL;
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+ int length = end - begin;
+ char *str = XtMalloc(length + 1);
+ XmTextGetSubstring(text->obj, begin, length, length + 1, str);
+ text->value.ptr = str;
+ text->value.free = (ui_freefunc)XtFree;
+ return str;
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+ text->value.ptr = NULL;
+ XmTextInsert(text->obj, pos, str);
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+}
+
+void ui_textarea_setposition(UiText *text, int pos) {
+ XmTextSetInsertionPosition(text->obj, pos);
+}
+
+int ui_textarea_position(UiText *text) {
+ long begin;
+ long end;
+ XmTextGetSelectionPosition(text->obj, &begin, &end);
+ text->pos = begin;
+ return text->pos;
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+ XmTextGetSelectionPosition(text->obj, (long*)begin, (long*)end);
+}
+
+int ui_textarea_length(UiText *text) {
+ return (int)XmTextGetLastPosition(text->obj);
+}
+
+
+void ui_text_set(UiText *text, char *str) {
+ if(text->set) {
+ text->set(text, str);
+ } else {
+ if(text->value.ptr) {
+ text->value.free(text->value.ptr);
+ }
+ text->value.ptr = XtNewString(str);
+ text->value.free = (ui_freefunc)XtFree;
+ }
+}
+
+char* ui_text_get(UiText *text) {
+ if(text->get) {
+ return text->get(text);
+ } else {
+ return text->value.ptr;
+ }
+}
+
+
+UiUndoMgr* ui_create_undomgr() {
+ UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
+ mgr->begin = NULL;
+ mgr->end = NULL;
+ mgr->cur = NULL;
+ mgr->length = 0;
+ mgr->event = 1;
+ return mgr;
+}
+
+void ui_destroy_undomgr(UiUndoMgr *mgr) {
+ UiTextBufOp *op = mgr->begin;
+ while(op) {
+ UiTextBufOp *nextOp = op->next;
+ if(op->text) {
+ free(op->text);
+ }
+ free(op);
+ op = nextOp;
+ }
+ free(mgr);
+}
+
+void ui_text_selection_callback(
+ Widget widget,
+ UiTextArea *textarea,
+ XtPointer data)
+{
+ long left = 0;
+ long right = 0;
+ XmTextGetSelectionPosition(widget, &left, &right);
+ int sel = left < right ? 1 : 0;
+ if(sel != textarea->last_selection_state) {
+ if(sel) {
+ ui_set_group(textarea->ctx, UI_GROUP_SELECTION);
+ } else {
+ ui_unset_group(textarea->ctx, UI_GROUP_SELECTION);
+ }
+ }
+ textarea->last_selection_state = sel;
+}
+
+void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data) {
+ UiText *value = var->value;
+ if(!value->obj) {
+ // TODO: bug, fix
+ return;
+ }
+ if(!value->undomgr) {
+ value->undomgr = ui_create_undomgr();
+ }
+
+ XmTextVerifyCallbackStruct *txv = (XmTextVerifyCallbackStruct*)data;
+ int type = txv->text->length > 0 ? UI_TEXTBUF_INSERT : UI_TEXTBUF_DELETE;
+ UiUndoMgr *mgr = value->undomgr;
+ if(!mgr->event) {
+ return;
+ }
+
+ char *text = txv->text->ptr;
+ int length = txv->text->length;
+
+ if(mgr->cur) {
+ UiTextBufOp *elm = mgr->cur->next;
+ if(elm) {
+ mgr->cur->next = NULL;
+ mgr->end = mgr->cur;
+ while(elm) {
+ elm->prev = NULL;
+ UiTextBufOp *next = elm->next;
+ ui_free_textbuf_op(elm);
+ elm = next;
+ }
+ }
+
+ UiTextBufOp *last_op = mgr->cur;
+ if(
+ last_op->type == UI_TEXTBUF_INSERT &&
+ ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
+ {
+ // append text to last op
+ int ln = last_op->len;
+ char *newtext = malloc(ln + length + 1);
+ memcpy(newtext, last_op->text, ln);
+ memcpy(newtext+ln, text, length);
+ newtext[ln+length] = '\0';
+
+ last_op->text = newtext;
+ last_op->len = ln + length;
+ last_op->end += length;
+
+ return;
+ }
+ }
+
+ char *str;
+ if(type == UI_TEXTBUF_INSERT) {
+ str = malloc(length + 1);
+ memcpy(str, text, length);
+ str[length] = 0;
+ } else {
+ length = txv->endPos - txv->startPos;
+ str = malloc(length + 1);
+ XmTextGetSubstring(value->obj, txv->startPos, length, length+1, str);
+ }
+
+ UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+ op->prev = NULL;
+ op->next = NULL;
+ op->type = type;
+ op->start = txv->startPos;
+ op->end = txv->endPos + 1;
+ op->len = length;
+ op->text = str;
+
+ cx_linked_list_add(
+ (void**)&mgr->begin,
+ (void**)&mgr->end,
+ offsetof(UiTextBufOp, prev),
+ offsetof(UiTextBufOp, next),
+ op);
+
+ mgr->cur = op;
+}
+
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen) {
+ // return 1 if oldstr + newstr are one word
+
+ int has_space = 0;
+ for(int i=0;i<oldlen;i++) {
+ if(oldstr[i] < 33) {
+ has_space = 1;
+ break;
+ }
+ }
+
+ for(int i=0;i<newlen;i++) {
+ if(has_space && newstr[i] > 32) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void ui_free_textbuf_op(UiTextBufOp *op) {
+ if(op->text) {
+ free(op->text);
+ }
+ free(op);
+}
+
+
+void ui_text_undo(UiText *value) {
+ UiUndoMgr *mgr = value->undomgr;
+
+ if(mgr->cur) {
+ UiTextBufOp *op = mgr->cur;
+ mgr->event = 0;
+ switch(op->type) {
+ case UI_TEXTBUF_INSERT: {
+ XmTextReplace(value->obj, op->start, op->end, "");
+ break;
+ }
+ case UI_TEXTBUF_DELETE: {
+ XmTextInsert(value->obj, op->start, op->text);
+ break;
+ }
+ }
+ mgr->event = 1;
+ mgr->cur = mgr->cur->prev;
+ }
+}
+
+void ui_text_redo(UiText *value) {
+ UiUndoMgr *mgr = value->undomgr;
+
+ UiTextBufOp *elm = NULL;
+ if(mgr->cur) {
+ if(mgr->cur->next) {
+ elm = mgr->cur->next;
+ }
+ } else if(mgr->begin) {
+ elm = mgr->begin;
+ }
+
+ if(elm) {
+ UiTextBufOp *op = elm;
+ mgr->event = 0;
+ switch(op->type) {
+ case UI_TEXTBUF_INSERT: {
+ XmTextInsert(value->obj, op->start, op->text);
+ break;
+ }
+ case UI_TEXTBUF_DELETE: {
+ XmTextReplace(value->obj, op->start, op->end, "");
+ break;
+ }
+ }
+ mgr->event = 1;
+ mgr->cur = elm;
+ }
+}
+
+
+/* ------------------------- textfield ------------------------- */
+
+static UIWIDGET create_textfield(UiObject *obj, int width, UiBool frameless, UiBool password, UiString *value) {
+ UiContainer *ct = uic_get_current_container(obj);
+ int n = 0;
+ Arg args[16];
+ XtSetArg(args[n], XmNeditMode, XmSINGLE_LINE_EDIT);
+ n++;
+ if(width > 0) {
+ XtSetArg(args[n], XmNcolumns, width / 2 + 1);
+ n++;
+ }
+ if(frameless) {
+ XtSetArg(args[n], XmNshadowThickness, 0);
+ n++;
+ }
+ if(password) {
+ // TODO
+ }
+
+ Widget parent = ct->prepare(ct, args, &n, FALSE);
+ Widget textfield = XmCreateText(parent, "text_field", args, n);
+ ct->add(ct, textfield);
+ XtManageChild(textfield);
+
+ // bind value
+ if(value) {
+ if(value->value.ptr) {
+ XmTextSetString(textfield, value->value.ptr);
+ value->value.free(value->value.ptr);
+ }
+
+ value->set = ui_textfield_set;
+ value->get = ui_textfield_get;
+ value->value.ptr = NULL;
+ value->obj = textfield;
+ }
+
+ return textfield;
+}
+
+static UIWIDGET create_textfield_nv(UiObject *obj, int width, UiBool frameless, UiBool password, char *varname) {
+ UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_STRING);
+ if(var) {
+ UiString *value = var->value;
+ return ui_textfield(obj, value);
+ } else {
+ // TODO: error
+ }
+ return NULL;
+}
+
+UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
+ return create_textfield(obj, 0, FALSE, FALSE, value);
+}
+
+UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
+ return create_textfield_nv(obj, 0, FALSE, FALSE, varname);
+}
+
+UIWIDGET ui_textfield_w(UiObject *obj, int width, UiString *value) {
+ return create_textfield(obj, width, FALSE, FALSE, value);
+}
+
+UIWIDGET ui_textfield_wnv(UiObject *obj, int width, char *varname) {
+ return create_textfield_nv(obj, width, FALSE, FALSE, varname);
+}
+
+UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
+ return create_textfield(obj, 0, TRUE, FALSE, value);
+}
+
+UIWIDGET ui_frameless_textfield_nv(UiObject *obj, char *varname) {
+ return create_textfield_nv(obj, 0, TRUE, FALSE, varname);
+}
+
+UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
+ return create_textfield(obj, 0, FALSE, TRUE, value);
+}
+
+UIWIDGET ui_passwordfield_nv(UiObject *obj, char *varname) {
+ return create_textfield_nv(obj, 0, FALSE, TRUE, varname);
+}
+
+UIWIDGET ui_passwordfield_w(UiObject *obj, int width, UiString *value) {
+ return create_textfield(obj, width, FALSE, TRUE, value);
+}
+
+UIWIDGET ui_passwordfield_wnv(UiObject *obj, int width, char *varname) {
+ return create_textfield_nv(obj, width, FALSE, TRUE, varname);
+}
+
+
+char* ui_textfield_get(UiString *str) {
+ if(str->value.ptr) {
+ str->value.free(str->value.ptr);
+ }
+ char *value = XmTextGetString(str->obj);
+ str->value.ptr = value;
+ str->value.free = (ui_freefunc)XtFree;
+ return value;
+}
+
+void ui_textfield_set(UiString *str, char *value) {
+ XmTextSetString(str->obj, value);
+ if(str->value.ptr) {
+ str->value.free(str->value.ptr);
+ }
+ str->value.ptr = NULL;
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TEXT_H
+#define TEXT_H
+
+#include "../ui/text.h"
+#include "toolkit.h"
+#include <cx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UI_TEXTBUF_INSERT 0
+#define UI_TEXTBUF_DELETE 1
+typedef struct UiTextBufOp UiTextBufOp;
+struct UiTextBufOp {
+ UiTextBufOp *prev;
+ UiTextBufOp *next;
+ int type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE
+ int start;
+ int end;
+ int len;
+ char *text;
+};
+
+typedef struct UiUndoMgr {
+ UiTextBufOp *begin;
+ UiTextBufOp *end;
+ UiTextBufOp *cur;
+ int length;
+ int event;
+} UiUndoMgr;
+
+typedef struct UiTextArea {
+ UiContext *ctx;
+ int last_selection_state;
+} UiTextArea;
+
+char* ui_textarea_get(UiText *text);
+void ui_textarea_set(UiText *text, const char *str);
+char* ui_textarea_getsubstr(UiText *text, int begin, int end);
+void ui_textarea_insert(UiText *text, int pos, char *str);
+void ui_textarea_setposition(UiText *text, int pos);
+int ui_textarea_position(UiText *text);
+void ui_textarea_selection(UiText *text, int *begin, int *end);
+int ui_textarea_length(UiText *text);
+
+UiUndoMgr* ui_create_undomgr();
+void ui_destroy_undomgr(UiUndoMgr *mgr);
+void ui_text_selection_callback(
+ Widget widget,
+ UiTextArea *textarea,
+ XtPointer data);
+void ui_text_modify_callback(Widget widget, UiVar *var, XtPointer data);
+int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen);
+void ui_free_textbuf_op(UiTextBufOp *op);
+
+char* ui_textfield_get(UiString *str);
+void ui_textfield_set(UiString *str, char *value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEXT_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "toolbar.h"
+#include "button.h"
+#include "stock.h"
+#include "list.h"
+
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+#include "../common/context.h"
+
+static CxMap *toolbar_items;
+static CxList *defaults;
+
+void ui_toolbar_init() {
+ toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+ defaults = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+}
+
+void ui_toolitem(char *name, char *label, ui_callback f, void *userdata) {
+ UiToolItem *item = malloc(sizeof(UiToolItem));
+ item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget;
+ item->label = label;
+ item->image = NULL;
+ item->callback = f;
+ item->userdata = userdata;
+ item->groups = NULL;
+ item->isimportant = FALSE;
+
+ cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *userdata) {
+ ui_toolitem_stgr(name, stockid, f, userdata, -1);
+}
+
+void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *userdata, ...) {
+ UiStToolItem *item = malloc(sizeof(UiStToolItem));
+ item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_widget;
+ item->stockid = stockid;
+ item->callback = f;
+ item->userdata = userdata;
+ item->groups = NULL;
+ item->isimportant = FALSE;
+
+ // add groups
+ va_list ap;
+ va_start(ap, userdata);
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ if(!item->groups) {
+ item->groups = cxArrayListCreateSimple(sizeof(int), 16);
+ }
+ cxListAdd(item->groups, &group);
+ }
+ va_end(ap);
+
+ cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata) {
+ // TODO
+
+ UiToolItem *item = malloc(sizeof(UiToolItem));
+ item->item.add_to = (ui_toolbar_add_f)add_toolitem_widget;
+ item->label = label;
+ item->image = img;
+ item->callback = f;
+ item->userdata = udata;
+ item->groups = NULL;
+ item->isimportant = FALSE;
+
+ cxMapPut(toolbar_items, name, item);
+}
+
+
+void ui_toolitem_toggle_stgr(char *name, char *stockid, ui_callback f, void *udata, ...) {
+ // TODO
+
+ UiStToolItem *item = malloc(sizeof(UiStToolItem));
+ item->item.add_to = (ui_toolbar_add_f)add_toolitem_st_toggle_widget;
+ item->stockid = stockid;
+ item->callback = f;
+ item->userdata = udata;
+ item->groups = NULL;
+ item->isimportant = FALSE;
+
+ // add groups
+ va_list ap;
+ va_start(ap, udata);
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ if(!item->groups) {
+ item->groups = cxArrayListCreateSimple(sizeof(int), 16);
+ }
+ cxListAdd(item->groups, &group);
+ }
+ va_end(ap);
+
+ cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolitem_toggle_imggr(char *name, char *label, char *img, ui_callback f, void *udata, ...) {
+ // TODO
+
+ UiToolItem *item = malloc(sizeof(UiToolItem));
+ item->item.add_to = (ui_toolbar_add_f)add_toolitem_toggle_widget;
+ item->label = label;
+ item->image = img;
+ item->callback = f;
+ item->userdata = udata;
+ item->groups = NULL;
+ item->isimportant = FALSE;
+
+ // add groups
+ va_list ap;
+ va_start(ap, udata);
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ if(!item->groups) {
+ item->groups = cxArrayListCreateSimple(sizeof(int), 16);
+ }
+ cxListAdd(item->groups, &group);
+ }
+ va_end(ap);
+
+ cxMapPut(toolbar_items, name, item);
+}
+
+void ui_toolbar_combobox(
+ char *name,
+ UiList *list,
+ ui_getvaluefunc getvalue,
+ ui_callback f,
+ void *udata)
+{
+ UiToolbarComboBox *cb = malloc(sizeof(UiToolbarComboBox));
+ cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox;
+ cb->list = list;
+ cb->getvalue = getvalue;
+ cb->callback = f;
+ cb->userdata = udata;
+
+ cxMapPut(toolbar_items, name, cb);
+}
+
+void ui_toolbar_combobox_str(
+ char *name,
+ UiList *list,
+ ui_callback f,
+ void *udata)
+{
+ ui_toolbar_combobox(name, list, ui_strmodel_getvalue, f, udata);
+}
+
+void ui_toolbar_combobox_nv(
+ char *name,
+ char *listname,
+ ui_getvaluefunc getvalue,
+ ui_callback f,
+ void *udata)
+{
+ UiToolbarComboBoxNV *cb = malloc(sizeof(UiToolbarComboBoxNV));
+ cb->item.add_to = (ui_toolbar_add_f)add_toolbar_combobox_nv;
+ cb->listname = listname;
+ cb->getvalue = getvalue;
+ cb->callback = f;
+ cb->userdata = udata;
+
+ cxMapPut(toolbar_items, name, cb);
+}
+
+
+void ui_toolbar_add_default(char *name) {
+ char *s = strdup(name);
+ cxListAdd(defaults, s);
+}
+
+Widget ui_create_toolbar(UiObject *obj, Widget parent) {
+ if(!defaults) {
+ return NULL;
+ }
+
+ Arg args[8];
+ XtSetArg(args[0], XmNshadowType, XmSHADOW_ETCHED_OUT);
+ XtSetArg(args[1], XmNshadowThickness, 1);
+ XtSetArg(args[2], XmNtopAttachment, XmATTACH_FORM);
+ XtSetArg(args[3], XmNleftAttachment, XmATTACH_FORM);
+ XtSetArg(args[4], XmNrightAttachment, XmATTACH_FORM);
+ Widget frame = XmCreateFrame(parent, "toolbar_frame", args, 5);
+
+ XtSetArg(args[0], XmNorientation, XmHORIZONTAL);
+ XtSetArg(args[1], XmNpacking, XmPACK_TIGHT);
+ XtSetArg(args[2], XmNspacing, 1);
+ Widget toolbar = XmCreateRowColumn (frame, "toolbar", args, 3);
+
+ CxIterator i = cxListIterator(defaults);
+ cx_foreach(char *, def, i) {
+ UiToolItemI *item = cxMapGet(toolbar_items, def);
+ if(item) {
+ item->add_to(toolbar, item, obj);
+ } else if(!strcmp(def, "@separator")) {
+ // TODO
+ } else {
+ fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", def);
+ }
+ }
+
+ XtManageChild(toolbar);
+ XtManageChild(frame);
+
+ return frame;
+}
+
+void add_toolitem_widget(Widget parent, UiToolItem *item, UiObject *obj) {
+ Arg args[4];
+
+ XmString label = XmStringCreateLocalized(item->label);
+ XtSetArg(args[0], XmNlabelString, label);
+ XtSetArg(args[1], XmNshadowThickness, 1);
+ XtSetArg(args[2], XmNtraversalOn, FALSE);
+ Widget button = XmCreatePushButton(parent, "toolbar_button", args, 3);
+
+ XmStringFree(label);
+
+ if(item->callback) {
+ UiEventData *event = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = item->userdata;
+ event->callback = item->callback;
+ XtAddCallback(
+ button,
+ XmNactivateCallback,
+ (XtCallbackProc)ui_push_button_callback,
+ event);
+ }
+
+ XtManageChild(button);
+
+ if(item->groups) {
+ uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups);
+ }
+}
+
+void add_toolitem_st_widget(Widget parent, UiStToolItem *item, UiObject *obj) {
+ Arg args[8];
+
+ UiStockItem *stock_item = ui_get_stock_item(item->stockid);
+
+ XmString label = XmStringCreateLocalized(stock_item->label);
+ XtSetArg(args[0], XmNlabelString, label);
+ XtSetArg(args[1], XmNshadowThickness, 1);
+ XtSetArg(args[2], XmNtraversalOn, FALSE);
+ Widget button = XmCreatePushButton(parent, "toolbar_button", args, 3);
+
+ XmStringFree(label);
+
+ if(item->callback) {
+ UiEventData *event = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = item->userdata;
+ event->callback = item->callback;
+ XtAddCallback(
+ button,
+ XmNactivateCallback,
+ (XtCallbackProc)ui_push_button_callback,
+ event);
+ }
+
+ XtManageChild(button);
+
+ if(item->groups) {
+ uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups);
+ }
+}
+
+void add_toolitem_toggle_widget(Widget parent, UiToolItem *item, UiObject *obj) {
+ Arg args[8];
+
+ XmString label = XmStringCreateLocalized(item->label);
+ XtSetArg(args[0], XmNlabelString, label);
+ XtSetArg(args[1], XmNshadowThickness, 1);
+ XtSetArg(args[2], XmNtraversalOn, FALSE);
+ XtSetArg(args[3], XmNindicatorOn, XmINDICATOR_NONE);
+ Widget button = XmCreateToggleButton(parent, "toolbar_toggle_button", args, 4);
+
+ XmStringFree(label);
+
+ if(item->callback) {
+ UiEventData *event = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiEventData));
+ event->obj = obj;
+ event->userdata = item->userdata;
+ event->callback = item->callback;
+ XtAddCallback(
+ button,
+ XmNvalueChangedCallback,
+ (XtCallbackProc)ui_toggle_button_callback,
+ event);
+ }
+
+ XtManageChild(button);
+
+ if(item->groups) {
+ uic_add_group_widget(obj->ctx, button, (ui_enablefunc)XtSetSensitive, item->groups);
+ }
+}
+
+void add_toolitem_st_toggle_widget(Widget parent, UiStToolItem *item, UiObject *obj) {
+
+}
+
+void add_toolbar_combobox(Widget tb, UiToolbarComboBox *item, UiObject *obj) {
+ UiListView *listview = cxMalloc(
+ obj->ctx->allocator,
+ sizeof(UiListView));
+
+ UiVar *var = cxMalloc(obj->ctx->allocator, sizeof(UiVar));
+ var->value = item->list;
+ var->type = UI_VAR_SPECIAL;
+
+ Arg args[8];
+ XtSetArg(args[0], XmNshadowThickness, 1);
+ XtSetArg(args[1], XmNindicatorOn, XmINDICATOR_NONE);
+ XtSetArg(args[2], XmNtraversalOn, FALSE);
+ XtSetArg(args[3], XmNwidth, 120);
+ Widget combobox = XmCreateDropDownList(tb, "toolbar_combobox", args, 4);
+ XtManageChild(combobox);
+ listview->widget = combobox;
+ listview->list = var;
+ listview->getvalue = item->getvalue;
+
+ ui_listview_update(NULL, listview);
+
+ if(item->callback) {
+ // TODO:
+
+ }
+}
+
+void add_toolbar_combobox_nv(Widget tb, UiToolbarComboBoxNV *item, UiObject *obj) {
+
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TOOLBAR_H
+#define TOOLBAR_H
+
+#include "../ui/toolbar.h"
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiToolItemI UiToolItemI;
+typedef struct UiToolItem UiToolItem;
+typedef struct UiStToolItem UiStToolItem;
+
+typedef struct UiToolbarComboBox UiToolbarComboBox;
+typedef struct UiToolbarComboBoxNV UiToolbarComboBoxNV;
+
+typedef void(*ui_toolbar_add_f)(Widget, UiToolItemI*, UiObject*);
+
+struct UiToolItemI {
+ ui_toolbar_add_f add_to;
+};
+
+struct UiToolItem {
+ UiToolItemI item;
+ char *label;
+ void *image;
+ ui_callback callback;
+ void *userdata;
+ CxList *groups;
+ Boolean isimportant;
+};
+
+struct UiStToolItem {
+ UiToolItemI item;
+ char *stockid;
+ ui_callback callback;
+ void *userdata;
+ CxList *groups;
+ Boolean isimportant;
+};
+
+struct UiToolbarComboBox {
+ UiToolItemI item;
+ UiList *list;
+ ui_getvaluefunc getvalue;
+ ui_callback callback;
+ void *userdata;
+};
+
+struct UiToolbarComboBoxNV {
+ UiToolItemI item;
+ char *listname;
+ ui_getvaluefunc getvalue;
+ ui_callback callback;
+ void *userdata;
+};
+
+void ui_toolbar_init();
+
+Widget ui_create_toolbar(UiObject *obj, Widget parent);
+
+void add_toolitem_widget(Widget tb, UiToolItem *item, UiObject *obj);
+void add_toolitem_st_widget(Widget tb, UiStToolItem *item, UiObject *obj);
+void add_toolitem_toggle_widget(Widget tb, UiToolItem *item, UiObject *obj);
+void add_toolitem_st_toggle_widget(Widget tb, UiStToolItem *item, UiObject *obj);
+
+void add_toolbar_combobox(Widget tb, UiToolbarComboBox *item, UiObject *obj);
+void add_toolbar_combobox_nv(Widget tb, UiToolbarComboBoxNV *item, UiObject *obj);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TOOLBAR_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#include "toolkit.h"
+#include "toolbar.h"
+#include "stock.h"
+#include "../common/document.h"
+#include "../common/properties.h"
+#include <cx/buffer.h>
+
+static XtAppContext app;
+static Display *display;
+static Widget active_window;
+static char *application_name;
+
+static ui_callback startup_func;
+static void *startup_data;
+static ui_callback open_func;
+void *open_data;
+static ui_callback exit_func;
+void *exit_data;
+
+static ui_callback appclose_fnc;
+static void *appclose_udata;
+
+static int is_toplevel_realized = 0;
+
+int event_pipe[2];
+
+
+static String fallback[] = {
+ //"*fontList: -dt-interface system-medium-r-normal-s*utf*:",
+ "*text_area*renderTable: f1",
+ "*f1*fontType: FONT_IS_XFT",
+ "*f1*fontName: Monospace",
+ "*f1*fontSize: 11",
+ "*renderTable: rt",
+ "*rt*fontType: FONT_IS_XFT",
+ "*rt*fontName: Sans",
+ "*rt*fontSize: 11",
+ NULL
+};
+
+void input_proc(XtPointer data, int *source, XtInputId *iid) {
+ void *ptr;
+ read(event_pipe[0], &ptr, sizeof(void*));
+}
+
+void ui_init(char *appname, int argc, char **argv) {
+ application_name = appname;
+
+ XtToolkitInitialize();
+ XtSetLanguageProc(NULL, NULL, NULL);
+ app = XtCreateApplicationContext();
+ XtAppSetFallbackResources(app, fallback);
+
+ display = XtOpenDisplay(app, NULL, appname, appname, NULL, 0, &argc, argv);
+ char **missing = NULL;
+ int nm = 0;
+ char *def = NULL;
+ XCreateFontSet(display, "-dt-interface system-medium-r-normal-s*utf*", &missing, &nm, &def);
+
+ uic_docmgr_init();
+ ui_toolbar_init();
+ ui_stock_init();
+
+ uic_load_app_properties();
+
+ if(pipe(event_pipe)) {
+ fprintf(stderr, "UiError: Cannot create event pipe\n");
+ exit(-1);
+ }
+ XtAppAddInput(
+ app,
+ event_pipe[0],
+ (XtPointer)XtInputReadMask,
+ input_proc,
+ NULL);
+}
+
+char* ui_appname() {
+ return application_name;
+}
+
+Display* ui_get_display() {
+ return display;
+}
+
+void ui_onstartup(ui_callback f, void *userdata) {
+ startup_func = f;
+ startup_data = userdata;
+}
+
+void ui_onopen(ui_callback f, void *userdata) {
+ open_func = f;
+ open_data = userdata;
+}
+
+void ui_onexit(ui_callback f, void *userdata) {
+ exit_func = f;
+ exit_data = userdata;
+}
+
+void ui_main() {
+ if(startup_func) {
+ startup_func(NULL, startup_data);
+ }
+ XtAppMainLoop(app);
+ if(exit_func) {
+ exit_func(NULL, exit_data);
+ }
+ uic_store_app_properties();
+}
+
+void ui_exit_mainloop() {
+ XtAppSetExitFlag(app);
+}
+
+void ui_secondary_event_loop(int *loop) {
+ while(*loop && !XtAppGetExitFlag(app)) {
+ XEvent event;
+ XtAppNextEvent(app, &event);
+ XtDispatchEvent(&event);
+ }
+}
+
+void ui_show(UiObject *obj) {
+ uic_check_group_widgets(obj->ctx);
+ XtRealizeWidget(obj->widget);
+ ui_window_dark_theme(XtDisplay(obj->widget), XtWindow(obj->widget)); // TODO: if
+}
+
+// implemented in window.c
+//void ui_close(UiObject *obj)
+
+void ui_set_enabled(UIWIDGET widget, int enabled) {
+ XtSetSensitive(widget, enabled);
+}
+
+void ui_set_show_all(UIWIDGET widget, int value) {
+ if(!value) {
+ XtUnmanageChild(widget);
+ }
+}
+
+void ui_set_visible(UIWIDGET widget, int visible) {
+ if(visible) {
+ XtManageChild(widget);
+ } else {
+ XtUnmanageChild(widget);
+ }
+}
+
+static Boolean ui_job_finished(void *data) {
+ printf("WorkProc\n");
+ UiJob *job = data;
+
+ UiEvent event;
+ event.obj = job->obj;
+ event.window = job->obj->window;
+ event.document = job->obj->ctx->document;
+ event.intval = 0;
+ event.eventdata = NULL;
+
+ job->finish_callback(&event, job->finish_data);
+ free(job);
+ return TRUE;
+}
+
+static void* ui_jobthread(void *data) {
+ UiJob *job = data;
+ int result = job->job_func(job->job_data);
+ if(!result) {
+ printf("XtAppAddWorkProc\n");
+ write(event_pipe[1], &job, sizeof(void*)); // hack
+ XtAppAddWorkProc(app, ui_job_finished, job);
+
+ }
+ return NULL;
+}
+
+void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
+ UiJob *job = malloc(sizeof(UiJob));
+ job->obj = obj;
+ job->job_func = tf;
+ job->job_data = td;
+ job->finish_callback = f;
+ job->finish_data = fd;
+ pthread_t pid;
+ pthread_create(&pid, NULL, ui_jobthread, job);
+}
+
+void ui_clipboard_set(char *str) {
+ printf("copy: {%s}\n", str);
+ int length = strlen(str) + 1;
+
+ Display *dp = XtDisplayOfObject(active_window);
+ Window window = XtWindowOfObject(active_window);
+
+ XmString label = XmStringCreateLocalized("toolkit_clipboard");
+ long id = 0;
+
+ while(XmClipboardStartCopy(
+ dp,
+ window,
+ label,
+ CurrentTime,
+ NULL,
+ NULL,
+ &id) == ClipboardLocked);
+ XmStringFree(label);
+
+ while(XmClipboardCopy(
+ dp,
+ window,
+ id,
+ "STRING",
+ str,
+ length,
+ 1,
+ NULL) == ClipboardLocked);
+
+ while(XmClipboardEndCopy(dp, window, id) == ClipboardLocked);
+}
+
+char* ui_clipboard_get() {
+ Display *dp = XtDisplayOfObject(active_window);
+ Window window = XtWindowOfObject(active_window);
+
+ long id;
+ size_t size = 128;
+ char *buf = malloc(size);
+
+ int r;
+ for(;;) {
+ r = XmClipboardRetrieve(dp, window, "STRING", buf, size, NULL, &id);
+ if(r == ClipboardSuccess) {
+ break;
+ } else if(r == ClipboardTruncate) {
+ size *= 2;
+ buf = realloc(buf, size);
+ } else if(r == ClipboardNoData) {
+ free(buf);
+ buf = NULL;
+ break;
+ }
+ }
+
+ return buf;
+}
+
+void ui_set_active_window(Widget w) {
+ active_window = w;
+}
+
+Widget ui_get_active_window() {
+ return active_window;
+}
+
+void ui_window_dark_theme(Display *dp, Window window) {
+ Atom atom = XInternAtom(dp, "_GTK_THEME_VARIANT", False);
+ Atom type = XInternAtom(dp, "UTF8_STRING", False);
+ XChangeProperty(
+ dp,
+ window,
+ atom,
+ type,
+ 8,
+ PropModeReplace,
+ (const unsigned char*)"dark",
+ 4);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TOOLKIT_H
+#define TOOLKIT_H
+
+#include <inttypes.h>
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+Display* ui_get_display();
+
+typedef struct UiEventData {
+ UiObject *obj;
+ ui_callback callback;
+ void *userdata;
+ int value;
+} UiEventData;
+
+typedef struct UiJob {
+ UiObject *obj;
+ ui_threadfunc job_func;
+ void *job_data;
+ ui_callback finish_callback;
+ void *finish_data;
+} UiJob;
+
+typedef enum UiOrientation UiOrientation;
+enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
+
+void ui_exit_mainloop();
+
+void ui_set_active_window(Widget w);
+Widget ui_get_active_window();
+
+void ui_secondary_event_loop(int *loop);
+void ui_window_dark_theme(Display *dp, Window window);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TOOLKIT_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "tree.h"
+
+#include "container.h"
+#include "../common/object.h"
+#include "../common/context.h"
+#include <cx/utils.h>
+#include <cx/compare.h>
+#include <cx/printf.h>
+
+UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb) {
+ // TODO: check if modelinfo is complete
+
+ Arg args[32];
+ int n = 0;
+
+ // create scrolled window
+ UiContainer *ct = uic_get_current_container(obj);
+ Widget parent = ct->prepare(ct, args, &n, TRUE);
+
+ XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC);
+ n++;
+ XtSetArg(args[n], XmNshadowThickness, 0);
+ n++;
+ Widget scrollw = XmCreateScrolledWindow(parent, "scroll_win", args, n);
+ ct->add(ct, scrollw);
+ XtManageChild(scrollw);
+
+ // create table headers
+ XmStringTable header = (XmStringTable)XtMalloc(
+ model->columns * sizeof(XmString));
+ for(int i=0;i<model->columns;i++) {
+ header[i] = XmStringCreateLocalized(model->titles[i]);
+ }
+ n = 0;
+ XtSetArg(args[n], XmNdetailColumnHeading, header);
+ n++;
+ XtSetArg(args[n], XmNdetailColumnHeadingCount, model->columns);
+ n++;
+
+ // set res
+ XtSetArg(args[n], XmNlayoutType, XmDETAIL);
+ n++;
+ XtSetArg(args[n], XmNentryViewType, XmSMALL_ICON);
+ n++;
+ XtSetArg(args[n], XmNselectionPolicy, XmSINGLE_SELECT);
+ n++;
+ XtSetArg(args[n], XmNwidth, 600);
+ n++;
+
+ // create widget
+ //UiContainer *ct = uic_get_current_container(obj);
+ //Widget parent = ct->add(ct, args, &n);
+
+ Widget container = XmCreateContainer(scrollw, "table", args, n);
+ XtManageChild(container);
+
+ // add callbacks
+ UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
+ event->obj = obj;
+ event->activate = cb.activate;
+ event->selection = cb.selection;
+ event->userdata = cb.userdata;
+ event->last_selection = NULL;
+ if(cb.selection) {
+ XtAddCallback(
+ container,
+ XmNselectionCallback,
+ (XtCallbackProc)ui_table_select_callback,
+ event);
+ }
+ if(cb.activate) {
+ XtAddCallback(
+ container,
+ XmNdefaultActionCallback,
+ (XtCallbackProc)ui_table_action_callback,
+ event);
+ }
+
+ // add initial data
+ UiList *list = var->value;
+ void *data = list->first(list);
+ int width = 0;
+ while(data) {
+ int w = ui_add_icon_gadget(container, model, data);
+ if(w > width) {
+ width = w;
+ }
+ data = list->next(list);
+ }
+
+ UiTableView *tableview = cxMalloc(obj->ctx->allocator, sizeof(UiTableView));
+ tableview->widget = container;
+ tableview->var = var;
+ tableview->model = model;
+
+ // set new XmContainer width
+ XtVaSetValues(container, XmNwidth, width, NULL);
+
+ // cleanup
+ for(int i=0;i<model->columns;i++) {
+ XmStringFree(header[i]);
+ }
+ XtFree((char*)header);
+
+ return scrollw;
+}
+
+UIWIDGET ui_table(UiObject *obj, UiList *data, UiModel *model, UiListCallbacks cb) {
+ UiVar *var = malloc(sizeof(UiVar));
+ var->value = data;
+ var->type = UI_VAR_SPECIAL;
+ return ui_table_var(obj, var, model, cb);
+}
+
+void ui_table_update(UiEvent *event, UiTableView *view) {
+ // clear container
+ Widget *children;
+ int nc;
+
+ XtVaGetValues(
+ view->widget,
+ XmNchildren,
+ &children,
+ XmNnumChildren,
+ &nc,
+ NULL);
+
+ for(int i=0;i<nc;i++) {
+ XtDestroyWidget(children[i]);
+ }
+
+ UiList *list = view->var->value;
+
+ void *data = list->first(list);
+ int width = 0;
+ while(data) {
+ int w = ui_add_icon_gadget(view->widget, view->model, data);
+ if(w > width) {
+ width = w;
+ }
+ data = list->next(list);
+ }
+
+}
+
+#define UI_COL_CHAR_WIDTH 12
+
+int ui_add_icon_gadget(Widget container, UiModel *model, void *data) {
+ int width = 50;
+
+ if(model->columns == 0) {
+ return width;
+ }
+
+ XmString label = NULL;
+ Arg args[8];
+ Boolean f;
+ // first column
+ if(model->types[0] != 12345678) { // TODO: icon/label type
+ char *str = ui_type_to_string(
+ model->types[0],
+ model->getvalue(data, 0),
+ &f);
+
+ // column width
+ width += strlen(str) * UI_COL_CHAR_WIDTH;
+
+
+ XmString label = XmStringCreateLocalized(str);
+ XtSetArg(args[0], XmNlabelString, label);
+ if(f) {
+ free(str);
+ }
+ } else {
+ // TODO
+ }
+
+ // remaining columns are the icon gadget details
+ XmStringTable details = (XmStringTable)XtMalloc(
+ (model->columns - 1) * sizeof(XmString));
+ for(int i=1;i<model->columns;i++) {
+ char *str = ui_type_to_string(
+ model->types[i],
+ model->getvalue(data, i),
+ &f);
+
+ // column width
+ width += strlen(str) * UI_COL_CHAR_WIDTH;
+
+ details[i - 1] = XmStringCreateLocalized(str);
+ if(f) {
+ free(str);
+ }
+ }
+ XtSetArg(args[1], XmNdetail, details);
+ XtSetArg(args[2], XmNdetailCount, model->columns - 1);
+ XtSetArg(args[3], XmNshadowThickness, 0);
+ // create widget
+ Widget item = XmCreateIconGadget(container, "table_item", args, 4);
+ XtManageChild(item);
+
+ // cleanup
+ XmStringFree(label);
+ for(int i=0;i<model->columns-1;i++) {
+ XmStringFree(details[i]);
+ }
+ XtFree((char*)details);
+
+ return width;
+}
+
+char* ui_type_to_string(UiModelType type, void *data, Boolean *free) {
+ switch(type) {
+ case UI_STRING: *free = FALSE; return data;
+ case UI_INTEGER: {
+ *free = TRUE;
+ int *val = data;
+ cxmutstr str = cx_asprintf("%d", *val);
+ return str.ptr;
+ }
+ case UI_ICON: break; // TODO
+ case UI_ICON_TEXT: break; // TODO
+ }
+ *free = FALSE;
+ return NULL;
+}
+
+void ui_table_action_callback(
+ Widget widget,
+ UiTreeEventData *event,
+ XmContainerSelectCallbackStruct *sel)
+{
+ UiListSelection *selection = ui_list_selection(sel);
+
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = selection;
+ e.intval = selection->count > 0 ? selection->rows[0] : -1;
+ event->activate(&e, event->userdata);
+
+ free(event->last_selection->rows);
+ free(event->last_selection);
+ event->last_selection = selection;
+}
+
+void ui_table_select_callback(
+ Widget widget,
+ UiTreeEventData *event,
+ XmContainerSelectCallbackStruct *sel)
+{
+ UiListSelection *selection = ui_list_selection(sel);
+ if(!ui_compare_list_selection(selection, event->last_selection)) {
+ UiEvent e;
+ e.obj = event->obj;
+ e.window = event->obj->window;
+ e.document = event->obj->ctx->document;
+ e.eventdata = selection;
+ e.intval = selection->count > 0 ? selection->rows[0] : -1;
+ event->selection(&e, event->userdata);
+ }
+ if(event->last_selection) {
+ free(event->last_selection->rows);
+ free(event->last_selection);
+ }
+ event->last_selection = selection;
+}
+
+UiListSelection* ui_list_selection(XmContainerSelectCallbackStruct *xs) {
+ UiListSelection *selection = malloc(sizeof(UiListSelection));
+ selection->count = xs->selected_item_count;
+ selection->rows = calloc(selection->count, sizeof(int));
+ for(int i=0;i<selection->count;i++) {
+ int index;
+ XtVaGetValues(xs->selected_items[i], XmNpositionIndex, &index, NULL);
+ selection->rows[i] = index;
+ }
+ return selection;
+}
+
+Boolean ui_compare_list_selection(UiListSelection *s1, UiListSelection *s2) {
+ if(!s1 || !s2) {
+ return FALSE;
+ }
+ if(s1->count != s2->count) {
+ return FALSE;
+ }
+ for(int i=0;i<s1->count;i++) {
+ if(s1->rows[i] != s2->rows[i]) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TREE_H
+#define TREE_H
+
+#include "../ui/tree.h"
+#include "../common/context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiTreeEventData {
+ UiObject *obj;
+ ui_callback activate;
+ ui_callback selection;
+ void *userdata;
+ UiListSelection *last_selection;
+} UiTreeEventData;
+
+typedef struct UiTableView {
+ Widget widget;
+ UiVar *var;
+ UiModel *model;
+} UiTableView;
+
+void ui_table_update(UiEvent *event, UiTableView *view);
+int ui_add_icon_gadget(Widget container, UiModel *model, void *data);
+char* ui_type_to_string(UiModelType type, void *data, Boolean *free);
+
+void ui_table_action_callback(
+ Widget widget,
+ UiTreeEventData *event,
+ XmContainerSelectCallbackStruct *sel);
+void ui_table_select_callback(
+ Widget widget,
+ UiTreeEventData *event,
+ XmContainerSelectCallbackStruct *sel);
+
+UiListSelection* ui_list_selection(XmContainerSelectCallbackStruct *xs);
+
+Boolean ui_compare_list_selection(UiListSelection *s1, UiListSelection *s2);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TREE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "toolkit.h"
+#include "menu.h"
+#include "toolbar.h"
+#include "container.h"
+#include "../ui/window.h"
+#include "../common/context.h"
+
+#include <cx/mempool.h>
+
+static int nwindows = 0;
+
+static int window_default_width = 600;
+static int window_default_height = 500;
+
+static void window_close_handler(Widget window, void *udata, void *cdata) {
+ UiObject *obj = udata;
+ UiEvent ev;
+ ev.window = obj->window;
+ ev.document = obj->ctx->document;
+ ev.obj = obj;
+ ev.eventdata = NULL;
+ ev.intval = 0;
+
+ if(obj->ctx->close_callback) {
+ obj->ctx->close_callback(&ev, obj->ctx->close_data);
+ }
+ // TODO: free UiObject
+
+ nwindows--;
+ if(nwindows == 0) {
+ ui_exit_mainloop();
+ }
+}
+
+static UiObject* create_window(char *title, void *window_data, UiBool simple) {
+ CxMempool *mp = cxBasicMempoolCreate(256);
+ const CxAllocator *a = mp->allocator;
+ UiObject *obj = cxCalloc(a, 1, sizeof(UiObject));
+ obj->ctx = uic_context(obj, mp);
+ obj->window = window_data;
+
+ Arg args[16];
+ int n = 0;
+
+ XtSetArg(args[0], XmNtitle, title);
+ //XtSetArg(args[1], XmNbaseWidth, window_default_width);
+ //XtSetArg(args[2], XmNbaseHeight, window_default_height);
+ XtSetArg(args[1], XmNminWidth, 100);
+ XtSetArg(args[2], XmNminHeight, 50);
+ XtSetArg(args[3], XmNwidth, window_default_width);
+ XtSetArg(args[4], XmNheight, window_default_height);
+
+ Widget toplevel = XtAppCreateShell(
+ "Test123",
+ "abc",
+ //applicationShellWidgetClass,
+ vendorShellWidgetClass,
+ ui_get_display(),
+ args,
+ 5);
+
+ Atom wm_delete_window;
+ wm_delete_window = XmInternAtom(
+ XtDisplay(toplevel),
+ "WM_DELETE_WINDOW",
+ 0);
+ XmAddWMProtocolCallback(
+ toplevel,
+ wm_delete_window,
+ window_close_handler,
+ obj);
+
+ // TODO: use callback
+ ui_set_active_window(toplevel);
+
+ Widget window = XtVaCreateManagedWidget(
+ title,
+ xmMainWindowWidgetClass,
+ toplevel,
+ NULL);
+ obj->widget = window;
+ Widget form = XtVaCreateManagedWidget(
+ "window_form",
+ xmFormWidgetClass,
+ window,
+ NULL);
+ Widget toolbar = NULL;
+
+ if(!simple) {
+ ui_create_menubar(obj);
+ toolbar = ui_create_toolbar(obj, form);
+ }
+
+ // window content
+ XtSetArg(args[0], XmNshadowType, XmSHADOW_ETCHED_OUT);
+ XtSetArg(args[1], XmNshadowThickness, 0);
+ XtSetArg(args[2], XmNleftAttachment, XmATTACH_FORM);
+ XtSetArg(args[3], XmNrightAttachment, XmATTACH_FORM);
+ XtSetArg(args[4], XmNbottomAttachment, XmATTACH_FORM);
+ if(toolbar) {
+ XtSetArg(args[5], XmNtopAttachment, XmATTACH_WIDGET);
+ XtSetArg(args[6], XmNtopWidget, toolbar);
+ n = 7;
+ } else {
+ XtSetArg(args[5], XmNtopAttachment, XmATTACH_FORM);
+ n = 6;
+ }
+ Widget frame = XmCreateFrame(form, "content_frame", args, n);
+ XtManageChild(frame);
+
+ Widget content_form = XmCreateForm(frame, "content_form", NULL, 0);
+ XtManageChild(content_form);
+ obj->container = ui_box_container(obj, content_form, 0, 0, UI_BOX_VERTICAL);
+
+ XtManageChild(form);
+
+ obj->widget = toplevel;
+ nwindows++;
+ return obj;
+}
+
+UiObject* ui_window(char *title, void *window_data) {
+ return create_window(title, window_data, FALSE);
+}
+
+UiObject* ui_simplewindow(char *title, void *window_data) {
+ return create_window(title, window_data, TRUE);
+}
+
+void ui_close(UiObject *obj) {
+ XtDestroyWidget(obj->widget);
+ window_close_handler(obj->widget, obj, NULL);
+}
+
+typedef struct FileDialogData {
+ int running;
+ char *file;
+} FileDialogData;
+
+static void filedialog_select(
+ Widget widget,
+ FileDialogData *data,
+ XmFileSelectionBoxCallbackStruct *selection)
+{
+ char *path = NULL;
+ XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &path);
+ data->running = 0;
+ data->file = strdup(path);
+ XtFree(path);
+ XtUnmanageChild(widget);
+}
+
+static void filedialog_cancel(
+ Widget widget,
+ FileDialogData *data,
+ XmFileSelectionBoxCallbackStruct *selection)
+
+{
+ data->running = 0;
+ XtUnmanageChild(widget);
+}
+
+char* ui_openfiledialog(UiObject *obj) {
+ Widget dialog = XmCreateFileSelectionDialog(obj->widget, "openfiledialog", NULL, 0);
+ XtManageChild(dialog);
+
+ FileDialogData data;
+ data.running = 1;
+ data.file = NULL;
+
+ XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, &data);
+ XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_cancel, &data);
+
+ ui_secondary_event_loop(&data.running);
+ return data.file;
+}
+
+char* ui_savefiledialog(UiObject *obj) {
+ return ui_openfiledialog(obj);
+}
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+QT_MAKEFILE = ../build/ui/qt/Makefile.mk
+
+UI_QT_LIB = ../build/ui/qt/
+
+$(QT_MAKEFILE): qt/qt4.pro
+ qmake-qt4 -o - qt/qt4.pro > $(QT_MAKEFILE)
+
+$(UI_LIB): $(QT_MAKEFILE) $(OBJ) FORCE
+ $(MAKE) -f $(QT_MAKEFILE)
+ $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ)
+
+FORCE:
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "button.h"
+#include "container.h"
+#include "toolkit.h"
+
+UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) {
+ QString str = QString::fromUtf8(label);
+ QPushButton *button = new QPushButton(str);
+
+ if(f) {
+ UiEventWrapper *event = new UiEventWrapper(obj, f, data);
+ button->connect(button, SIGNAL(clicked()), event, SLOT(slot()));
+ }
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(button, false);
+
+ return button;
+}
+
+
+
+// TODO: checkbox
+
+
+UIWIDGET ui_radiobutton(UiObject *obj, char *label, UiInteger *rgroup) {
+ QString str = QString::fromUtf8(label);
+ QRadioButton *button = new QRadioButton(str);
+ button->setAutoExclusive(false);
+
+ if(rgroup) {
+ QButtonGroup *buttonGroup = (QButtonGroup*)rgroup->obj;
+ if(!buttonGroup) {
+ buttonGroup = new QButtonGroup();
+ rgroup->obj = buttonGroup;
+ button->setChecked(true);
+ }
+ buttonGroup->addButton(button, buttonGroup->buttons().size());
+
+ rgroup->get = ui_radiobutton_get;
+ rgroup->set = ui_radiobutton_set;
+ }
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(button, false);
+
+ return button;
+}
+
+int ui_radiobutton_get(UiInteger *value) {
+ QButtonGroup *buttonGroup = (QButtonGroup*)value->obj;
+ value->value = buttonGroup->checkedId();
+ return value->value;
+}
+
+void ui_radiobutton_set(UiInteger *value, int i) {
+ QButtonGroup *buttonGroup = (QButtonGroup*)value->obj;
+ QAbstractButton *button = buttonGroup->button(i);
+ if(button) {
+ button->setChecked(true);
+ value->value = i;
+ }
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BUTTON_H
+#define BUTTON_H
+
+#include "toolkit.h"
+#include "../ui/button.h"
+#include <QPushButton>
+#include <QRadioButton>
+#include <QButtonGroup>
+
+extern "C" {
+
+int ui_radiobutton_get(UiInteger *value);
+
+void ui_radiobutton_set(UiInteger *value, int i);
+
+}
+
+#endif /* BUTTON_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include "container.h"
+
+#include <QSpacerItem>
+#include <QStackedWidget>
+
+
+/* -------------------- UiBoxContainer -------------------- */
+
+UiBoxContainer::UiBoxContainer(QBoxLayout* box) {
+ this->current = NULL;
+ this->menu = NULL;
+ this->box = box;
+ box->setContentsMargins(QMargins(0,0,0,0));
+ box->setSpacing(0);
+
+ ui_reset_layout(layout);
+}
+
+void UiBoxContainer::add(QWidget* widget, bool fill) {
+ if(layout.fill != UI_LAYOUT_UNDEFINED) {
+ fill = ui_lb2bool(layout.fill);
+ }
+
+ if(hasStretchedWidget && fill) {
+ fill = false;
+ fprintf(stderr, "UiError: container has 2 filled widgets");
+ }
+
+ box->addWidget(widget, fill);
+
+ if(!hasStretchedWidget) {
+ QSpacerItem *newspace = new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+ box->removeItem(space);
+ box->addSpacerItem(newspace);
+ space = newspace;
+ }
+
+ if(fill) {
+ hasStretchedWidget = true;
+ }
+ ui_reset_layout(layout);
+ current = widget;
+}
+
+UIWIDGET ui_box(UiObject *obj, QBoxLayout::Direction dir) {
+ UiContainer *ct = uic_get_current_container(obj);
+ QWidget *widget = new QWidget();
+ QBoxLayout *box = new QBoxLayout(dir);
+ widget->setLayout(box);
+ ct->add(widget, true);
+
+ UiObject *newobj = uic_object_new(obj, widget);
+ newobj->container = new UiBoxContainer(box);
+ uic_obj_add(obj, newobj);
+
+ return widget;
+}
+
+UIWIDGET ui_vbox(UiObject *obj) {
+ return ui_box(obj, QBoxLayout::TopToBottom);
+}
+
+UIWIDGET ui_hbox(UiObject *obj) {
+ return ui_box(obj, QBoxLayout::LeftToRight);
+}
+
+
+
+/* -------------------- UiGridContainer -------------------- */
+
+UiGridContainer::UiGridContainer(QGridLayout* grid, int margin, int columnspacing, int rowspacing) {
+ this->current = NULL;
+ this->menu = NULL;
+ this->grid = grid;
+ grid->setContentsMargins(QMargins(margin, margin, margin, margin));
+ grid->setHorizontalSpacing(columnspacing);
+ grid->setVerticalSpacing(rowspacing);
+ ui_reset_layout(layout);
+}
+
+void UiGridContainer::add(QWidget* widget, bool fill) {
+ if(layout.newline) {
+ x = 0;
+ y++;
+ }
+
+ Qt::Alignment alignment = Qt::AlignTop;
+ grid->setColumnStretch(x, layout.hexpand ? 1 : 0);
+ if(layout.vexpand) {
+ grid->setRowStretch(y, 1);
+ alignment = 0;
+ } else {
+ grid->setRowStretch(y, 0);
+ }
+
+ int gwidth = layout.gridwidth > 0 ? layout.gridwidth : 1;
+
+ grid->addWidget(widget, y, x, 1, gwidth, alignment);
+ x += gwidth;
+
+ ui_reset_layout(layout);
+ current = widget;
+}
+
+UIWIDGET ui_grid(UiObject *obj) {
+ return ui_grid_sp(obj, 0, 0, 0);
+}
+
+UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) {
+ UiContainer *ct = uic_get_current_container(obj);
+ QWidget *widget = new QWidget();
+ QGridLayout *grid = new QGridLayout();
+ widget->setLayout(grid);
+ ct->add(widget, true);
+
+ UiObject *newobj = uic_object_new(obj, widget);
+ newobj->container = new UiGridContainer(grid, margin, columnspacing, rowspacing);
+ uic_obj_add(obj, newobj);
+
+ return widget;
+}
+
+
+/* -------------------- UiTabViewContainer -------------------- */
+
+UiTabViewContainer::UiTabViewContainer(QTabWidget* tabwidget) {
+ this->current = NULL;
+ this->menu = NULL;
+ this->tabwidget = tabwidget;
+}
+
+void UiTabViewContainer::add(QWidget* widget, bool fill) {
+ QString str = QString::fromUtf8(layout.label);
+ tabwidget->addTab(widget, str);
+}
+
+
+/* -------------------- UiStackContainer -------------------- */
+
+UiStackContainer::UiStackContainer(QStackedWidget *stack) {
+ this->stack = stack;
+}
+
+void UiStackContainer::add(QWidget* widget, bool fill) {
+ stack->addWidget(widget);
+ current = widget;
+}
+
+UIWIDGET ui_tabview(UiObject *obj) {
+ QStackedWidget *tabwidget = new QStackedWidget();
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(tabwidget, true);
+
+ UiObject *tabviewobj = uic_object_new(obj, tabwidget);
+ tabviewobj->container = new UiStackContainer(tabwidget);
+ uic_obj_add(obj, tabviewobj);
+
+ return tabwidget;
+}
+
+void ui_tab(UiObject *obj, char *title) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.label = title;
+ ui_vbox(obj);
+}
+
+void ui_select_tab(UIWIDGET tabview, int tab) {
+ QStackedWidget *w = (QStackedWidget*)tabview;
+ w->setCurrentIndex(tab);
+}
+
+
+/* -------------------- UiSidebarContainer -------------------- */
+
+UiSidebarContainer::UiSidebarContainer(QSplitter *splitter) {
+ this->splitter = splitter;
+}
+
+UIWIDGET ui_sidebar(UiObject *obj) {
+ QSplitter *splitter = new QSplitter(Qt::Horizontal);
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(splitter, true);
+
+ UiObject *left = uic_object_new(obj, splitter);
+ left->container = new UiSidebarContainer(splitter);
+
+ UiObject *right = uic_object_new(obj, splitter);
+ right->container = new UiSidebarContainer(splitter);
+
+ uic_obj_add(obj, right);
+ uic_obj_add(obj, left);
+
+ return splitter;
+}
+
+void UiSidebarContainer::add(QWidget *widget, bool fill) {
+ splitter->addWidget(widget);
+}
+
+
+/* -------------------- layout functions -------------------- */
+
+void ui_layout_fill(UiObject *obj, UiBool fill) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.fill = ui_bool2lb(fill);
+}
+
+void ui_layout_hexpand(UiObject *obj, UiBool expand) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.hexpand = expand;
+}
+
+void ui_layout_vexpand(UiObject *obj, UiBool expand) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.vexpand = expand;
+}
+
+void ui_layout_gridwidth(UiObject *obj, int width) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.gridwidth = width;
+}
+
+void ui_newline(UiObject *obj) {
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->layout.newline = TRUE;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CONTAINER_H
+#define CONTAINER_H
+
+#include "toolkit.h"
+#include "window.h"
+
+#include <string.h>
+#include <QBoxLayout>
+#include <QGridLayout>
+#include <QTabWidget>
+#include <QStackedWidget>
+#include <QSplitter>
+
+#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)
+#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)
+#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))
+
+typedef struct UiLayout UiLayout;
+
+enum UiLayoutBool {
+ UI_LAYOUT_UNDEFINED = 0,
+ UI_LAYOUT_TRUE,
+ UI_LAYOUT_FALSE,
+};
+typedef enum UiLayoutBool UiLayoutBool;
+
+struct UiLayout {
+ UiLayoutBool fill;
+ bool newline;
+ char *label;
+ bool hexpand;
+ bool vexpand;
+ int gridwidth;
+};
+
+struct UiContainer {
+ UiLayout layout;
+ UIWIDGET current;
+ QMenu *menu;
+
+ virtual void add(QWidget *widget, bool fill) = 0;
+};
+
+class UiBoxContainer : public UiContainer {
+public:
+ QBoxLayout *box;
+ bool hasStretchedWidget = false;
+ QSpacerItem *space;
+
+ UiBoxContainer(QBoxLayout *box);
+
+ virtual void add(QWidget *widget, bool fill);
+};
+
+class UiGridContainer : public UiContainer {
+public:
+ QGridLayout *grid;
+ int x = 0;
+ int y = 0;
+
+ UiGridContainer(QGridLayout *grid, int margin, int columnspacing, int rowspacing);
+
+ virtual void add(QWidget *widget, bool fill);
+};
+
+class UiTabViewContainer : public UiContainer {
+public:
+ QTabWidget *tabwidget;
+
+ UiTabViewContainer(QTabWidget *tabwidget);
+ virtual void add(QWidget *widget, bool fill);
+};
+
+class UiStackContainer : public UiContainer {
+public:
+ QStackedWidget *stack;
+
+ UiStackContainer(QStackedWidget *stack);
+ virtual void add(QWidget *widget, bool fill);
+};
+
+class UiSidebarContainer : public UiContainer {
+public:
+ QSplitter *splitter;
+
+ UiSidebarContainer(QSplitter *splitter);
+ virtual void add(QWidget *widget, bool fill);
+};
+
+#endif /* CONTAINER_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "graphics.h"
+#include "container.h"
+
+
+DrawingArea::DrawingArea(UiObject *obj, ui_drawfunc cb, void *data) {
+ object = obj;
+ drawCallback = cb;
+ userdata = data;
+}
+
+DrawingArea::~DrawingArea() {
+
+}
+
+void DrawingArea::paintEvent(QPaintEvent *event) {
+ QPainter painter(this);
+
+ UiQtGraphics g;
+ g.g.width = this->width();
+ g.g.height = this->height();
+ g.painter = &painter;
+
+ UiEvent ev;
+ ev.obj = object;
+ ev.window = object->window;
+ ev.document = object->ctx->document;
+ ev.eventdata = NULL;
+ ev.intval = 0;
+
+ drawCallback(&ev, &g.g, userdata);
+}
+
+
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) {
+ DrawingArea *widget = new DrawingArea(obj, f, userdata);
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(widget, true);
+
+ return widget;
+}
+
+void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) {
+
+}
+
+void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
+
+}
+
+void ui_drawingarea_redraw(UIWIDGET drawingarea) {
+
+}
+
+
+/* -------------------- text layout functions -------------------- */
+
+UiTextLayout* ui_text(UiGraphics *g) {
+ UiTextLayout *textlayout = new UiTextLayout();
+ return textlayout;
+}
+
+void ui_text_free(UiTextLayout *text) {
+ delete text;
+}
+
+void ui_text_setstring(UiTextLayout *layout, char *str) {
+ layout->text.setText(QString::fromUtf8(str));
+}
+
+void ui_text_setstringl(UiTextLayout *layout, char *str, int len) {
+ layout->text.setText(QString::fromUtf8(str, len));
+}
+
+void ui_text_setfont(UiTextLayout *layout, char *font, int size) {
+ layout->font = QFont(QString::fromUtf8(font), size);
+}
+
+void ui_text_getsize(UiTextLayout *layout, int *width, int *height) {
+ QSizeF size = layout->text.size();
+ *width = (int)size.width();
+ *height = (int)size.height();
+}
+
+void ui_text_setwidth(UiTextLayout *layout, int width) {
+ layout->text.setTextWidth((qreal)width);
+}
+
+
+/* -------------------- drawing functions -------------------- */
+
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue) {
+ UiQtGraphics *gr = (UiQtGraphics*)g;
+ gr->color = QColor(red, green, blue);
+ gr->painter->setPen(gr->color);
+}
+
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) {
+ UiQtGraphics *gr = (UiQtGraphics*)g;
+
+ gr->painter->drawLine(x1, y1, x2, y2);
+}
+
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) {
+ UiQtGraphics *gr = (UiQtGraphics*)g;
+
+ QRect rect(x, y, w, h);
+ if(fill) {
+ gr->painter->fillRect(rect, gr->color);
+
+ } else {
+ gr->painter->drawRect(rect);
+ }
+}
+
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) {
+ UiQtGraphics *gr = (UiQtGraphics*)g;
+
+ gr->painter->setFont(text->font);
+ gr->painter->drawStaticText(x, y, text->text);
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef GRAPHICS_H
+#define GRAPHICS_H
+
+#include "toolkit.h"
+#include "../ui/graphics.h"
+
+#include <QWidget>
+#include <QPainter>
+#include <QColor>
+#include <QStaticText>
+
+typedef struct UiQtGraphics {
+ UiGraphics g;
+ QPainter *painter;
+ QColor color;
+} UiXlibGraphics;
+
+struct UiTextLayout {
+ QStaticText text;
+ QFont font;
+};
+
+
+class DrawingArea : public QWidget {
+ Q_OBJECT
+
+ UiObject *object;
+ ui_drawfunc drawCallback;
+ void *userdata;
+
+public:
+ DrawingArea(UiObject *obj, ui_drawfunc cb, void *data);
+ ~DrawingArea();
+
+ virtual void paintEvent(QPaintEvent * event);
+};
+
+#endif /* GRAPHICS_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "label.h"
+#include "container.h"
+#include "toolkit.h"
+
+UIWIDGET ui_label(UiObject *obj, char *label) {
+ QString str = QString::fromUtf8(label);
+ QLabel *widget = new QLabel(str);
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(widget, false);
+
+ return widget;
+}
+
+UIWIDGET ui_space(UiObject *obj) {
+ // TODO: maybe there is a better widget for this purpose
+ QLabel *widget = new QLabel();
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(widget, true);
+
+ return widget;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LABEL_H
+#define LABEL_H
+
+#include "toolkit.h"
+#include <QLabel>
+
+#endif /* LABEL_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <QMenuBar>
+#include <QAction>
+
+#include "menu.h"
+#include "toolkit.h"
+#include "../common/context.h"
+#include "../ui/properties.h"
+#include "../ui/window.h"
+#include "stock.h"
+#include "container.h"
+
+static UcxList *menus;
+static UcxList *current;
+
+/* -------------------------- UiMenu -------------------------- */
+
+UiMenu::UiMenu(char* label) {
+ this->items = NULL;
+ this->label = label;
+}
+
+void UiMenu::addMenuItem(UiMenuItemI* item) {
+ items = ucx_list_append(items, item);
+}
+
+void UiMenu::addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu) {
+ QMenu *m = NULL;
+ if(menubar) {
+ m = menubar->addMenu(label);
+ } else {
+ m = menu->addMenu(label);
+ }
+
+ UCX_FOREACH(elm, items) {
+ UiMenuItemI *item = (UiMenuItemI*)elm->data;
+ item->addTo(obj, NULL, m);
+ }
+}
+
+
+/* -------------------------- UiMenuItem -------------------------- */
+
+UiMenuItem::UiMenuItem(char* label, ui_callback f, void* userdata) {
+ this->label = label;
+ this->callback = f;
+ this->userdata = userdata;
+ this->groups = NULL;
+}
+
+void UiMenuItem::addGroup(int group) {
+ groups = ucx_list_append(groups, (void*)(intptr_t)group);
+}
+
+void UiMenuItem::setCheckable(bool c) {
+ checkable = c;
+}
+
+void UiMenuItem::addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu) {
+ QString str = QString::fromUtf8(label);
+ UiAction *action = new UiAction(obj, str, callback, userdata);
+ action->setCheckable(checkable);
+ if(checkable) {
+ action->setChecked(false);
+ }
+ menu->addAction(action);
+ QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+}
+
+
+/* -------------------------- UiStMenuItem -------------------------- */
+
+UiStMenuItem::UiStMenuItem(char* stockid, ui_callback f, void* userdata) {
+ this->stockid = stockid;
+ this->callback = f;
+ this->userdata = userdata;
+ this->groups = NULL;
+}
+
+void UiStMenuItem::addGroup(int group) {
+ groups = ucx_list_append(groups, (void*)(intptr_t)group);
+}
+
+void UiStMenuItem::addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu) {
+ UiStockItem *stockItem = ui_get_stock_item(stockid);
+
+ QString str = QString::fromUtf8(stockItem->label);
+ UiAction *action = new UiAction(obj, str, callback, userdata);
+ action->setIcon(QIcon::fromTheme(stockItem->icon_name));
+ action->setIconVisibleInMenu(true);
+ menu->addAction(action);
+ //UiEventWrapper *ev = new UiEventWrapper(callback, userdata);
+ QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+}
+
+
+/* -------------------------- UiMenuSeparator -------------------------- */
+
+void UiMenuSeparator::addTo(UiObject* obj, QMenuBar* menubar, QMenu* menu) {
+ menu->addSeparator();
+}
+
+
+/* -------------------------- UiCheckItemNV -------------------------- */
+
+UiCheckItemNV::UiCheckItemNV(char* label, char* varname) {
+ this->label = label;
+ this->varname = varname;
+}
+
+void UiCheckItemNV::addTo(UiObject* obj, QMenuBar* menubar, QMenu* menu) {
+ QString str = QString::fromUtf8(label);
+ UiAction *action = new UiAction(obj, str, NULL, NULL);
+ action->setCheckable(true);
+ menu->addAction(action);
+ QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+
+ UiVar *var = uic_connect_var(obj->ctx, varname, UI_VAR_INTEGER);
+ if(var) {
+ UiInteger *value = (UiInteger*)var->value;
+ action->setChecked(value->value);
+ value->obj = action;
+ value->get = ui_checkitem_get;
+ value->set = ui_checkitem_set;
+ value = 0;
+ } else {
+ // TODO: error
+ }
+}
+
+
+/* -------------------------- UiAction -------------------------- */
+
+UiAction::UiAction(UiObject *obj, QString &label, ui_callback f, void* userdata) : QAction(label, NULL) {
+ //QAction(label, NULL);
+ this->obj = obj;
+ this->callback = f;
+ this->userdata = userdata;
+}
+
+void UiAction::trigger() {
+ if(!callback) {
+ return;
+ }
+
+ UiEvent e;
+ e.obj = obj;
+ e.window = obj->window;
+ e.document = obj->ctx->document;
+ e.eventdata = NULL;
+
+ if(isCheckable()) {
+ e.intval = isChecked();
+ } else {
+ e.intval = 0;
+ }
+
+ callback(&e, userdata);
+}
+
+
+void ui_menu(char *label) {
+ // free current menu hierarchy
+ ucx_list_free(current);
+
+ // create menu
+ UiMenu *menu = new UiMenu(label);
+
+ current = ucx_list_prepend(NULL, menu);
+ menus = ucx_list_append(menus, menu);
+}
+
+void ui_submenu(char *label) {
+ UiMenu *menu = new UiMenu(label);
+
+ // add submenu to current menu
+ UiMenu *cm = (UiMenu*)current->data;
+ cm->addMenuItem(menu);
+
+ // set the submenu to current menu
+ current = ucx_list_prepend(current, menu);
+}
+
+void ui_submenu_end() {
+ if(ucx_list_size(current) < 2) {
+ return;
+ }
+ current = ucx_list_remove(current, current);
+ //UcxList *c = current;
+}
+
+
+void ui_menuitem(char *label, ui_callback f, void *userdata) {
+ ui_menuitem_gr(label, f, userdata, -1);
+}
+
+void ui_menuitem_st(char *stockid, ui_callback f, void *userdata) {
+ ui_menuitem_stgr(stockid, f, userdata, -1);
+}
+
+void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...) {
+ if(!current) {
+ return;
+ }
+
+ UiMenuItem *item = new UiMenuItem(label, f, userdata);
+
+ // add groups
+ va_list ap;
+ va_start(ap, userdata);
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ item->addGroup(group);
+ }
+ va_end(ap);
+
+ UiMenu *cm = (UiMenu*)current->data;
+ cm->addMenuItem(item);
+}
+
+void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...) {
+ if(!current) {
+ return;
+ }
+
+ UiStMenuItem *item = new UiStMenuItem(stockid, f, userdata);
+
+ // add groups
+ va_list ap;
+ va_start(ap, userdata);
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ item->addGroup(group);
+ }
+ va_end(ap);
+
+ UiMenu *cm = (UiMenu*)current->data;
+ cm->addMenuItem(item);
+}
+
+void ui_menuseparator() {
+ if(!current) {
+ return;
+ }
+
+ UiMenuSeparator *item = new UiMenuSeparator();
+ UiMenu *cm = (UiMenu*)current->data;
+ cm->addMenuItem(item);
+}
+
+void ui_checkitem(char *label, ui_callback f, void *userdata) {
+ if(!current) {
+ return;
+ }
+
+ UiMenuItem *item = new UiMenuItem(label, f, userdata);
+ item->setCheckable(true);
+
+ UiMenu *cm = (UiMenu*)current->data;
+ cm->addMenuItem(item);
+}
+
+void ui_checkitem_nv(char *label, char *vname) {
+ if(!current) {
+ return;
+ }
+
+ UiCheckItemNV *item = new UiCheckItemNV(label, vname);
+
+ UiMenu *cm = (UiMenu*)current->data;
+ cm->addMenuItem(item);
+}
+
+void ui_menuitem_list(UiList *items, ui_callback f, void *userdata) {
+
+}
+
+void ui_add_menus(UiObject *obj, QMainWindow *window) {
+ QMenuBar *mb = window->menuBar();
+
+ UCX_FOREACH(elm, menus) {
+ UiMenu *menu = (UiMenu*)elm->data;
+ menu->addTo(obj, mb, NULL);
+ }
+}
+
+int ui_checkitem_get(UiInteger *i) {
+ QAction *action = (QAction*)i->obj;
+ i->value = action->isChecked();
+ return i->value;
+}
+
+void ui_checkitem_set(UiInteger *i, int value) {
+ QAction *action = (QAction*)i->obj;
+ i->value = value;
+ action->setChecked(value);
+}
+
+
+/*
+ * widget menu functions
+ */
+
+UiContextMenuHandler::UiContextMenuHandler(QWidget *widget, QMenu* menu) {
+ this->widget = widget;
+ this->menu = menu;
+}
+
+void UiContextMenuHandler::contextMenuEvent(const QPoint & pos) {
+ menu->popup(widget->mapToGlobal(pos));
+}
+UIMENU ui_contextmenu(UiObject *obj) {
+ UiContainer *ct = uic_get_current_container(obj);
+ return ui_contextmenu_w(obj, ct->current);
+}
+
+UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) {
+ UiContainer *ct = uic_get_current_container(obj);
+
+ QMenu *menu = new QMenu(widget);
+ widget->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ UiContextMenuHandler *handler = new UiContextMenuHandler(widget, menu);
+ QObject::connect(
+ widget,
+ SIGNAL(customContextMenuRequested(QPoint)),
+ handler,
+ SLOT(contextMenuEvent(QPoint)));
+
+ ct->menu = menu;
+
+ return menu;
+}
+
+void ui_contextmenu_popup(UIMENU menu) {
+
+}
+
+void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) {
+ ui_widget_menuitem_gr(obj, label, f, userdata, -1);
+}
+
+void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) {
+ UiContainer *ct = uic_get_current_container(obj);
+ if(!ct->menu) {
+ return;
+ }
+
+ // add groups
+ UcxList *groups = NULL;
+ va_list ap;
+ va_start(ap, userdata);
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ ucx_list_append(groups, (void*)(intptr_t)group);
+ }
+ va_end(ap);
+
+ // create menuitem
+ QString str = QString::fromUtf8(label);
+ UiAction *action = new UiAction(obj, str, f, userdata);
+ ct->menu->addAction(action);
+ QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+}
+
+void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata) {
+ ui_widget_menuitem_stgr(obj, stockid, f, userdata, -1);
+}
+
+void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) {
+ UiContainer *ct = uic_get_current_container(obj);
+ if(!ct->menu) {
+ return;
+ }
+
+ // add groups
+ UcxList *groups = NULL;
+ va_list ap;
+ va_start(ap, userdata);
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ ucx_list_append(groups, (void*)(intptr_t)group);
+ }
+ va_end(ap);
+
+ // create menuitem
+ UiStockItem *stockItem = ui_get_stock_item(stockid);
+
+ QString str = QString::fromUtf8(stockItem->label);
+ UiAction *action = new UiAction(obj, str, f, userdata);
+ action->setIcon(QIcon::fromTheme(stockItem->icon_name));
+ action->setIconVisibleInMenu(true);
+ ct->menu->addAction(action);
+ QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MENU_H
+#define MENU_H
+
+#include "../ui/menu.h"
+#include <ucx/list.h>
+
+#include <QMainWindow>
+#include <QMenu>
+#include <QMenuBar>
+#include <QContextMenuEvent>
+
+class UiMenuItemI {
+public:
+ virtual void addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu) = 0;
+};
+
+class UiMenu : public UiMenuItemI {
+public:
+
+ UcxList *items;
+ char *label;
+
+ UiMenu(char *label);
+
+ void addMenuItem(UiMenuItemI *item);
+
+ virtual void addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu);
+};
+
+class UiMenuItem : public UiMenuItemI {
+ char *label;
+ ui_callback callback;
+ void *userdata;
+ UcxList *groups;
+ bool checkable = false;
+
+public:
+ UiMenuItem(char *label, ui_callback f, void *userdata);
+ void addGroup(int group);
+ void setCheckable(bool c);
+
+ virtual void addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu);
+};
+
+class UiStMenuItem : public UiMenuItemI {
+ char *stockid;
+ ui_callback callback;
+ void *userdata;
+ UcxList *groups;
+
+public:
+ UiStMenuItem(char *stockid, ui_callback f, void *userdata);
+ void addGroup(int group);
+
+ virtual void addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu);
+};
+
+class UiMenuSeparator : public UiMenuItemI {
+public:
+ virtual void addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu);
+};
+
+class UiCheckItemNV : public UiMenuItemI {
+ char *label;
+ char *varname;
+
+public:
+ UiCheckItemNV(char *label, char *varname);
+ virtual void addTo(UiObject *obj, QMenuBar *menubar, QMenu *menu);
+};
+
+
+class UiAction : public QAction {
+ Q_OBJECT
+
+ UiObject *obj;
+ ui_callback callback;
+ void *userdata;
+
+public:
+ UiAction(UiObject *obj, QString &label, ui_callback f, void *userdata);
+
+private slots:
+ void trigger();
+};
+
+void ui_add_menus(UiObject *obj, QMainWindow *window);
+
+extern "C" int ui_checkitem_get(UiInteger *i);
+extern "C" void ui_checkitem_set(UiInteger *i, int value);
+
+class UiContextMenuHandler : public QObject {
+ Q_OBJECT
+
+ QWidget *widget;
+ QMenu *menu;
+
+public:
+ UiContextMenuHandler(QWidget *widget, QMenu *menu);
+
+public slots:
+ void contextMenuEvent(const QPoint & pos);
+};
+
+#endif /* MENU_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "model.h"
+
+UiListSelection* listSelection(QItemSelectionModel *s) {
+ UiListSelection *selection = new UiListSelection();
+
+ QModelIndexList list = s->selectedRows();
+ selection->count = list.count();
+ if(selection->count > 0) {
+ selection->rows = new int[selection->count];
+ }
+
+ QModelIndex index;
+ int i=0;
+ foreach(index, list) {
+ selection->rows[i] = index.row();
+ i++;
+ }
+ return selection;
+}
+
+ListModel::ListModel(UiObject* obj, QListView* view, UiListPtr* list, ui_model_getvalue_f getvalue, ui_callback f, void* userdata) {
+ this->obj = obj;
+ this->view = view;
+ this->list = list;
+ this->getvalue = getvalue;
+ this->callback = f;
+ this->userdata = userdata;
+}
+
+int ListModel::rowCount(const QModelIndex& parent) const {
+ return list->list->count(list->list);
+}
+
+QVariant ListModel::data(const QModelIndex &index, int role) const {
+ if(role == Qt::DisplayRole) {
+ UiList *ls = list->list;
+ void *rowData = ls->get(ls, index.row());
+ if(rowData && getvalue) {
+ void *value = getvalue(rowData, 0);
+ return QString::fromUtf8((char*)value);
+ }
+ }
+ return QVariant();
+}
+
+void ListModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) {
+ UiListSelection *selection = listSelection(view->selectionModel());
+
+ UiEvent e;
+ e.obj = obj;
+ e.window = obj->window;
+ e.document = obj->ctx->document;
+ e.eventdata = selection;
+ e.intval = selection->count > 0 ? selection->rows[0] : -1;
+ callback(&e, userdata);
+
+ if(selection->count > 0) {
+ delete selection->rows;
+ }
+ delete selection;
+}
+
+TableModel::TableModel(UiObject *obj, QTreeView *view, UiListPtr *list, UiModelInfo *info) {
+ this->obj = obj;
+ this->list = list;
+ this->info = info;
+ this->view = view;
+}
+
+int TableModel::rowCount(const QModelIndex &parent) const {
+ return list->list->count(list->list);
+}
+
+int TableModel::columnCount(const QModelIndex &parent) const {
+ return info->columns;
+}
+
+QVariant TableModel::data(const QModelIndex &index, int role) const {
+ if(role == Qt::DisplayRole) {
+ UiList *ls = list->list;
+ void *rowData = ls->get(ls, index.row());
+ if(rowData && info->getvalue) {
+ void *value = info->getvalue(rowData, index.column());
+ switch(info->types[index.column()]) {
+ case UI_STRING: {
+ return QString::fromUtf8((char*)value);
+ }
+ case UI_INTEGER: {
+ int *intptr = (int*)value;
+ return QVariant(*intptr);
+ }
+ }
+ }
+ }
+ return QVariant();
+}
+
+QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const {
+ if(role == Qt::DisplayRole) {
+ char *label = info->titles[section];
+ return QString::fromUtf8(label);
+ }
+ return QVariant();
+}
+
+void TableModel::update() {
+ emit dataChanged(QModelIndex(),QModelIndex());
+}
+
+void TableModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) {
+ UiListSelection *selection = listSelection(view->selectionModel());
+
+ UiEvent e;
+ e.obj = obj;
+ e.window = obj->window;
+ e.document = obj->ctx->document;
+ e.eventdata = selection;
+ e.intval = selection->count > 0 ? selection->rows[0] : -1;
+ info->selection(&e, info->userdata);
+
+ if(selection->count > 0) {
+ delete selection->rows;
+ }
+ delete selection;
+}
+
+void TableModel::activate(const QModelIndex &) {
+ UiListSelection *selection = listSelection(view->selectionModel());
+
+ UiEvent e;
+ e.obj = obj;
+ e.window = obj->window;
+ e.document = obj->ctx->document;
+ e.eventdata = selection;
+ e.intval = selection->count > 0 ? selection->rows[0] : -1;
+ info->activate(&e, info->userdata);
+
+ if(selection->count > 0) {
+ delete selection->rows;
+ }
+ delete selection;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MODEL_H
+#define MODEL_H
+
+#include "toolkit.h"
+#include "../ui/tree.h"
+#include "../common/context.h"
+#include <QListView>
+#include <QTreeView>
+#include <QAbstractListModel>
+#include <QAbstractTableModel>
+#include <QAbstractItemModel>
+#include <QItemSelectionModel>
+
+class ListModel : public QAbstractListModel {
+ Q_OBJECT
+
+ UiObject *obj;
+ UiListPtr *list;
+ ui_model_getvalue_f getvalue;
+ ui_callback callback;
+ void *userdata;
+ QListView *view;
+
+public:
+ ListModel(UiObject *obj, QListView *view, UiListPtr *list, ui_model_getvalue_f getvalue, ui_callback f, void *userdata);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+public slots:
+ void selectionChanged(
+ const QItemSelection & selected,
+ const QItemSelection & deselected);
+};
+
+class TableModel : public QAbstractTableModel {
+ Q_OBJECT
+
+ UiObject *obj;
+ UiListPtr *list;
+ UiModelInfo *info;
+ QTreeView *view;
+public:
+ TableModel(UiObject *obj, QTreeView *view, UiListPtr *list, UiModelInfo *info);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+
+ void update();
+
+public slots:
+ void selectionChanged(
+ const QItemSelection & selected,
+ const QItemSelection & deselected);
+ void activate(const QModelIndex &);
+};
+
+UiListSelection* listSelection(QItemSelectionModel *s);
+
+#endif /* MODEL_H */
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2012 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+QT_SRC_DIR = ui/qt/
+QT_OBJPRE = $(OBJ_DIR)/$(QT_SRC_DIR)
+
+#QTOBJ =
+
+TOOLKITOBJS += $(QTOBJ:%=$(QT_OBJPRE)%)
+TOOLKITSOURCE += $(QTOBJ:%.o=qt/%.cpp)
+
--- /dev/null
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2014 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+TARGET = uitk
+TEMPLATE = lib
+CONFIG += staticlib warn_off debug
+DESTDIR = ../build/lib
+MOC_DIR = ../build/ui/qt
+OBJECTS_DIR = ../build/ui/qt
+
+DEFINES += UI_QT4
+
+SOURCES += toolkit.cpp
+SOURCES += window.cpp
+SOURCES += menu.cpp
+SOURCES += toolbar.cpp
+SOURCES += stock.cpp
+SOURCES += container.cpp
+SOURCES += text.cpp
+SOURCES += model.cpp
+SOURCES += tree.cpp
+SOURCES += button.cpp
+SOURCES += label.cpp
+SOURCES += graphics.cpp
+
+HEADERS += toolkit.h
+HEADERS += window.h
+HEADERS += menu.h
+HEADERS += toolbar.h
+HEADERS += stock.h
+HEADERS += container.h
+HEADERS += text.h
+HEADERS += model.h
+HEADERS += tree.h
+HEADERS += button.h
+HEADERS += label.h
+HEADERS += graphics.h
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <ucx/map.h>
+
+#include "stock.h"
+#include "../ui/properties.h"
+
+static UcxMap *stock_items;
+
+void ui_stock_init() {
+ stock_items = ucx_map_new(64);
+
+ ui_add_stock_item(UI_STOCK_NEW, "New", "document-new");
+ ui_add_stock_item(UI_STOCK_OPEN, "Open", "document-open");
+ ui_add_stock_item(UI_STOCK_SAVE, "Save", "document-save");
+ ui_add_stock_item(UI_STOCK_SAVE_AS, "Save as ...", "document-save-as");
+ ui_add_stock_item(UI_STOCK_REVERT_TO_SAVED, "Revert to saved", "document-revert");
+ ui_add_stock_item(UI_STOCK_CLOSE, "Close", "window-close");
+ ui_add_stock_item(UI_STOCK_UNDO, "Undo", "edit-undo");
+ ui_add_stock_item(UI_STOCK_REDO, "Redo", "edit-redo");
+ ui_add_stock_item(UI_STOCK_GO_BACK, "Back", "go-previous");
+ ui_add_stock_item(UI_STOCK_GO_FORWARD, "Forward", "go-next");
+ ui_add_stock_item(UI_STOCK_CUT, "Cut", "edit-cut");
+ ui_add_stock_item(UI_STOCK_COPY, "Copy", "edit-copy");
+ ui_add_stock_item(UI_STOCK_PASTE, "Paste", "edit-paste");
+ ui_add_stock_item(UI_STOCK_DELETE, "Delete", "edit-delete");
+}
+
+void ui_add_stock_item(char *id, char *label, char *icon) {
+ UiStockItem *item = new UiStockItem(label, icon);
+ ucx_map_cstr_put(stock_items, id, item);
+}
+
+UiStockItem* ui_get_stock_item(char *id) {
+ UiStockItem *item = (UiStockItem*)ucx_map_cstr_get(stock_items, id);
+ if(item) {
+ char *label = uistr_n(id);
+ if(label) {
+ item->label = label;
+ }
+ }
+ return item;
+}
+
+
+UiStockItem::UiStockItem(char* label, char* icon_name) {
+ this->label = label;
+ this->icon_name = icon_name;
+}
+
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef STOCK_H
+#define STOCK_H
+
+#include "../ui/stock.h"
+
+class UiStockItem {
+public:
+
+ char *label;
+ char *icon_name;
+
+ UiStockItem(char *label, char *icon_name);
+};
+
+
+void ui_stock_init();
+void ui_add_stock_item(char *id, char *label, char *icon);
+UiStockItem* ui_get_stock_item(char *id);
+
+#endif /* STOCK_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "text.h"
+#include "container.h"
+
+#include "../common/context.h"
+#include "../common/document.h"
+
+UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
+ QTextDocument *txtdoc = value && value->obj ? (QTextDocument*)value->obj : new QTextDocument();
+
+ if(value) {
+ if(value->value && value->obj) {
+ QString str = QString::fromUtf8(value->value);
+ txtdoc->setPlainText(str);
+ }
+
+ value->get = ui_textarea_get;
+ value->set = ui_textarea_set;
+ value->getsubstr = ui_textarea_getsubstr;
+ value->insert = ui_textarea_insert;
+ value->setposition = ui_textarea_setposition;
+ value->position = ui_textarea_position;
+ value->selection = ui_textarea_selection;
+ value->length = ui_textarea_length;
+ value->remove = ui_textarea_remove;
+ value->obj = txtdoc;
+ value->value = NULL;
+ }
+
+ UiContainer *ct = uic_get_current_container(obj);
+ QTextEdit *textedit = new QTextEdit();
+ textedit->setDocument(txtdoc);
+ ct->add(textedit, true);
+
+ return textedit;
+}
+
+UIWIDGET ui_textarea_nv(UiObject *obj, char *varname) {
+ UiVar *var = uic_connect_var(obj->ctx, varname, UI_VAR_TEXT);
+ if(var) {
+ UiText *value = (UiText*)var->value;
+ return ui_textarea(obj, value);
+ } else {
+ // TODO: error
+ }
+ return NULL;
+}
+
+
+char* ui_textarea_get(UiText *text) {
+ if(text->value) {
+ free(text->value);
+ }
+
+ QTextDocument *doc = (QTextDocument*)text->obj;
+ QString str = doc->toPlainText();
+ QByteArray array = str.toLocal8Bit();
+ const char *cstr = array.constData();
+
+ if(text->value) {
+ free(text->value);
+ }
+ text->value = strdup(cstr);
+ return text->value;
+}
+
+void ui_textarea_set(UiText *text, char *str) {
+ // set text
+ QTextDocument *doc = (QTextDocument*)text->obj;
+ QString qstr = QString::fromUtf8(str);
+ doc->setPlainText(qstr);
+ // cleanup
+ if(text->value) {
+ free(text->value);
+ }
+ text->value = NULL;
+}
+
+char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
+ QTextDocument *doc = (QTextDocument*)text->obj;
+ return NULL; // TODO
+}
+
+void ui_textarea_insert(UiText *text, int pos, char *str) {
+ QTextDocument *doc = (QTextDocument*)text->obj;
+}
+
+void ui_textarea_setposition(UiText *text, int pos) {
+ // TODO
+}
+
+int ui_textarea_position(UiText *text) {
+ QTextDocument *doc = (QTextDocument*)text->obj;
+ return 0; // TODO
+}
+
+void ui_textarea_selection(UiText *text, int *begin, int *end) {
+ QTextDocument *doc = (QTextDocument*)text->obj;
+}
+
+int ui_textarea_length(UiText *text) {
+ QTextDocument *doc = (QTextDocument*)text->obj;
+ return 0; // TODO
+}
+
+void ui_textarea_remove(UiText *text, int begin, int end) {
+ QTextDocument *doc = (QTextDocument*)text->obj;
+}
+
+
+/* ------------------- TextField ------------------- */
+
+UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
+ QLineEdit *textfield = new QLineEdit();
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(textfield, false);
+
+ if(value) {
+ if(value->value) {
+ QString str = QString::fromUtf8(value->value);
+ textfield->setText(str);
+ free(value->value);
+ value->value = NULL;
+ }
+ value->set = ui_textfield_set;
+ value->get = ui_textfield_get;
+ value->obj = textfield;
+ }
+
+ return textfield;
+}
+
+UIWIDGET ui_textfield_nv(UiObject *obj, char *varname) {
+ UiVar *var = uic_connect_var(obj->ctx, varname, UI_VAR_STRING);
+ if(var) {
+ UiString *value = (UiString*)var->value;
+ return ui_textfield(obj, value);
+ } else {
+ // TODO: error
+ }
+ return NULL;
+}
+
+char* ui_textfield_get(UiString *str) {
+ QLineEdit *textfield = (QLineEdit*)str->obj;
+ QString qstr = textfield->text();
+
+ if(str->value) {
+ free(str->value);
+ }
+ QByteArray array = qstr.toLocal8Bit();
+ const char *cstr = array.constData();
+ str->value = strdup(cstr);
+
+ return str->value;
+}
+
+void ui_textfield_set(UiString *str, char *value) {
+ QLineEdit *textfield = (QLineEdit*)str->obj;
+ QString qstr = QString::fromUtf8(value);
+ textfield->setText(qstr);
+
+ if(str->value) {
+ free(str->value);
+ }
+ str->value = NULL;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TEXT_H
+#define TEXT_H
+
+#include "toolkit.h"
+#include "../ui/text.h"
+#include <QTextEdit>
+#include <QLineEdit>
+
+// value implementations
+extern "C" {
+ char* ui_textarea_get(UiText *text);
+ void ui_textarea_set(UiText *text, char *str);
+ char* ui_textarea_getsubstr(UiText *text, int begin, int end);
+ void ui_textarea_insert(UiText *text, int pos, char *str);
+ void ui_textarea_setposition(UiText *text, int pos);
+ int ui_textarea_position(UiText *text);
+ void ui_textarea_selection(UiText *text, int *begin, int *end);
+ int ui_textarea_length(UiText *text);
+ void ui_textarea_remove(UiText *text, int begin, int end);
+
+ char* ui_textfield_get(UiString *str);
+ void ui_textfield_set(UiString *str, char *value);
+}
+
+
+
+#endif /* TEXT_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <ucx/map.h>
+#include <inttypes.h>
+
+#include "toolbar.h"
+#include "menu.h"
+#include "stock.h"
+
+static UcxMap *toolbar_items = ucx_map_new(16);
+static UcxList *defaults;
+
+/* ------------------------- UiToolItem ------------------------- */
+
+UiToolItem::UiToolItem(char *label, ui_callback f, void *userdata) {
+ this->label = label;
+ this->image = NULL;
+ this->callback = f;
+ this->userdata = userdata;
+ this->isimportant = false;
+ this->groups = NULL;
+}
+
+void UiToolItem::addGroup(int group) {
+ groups = ucx_list_append(groups, (void*)(intptr_t)group);
+}
+
+void UiToolItem::addTo(UiObject *obj, QToolBar *toolbar) {
+ QString str = QString::fromUtf8(label);
+ UiAction *action = new UiAction(obj, str, callback, userdata);
+ action->setIcon(QIcon::fromTheme(image));
+ toolbar->addAction(action);
+ QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+}
+
+
+/* ------------------------- UiStockToolItem ------------------------- */
+
+UiStockToolItem::UiStockToolItem(char *stockid, ui_callback f, void *userdata) {
+ this->stockid = stockid;
+ this->callback = f;
+ this->userdata = userdata;
+ this->isimportant = false;
+ this->groups = NULL;
+}
+
+void UiStockToolItem::addGroup(int group) {
+ groups = ucx_list_append(groups, (void*)(intptr_t)group);
+}
+
+void UiStockToolItem::addTo(UiObject *obj, QToolBar *toolbar) {
+ UiStockItem *stockItem = ui_get_stock_item(stockid);
+ QString str = QString::fromUtf8(stockItem->label);
+
+ UiAction *action = new UiAction(obj, str, callback, userdata);
+ action->setIcon(QIcon::fromTheme(stockItem->icon_name));
+ toolbar->addAction(action);
+ QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger()));
+}
+
+
+
+void ui_toolitem_vstgr(
+ char *name,
+ char *stockid,
+ int isimportant,
+ ui_callback f,
+ void *userdata,
+ va_list ap)
+{
+ UiStockToolItem *item = new UiStockToolItem(stockid, f, userdata);
+ item->isimportant = isimportant;
+
+ // add groups
+ int group;
+ while((group = va_arg(ap, int)) != -1) {
+ item->addGroup(group);
+ }
+
+ ucx_map_cstr_put(toolbar_items, name, item);
+}
+
+void ui_toolitem_img(char *name, char *label, char *img, ui_callback f, void *udata) {
+ UiToolItem *item = new UiToolItem(label, f, udata);
+ item->image = img;
+ item->isimportant = false;
+
+ ucx_map_cstr_put(toolbar_items, name, item);
+}
+
+void ui_toolitem(char *name, char *label, ui_callback f, void *udata) {
+ ui_toolitem_img(name, label, NULL, f, udata);
+}
+
+void ui_toolitem_st(char *name, char *stockid, ui_callback f, void *userdata) {
+ ui_toolitem_stgr(name, stockid, f, userdata, -1);
+}
+
+void ui_toolitem_sti(char *name, char *stockid, ui_callback f, void *userdata) {
+ ui_toolitem_stgri(name, stockid, f, userdata, -1);
+}
+
+void ui_toolitem_stgr(char *name, char *stockid, ui_callback f, void *userdata, ...) {
+ va_list ap;
+ va_start(ap, userdata);
+ ui_toolitem_vstgr(name, stockid, 0, f, userdata, ap);
+ va_end(ap);
+}
+
+void ui_toolitem_stgri(char *name, char *stockid, ui_callback f, void *userdata, ...) {
+ va_list ap;
+ va_start(ap, userdata);
+ ui_toolitem_vstgr(name, stockid, 1, f, userdata, ap);
+ va_end(ap);
+}
+
+
+void ui_toolbar_add_default(char *name) {
+ char *s = strdup(name);
+ defaults = ucx_list_append(defaults, s);
+}
+
+
+QToolBar* ui_create_toolbar(UiObject *obj) {
+ QToolBar *toolbar = new QToolBar();
+
+ UCX_FOREACH(elm, defaults) {
+ UiToolItemI *item = (UiToolItemI*)ucx_map_cstr_get(toolbar_items, (char*)elm->data);
+ if(item) {
+ item->addTo(obj, toolbar);
+ } else if(!strcmp((char*)elm->data, "@separator")) {
+
+ } else {
+ fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", elm->data);
+ }
+ }
+
+ return toolbar;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TOOLBAR_H
+#define TOOLBAR_H
+
+#include "toolkit.h"
+#include "../ui/toolbar.h"
+#include <ucx/list.h>
+#include <QToolBar>
+
+class UiToolItemI {
+public:
+ virtual void addTo(UiObject *obj, QToolBar *toolbar) = 0;
+};
+
+class UiToolItem : public UiToolItemI {
+public:
+ char *label;
+ char *image;
+ ui_callback callback;
+ void *userdata;
+ UcxList *groups;
+ bool isimportant;
+
+ UiToolItem(char *label, ui_callback f, void *userdata);
+ void addGroup(int group);
+ virtual void addTo(UiObject *obj, QToolBar *toolbar);
+};
+
+class UiStockToolItem : public UiToolItemI {
+public:
+ char *stockid;
+ ui_callback callback;
+ void *userdata;
+ UcxList *groups;
+ bool isimportant;
+
+ UiStockToolItem(char *stockid, ui_callback f, void *userdata);
+ void addGroup(int group);
+ virtual void addTo(UiObject *obj, QToolBar *toolbar);
+};
+
+
+void ui_toolitem_vstgr(
+ char *name,
+ char *stockid,
+ int isimportant,
+ ui_callback f,
+ void *userdata,
+ va_list ap);
+
+
+QToolBar* ui_create_toolbar(UiObject *obj);
+
+
+#endif /* TOOLBAR_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "toolkit.h"
+#include "window.h"
+#include "stock.h"
+
+#include "../common/document.h"
+#include "../common/properties.h"
+
+static char *application_name;
+
+static ui_callback appclose_fnc;
+static void *appclose_udata;
+
+//static QApplication app(qargc, qargv);
+int app_argc;
+char **app_argv;
+QApplication *application = NULL;
+
+void ui_init(char *appname, int argc, char **argv) {
+ application_name = appname;
+
+ app_argc = argc;
+ app_argv = argv;
+ application = new QApplication(app_argc, app_argv);
+
+ uic_docmgr_init();
+
+ uic_load_app_properties();
+
+ ui_stock_init();
+}
+
+char* ui_appname() {
+ return application_name;
+}
+
+void ui_exitfunc(ui_callback f, void *udata) {
+ appclose_fnc = f;
+ appclose_udata = udata;
+}
+
+void ui_openfilefunc(ui_callback f, void *userdata) {
+ // OS X only
+}
+
+void ui_main() {
+ application->exec();
+
+ if(appclose_fnc) {
+ appclose_fnc(NULL, appclose_udata);
+ }
+ uic_store_app_properties();
+
+ delete application;
+}
+
+void ui_show(UiObject *obj) {
+ obj->widget->show();
+}
+
+void ui_close(UiObject *obj) {
+ QMainWindow *window = (QMainWindow*)obj->widget;
+ window->close();
+}
+
+void ui_set_enabled(UIWIDGET widget, int enabled) {
+
+}
+
+void ui_set_visible(UIWIDGET widget, int visible) {
+
+}
+
+
+
+
+UiEventWrapper::UiEventWrapper(UiObject *obj, ui_callback f, void* userdata) {
+ this->obj = obj;
+ this->callback = f;
+ this->userdata = userdata;
+}
+
+void UiEventWrapper::slot() {
+ UiEvent e;
+ e.obj = obj;
+ e.window = obj->window;
+ e.document = obj->ctx->document;
+ e.eventdata = NULL;
+ e.intval = 0;
+ callback(&e, userdata);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TOOLKIT_H
+#define TOOLKIT_H
+
+#include "../ui/toolkit.h"
+#include "../common/context.h"
+#include "../common/object.h"
+
+#include <QApplication>
+
+class UiEventWrapper : public QObject {
+ Q_OBJECT
+
+ UiObject *obj;
+ ui_callback callback;
+ void *userdata;
+
+public:
+ UiEventWrapper(UiObject *obj, ui_callback f, void *userdata);
+
+public slots:
+ void slot();
+};
+
+
+#endif /* TOOLKIT_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "tree.h"
+#include "container.h"
+
+#include <QTreeView>
+#include <QTreeWidgetItem>
+#include <QListView>
+
+
+extern "C" void* ui_strmodel_getvalue(void *elm, int column) {
+ return column == 0 ? elm : NULL;
+}
+
+UIWIDGET ui_listview_str(UiObject *obj, UiList *list, ui_callback f, void *udata) {
+ return ui_listview(obj, list, ui_strmodel_getvalue, f, udata);
+}
+UIWIDGET ui_listview_var(UiObject *obj, UiListPtr *list, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
+ QListView *view = new QListView();
+ ListModel *model = new ListModel(obj, view, list, getvalue, f, udata);
+ view->setModel(model);
+
+ // TODO: observer update
+
+ QItemSelectionModel *s = view->selectionModel();
+ QObject::connect(
+ s,
+ SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
+ model,
+ SLOT(selectionChanged(const QItemSelection &, const QItemSelection &)));
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(view, true);
+ return view;
+}
+
+UIWIDGET ui_listview(UiObject *obj, UiList *list, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
+ UiListPtr *listptr = (UiListPtr*)ucx_mempool_malloc(obj->ctx->mempool, sizeof(UiListPtr));
+ listptr->list = list;
+ return ui_listview_var(obj, listptr, getvalue, f, udata);
+}
+
+UIWIDGET ui_listview_nv(UiObject *obj, char *varname, ui_model_getvalue_f getvalue, ui_callback f, void *udata) {
+ UiVar *var = uic_connect_var(obj->ctx, varname, UI_VAR_LIST);
+ if(var) {
+ UiListVar *value = (UiListVar*)var->value;
+ return ui_listview_var(obj, value->listptr, getvalue, f, udata);
+ } else {
+ // TODO: error
+ }
+ return NULL;
+}
+
+
+UIWIDGET ui_table_var(UiObject *obj, UiListPtr *list, UiModelInfo *modelinfo) {
+ QTreeView *view = new QTreeView();
+ TableModel *model = new TableModel(obj, view, list, modelinfo);
+ view->setModel(model);
+
+ view->setItemsExpandable(false);
+ view->setRootIsDecorated(false);
+
+ // TODO: observer update
+ UiTableView *u = new UiTableView();
+ u->widget = view;
+ u->model = model;
+ list->list->observers = ui_add_observer(
+ list->list->observers,
+ (ui_callback)ui_table_update,
+ u);
+
+ view->setSelectionMode(QAbstractItemView::ExtendedSelection);
+ QItemSelectionModel *s = view->selectionModel();
+ QObject::connect(
+ s,
+ SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
+ model,
+ SLOT(selectionChanged(const QItemSelection &, const QItemSelection &)));
+ QObject::connect(
+ view,
+ SIGNAL(doubleClicked(const QModelIndex &)),
+ model,
+ SLOT(activate(const QModelIndex &)));
+
+
+ UiContainer *ct = uic_get_current_container(obj);
+ ct->add(view, true);
+ return view;
+}
+
+void ui_table_update(UiEvent *event, UiTableView *view) {
+ // TODO
+ printf("update\n");
+
+ //view->model->update();
+ view->widget->setModel(NULL);
+ view->widget->setModel(view->model);
+}
+
+UIWIDGET ui_table(UiObject *obj, UiList *list, UiModelInfo *modelinfo) {
+ UiListPtr *listptr = (UiListPtr*)ucx_mempool_malloc(obj->ctx->mempool, sizeof(UiListPtr));
+ listptr->list = list;
+ return ui_table_var(obj, listptr, modelinfo);
+}
+
+UIWIDGET ui_table_nv(UiObject *obj, char *varname, UiModelInfo *modelinfo) {
+ UiVar *var = uic_connect_var(obj->ctx, varname, UI_VAR_LIST);
+ if(var) {
+ UiListVar *value = (UiListVar*)var->value;
+ return ui_table_var(obj, value->listptr, modelinfo);
+ } else {
+ // TODO: error
+ }
+ return NULL;
+}
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TREE_H
+#define TREE_H
+
+#include "../ui/tree.h"
+#include "model.h"
+
+#include <QTableView>
+
+class UiTableView {
+public:
+ QTreeView *widget;
+ TableModel *model;
+};
+
+extern "C" void ui_table_update(UiEvent *event, UiTableView *view);
+
+#endif /* TREE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <ucx/mempool.h>
+#include "../common/context.h"
+
+#include "window.h"
+#include "menu.h"
+#include "toolbar.h"
+#include "container.h"
+
+#include <QVBoxLayout>
+#include <QFileDialog>
+
+static UiObject* create_window(char *title, void *window_data, bool simple) {
+ UcxMempool *mp = ucx_mempool_new(256);
+ UiObject *obj = (UiObject*)ucx_mempool_calloc(mp, 1, sizeof(UiObject));
+ obj->ctx = uic_context(obj, mp);
+ obj->window = window_data;
+ obj->next = NULL;
+
+ QMainWindow *window = new QMainWindow();
+ obj->widget = window;
+
+ if(!simple) {
+ ui_add_menus(obj, window);
+ QToolBar *toolbar = ui_create_toolbar(obj);
+ window->addToolBar(Qt::TopToolBarArea, toolbar);
+ }
+
+ QBoxLayout *box = new QVBoxLayout();
+ QWidget *boxWidget = new QWidget();
+ boxWidget->setLayout(box);
+ window->setCentralWidget(boxWidget);
+ obj->container = new UiBoxContainer(box);
+
+ obj->widget = window;
+ return obj;
+}
+
+UiObject* ui_window(char *title, void *window_data) {
+ return create_window(title, window_data, FALSE);
+}
+
+UiObject* ui_simplewindow(char *title, void *window_data) {
+ return create_window(title, window_data, TRUE);
+}
+
+
+char* ui_openfiledialog(UiObject *obj) {
+ QString fileName = QFileDialog::getOpenFileName(obj->widget);
+ if(fileName.size() > 0) {
+ QByteArray array = fileName.toLocal8Bit();
+ const char *cstr = array.constData();
+ return strdup(cstr);
+ } else {
+ return NULL;
+ }
+}
+
+char* ui_savefiledialog(UiObject *obj) {
+ QString fileName = QFileDialog::getSaveFileName(obj->widget);
+ if(fileName.size() > 0) {
+ QByteArray array = fileName.toLocal8Bit();
+ const char *cstr = array.constData();
+ return strdup(cstr);
+ } else {
+ return NULL;
+ }
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef WINDOW_H
+#define WINDOW_H
+
+#include "../ui/window.h"
+
+#include <QMainWindow>
+
+
+
+#endif /* WINDOW_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_BUTTON_H
+#define UI_BUTTON_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiButtonArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ const char *name;
+ const char *style_class;
+
+ const char* label;
+ const char* stockid;
+ const char* icon;
+ UiLabelType labeltype;
+ ui_callback onclick;
+ void* onclickdata;
+
+ const int* groups;
+} UiButtonArgs;
+
+typedef struct UiToggleArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ const char *name;
+ const char *style_class;
+
+ const char* label;
+ const char* stockid;
+ const char* icon;
+ UiLabelType labeltype;
+ UiInteger* value;
+ const char* varname;
+ ui_callback onchange;
+ void* onchangedata;
+ int enable_group;
+
+ const int* groups;
+} UiToggleArgs;
+
+#define ui_button(obj, ...) ui_button_create(obj, (UiButtonArgs){ __VA_ARGS__ } )
+#define ui_togglebutton(obj, ...) ui_togglebutton_create(obj, (UiToggleArgs){ __VA_ARGS__ } )
+#define ui_checkbox(obj, ...) ui_checkbox_create(obj, (UiToggleArgs){ __VA_ARGS__ } )
+#define ui_switch(obj, ...) ui_switch_create(obj, (UiToggleArgs){ __VA_ARGS__ } )
+#define ui_radiobutton(obj, ...) ui_radiobutton_create(obj, (UiToggleArgs){ __VA_ARGS__ } )
+
+UIEXPORT UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args);
+UIEXPORT UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args);
+UIEXPORT UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args);
+UIEXPORT UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args);
+UIEXPORT UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_BUTTON_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_CONTAINER_H
+#define UI_CONTAINER_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum UiSubContainerType {
+ UI_CONTAINER_VBOX = 0,
+ UI_CONTAINER_HBOX,
+ UI_CONTAINER_GRID,
+ UI_CONTAINER_NO_SUB
+} UiSubContainerType;
+
+typedef enum UiTabViewType {
+ UI_TABVIEW_DEFAULT = 0,
+ UI_TABVIEW_DOC,
+ UI_TABVIEW_NAVIGATION_SIDE,
+ UI_TABVIEW_NAVIGATION_TOP,
+ UI_TABVIEW_NAVIGATION_TOP2,
+ UI_TABVIEW_INVISIBLE
+} UiTabViewType;
+
+typedef enum UiHeaderbarAlternative {
+ UI_HEADERBAR_ALTERNATIVE_DEFAULT = 0,
+ UI_HEADERBAR_ALTERNATIVE_TOOLBAR,
+ UI_HEADERBAR_ALTERNATIVE_BOX
+} UiHeaderbarAlternative;
+
+typedef struct UiContainerArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ const char *name;
+ const char *style_class;
+
+ int margin;
+ int spacing;
+ int columnspacing;
+ int rowspacing;
+} UiContainerArgs;
+
+typedef struct UiFrameArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ const char *name;
+ const char *style_class;
+
+ UiSubContainerType subcontainer;
+
+ int margin;
+ int spacing;
+ int columnspacing;
+ int rowspacing;
+
+ const char* label;
+ UiBool isexpanded;
+} UiFrameArgs;
+
+typedef struct UiTabViewArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ const char *name;
+ const char *style_class;
+
+ UiTabViewType tabview;
+
+ UiSubContainerType subcontainer;
+
+ UiInteger *value;
+ const char* varname;
+
+ int margin;
+ int spacing;
+ int columnspacing;
+ int rowspacing;
+
+ const char* label;
+ UiBool isexpanded;
+} UiTabViewArgs;
+
+typedef struct UiHeaderbarArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ const char *name;
+ const char *style_class;
+
+ UiBool showtitle;
+ UiBool showwindowbuttons;
+
+ UiHeaderbarAlternative alternative;
+ int alt_spacing;
+} UiHeaderbarArgs;
+
+
+
+#define UI_CTN(obj, ctn) for(ctn;ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_vbox(obj, ...) for(ui_vbox_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_hbox(obj, ...) for(ui_hbox_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_grid(obj, ...) for(ui_grid_create(obj, (UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_frame(obj, ...) for(ui_frame_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_expander(obj, ...) for(ui_expander_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_scrolledwindow(obj, ...) for(ui_scrolledwindow_create(obj, (UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_tabview(obj, ...) for(ui_tabview_create(obj, (UiTabViewArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_headerbar(obj, ...) for(ui_headerbar_create(obj, (UiHeaderbarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_vbox0(obj) for(ui_vbox_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_hbox0(obj) for(ui_hbox_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_grid0(obj) for(ui_grid_create(obj, (UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_frame0(obj) for(ui_frame_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_expander0(obj) for(ui_expande_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_scrolledwindow0(obj) for(ui_scrolledwindow_create(obj, (UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_tabview0(obj) for(ui_tabview_create(obj, (UiTabViewArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_headerbar0(obj) for(ui_headerbar_create(obj, (UiHeaderbarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_tab(obj, label) for(ui_tab_create(obj, label);ui_container_finish(obj);ui_container_begin_close(obj))
+
+#define ui_headerbar_start(obj) for(ui_headerbar_start_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_headerbar_center(obj) for(ui_headerbar_center_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+#define ui_headerbar_end(obj) for(ui_headerbar_end_create(obj);ui_container_finish(obj);ui_container_begin_close(obj))
+
+UIEXPORT void ui_end(UiObject *obj);
+
+UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args);
+UIEXPORT UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args);
+UIEXPORT UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args);
+UIEXPORT UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs args);
+UIEXPORT UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs args);
+UIEXPORT UIWIDGET ui_scrolledwindow_create(UiObject *obj, UiFrameArgs args);
+
+UIEXPORT UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs args);
+UIEXPORT void ui_tab_create(UiObject *obj, const char* title);
+UIEXPORT void ui_tabview_select(UIWIDGET tabview, int tab);
+UIEXPORT void ui_tabview_remove(UIWIDGET tabview, int tab);
+UIEXPORT UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index);
+
+UIEXPORT UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args);
+UIEXPORT void ui_headerbar_start_create(UiObject *obj);
+UIEXPORT void ui_headerbar_center_create(UiObject *obj);
+UIEXPORT void ui_headerbar_end_create(UiObject *obj);
+
+
+UIEXPORT UIWIDGET ui_scrolledwindow_deprecated(UiObject *obj); // TODO
+
+UIEXPORT UIWIDGET ui_sidebar(UiObject *obj); // TODO
+
+UIEXPORT UIWIDGET ui_hsplitpane(UiObject *obj, int max); // TODO
+UIEXPORT UIWIDGET ui_vsplitpane(UiObject *obj, int max); // TODO
+
+
+// box container layout functions
+UIEXPORT void ui_layout_fill(UiObject *obj, UiBool fill);
+// grid container layout functions
+UIEXPORT void ui_layout_hexpand(UiObject *obj, UiBool expand);
+UIEXPORT void ui_layout_vexpand(UiObject *obj, UiBool expand);
+UIEXPORT void ui_layout_hfill(UiObject *obj, UiBool fill);
+UIEXPORT void ui_layout_vfill(UiObject *obj, UiBool fill);
+UIEXPORT void ui_layout_width(UiObject *obj, int width);
+UIEXPORT void ui_layout_height(UiObject* obj, int width);
+UIEXPORT void ui_layout_colspan(UiObject *obj, int cols);
+UIEXPORT void ui_layout_rowspan(UiObject* obj, int rows);
+UIEXPORT void ui_newline(UiObject *obj);
+
+// TODO
+UIEXPORT UiTabbedPane* ui_tabbed_document_view(UiObject *obj);
+UIEXPORT UiObject* ui_document_tab(UiTabbedPane *view);
+
+
+/* used for macro */
+UIEXPORT void ui_container_begin_close(UiObject *obj);
+UIEXPORT int ui_container_finish(UiObject *obj);
+
+#define UI_APPLY_LAYOUT1(obj, args) \
+ if(args.fill != UI_DEFAULT) ui_layout_fill(obj, args.fill == UI_ON ? 1 : 0 ); \
+ if(args.hexpand) ui_layout_hexpand(obj, 1); \
+ if(args.vexpand) ui_layout_vexpand(obj, 1); \
+ if(args.hfill) ui_layout_hfill(obj, 1); \
+ if(args.vfill) ui_layout_vfill(obj, 1); \
+ if(args.colspan > 0) ui_layout_colspan(obj, args.colspan); \
+ if(args.rowspan > 0) ui_layout_rowspan(obj, args.rowspan); \
+ /*force caller to add ';'*/(void)0
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_CONTAINER_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * display widgets without user input
+ */
+
+#ifndef UI_DISPLAY_H
+#define UI_DISPLAY_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum UiAlignment {
+ UI_ALIGN_DEFAULT = 0,
+ UI_ALIGN_LEFT,
+ UI_ALIGN_RIGHT,
+ UI_ALIGN_CENTER
+};
+
+typedef enum UiAlignment UiAlignment;
+
+enum UiLabelStyle {
+ UI_LABEL_STYLE_DEFAULT = 0,
+ UI_LABEL_STYLE_TITLE,
+ UI_LABEL_STYLE_SUBTITLE,
+ UI_LABEL_STYLE_DIM
+};
+
+typedef enum UiLabelStyle UiLabelStyle;
+
+typedef struct UiLabelArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+
+ const char* label;
+ UiAlignment align;
+ UiLabelStyle style;
+ UiString* value;
+ const char* varname;
+} UiLabelArgs;
+
+typedef struct UiProgressbarArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ int width;
+
+ double min;
+ double max;
+ UiDouble* value;
+ const char* varname;
+} UiProgressbarArgs;
+
+typedef struct UiProgressbarSpinnerArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+
+ UiInteger* value;
+ const char* varname;
+} UiProgressbarSpinnerArgs;
+
+/* label widgets */
+
+#define ui_label(obj, ...) ui_label_create(obj, (UiLabelArgs) { __VA_ARGS__ })
+#define ui_llabel(obj, ...) ui_llabel_create(obj, (UiLabelArgs) { __VA_ARGS__ })
+#define ui_rlabel(obj, ...) ui_rlabel_create(obj, (UiLabelArgs) { __VA_ARGS__ })
+
+
+UIEXPORT UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args);
+UIEXPORT UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args);
+UIEXPORT UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args);
+
+UIWIDGET ui_space_deprecated(UiObject *obj);
+UIWIDGET ui_separator_deprecated(UiObject *obj);
+
+/* progress bar/spinner */
+
+#define ui_progressbar(obj, ...) ui_progressbar_create(obj, (UiProgressbarArgs) { __VA_ARGS__ } )
+#define ui_progressspinner(obj, ...) ui_progressspinner_create(obj, (UiProgressbarSpinnerArgs) { __VA_ARGS__ } )
+
+UIEXPORT UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs args);
+UIEXPORT UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_DISPLAY_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_DND_H
+#define UI_DND_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UI_DND_FILE_TARGET "XdndDirectSave0"
+
+UIEXPORT void ui_selection_settext(UiDnD *sel, char *str, int len);
+UIEXPORT void ui_selection_seturis(UiDnD *sel, char **uris, int nelm);
+
+UIEXPORT char* ui_selection_gettext(UiDnD *sel);
+UIEXPORT UiFileList ui_selection_geturis(UiDnD *sel);
+
+UIEXPORT UiDnDAction ui_dnd_result(UiDnD *dnd);
+UIEXPORT UiBool ui_dnd_need_delete(UiDnD *dnd);
+
+UIEXPORT void ui_dnd_accept(UiDnD *dnd, UiBool accept);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_DND_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_ENTRY_H
+#define UI_ENTRY_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+typedef struct UiSpinnerArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ const char *name;
+ const char *style_class;
+
+ double step;
+ int digits;
+ UiInteger *intvalue;
+ UiDouble* doublevalue;
+ UiRange *rangevalue;
+ const char* varname;
+ ui_callback onchange;
+ void* onchangedata;
+
+ const int *groups;
+} UiSpinnerArgs;
+
+
+
+UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs args);
+
+#define ui_spinner(obj, ...) ui_spinner_create(obj, (UiSpinnerArgs){ __VA_ARGS__ } )
+
+void ui_spinner_setrange(UIWIDGET spinner, double min, double max);
+void ui_spinner_setdigits(UIWIDGET spinner, int digits);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_ENTRY_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_GRAPHICS_H
+#define UI_GRAPHICS_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiGraphics UiGraphics;
+typedef struct UiTextLayout UiTextLayout;
+
+typedef void(*ui_drawfunc)(UiEvent*, UiGraphics*, void*);
+
+struct UiGraphics {
+ int width;
+ int height;
+};
+
+UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata);
+void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u);
+void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height);
+void ui_drawingarea_redraw(UIWIDGET drawingarea);
+
+// text layout
+UiTextLayout* ui_text(UiGraphics *g);
+void ui_text_free(UiTextLayout *text);
+void ui_text_setstring(UiTextLayout *layout, char *str);
+void ui_text_setstringl(UiTextLayout *layout, char *str, int len);
+void ui_text_setfont(UiTextLayout *layout, char *font, int size);
+void ui_text_getsize(UiTextLayout *layout, int *width, int *height);
+void ui_text_setwidth(UiTextLayout *layout, int width);
+
+// drawing functions
+void ui_graphics_color(UiGraphics *g, int red, int green, int blue);
+void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2);
+void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill);
+void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_GRAPHICS_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_ICONS_H
+#define UI_ICONS_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef UI_GTK
+
+#define UI_ICON_HOME "go-home"
+#define UI_ICON_NEW_WINDOW "list-add"
+#define UI_ICON_REFRESH "view-refresh"
+#define UI_ICON_NEW_FOLDER "folder-new"
+#define UI_ICON_ADD "document-new"
+#define UI_ICON_UPLOAD "document-open"
+#define UI_ICON_SAVE_LOCAL "document-save-as"
+#define UI_ICON_DELETE "edit-delete"
+#define UI_ICON_DOCK_LEFT ""
+#define UI_ICON_DOCK_RIGHT ""
+#define UI_ICON_GO_BACK "go-previous"
+#define UI_ICON_GO_FORWARD "go-next"
+
+#endif /* UI_GTK */
+
+
+
+#ifdef UI_WINUI
+
+#define UI_ICON_HOME "Home"
+#define UI_ICON_NEW_WINDOW "NewWindow"
+#define UI_ICON_REFRESH "Refresh"
+#define UI_ICON_NEW_FOLDER "NewFolder"
+#define UI_ICON_ADD "Add"
+#define UI_ICON_UPLOAD "Upload"
+#define UI_ICON_SAVE_LOCAL "SaveLocal"
+#define UI_ICON_DELETE "Delete"
+#define UI_ICON_DOCK_LEFT "DockLeft"
+#define UI_ICON_DOCK_RIGHT "DockRight"
+#define UI_ICON_GO_BACK "Back"
+#define UI_ICON_GO_FORWARD "Forward"
+
+#endif /* UI_WINUI */
+
+
+UIEXPORT UiIcon* ui_icon(const char* name, size_t size);
+UIEXPORT UiIcon* ui_icon_unscaled(const char *name, int size);
+UIEXPORT UiIcon* ui_imageicon(const char* file);
+UIEXPORT void ui_icon_free(UiIcon* icon);
+
+UIEXPORT UiIcon* ui_foldericon(size_t size);
+UIEXPORT UiIcon* ui_fileicon(size_t size);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_ICONS_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_IMAGE_H
+#define UI_IMAGE_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UI_IMAGE_OBJECT_TYPE "image"
+
+typedef struct UiImageViewerArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ const char *name;
+ const char *style_class;
+
+ UiBool scrollarea;
+ UiBool autoscale;
+ UiGeneric *value;
+ const char *varname;
+ UiMenuBuilder *contextmenu;
+} UiImageViewerArgs;
+
+#define ui_imageviewer(obj, ...) ui_imageviewer_create(obj, (UiImageViewerArgs){ __VA_ARGS__ } )
+
+UIEXPORT UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args);
+
+UIEXPORT int ui_image_load_file(UiGeneric *obj, const char *path);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_IMAGE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_MENU_H
+#define UI_MENU_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+typedef struct UiMenuItemArgs {
+ const char* label;
+ const char* stockid;
+ const char* icon;
+
+ ui_callback onclick;
+ void* onclickdata;
+
+ const int* groups;
+} UiMenuItemArgs;
+
+typedef struct UiMenuToggleItemArgs {
+ const char* label;
+ const char* stockid;
+ const char* icon;
+
+ const char* varname;
+ ui_callback onchange;
+ void* onchangedata;
+
+ const int* groups;
+} UiMenuToggleItemArgs;
+
+typedef struct UiMenuItemListArgs {
+ const char* varname;
+ ui_getvaluefunc getvalue;
+ ui_callback onselect;
+ void* onselectdata;
+} UiMenuItemListArgs;
+
+#define ui_menu(label) for(ui_menu_create(label);ui_menu_is_open();ui_menu_close())
+
+#define ui_menuitem(...) ui_menuitem_create((UiMenuItemArgs){ __VA_ARGS__ })
+#define ui_menu_toggleitem(...) ui_menu_toggleitem_create((UiMenuToggleItemArgs){ __VA_ARGS__ })
+#define ui_menu_radioitem(...) ui_menu_radioitem_create((UiMenuToggleItemArgs){ __VA_ARGS__ })
+#define ui_menu_itemlist(...) ui_menu_itemlist_create((UiMenuItemListArgs) { __VA_ARGS__ } )
+#define ui_menu_togglelist(...) ui_menu_itemlist_create((UiMenuItemListArgs) { __VA_ARGS} )
+#define ui_menu_radiolist(...) ui_menu_itemlist_create((UiMenuItemListArgs) { __VA_ARGS} )
+
+UIEXPORT void ui_menu_create(const char* label);
+UIEXPORT void ui_menuitem_create(UiMenuItemArgs args);
+UIEXPORT void ui_menu_toggleitem_create(UiMenuToggleItemArgs args);
+UIEXPORT void ui_menu_radioitem_create(UiMenuToggleItemArgs args);
+
+UIEXPORT void ui_menuseparator();
+
+UIEXPORT void ui_menu_itemlist_create(UiMenuItemListArgs args);
+UIEXPORT void ui_menu_toggleitemlist_create(UiMenuItemListArgs args);
+UIEXPORT void ui_menu_radioitemlist_create(UiMenuItemListArgs args);
+
+UIEXPORT void ui_menu_end(void); // TODO: private
+
+/*
+ * widget menu functions
+ */
+
+#define ui_contextmenu(builder) for(ui_contextmenu_builder(builder);ui_menu_is_open();ui_menu_close())
+
+UIEXPORT void ui_contextmenu_builder(UiMenuBuilder **out_builder);
+UIEXPORT void ui_menubuilder_free(UiMenuBuilder *builder);
+UIEXPORT UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget);
+UIEXPORT void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y);
+
+// used for macro
+UIEXPORT void ui_menu_close(void);
+UIEXPORT int ui_menu_is_open(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_MENU_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_PROPERTIES_H
+#define UI_PROPERTIES_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char* ui_get_property(const char *name);
+void ui_set_property(const char *name, const char *value);
+const char* ui_set_default_property(const char *name, const char *value);
+
+int ui_properties_store(void);
+
+void ui_locales_dir(char *path);
+void ui_pixmaps_dir(char *path);
+
+void ui_load_lang(char *locale);
+void ui_load_lang_def(char *locale, char *default_locale);
+
+char* uistr(char *name);
+char* uistr_n(char *name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_PROPERTIES_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_RANGE_H
+#define UI_RANGE_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata);
+UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_RANGE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_STOCK_H
+#define UI_STOCK_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// motif stock ids
+#if UI_MOTIF || UI_COCOA || UI_QT4 || UI_QT5
+
+#define UI_STOCK_NEW "ui.stock.New"
+#define UI_STOCK_OPEN "ui.stock.Open"
+#define UI_STOCK_SAVE "ui.stock.Save"
+#define UI_STOCK_SAVE_AS "ui.stock.SaveAs"
+#define UI_STOCK_REVERT_TO_SAVED "ui.stock.RevertToSaved"
+#define UI_STOCK_GO_BACK "ui.stock.GoBack"
+#define UI_STOCK_GO_FORWARD "ui.stock.GoForward"
+#define UI_STOCK_ADD "ui.stock.Add"
+#define UI_STOCK_CLOSE "ui.stock.Close"
+
+#define UI_STOCK_UNDO "ui.stock.Undo"
+#define UI_STOCK_REDO "ui.stock.Redo"
+#define UI_STOCK_CUT "ui.stock.Cut"
+#define UI_STOCK_COPY "ui.stock.Copy"
+#define UI_STOCK_PASTE "ui.stock.Paste"
+#define UI_STOCK_DELETE "ui.stock.Delete"
+
+#endif
+
+#if UI_GTK2 || UI_GTK3
+
+#define UI_STOCK_NEW GTK_STOCK_NEW
+#define UI_STOCK_OPEN GTK_STOCK_OPEN
+#define UI_STOCK_SAVE GTK_STOCK_SAVE
+#define UI_STOCK_SAVE_AS GTK_STOCK_SAVE_AS
+#define UI_STOCK_REVERT_TO_SAVED GTK_STOCK_REVERT_TO_SAVED
+#define UI_STOCK_UNDO GTK_STOCK_UNDO
+#define UI_STOCK_REDO GTK_STOCK_REDO
+#define UI_STOCK_GO_BACK GTK_STOCK_GO_BACK
+#define UI_STOCK_GO_FORWARD GTK_STOCK_GO_FORWARD
+#define UI_STOCK_ADD GTK_STOCK_ADD
+#define UI_STOCK_CLOSE GTK_STOCK_CLOSE
+
+#define UI_STOCK_UNDO GTK_STOCK_UNDO
+#define UI_STOCK_REDO GTK_STOCK_REDO
+#define UI_STOCK_CUT GTK_STOCK_CUT
+#define UI_STOCK_COPY GTK_STOCK_COPY
+#define UI_STOCK_PASTE GTK_STOCK_PASTE
+#define UI_STOCK_DELETE GTK_STOCK_DELETE
+
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_STOCK_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_TEXT_H
+#define UI_TEXT_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiTextAreaArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ int width;
+ const char *name;
+ const char *style_class;
+
+ UiText *value;
+ const char *varname;
+ ui_callback onchange;
+ void *onchangedata;
+
+ const int *groups;
+} UiTextAreaArgs;
+
+typedef struct UiTextFieldArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ int width;
+ const char *name;
+ const char *style_class;
+
+ UiString* value;
+ const char *varname;
+ ui_callback onchange;
+ void *onchangedata;
+
+ const int *groups;
+} UiTextFieldArgs;
+
+typedef struct UiPathElmRet {
+ char *name;
+ size_t name_len;
+ char *path;
+ size_t path_len;
+} UiPathElm;
+
+typedef UiPathElm*(*ui_pathelm_func)(const char *full_path, size_t len, size_t *ret_nelm, void *data);
+
+
+
+typedef struct UiPathTextFieldArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ const char *name;
+ const char *style_class;
+
+ UiString *value;
+ const char *varname;
+
+ ui_pathelm_func getpathelm;
+ void *getpathelmdata;
+
+ ui_callback onactivate;
+ void *onactivatedata;
+
+ ui_callback ondragstart;
+ void *ondragstartdata;
+ ui_callback ondragcomplete;
+ void *ondragcompletedata;
+ ui_callback ondrop;
+ void *ondropsdata;
+} UiPathTextFieldArgs;
+
+#define ui_textarea(obj, ...) ui_textarea_create(obj, (UiTextAreaArgs) { __VA_ARGS__ })
+
+UIEXPORT UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args);
+
+UIEXPORT UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea);
+
+UIEXPORT void ui_text_undo(UiText *value);
+UIEXPORT void ui_text_redo(UiText *value);
+
+#define ui_textfield(obj, ...) ui_textfield_create(obj, (UiTextFieldArgs) { __VA_ARGS__ })
+#define ui_frameless_textfield(obj, ...) ui_frameless_field_create(obj, (UiTextFieldArgs) { __VA_ARGS__ })
+#define ui_passwordfield(obj, ...) ui_passwordfield_create(obj, (UiTextFieldArgs) { __VA_ARGS__ })
+#define ui_path_textfield(obj, ...) ui_path_textfield_create(obj, (UiPathTextFieldArgs) { __VA_ARGS__ } )
+
+UIEXPORT UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args);
+UIEXPORT UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args);
+UIEXPORT UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args);
+
+UIEXPORT UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_TEXT_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_TOOLBAR_H
+#define UI_TOOLBAR_H
+
+#include "toolkit.h"
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiToolbarItemArgs {
+ const char* label;
+ const char* stockid;
+ const char* icon;
+
+ ui_callback onclick;
+ void* onclickdata;
+
+ const int *groups;
+} UiToolbarItemArgs;
+
+typedef struct UiToolbarToggleItemArgs {
+ const char* label;
+ const char* stockid;
+ const char* icon;
+
+ const char* varname;
+ ui_callback onchange;
+ void* onchangedata;
+
+ const int *groups;
+} UiToolbarToggleItemArgs;
+
+typedef struct UiToolbarMenuArgs {
+ const char* label;
+ const char* stockid;
+ const char* icon;
+} UiToolbarMenuArgs;
+
+enum UiToolbarPos {
+ UI_TOOLBAR_LEFT = 0,
+ UI_TOOLBAR_CENTER,
+ UI_TOOLBAR_RIGHT
+};
+
+#define ui_toolbar_item(name, ...) ui_toolbar_item_create(name, (UiToolbarItemArgs){ __VA_ARGS__ } )
+#define ui_toolbar_toggleitem(name, ...) ui_toolbar_toggleitem_create(name, (UiToolbarToggleItemArgs){ __VA_ARGS__ } )
+
+#define ui_toolbar_menu(name, ...) for(ui_toolbar_menu_create(name, (UiToolbarMenuArgs){ __VA_ARGS__ });ui_menu_is_open();ui_menu_close())
+#define ui_toolbar_appmenu() for(ui_toolbar_menu_create(NULL, (UiToolbarMenuArgs){ 0 });ui_menu_is_open();ui_menu_close())
+
+
+UIEXPORT void ui_toolbar_item_create(const char* name, UiToolbarItemArgs args);
+UIEXPORT void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs args);
+UIEXPORT void ui_toolbar_menu_create(const char* name, UiToolbarMenuArgs args);
+
+UIEXPORT void ui_toolbar_add_default(const char *name, enum UiToolbarPos pos);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_TOOLBAR_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_TOOLKIT_H
+#define UI_TOOLKIT_H
+
+#include <inttypes.h>
+
+#ifdef UI_COCOA
+
+#ifdef __OBJC__
+#import <Cocoa/Cocoa.h>
+#define UIWIDGET NSView*
+#define UIMENU NSMenu*
+#else
+typedef void* UIWIDGET;
+typedef void* UIMENU;
+#endif
+
+#elif UI_GTK2 || UI_GTK3 || UI_GTK4
+
+#include <gtk/gtk.h>
+#define UIWIDGET GtkWidget*
+
+#if UI_GTK2 || UI_GTK3
+#define UIMENU GtkMenu*
+#endif
+#ifdef UI_GTK4
+#define UIMENU GtkPopoverMenu*
+#endif
+
+#define UI_GTK
+
+#ifdef UI_LIBADWAITA
+#include <adwaita.h>
+#endif
+
+#elif UI_MOTIF
+
+#include <Xm/XmAll.h>
+#define UIWIDGET Widget
+#define UIMENU Widget
+
+#elif defined(UI_QT4) || defined(UI_QT5)
+#ifdef __cplusplus
+#include <QApplication>
+#include <QWidget>
+#include <QMenu>
+#define UIWIDGET QWidget*
+#define UIMENU QMenu*
+#else /* __cplusplus */
+#define UIWIDGET void*
+#define UIMENU void*
+#endif
+
+#elif UI_WINUI
+
+#define UIEXPORT __declspec(dllexport)
+
+#ifdef __cplusplus
+
+#include <functional>
+#ifndef UI_WINUI_PCH
+#include <Windows.h>
+#undef GetCurrentTime
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.UI.Xaml.Interop.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#endif
+
+class UiWindow {
+public:
+ winrt::Microsoft::UI::Xaml::Window window { nullptr };
+
+ UiWindow(winrt::Microsoft::UI::Xaml::Window& win);
+};
+
+class UiWidget {
+public:
+ winrt::Microsoft::UI::Xaml::UIElement uielement;
+ void* data1 = nullptr;
+ void* data2 = nullptr;
+ void* data3 = nullptr;
+ std::function<void()> Show;
+ UiWidget(winrt::Microsoft::UI::Xaml::UIElement& elm);
+
+};
+
+#define UIWIDGET UiWidget*
+#define UIWINDOW UiWindow*
+#define UIMENU void*
+
+/*
+// winrt::Microsoft::UI::Xaml::UIElement
+#define UIWIDGET void*
+// winrt::Microsoft::UI::Xaml::Window
+#define UIWINDOW void*
+#define UIMENU void*
+*/
+
+#else
+#define UIWIDGET void*
+#define UIWINDOW void*
+#define UIMENU void*
+#endif
+
+
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef UIEXPORT
+#define UIEXPORT
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UI_GROUP_SELECTION 20000
+
+#define UI_GROUPS(...) (const int[]){ __VA_ARGS__, -1 }
+
+/* public types */
+typedef int UiBool;
+
+typedef struct UiObject UiObject;
+typedef struct UiEvent UiEvent;
+typedef struct UiMouseEvent UiMouseEvent;
+typedef struct UiObserver UiObserver;
+
+typedef struct UiInteger UiInteger;
+typedef struct UiDouble UiDouble;
+typedef struct UiString UiString;
+typedef struct UiText UiText;
+typedef struct UiList UiList;
+typedef struct UiRange UiRange;
+typedef struct UiGeneric UiGeneric;
+
+typedef struct UiStr UiStr;
+
+typedef struct UiFileList UiFileList;
+
+typedef struct UiListSelection UiListSelection;
+
+/* begin opaque types */
+typedef struct UiContext UiContext;
+typedef struct UiContainer UiContainer;
+typedef struct UiMenuBuilder UiMenuBuilder;
+
+typedef struct UiIcon UiIcon;
+typedef struct UiImage UiImage;
+
+typedef struct UiDnD UiDnD;
+
+typedef struct UiThreadpool UiThreadpool;
+/* end opaque types */
+
+typedef struct UiTabbedPane UiTabbedPane;
+
+typedef enum UiTri UiTri;
+typedef enum UiLabelType UiLabelType;
+
+typedef enum UiDnDAction UiDnDAction;
+
+enum UiMouseEventType { UI_PRESS = 0, UI_PRESS2 };
+
+enum UiLabelType { UI_LABEL_DEFAULT, UI_LABEL_TEXT, UI_LABEL_ICON, UI_LABEL_TEXT_ICON };
+
+enum UiDnDAction { UI_DND_ACTION_NONE, UI_DND_ACTION_COPY, UI_DND_ACTION_MOVE, UI_DND_ACTION_LINK, UI_DND_ACTION_CUSTOM };
+
+typedef void(*ui_callback)(UiEvent*, void*); /* event, user data */
+
+typedef void*(*ui_getvaluefunc)(void*, int);
+
+typedef int(*ui_threadfunc)(void*);
+
+typedef void(*ui_freefunc)(void*);
+
+typedef void(*ui_enablefunc)(void*, int);
+
+struct UiObject {
+ /*
+ * native widget
+ */
+ UIWIDGET widget;
+
+#ifdef UI_WINUI
+ /*
+ * native window object
+ */
+ UIWINDOW wobj;
+#endif
+
+ /*
+ * user window data
+ */
+ void *window;
+
+ /*
+ * window context
+ */
+ UiContext *ctx;
+
+ /*
+ * container interface
+ */
+ UiContainer *container;
+
+ /*
+ * next container object
+ */
+ UiObject *next;
+
+ /*
+ * obj destroy func
+ */
+ void (*destroy)(UiObject *obj);
+
+ /*
+ * reference counter
+ */
+ unsigned int ref;
+};
+
+struct UiTabbedPane {
+ /*
+ * native widget
+ */
+ UIWIDGET widget;
+
+ /*
+ * current document
+ */
+ void *document;
+
+ /*
+ * parent context
+ */
+ UiContext *ctx;
+};
+
+struct UiEvent {
+ UiObject *obj;
+ void *document;
+ void *window;
+ void *eventdata;
+ int intval;
+};
+
+struct UiMouseEvent {
+ int x;
+ int y;
+ enum UiMouseEventType type;
+ int button;
+};
+
+struct UiObserver {
+ ui_callback callback;
+ void *data;
+ UiObserver *next;
+};
+
+struct UiStr {
+ char *ptr;
+ void (*free)(void *v);
+};
+
+struct UiInteger {
+ int64_t (*get)(UiInteger*);
+ void (*set)(UiInteger*, int64_t);
+ void *obj;
+
+ int64_t value;
+ UiObserver *observers;
+};
+
+struct UiDouble {
+ double (*get)(UiDouble*);
+ void (*set)(UiDouble*, double);
+ void *obj;
+
+ double value;
+ UiObserver *observers;
+};
+
+struct UiString {
+ char* (*get)(UiString*);
+ void (*set)(UiString*, const char*);
+ void *obj;
+
+ UiStr value;
+ UiObserver *observers;
+};
+
+struct UiText {
+ void (*set)(UiText*, const char*);
+ char* (*get)(UiText*);
+ char* (*getsubstr)(UiText*, int, int); /* text, begin, end */
+ void (*insert)(UiText*, int, char*);
+ void (*setposition)(UiText*,int);
+ int (*position)(UiText*);
+ void (*selection)(UiText*, int*, int*); /* text, begin, end */
+ int (*length)(UiText*);
+ void (*remove)(UiText*, int, int); /* text, begin, end */
+ UiStr value;
+ int pos;
+ void *obj;
+ void *undomgr;
+ // TODO: replacefunc, ...
+ UiObserver *observers;
+};
+
+struct UiGeneric {
+ void* (*get)(UiGeneric*);
+ const char* (*get_type)(UiGeneric*);
+ int (*set)(UiGeneric*, void *, const char *type);
+ void *obj;
+
+ void *value;
+ const char *type;
+ UiObserver *observers;
+};
+
+/*
+ * abstract list
+ */
+struct UiList {
+ /* get the first element */
+ void*(*first)(UiList *list);
+ /* get the next element */
+ void*(*next)(UiList *list);
+ /* get the nth element */
+ void*(*get)(UiList *list, int i);
+ /* get the number of elements */
+ int(*count)(UiList *list);
+ /* iterator changes after first() next() and get() */
+ void *iter;
+ /* private - implementation dependent */
+ void *data;
+
+ /* binding functions */
+ void (*update)(UiList *list, int i);
+ UiListSelection (*getselection)(UiList *list);
+ void (*setselection)(UiList *list, UiListSelection selection);
+ /* binding object */
+ void *obj;
+
+ /* list of observers */
+ UiObserver *observers;
+};
+
+
+struct UiListSelection {
+ /*
+ * number of selected items
+ */
+ int count;
+
+ /*
+ * indices of selected rows
+ */
+ int *rows;
+};
+
+struct UiRange {
+ double (*get)(UiRange *range);
+ void (*set)(UiRange *range, double value);
+ void (*setrange)(UiRange *range, double min, double max);
+ void (*setextent)(UiRange *range, double extent);
+ double value;
+ double min;
+ double max;
+ double extent;
+ void *obj;
+ /* list of observers */
+ UiObserver *observers;
+};
+
+enum UiTri {
+ UI_DEFAULT = 0,
+ UI_ON,
+ UI_OFF
+};
+
+struct UiFileList {
+ char **files;
+ size_t nfiles;
+};
+
+typedef struct UiCondVar {
+ void *data;
+ int intdata;
+} UiCondVar;
+
+
+UIEXPORT void ui_init(const char *appname, int argc, char **argv);
+UIEXPORT const char* ui_appname();
+
+UIEXPORT UiContext* ui_global_context(void);
+
+UIEXPORT void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata);
+
+UIEXPORT void ui_context_destroy(UiContext *ctx);
+
+UIEXPORT void ui_object_ref(UiObject *obj);
+UIEXPORT void ui_object_unref(UiObject *obj);
+
+UIEXPORT void ui_onstartup(ui_callback f, void *userdata);
+UIEXPORT void ui_onopen(ui_callback f, void *userdata);
+UIEXPORT void ui_onexit(ui_callback f, void *userdata);
+
+UIEXPORT void ui_main();
+UIEXPORT void ui_show(UiObject *obj);
+UIEXPORT void ui_close(UiObject *obj);
+
+UIEXPORT void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd);
+UIEXPORT void ui_call_mainthread(ui_threadfunc tf, void* td);
+UIEXPORT UiThreadpool* ui_threadpool_create(int nthreads);
+UIEXPORT void ui_threadpool_destroy(UiThreadpool* pool);
+UIEXPORT void ui_threadpool_job(UiThreadpool* pool, UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd);
+
+UIEXPORT void* ui_document_new(size_t size);
+UIEXPORT void ui_document_destroy(void *doc);
+
+UIEXPORT void ui_set_document(UiObject *obj, void *document); // deprecated
+UIEXPORT void ui_detach_document(UiObject *obj); // deprecated
+UIEXPORT void* ui_get_document(UiObject *obj); // deprecated
+UIEXPORT void ui_set_subdocument(void *document, void *sub); // deprecated
+UIEXPORT void ui_detach_subdocument(void *document, void *sub); // deprecated
+UIEXPORT void* ui_get_subdocument(void *document); // deprecated
+
+UIEXPORT UiContext* ui_document_context(void *doc);
+
+UIEXPORT void ui_attach_document(UiContext *ctx, void *document);
+UIEXPORT void ui_detach_document2(UiContext *ctx, void *document);
+
+UIEXPORT void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...);
+
+UIEXPORT void ui_set_group(UiContext *ctx, int group);
+UIEXPORT void ui_unset_group(UiContext *ctx, int group);
+UIEXPORT int* ui_active_groups(UiContext *ctx, int *ngroups);
+
+UIEXPORT void* ui_allocator(UiContext *ctx);
+UIEXPORT void* ui_cx_mempool(UiContext *ctx);
+
+UIEXPORT void* ui_malloc(UiContext *ctx, size_t size);
+UIEXPORT void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize);
+UIEXPORT void ui_free(UiContext *ctx, void *ptr);
+UIEXPORT void* ui_realloc(UiContext *ctx, void *ptr, size_t size);
+UIEXPORT char* ui_strdup(UiContext *ctx, const char *str);
+
+// types
+
+UIEXPORT UiInteger* ui_int_new(UiContext *ctx, char *name);
+UIEXPORT UiDouble* ui_double_new(UiContext *ctx, char *name);
+UIEXPORT UiString* ui_string_new(UiContext *ctx, char *name);
+UIEXPORT UiText* ui_text_new(UiContext *ctx, char *name);
+UIEXPORT UiRange* ui_range_new(UiContext *ctx, char *name);
+UIEXPORT UiGeneric* ui_generic_new(UiContext *ctx, char *name);
+
+#define ui_get(v) _Generic(v, \
+ UiInteger*: ui_int_get, \
+ UiDouble*: ui_double_get, \
+ UiString*: ui_string_get, \
+ UiText*:ui_text_get) (v)
+
+#define ui_set(v, n) _Generic(v, \
+ UiInteger*: ui_int_set, \
+ UiDouble*: ui_double_set, \
+ UiString*: ui_string_set, \
+ UiText*:ui_text_set) (v, n)
+
+UIEXPORT void ui_int_set(UiInteger *i, int64_t value);
+UIEXPORT int64_t ui_int_get(UiInteger *i);
+UIEXPORT void ui_double_set(UiDouble *d, double value);
+UIEXPORT double ui_double_get(UiDouble *d);
+UIEXPORT void ui_string_set(UiString *s, const char *value);
+UIEXPORT char* ui_string_get(UiString *s);
+UIEXPORT void ui_text_set(UiText *s, const char* value);
+UIEXPORT char* ui_text_get(UiText *s);
+
+UIEXPORT void ui_var_set_int(UiContext *ctx, const char *name, int64_t value);
+UIEXPORT int64_t ui_var_get_int(UiContext *ctx, const char *name);
+UIEXPORT void ui_var_set_double(UiContext *ctx, const char *name, double value);
+UIEXPORT double ui_var_get_double(UiContext *ctx, const char *name);
+UIEXPORT void ui_var_set_string(UiContext *ctx, const char *name, char *value);
+UIEXPORT char* ui_var_get_string(UiContext *ctx, const char *name);
+
+UIEXPORT UiObserver* ui_observer_new(ui_callback f, void *data);
+UIEXPORT UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer);
+UIEXPORT UiObserver* ui_add_observer(UiObserver *list, ui_callback f, void *data);
+UIEXPORT void ui_notify(UiObserver *observer, void *data);
+UIEXPORT void ui_notify_except(UiObserver *observer, UiObserver *exc, void *data);
+UIEXPORT void ui_notify_evt(UiObserver *observer, UiEvent *event);
+
+
+UIEXPORT UiList* ui_list_new(UiContext *ctx, char *name);
+UIEXPORT void* ui_list_first(UiList *list);
+UIEXPORT void* ui_list_next(UiList *list);
+UIEXPORT void* ui_list_get(UiList *list, int i);
+UIEXPORT int ui_list_count(UiList *list);
+UIEXPORT void ui_list_append(UiList *list, void *data);
+UIEXPORT void ui_list_prepend(UiList *list, void *data);
+UIEXPORT void ui_list_remove(UiList *list, int i);
+UIEXPORT void ui_list_clear(UiList *list);
+UIEXPORT void ui_list_update(UiList *list);
+UIEXPORT void ui_list_addobsv(UiList *list, ui_callback f, void *data);
+UIEXPORT void ui_list_notify(UiList *list);
+
+UIEXPORT UiListSelection ui_list_getselection(UiList *list);
+UIEXPORT void ui_list_setselection(UiList *list, int index);
+
+UIEXPORT UiFileList ui_filelist_copy(UiFileList list);
+UIEXPORT void ui_filelist_free(UiFileList list);
+
+UIEXPORT void ui_clipboard_set(char *str);
+UIEXPORT char* ui_clipboard_get();
+
+UIEXPORT void ui_add_image(char *imgname, char *filename); // TODO: remove?
+
+// general widget functions
+UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled);
+UIEXPORT void ui_set_show_all(UIWIDGET widget, int value);
+UIEXPORT void ui_set_visible(UIWIDGET widget, int visible);
+
+
+
+UIEXPORT void ui_listselection_free(UiListSelection selection);
+
+
+UIEXPORT UiStr ui_str(char *cstr);
+UIEXPORT UiStr ui_str_free(char *str, void (*free)(void *v));
+
+
+UIEXPORT char* ui_getappdir(void);
+UIEXPORT char* ui_configfile(char *name);
+
+UIEXPORT UiCondVar* ui_condvar_create(void);
+UIEXPORT void ui_condvar_wait(UiCondVar *var);
+UIEXPORT void ui_condvar_signal(UiCondVar *var, void *data, int intdata);
+UIEXPORT void ui_condvar_destroy(UiCondVar *var);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_TOOLKIT_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_TREE_H
+#define UI_TREE_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiModel UiModel;
+typedef struct UiListCallbacks UiListCallbacks;
+typedef struct UiListDnd UiListDnd;
+
+typedef struct UiListArgs UiListArgs;
+
+typedef enum UiModelType {
+ UI_STRING = 0,
+ UI_STRING_FREE,
+ UI_INTEGER,
+ UI_ICON,
+ UI_ICON_TEXT,
+ UI_ICON_TEXT_FREE
+} UiModelType;
+
+struct UiModel {
+ /*
+ * number of columns
+ */
+ int columns;
+
+ /*
+ * array of column types
+ * array length is the number of columns
+ */
+ UiModelType *types;
+
+ /*
+ * array of column titles
+ * array length is the number of columns
+ */
+ char **titles;
+
+ /*
+ * array of column size hints
+ */
+ int *columnsize;
+
+ /*
+ * function for translating model data to view data
+ * first argument is the pointer returned by UiList->get or UiTree->get
+ * second argument is the column index
+ * TODO: return
+ */
+ void*(*getvalue)(void*, int);
+};
+
+struct UiListCallbacks {
+ /*
+ * selection callback
+ */
+ ui_callback activate;
+
+ /*
+ * cursor callback
+ */
+ ui_callback selection;
+
+ /*
+ * userdata for all callbacks
+ */
+ void *userdata;
+};
+
+struct UiListArgs {
+ UiTri fill;
+ UiBool hexpand;
+ UiBool vexpand;
+ UiBool hfill;
+ UiBool vfill;
+ int colspan;
+ int rowspan;
+ const char *name;
+ const char *style_class;
+
+ UiList* list;
+ const char* varname;
+ UiModel* model;
+ ui_getvaluefunc getvalue;
+ ui_callback onactivate;
+ void* onactivatedata;
+ ui_callback onselection;
+ void* onselectiondata;
+ ui_callback ondragstart;
+ void* ondragstartdata;
+ ui_callback ondragcomplete;
+ void* ondragcompletedata;
+ ui_callback ondrop;
+ void* ondropsdata;
+ UiBool multiselection;
+ UiMenuBuilder *contextmenu;
+
+ const int *groups;
+};
+
+UIEXPORT UiModel* ui_model(UiContext *ctx, ...);
+UIEXPORT UiModel* ui_model_copy(UiContext *ctx, UiModel* model);
+UIEXPORT void ui_model_free(UiContext *ctx, UiModel *mi);
+
+#define ui_listview(obj, ...) ui_listview_create(obj, (UiListArgs) { __VA_ARGS__ } )
+#define ui_table(obj, ...) ui_table_create(obj, (UiListArgs) { __VA_ARGS__ } )
+#define ui_combobox(obj, ...) ui_combobox_create(obj, (UiListArgs) { __VA_ARGS__ } )
+#define ui_breadcrumbbar(obj, ...) ui_breadcrumbbar_create(obj, (UiListArgs) { __VA_ARGS__ } )
+
+UIEXPORT UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args);
+UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs args);
+UIEXPORT UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args);
+UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs args);
+
+void ui_table_dragsource_deprecated(UIWIDGET tablewidget, int actions, char *target0, ...);
+void ui_table_dragsource_a_deprecated(UIWIDGET tablewidget, int actions, char **targets, int nelm);
+void ui_table_dragdest_deprecated(UIWIDGET tablewidget, int actions, char *target0, ...);
+void ui_table_dragdest_a_deprecated(UIWIDGET tablewidget, int actions, char **targets, int nelm);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_TREE_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_H
+#define UI_H
+
+#include "toolkit.h"
+#include "container.h"
+#include "menu.h"
+#include "toolbar.h"
+#include "window.h"
+#include "stock.h"
+#include "button.h"
+#include "text.h"
+#include "properties.h"
+#include "tree.h"
+#include "graphics.h"
+#include "entry.h"
+#include "range.h"
+#include "image.h"
+#include "display.h"
+#include "dnd.h"
+#include "icons.h"
+
+#endif /* UI_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_WINDOW_H
+#define UI_WINDOW_H
+
+#include "toolkit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UI_FILEDIALOG_SELECT_SINGLE 0
+#define UI_FILEDIALOG_SELECT_MULTI 1
+#define UI_FILEDIALOG_SELECT_FOLDER 2
+
+typedef struct UiDialogArgs {
+ const char *title;
+ const char *content;
+ const char *button1_label;
+ const char *button2_label;
+ const char *closebutton_label;
+ const char *input_value;
+ UiBool input;
+ UiBool password;
+ ui_callback result;
+ void *resultdata;
+} UiDialogArgs;
+
+typedef struct UiDialogWindowArgs {
+ UiTri modal;
+ UiTri titlebar_buttons;
+ UiTri show_closebutton;
+ const char *title;
+ const char *lbutton1;
+ const char *lbutton2;
+ const char *rbutton3;
+ const char *rbutton4;
+ const int *lbutton1_groups;
+ const int *lbutton2_groups;
+ const int *rbutton3_groups;
+ const int *rbutton4_groups;
+ int default_button;
+ int width;
+ int height;
+ ui_callback onclick;
+ void *onclickdata;
+} UiDialogWindowArgs;
+
+UIEXPORT UiObject* ui_window(const char *title, void *window_data);
+UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data);
+UIEXPORT UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args);
+
+#define ui_dialog_window(parent, ...) ui_dialog_window_create(parent, (UiDialogWindowArgs){ __VA_ARGS__ });
+#define ui_dialog_window0(parent) ui_dialog_window_create(parent, (UiDialogWindowArgs){ 0 });
+
+UIEXPORT void ui_window_size(UiObject *obj, int width, int height);
+
+#define ui_dialog(parent, ...) ui_dialog_create(parent, (UiDialogArgs){ __VA_ARGS__ } )
+
+UIEXPORT void ui_dialog_create(UiObject *parent, UiDialogArgs args);
+
+UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata);
+UIEXPORT void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINDOW_H */
+
--- /dev/null
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+namespace winui\r
+{\r
+}\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Application\r
+ x:Class="winui.App"\r
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"\r
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"\r
+ xmlns:local="using:winui">\r
+ <Application.Resources>\r
+ <ResourceDictionary>\r
+ <ResourceDictionary.MergedDictionaries>\r
+ <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />\r
+ <!-- Other merged dictionaries here -->\r
+ </ResourceDictionary.MergedDictionaries>\r
+ <!-- Other app resources here -->\r
+ <SolidColorBrush x:Key="WindowCaptionBackground">Transparent</SolidColorBrush>\r
+ <SolidColorBrush x:Key="WindowCaptionBackgroundDisabled">Transparent</SolidColorBrush>\r
+ </ResourceDictionary>\r
+ </Application.Resources>\r
+</Application>\r
--- /dev/null
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+#include "pch.h"\r
+\r
+#include "App.xaml.h"\r
+#include "MainWindow.xaml.h"\r
+\r
+#include "toolkit.h"\r
+\r
+using namespace winrt;\r
+using namespace Windows::Foundation;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Microsoft::UI::Xaml::Navigation;\r
+using namespace winui;\r
+using namespace winui::implementation;\r
+\r
+// To learn more about WinUI, the WinUI project structure,\r
+// and more about our project templates, see: http://aka.ms/winui-project-info.\r
+\r
+/// <summary>\r
+/// Initializes the singleton application object. This is the first line of authored code\r
+/// executed, and as such is the logical equivalent of main() or WinMain().\r
+/// </summary>\r
+App::App()\r
+{\r
+ InitializeComponent();\r
+\r
+#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION\r
+ UnhandledException([this](IInspectable const&, UnhandledExceptionEventArgs const& e)\r
+ {\r
+ if (IsDebuggerPresent())\r
+ {\r
+ auto errorMessage = e.Message();\r
+ __debugbreak();\r
+ }\r
+ });\r
+#endif\r
+}\r
+\r
+/// <summary>\r
+/// Invoked when the application is launched.\r
+/// </summary>\r
+/// <param name="e">Details about the launch request and process.</param>\r
+void App::OnLaunched(LaunchActivatedEventArgs const&)\r
+{\r
+ ui_app_run_startup();\r
+ //window = make<MainWindow>();\r
+ //window.Activate();\r
+}\r
--- /dev/null
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+#pragma once\r
+\r
+#include "App.xaml.g.h"\r
+\r
+namespace winrt::winui::implementation\r
+{\r
+ struct App : AppT<App>\r
+ {\r
+ App();\r
+\r
+ void OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&);\r
+\r
+ private:\r
+ winrt::Microsoft::UI::Xaml::Window window{ nullptr };\r
+ };\r
+}\r
--- /dev/null
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+namespace winui\r
+{\r
+ [default_interface]\r
+ runtimeclass MainWindow : Microsoft.UI.Xaml.Window\r
+ {\r
+ MainWindow();\r
+ Int32 MyProperty;\r
+ }\r
+}\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Window\r
+ x:Class="winui.MainWindow"\r
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"\r
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"\r
+ xmlns:local="using:winui"\r
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"\r
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"\r
+ mc:Ignorable="d">\r
+\r
+ <Window.SystemBackdrop>\r
+ <MicaBackdrop />\r
+ </Window.SystemBackdrop>\r
+\r
+ <Grid RowDefinitions="Auto, Auto" ColumnDefinitions="*" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">\r
+ <StackPanel x:Name="AppTitleBar" Orientation="Horizontal" Grid.Column="0" Grid.Row="0" Padding="10,5,5,10">\r
+ <TextBlock x:Name="AppTitleTextBlock" Text="Window Title" CanDrag="True"></TextBlock>\r
+ </StackPanel>\r
+ </Grid>\r
+</Window>\r
--- /dev/null
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+#include "pch.h"\r
+#include "MainWindow.xaml.h"\r
+#if __has_include("MainWindow.g.cpp")\r
+#include "MainWindow.g.cpp"\r
+#endif\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+\r
+// To learn more about WinUI, the WinUI project structure,\r
+// and more about our project templates, see: http://aka.ms/winui-project-info.\r
+\r
+namespace winrt::winui::implementation\r
+{\r
+ MainWindow::MainWindow()\r
+ {\r
+ InitializeComponent();\r
+ //ExtendsContentIntoTitleBar(true);\r
+ //SetTitleBar(AppTitleBar());\r
+ }\r
+\r
+ int32_t MainWindow::MyProperty()\r
+ {\r
+ throw hresult_not_implemented();\r
+ }\r
+\r
+ void MainWindow::MyProperty(int32_t /* value */)\r
+ {\r
+ throw hresult_not_implemented();\r
+ }\r
+\r
+ void MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&)\r
+ {\r
+ \r
+ }\r
+}\r
--- /dev/null
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+#pragma once\r
+\r
+#include "MainWindow.g.h"\r
+\r
+namespace winrt::winui::implementation\r
+{\r
+ struct MainWindow : MainWindowT<MainWindow>\r
+ {\r
+ MainWindow();\r
+\r
+ int32_t MyProperty();\r
+ void MyProperty(int32_t value);\r
+\r
+ void myButton_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args);\r
+ };\r
+}\r
+\r
+namespace winrt::winui::factory_implementation\r
+{\r
+ struct MainWindow : MainWindowT<MainWindow, implementation::MainWindow>\r
+ {\r
+ };\r
+}\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+\r
+<Package\r
+ xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"\r
+ xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"\r
+ xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"\r
+ xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"\r
+ IgnorableNamespaces="uap rescap">\r
+\r
+ <Identity\r
+ Name="e3554f39-079b-4a0b-aa08-4ce5c4306644"\r
+ Publisher="CN=Olaf"\r
+ Version="1.0.0.0" />\r
+\r
+ <mp:PhoneIdentity PhoneProductId="e3554f39-079b-4a0b-aa08-4ce5c4306644" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>\r
+\r
+ <Properties>\r
+ <DisplayName>winui</DisplayName>\r
+ <PublisherDisplayName>Olaf</PublisherDisplayName>\r
+ <Logo>Assets\StoreLogo.png</Logo>\r
+ </Properties>\r
+\r
+ <Dependencies>\r
+ <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />\r
+ <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />\r
+ </Dependencies>\r
+\r
+ <Resources>\r
+ <Resource Language="x-generate"/>\r
+ </Resources>\r
+\r
+ <Applications>\r
+ <Application Id="App"\r
+ Executable="$targetnametoken$.exe"\r
+ EntryPoint="$targetentrypoint$">\r
+ <uap:VisualElements\r
+ DisplayName="winui"\r
+ Description="winui"\r
+ BackgroundColor="transparent"\r
+ Square150x150Logo="Assets\Square150x150Logo.png"\r
+ Square44x44Logo="Assets\Square44x44Logo.png">\r
+ <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />\r
+ <uap:SplashScreen Image="Assets\SplashScreen.png" />\r
+ </uap:VisualElements>\r
+ </Application>\r
+ </Applications>\r
+\r
+ <Capabilities>\r
+ <rescap:Capability Name="runFullTrust" />\r
+ </Capabilities>\r
+</Package>\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">\r
+ <assemblyIdentity version="1.0.0.0" name="winui.app"/>\r
+\r
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">\r
+ <application>\r
+ <!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8. \r
+ For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 \r
+ \r
+ It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->\r
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />\r
+ </application>\r
+ </compatibility>\r
+ \r
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">\r
+ <windowsSettings>\r
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>\r
+ </windowsSettings>\r
+ </application>\r
+</assembly>
\ No newline at end of file
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "appmenu.h"\r
+\r
+#include <cx/linked_list.h>\r
+#include <cx/array_list.h>\r
+\r
+#include "../common/context.h"\r
+#include "../common/object.h"\r
+\r
+#include "util.h"\r
+\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Windows::UI::Xaml::Interop;\r
+\r
+static void add_top_menu_widget(MenuBar &parent, int i, UiMenuItemI* item, UiObject* obj);\r
+\r
+static void add_menu_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);\r
+static void add_menuitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);\r
+static void add_menuseparator_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);\r
+static void add_checkitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);\r
+static void add_radioitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);\r
+static void add_menuitem_list_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj);\r
+\r
+static ui_menu_add_f createMenuItem[] = {\r
+ /* UI_MENU */ add_menu_widget,\r
+ /* UI_MENU_ITEM */ add_menuitem_widget,\r
+ /* UI_MENU_CHECK_ITEM */ add_checkitem_widget,\r
+ /* UI_MENU_RADIO_ITEM */ add_radioitem_widget,\r
+ /* UI_MENU_ITEM_LIST */ add_menuitem_list_widget,\r
+ /* UI_MENU_CHECKITEM_LIST */ add_menuitem_list_widget,\r
+ /* UI_MENU_RADIOITEM_LIST */ add_menuitem_list_widget,\r
+ /* UI_MENU_SEPARATOR */ add_menuseparator_widget\r
+};\r
+\r
+winrt::Microsoft::UI::Xaml::Controls::MenuBar ui_create_menubar(UiObject* obj) {\r
+ MenuBar mb = MenuBar();\r
+\r
+ UiMenu* menus_begin = uic_get_menu_list();\r
+\r
+ UiMenu* ls = menus_begin;\r
+ while (ls) {\r
+ UiMenu* menu = ls;\r
+ add_top_menu_widget(mb, 0, &menu->item, obj);\r
+\r
+ ls = (UiMenu*)ls->item.next;\r
+ }\r
+\r
+ return mb;\r
+}\r
+\r
+static void add_top_menu_widget(MenuBar& parent, int i, UiMenuItemI* item, UiObject* obj) {\r
+ UiMenu* menu = (UiMenu*)item;\r
+\r
+ MenuBarItem mi = MenuBarItem();\r
+ wchar_t* wlabel = str2wstr(menu->label, NULL);\r
+ mi.Title(wlabel);\r
+ free(wlabel);\r
+\r
+ UiMenuItemI* it = menu->items_begin;\r
+ int index = 0;\r
+ while (it) {\r
+ createMenuItem[it->type](mi.Items(), index, it, obj);\r
+\r
+ it = it->next;\r
+ index++;\r
+ }\r
+\r
+ parent.Items().Append(mi);\r
+}\r
+\r
+static void add_menu_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {\r
+ UiMenu* menu = (UiMenu*)item;\r
+\r
+ MenuFlyoutSubItem mi = MenuFlyoutSubItem();\r
+ wchar_t* wlabel = str2wstr(menu->label, NULL);\r
+ mi.Text(wlabel);\r
+ free(wlabel);\r
+\r
+ parent.Append(mi);\r
+\r
+ UiMenuItemI* it = menu->items_begin;\r
+ int index = 0;\r
+ while (it) {\r
+ createMenuItem[it->type](mi.Items(), index, it, obj);\r
+\r
+ it = it->next;\r
+ index++;\r
+ }\r
+\r
+ \r
+}\r
+\r
+static void add_menuitem_widget(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int i, UiMenuItemI* item, UiObject* obj) {\r
+ UiMenuItem* it = (UiMenuItem*)item;\r
+ \r
+ MenuFlyoutItem mi = MenuFlyoutItem();\r
+ wchar_t* wlabel = str2wstr(it->label, NULL);\r
+ mi.Text(wlabel);\r
+ free(wlabel);\r
+\r
+ parent.Append(mi);\r
+}\r
+\r
+static void add_menuseparator_widget(\r
+ winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent,\r
+ int i,\r
+ UiMenuItemI* item,\r
+ UiObject* obj)\r
+{\r
+\r
+}\r
+\r
+static void add_checkitem_widget(\r
+ winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent,\r
+ int i,\r
+ UiMenuItemI* item,\r
+ UiObject* obj)\r
+{\r
+\r
+}\r
+\r
+static void add_radioitem_widget(\r
+ winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent,\r
+ int i,\r
+ UiMenuItemI* item,\r
+ UiObject* obj)\r
+{\r
+\r
+}\r
+\r
+\r
+class UiMenuList {\r
+public:\r
+ UiObject *obj = nullptr;\r
+ winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent = { nullptr };\r
+ UiMenuItemType type;\r
+ int prevSize = 0;\r
+ int insertPos = 0;\r
+ UiVar* var = nullptr;\r
+ ui_getvaluefunc getvalue = nullptr;\r
+ ui_callback callback = nullptr;\r
+ void* userdata = nullptr;\r
+\r
+ UiMenuList() {\r
+\r
+ }\r
+\r
+ void updateItems() {\r
+ UiList* list = (UiList*)var->value;\r
+\r
+ // delete previous items\r
+ for (int i = 0; i < prevSize; i++) {\r
+ parent.RemoveAt(insertPos);\r
+ }\r
+ \r
+ // insert new items\r
+ int count = 0;\r
+ void* elm = list->first(list);\r
+ while (elm) {\r
+ char *menuItemLabel = (char*) (getvalue ? getvalue(elm, 0) : elm);\r
+\r
+ MenuFlyoutItem mi = MenuFlyoutItem();\r
+ wchar_t* wlabel = str2wstr(menuItemLabel ? menuItemLabel : "", NULL);\r
+ mi.Text(wlabel);\r
+ free(wlabel);\r
+\r
+ if (callback) {\r
+ mi.Click([this, elm, count](Windows::Foundation::IInspectable const& sender, RoutedEventArgs const& e)\r
+ {\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = elm;\r
+ evt.intval = count;\r
+ callback(&evt, userdata);\r
+ });\r
+ }\r
+\r
+ parent.InsertAt(insertPos + count, mi);\r
+\r
+ elm = list->next(list);\r
+ count++;\r
+ }\r
+\r
+ prevSize = count;\r
+ }\r
+};\r
+\r
+extern "C" void destroy_ui_menu_list(void* ptr) {\r
+ UiMenuList* ls = (UiMenuList*)ptr;\r
+ delete ls;\r
+}\r
+\r
+static void ui_context_add_menu_list_destructor(UiContext* ctx, UiMenuList* list) {\r
+ cxMempoolRegister(ctx->mp, list, destroy_ui_menu_list);\r
+}\r
+\r
+static void ui_menulist_update(UiEvent* event, void* userdata) {\r
+ UiMenuList* mlist = (UiMenuList*)userdata;\r
+ mlist->updateItems();\r
+}\r
+\r
+static void add_menuitem_list_widget(\r
+ winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent,\r
+ int i,\r
+ UiMenuItemI* item,\r
+ UiObject* obj)\r
+{\r
+ UiMenuItemList* it = (UiMenuItemList*)item;\r
+ if (!it->varname) {\r
+ return;\r
+ }\r
+ \r
+ uint32_t size = parent.Size();\r
+ \r
+ UiVar* var = uic_create_var(ui_global_context(), it->varname, UI_VAR_LIST);\r
+\r
+ UiMenuList* mlist = new UiMenuList();\r
+ mlist->obj = obj;\r
+ mlist->parent = parent;\r
+ mlist->getvalue = it->getvalue;\r
+ mlist->callback = it->callback;\r
+ mlist->userdata = it->userdata;\r
+ mlist->prevSize = 0;\r
+ mlist->insertPos = size;\r
+ mlist->type = item->type;\r
+ mlist->var = var;\r
+ ui_context_add_menu_list_destructor(obj->ctx, mlist);\r
+\r
+ UiList* list = (UiList*)var->value;\r
+ list->observers = ui_add_observer(list->observers, ui_menulist_update, mlist);\r
+\r
+ mlist->updateItems();\r
+}\r
+\r
+\r
+\r
+\r
+winrt::Microsoft::UI::Xaml::Controls::MenuFlyout ui_create_menu_flyout(UiObject* obj, UiMenu* menudef) {\r
+ MenuFlyout flyout = MenuFlyout();\r
+\r
+ UiMenuItemI* it = menudef->items_begin;\r
+ int index = 0;\r
+ while (it) {\r
+ createMenuItem[it->type](flyout.Items(), index, it, obj);\r
+\r
+ it = it->next;\r
+ index++;\r
+ }\r
+\r
+ return flyout;\r
+}\r
+\r
+UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget) {\r
+ return NULL;\r
+}\r
+\r
+void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y) {\r
+ \r
+}\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "toolkit.h"\r
+#include "../common/menu.h"\r
+\r
+#include <Windows.h>\r
+#undef GetCurrentTime\r
+#include <winrt/Windows.Foundation.Collections.h>\r
+#include <winrt/Windows.UI.Xaml.Interop.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>\r
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>\r
+#include <winrt/Microsoft.UI.Xaml.Markup.h>\r
+\r
+\r
+\r
+typedef void(*ui_menu_add_f)(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::UI::Xaml::Controls::MenuFlyoutItemBase> parent, int, UiMenuItemI*, UiObject*);\r
+\r
+winrt::Microsoft::UI::Xaml::Controls::MenuBar ui_create_menubar(UiObject* obj);\r
+\r
+winrt::Microsoft::UI::Xaml::Controls::MenuFlyout ui_create_menu_flyout(UiObject* obj, UiMenu* menudef);\r
+\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "button.h"\r
+\r
+#include "util.h"\r
+#include "container.h"\r
+#include "icons.h"\r
+\r
+#include "../common/object.h"\r
+#include "../common/context.h"\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;\r
+\r
+\r
+\r
+void ui_set_button_label(ButtonBase button, const char* label, const char* stockid, const char *icon, UiLabelType type) {\r
+ // TODO: stockid\r
+\r
+ if (type == UI_LABEL_ICON) {\r
+ label = NULL;\r
+ }\r
+ else if (type == UI_LABEL_TEXT) {\r
+ icon = NULL;\r
+ }\r
+\r
+ IconElement icon_elm = { nullptr };\r
+ if (icon) {\r
+ icon_elm = ui_get_icon(icon);\r
+ }\r
+\r
+ if (label && icon_elm) {\r
+ StackPanel panel = StackPanel();\r
+ panel.Orientation(Orientation::Horizontal);\r
+ panel.Spacing(5);\r
+ \r
+ panel.Children().Append(icon_elm);\r
+\r
+ wchar_t* wlabel = str2wstr(label, nullptr);\r
+ TextBlock label = TextBlock();\r
+ label.Text(wlabel);\r
+ panel.Children().Append(label);\r
+ free(wlabel);\r
+\r
+ button.Content(panel);\r
+ }\r
+ else if (label) {\r
+ wchar_t* wlabel = str2wstr(label, nullptr);\r
+ button.Content(box_value(wlabel));\r
+ free(wlabel);\r
+ }\r
+ else if (icon_elm) {\r
+ button.Content(ui_get_icon(icon));\r
+ }\r
+}\r
+\r
+\r
+UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create button with label\r
+ Button button = Button();\r
+ ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype);\r
+\r
+ // create toolkit wrapper object and register destructor\r
+ UIElement elm = button;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+ // register callback\r
+ if (args.onclick) {\r
+ ui_callback cbfunc = args.onclick;\r
+ void* cbdata = args.onclickdata;\r
+ button.Click([cbfunc, cbdata, obj](IInspectable const& sender, RoutedEventArgs) {\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = nullptr;\r
+ evt.intval = 0;\r
+ cbfunc(&evt, cbdata);\r
+ });\r
+ }\r
+\r
+ // add button to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(button, false);\r
+\r
+ return widget;\r
+}\r
+\r
+\r
+void togglebutton_register_checked_observers(ToggleButton button, UiObject* obj, UiVar* var) {\r
+ button.Checked([button, obj, var](IInspectable const& sender, RoutedEventArgs) {\r
+ UiInteger* i = (UiInteger*)var->value;\r
+ UiEvent evt = ui_create_int_event(obj, i->get(i));\r
+ ui_notify_evt(i->observers, &evt);\r
+ });\r
+}\r
+\r
+void togglebutton_register_unchecked_observers(ToggleButton button, UiObject* obj, UiVar* var) {\r
+ button.Unchecked([button, obj, var](IInspectable const& sender, RoutedEventArgs) {\r
+ UiInteger* i = (UiInteger*)var->value;\r
+ UiEvent evt = ui_create_int_event(obj, i->get(i));\r
+ ui_notify_evt(i->observers, &evt);\r
+ });\r
+}\r
+\r
+void togglebutton_register_callback(ToggleButton button, UiObject *obj, UiToggleArgs& args) {\r
+ ui_callback callback = args.onchange;\r
+ void* cbdata = args.onchangedata;\r
+ if (callback) {\r
+ button.Checked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) {\r
+ UiEvent evt = ui_create_int_event(obj, true);\r
+ callback(&evt, cbdata);\r
+ });\r
+ button.Unchecked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) {\r
+ UiEvent evt = ui_create_int_event(obj, false);\r
+ callback(&evt, cbdata);\r
+ });\r
+ }\r
+}\r
+\r
+// for some stupid reason the ToggleSwitch is not a ToggleButton and we need to write the same\r
+// code again, because although everything is basically the same, it is named differently\r
+static void switch_register_observers(ToggleSwitch button, UiObject* obj, UiVar* var) {\r
+ button.Toggled([button, obj, var](IInspectable const& sender, RoutedEventArgs) {\r
+ UiInteger* i = (UiInteger*)var->value;\r
+ UiEvent evt = ui_create_int_event(obj, i->get(i));\r
+ ui_notify_evt(i->observers, &evt);\r
+ });\r
+}\r
+\r
+static void switch_register_callback(ToggleSwitch button, UiObject* obj, UiToggleArgs& args) {\r
+ ui_callback callback = args.onchange;\r
+ void* cbdata = args.onchangedata;\r
+ if (callback) {\r
+ button.Toggled([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) {\r
+ UiEvent evt = ui_create_int_event(obj, button.IsOn());\r
+ callback(&evt, cbdata);\r
+ });\r
+ }\r
+}\r
+\r
+static void togglebutton_changed(UiObject *obj, bool checked, ui_callback onchange, void *onchangedata, int enable_state) {\r
+ if (onchange) {\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = nullptr;\r
+ evt.intval = checked;\r
+ onchange(&evt, onchangedata);\r
+ }\r
+ if (enable_state > 0) {\r
+ if (checked) {\r
+ ui_set_group(obj->ctx, enable_state);\r
+ } else {\r
+ ui_unset_group(obj->ctx, enable_state);\r
+ }\r
+ }\r
+}\r
+\r
+static UIWIDGET create_togglebutton(UiObject *obj, ToggleButton button, UiToggleArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // set label\r
+ ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype);\r
+ togglebutton_register_callback(button, obj, args);\r
+\r
+ // create toolkit wrapper object and register destructor\r
+ UIElement elm = button;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+ // bind variable\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);\r
+ if (var) {\r
+ UiInteger* value = (UiInteger*)var->value;\r
+ value->obj = widget;\r
+ value->get = ui_toggle_button_get;\r
+ value->set = ui_toggle_button_set;\r
+\r
+ // listener for notifying observers\r
+ togglebutton_register_checked_observers(button, obj, var);\r
+ togglebutton_register_unchecked_observers(button, obj, var);\r
+ }\r
+\r
+ if (args.enable_group > 0 || args.onchange) {\r
+ button.Checked([obj, args](IInspectable const& sender, RoutedEventArgs) {\r
+ togglebutton_changed(obj, true, args.onchange, args.onchangedata, args.enable_group);\r
+ });\r
+ button.Unchecked([obj, args](IInspectable const& sender, RoutedEventArgs) {\r
+ togglebutton_changed(obj, false, args.onchange, args.onchangedata, args.enable_group);\r
+ });\r
+ }\r
+\r
+ // add button to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(button, false);\r
+\r
+ return widget;\r
+}\r
+\r
+UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) {\r
+ ToggleButton button = ToggleButton();\r
+ return create_togglebutton(obj, button, args);\r
+}\r
+\r
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {\r
+ CheckBox button = CheckBox();\r
+ return create_togglebutton(obj, button, args);\r
+}\r
+\r
+UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) {\r
+ ToggleSwitch button = ToggleSwitch();\r
+ if (args.label) {\r
+ wchar_t* wlabel = str2wstr(args.label, nullptr);\r
+ button.Header(box_value(wlabel));\r
+ free(wlabel);\r
+ }\r
+ switch_register_callback(button, obj, args);\r
+\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create toolkit wrapper object and register destructor\r
+ UIElement elm = button;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+ // bind variable\r
+ UiVar* var = nullptr;\r
+ if (args.value) {\r
+ var = uic_create_value_var(current->ctx, args.value);\r
+ }\r
+ else if (args.varname) {\r
+ var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER);\r
+ }\r
+ if (var) {\r
+ UiInteger* value = (UiInteger*)var->value;\r
+ value->obj = widget;\r
+ value->get = ui_toggle_button_get;\r
+ value->set = ui_toggle_button_set;\r
+\r
+ // listener for notifying observers\r
+ switch_register_observers(button, obj, var);\r
+ }\r
+\r
+ // add button to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(button, false);\r
+\r
+ return widget;\r
+}\r
+\r
+UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args) {\r
+ RadioButton button = RadioButton();\r
+\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // set label\r
+ ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype);\r
+ togglebutton_register_callback(button, obj, args);\r
+\r
+ // create toolkit wrapper object and register destructor\r
+ UIElement elm = button;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+ UiVar* var = nullptr;\r
+ if (args.value) {\r
+ var = uic_create_value_var(current->ctx, args.value);\r
+ }\r
+ else if (args.varname) {\r
+ var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER);\r
+ }\r
+\r
+ // bind radio button to the value\r
+ if (var) {\r
+ UiInteger* value = (UiInteger*)var->value;\r
+\r
+ // store a list of radio buttons in the value\r
+ CxList* radioButtons = (CxList*) (value->obj ? value->obj : cxLinkedListCreate(current->ctx->allocator, NULL, CX_STORE_POINTERS));\r
+ // get or create the group name\r
+ static int groupCount = 0;\r
+ winrt::hstring groupName;\r
+ if (cxListSize(radioButtons) == 0) {\r
+ groupName = winrt::to_hstring(groupCount++);\r
+ } else {\r
+ UiWidget* firstButtonWidget = (UiWidget*)cxListAt(radioButtons, 0);\r
+ RadioButton firstRadioButton = firstButtonWidget->uielement.as<RadioButton>();\r
+ groupName = firstRadioButton.GroupName();\r
+ }\r
+\r
+ // set the group name for the new radiobutton\r
+ cxListAdd(radioButtons, widget);\r
+ button.GroupName(groupName);\r
+\r
+ value->obj = radioButtons;\r
+ value->get = ui_radio_button_get;\r
+ value->set = ui_radio_button_set;\r
+\r
+ // listener for notifying observers (only checked, not unchecked)\r
+ togglebutton_register_checked_observers(button, obj, var);\r
+ }\r
+\r
+ // add button to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(button, false);\r
+\r
+ return widget;\r
+}\r
+\r
+\r
+int64_t ui_toggle_button_get(UiInteger* integer) {\r
+ UiWidget* widget = (UiWidget*)integer->obj;\r
+ ToggleButton toggleButton = widget->uielement.as<ToggleButton>();\r
+ int val = toggleButton.IsChecked().GetBoolean();\r
+ integer->value = val;\r
+ return val;\r
+}\r
+\r
+void ui_toggle_button_set(UiInteger* integer, int64_t value) {\r
+ UiWidget* widget = (UiWidget*)integer->obj;\r
+ ToggleButton toggleButton = widget->uielement.as<ToggleButton>();\r
+ toggleButton.IsChecked((bool)value);\r
+ integer->value = value;\r
+}\r
+\r
+int64_t ui_switch_get(UiInteger * integer) {\r
+ UiWidget* widget = (UiWidget*)integer->obj;\r
+ ToggleSwitch toggleButton = widget->uielement.as<ToggleSwitch>();\r
+ int val = toggleButton.IsOn();\r
+ integer->value = val;\r
+ return val;\r
+}\r
+\r
+void ui_switch_set(UiInteger * integer, int64_t value) {\r
+ UiWidget* widget = (UiWidget*)integer->obj;\r
+ ToggleSwitch toggleButton = widget->uielement.as<ToggleSwitch>();\r
+ toggleButton.IsOn((bool)value);\r
+ integer->value = value;\r
+}\r
+\r
+int64_t ui_radio_button_get(UiInteger * integer) {\r
+ CxList* list = (CxList*)integer->obj;\r
+ CxIterator i = cxListIterator(list);\r
+ int selection = -1;\r
+ cx_foreach(UiWidget*, widget, i) {\r
+ ToggleButton button = widget->uielement.as<ToggleButton>();\r
+ if (button.IsChecked().GetBoolean()) {\r
+ selection = i.index;\r
+ break;\r
+ }\r
+ }\r
+ integer->value = selection;\r
+ return selection;\r
+}\r
+\r
+void ui_radio_button_set(UiInteger * integer, int64_t value) {\r
+ CxList* list = (CxList*)integer->obj;\r
+ UiWidget* widget = (UiWidget*)cxListAt(list, value);\r
+ if (widget) {\r
+ ToggleButton button = widget->uielement.as<ToggleButton>();\r
+ button.IsChecked(true);\r
+ integer->value = value;\r
+ }\r
+}\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "toolkit.h"\r
+#include "../ui/container.h"\r
+\r
+#include "../ui/button.h"\r
+\r
+#include "../common/context.h"\r
+\r
+void ui_set_button_label(winrt::Microsoft::UI::Xaml::Controls::Primitives::ButtonBase button, const char* label, const char* stockid, const char *icon, UiLabelType type);\r
+\r
+void togglebutton_register_checked_observers(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiVar* var);\r
+void togglebutton_register_unchecked_observers(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiVar* var);\r
+void togglebutton_register_callback(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiToggleArgs& args);\r
+\r
+extern "C" int64_t ui_toggle_button_get(UiInteger * integer);\r
+extern "C" void ui_toggle_button_set(UiInteger * integer, int64_t value);\r
+\r
+extern "C" int64_t ui_switch_get(UiInteger * integer);\r
+extern "C" void ui_switch_set(UiInteger * integer, int64_t value);\r
+\r
+extern "C" int64_t ui_radio_button_get(UiInteger * integer);\r
+extern "C" void ui_radio_button_set(UiInteger * integer, int64_t value);\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "commandbar.h"\r
+\r
+#include "util.h"\r
+#include "../common/object.h"\r
+#include "../common/context.h"\r
+\r
+#include "button.h"\r
+#include "appmenu.h"\r
+#include "icons.h"\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Windows::UI::Xaml::Interop;\r
+\r
+static void create_item(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItemI* i);\r
+static void create_cmditem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItem* item);\r
+static void create_toggleitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarToggleItem* item);\r
+static void create_menuitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* item);\r
+\r
+static void create_appmenu_items(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* i);\r
+\r
+CommandBar ui_create_toolbar(UiObject *obj, CxList* defaults, bool addappmenu) {\r
+ \r
+ CommandBar cb = CommandBar();\r
+ cb.DefaultLabelPosition(CommandBarDefaultLabelPosition::Right);\r
+\r
+ // add pre-configured items\r
+ CxIterator i = cxListIterator(defaults);\r
+ cx_foreach(char*, def, i) {\r
+ UiToolbarItemI* item = uic_toolbar_get_item(def);\r
+ if (!item) {\r
+ exit(-1); // TODO: maybe an error dialog?\r
+ }\r
+ create_item(obj, cb.PrimaryCommands(), item);\r
+ }\r
+\r
+ // add appmenu\r
+ if (addappmenu) {\r
+ UiToolbarMenuItem* appmenu = uic_get_appmenu();\r
+ if (appmenu) {\r
+ create_appmenu_items(obj, cb.SecondaryCommands(), appmenu);\r
+ }\r
+ }\r
+\r
+ return cb;\r
+}\r
+\r
+static void create_item(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItemI* i) {\r
+ switch (i->type) {\r
+ case UI_TOOLBAR_ITEM: {\r
+ create_cmditem(obj, cb, (UiToolbarItem*)i);\r
+ break;\r
+ }\r
+ case UI_TOOLBAR_TOGGLEITEM: {\r
+ create_toggleitem(obj, cb, (UiToolbarToggleItem*)i);\r
+ break;\r
+ }\r
+ case UI_TOOLBAR_MENU: {\r
+ create_menuitem(obj, cb, (UiToolbarMenuItem*)i);\r
+ break;\r
+ }\r
+ }\r
+}\r
+\r
+static void create_appmenu_items(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* i) {\r
+ for (UiMenuItemI* mi = i->menu.items_begin; mi; mi = mi->next) {\r
+ // convert UiMenuItemI to UiToolbarItemI\r
+ switch (mi->type) {\r
+ case UI_MENU: {\r
+ UiMenu* mitem = (UiMenu*)mi;\r
+ UiToolbarMenuItem tbitem;\r
+ memset(&tbitem, 0, sizeof(UiToolbarMenuItem));\r
+ tbitem.item.type = UI_TOOLBAR_MENU;\r
+ tbitem.args.label = mitem->label;\r
+ tbitem.menu.items_begin = mitem->items_begin;\r
+ tbitem.menu.items_end = mitem->items_end;\r
+ create_menuitem(obj, cb, &tbitem);\r
+ break;\r
+ }\r
+ case UI_MENU_ITEM: {\r
+ UiMenuItem* mitem = (UiMenuItem*)mi;\r
+ UiToolbarItem tbitem;\r
+ memset(&tbitem, 0, sizeof(UiToolbarItem));\r
+ tbitem.item.type = UI_TOOLBAR_ITEM;\r
+ tbitem.args.label = mitem->label;\r
+ tbitem.args.onclick = mitem->callback;\r
+ tbitem.args.onclickdata = mitem->userdata;\r
+ create_cmditem(obj, cb, &tbitem);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+}\r
+\r
+static void create_cmditem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarItem* item) {\r
+ AppBarButton button = AppBarButton();\r
+ if (item->args.label) {\r
+ wchar_t* wlabel = str2wstr(item->args.label, nullptr);\r
+ button.Label(wlabel);\r
+ free(wlabel);\r
+ }\r
+ if(item->args.icon) {\r
+ button.Icon(ui_get_icon(item->args.icon));\r
+ }\r
+\r
+ UIElement elm = button;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(obj->ctx, widget);\r
+ ui_set_widget_groups(obj->ctx, widget, item->args.groups);\r
+\r
+ // register callback\r
+ if (item->args.onclick) {\r
+ ui_callback cbfunc = item->args.onclick;\r
+ void* cbdata = item->args.onclickdata;\r
+ button.Click([cbfunc, cbdata, obj](Windows::Foundation::IInspectable const& sender, RoutedEventArgs) {\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = nullptr;\r
+ evt.intval = 0;\r
+ cbfunc(&evt, cbdata);\r
+ });\r
+ }\r
+\r
+ cb.Append(button);\r
+}\r
+\r
+static void create_toggleitem(UiObject *obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarToggleItem* item) {\r
+ AppBarToggleButton button = AppBarToggleButton();\r
+ if (item->args.label) {\r
+ wchar_t* wlabel = str2wstr(item->args.label, nullptr);\r
+ button.Label(wlabel);\r
+ free(wlabel);\r
+ }\r
+ if (item->args.icon) {\r
+ button.Icon(ui_get_icon(item->args.icon));\r
+ }\r
+\r
+ UiVar* var = uic_widget_var(obj->ctx, obj->ctx, nullptr, item->args.varname, UI_VAR_INTEGER);\r
+ if (var) { \r
+ UIElement elm = button;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(obj->ctx, widget);\r
+ ui_set_widget_groups(obj->ctx, widget, item->args.groups);\r
+\r
+ UiInteger* value = (UiInteger*)var->value;\r
+ int64_t i = value->value;\r
+ value->get = ui_toggle_button_get;\r
+ value->set = ui_toggle_button_set;\r
+ value->obj = widget;\r
+ ui_toggle_button_set(value, i); // init togglebutton state\r
+\r
+ // listener for notifying observers\r
+ togglebutton_register_checked_observers(button, obj, var);\r
+ togglebutton_register_unchecked_observers(button, obj, var);\r
+ }\r
+\r
+ UiToggleArgs args = {};\r
+ args.onchange = item->args.onchange;\r
+ args.onchangedata = item->args.onchangedata;\r
+ togglebutton_register_callback(button, obj, args);\r
+\r
+\r
+ cb.Append(button);\r
+}\r
+\r
+static void create_menuitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector<ICommandBarElement> cb, UiToolbarMenuItem* item) {\r
+ AppBarButton button = AppBarButton();\r
+ if (item->args.label) {\r
+ wchar_t* wlabel = str2wstr(item->args.label, nullptr);\r
+ button.Label(wlabel);\r
+ free(wlabel);\r
+ }\r
+ if (item->args.icon) {\r
+ button.Icon(ui_get_icon(item->args.icon));\r
+ }\r
+\r
+ MenuFlyoutItem mi = MenuFlyoutItem();\r
+\r
+ MenuFlyout flyout = ui_create_menu_flyout(obj, &item->menu);\r
+ button.Flyout(flyout);\r
+\r
+ cb.Append(button);\r
+}\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "toolkit.h"\r
+#include "../ui/toolbar.h"\r
+#include "../common/toolbar.h"\r
+\r
+#include <Windows.h>\r
+#undef GetCurrentTime\r
+#include <winrt/Windows.Foundation.Collections.h>\r
+#include <winrt/Windows.UI.Xaml.Interop.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>\r
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>\r
+#include <winrt/Microsoft.UI.Xaml.Markup.h>\r
+\r
+winrt::Microsoft::UI::Xaml::Controls::CommandBar ui_create_toolbar(UiObject *obj, CxList* defaults, bool addappmenu);\r
+\r
+extern "C" int64_t ui_appbar_togglebutton_get(UiInteger * integer);\r
+extern "C" void ui_appbar_togglebutton_set(UiInteger * integer, int64_t value);
\ No newline at end of file
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "pch.h"
+#include "condvar.h"
+
+
+
+UiCondVar* ui_condvar_create(void) {
+ UiWinCondVar *var = new UiWinCondVar();
+ var->var.data = NULL;
+ var->var.intdata = 0;
+ var->set = 0;
+ return (UiCondVar*)var;
+}
+
+void ui_condvar_wait(UiCondVar *var) {
+ UiWinCondVar *p = (UiWinCondVar*)var;
+ std::unique_lock<std::mutex> lock(p->mutex);
+
+ if(!p->set) {
+ p->cond.wait(lock);
+ }
+ p->set = 0;
+}
+
+void ui_condvar_signal(UiCondVar *var, void *data, int intdata) {
+ UiWinCondVar *p = (UiWinCondVar*)var;
+ std::unique_lock<std::mutex> lock(p->mutex);
+ p->var.data = data;
+ p->var.intdata = intdata;
+ p->set = 1;
+ lock.unlock();
+ p->cond.notify_one();
+}
+
+void ui_condvar_destroy(UiCondVar *var) {
+ UiWinCondVar *p = (UiWinCondVar*)var;
+ delete p;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UI_WIN_CONDVAR_H
+#define UI_WIN_CONDVAR_H
+
+#include "toolkit.h"
+
+#include <queue>
+#include <mutex>
+#include <condition_variable>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct UiWinCondVar {
+ UiCondVar var;
+ int set;
+ std::mutex mutex;
+ std::condition_variable cond;
+};
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UI_WIN_CONDVAR_H */
+
--- /dev/null
+/*\r
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+*\r
+* Copyright 2023 Olaf Wintermann. All rights reserved.\r
+*\r
+* Redistribution and use in source and binary forms, with or without\r
+* modification, are permitted provided that the following conditions are met:\r
+*\r
+* 1. Redistributions of source code must retain the above copyright\r
+* notice, this list of conditions and the following disclaimer.\r
+*\r
+* 2. Redistributions in binary form must reproduce the above copyright\r
+* notice, this list of conditions and the following disclaimer in the\r
+* documentation and/or other materials provided with the distribution.\r
+*\r
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+* POSSIBILITY OF SUCH DAMAGE.\r
+*/\r
+\r
+#include "pch.h"\r
+\r
+#include "container.h"\r
+\r
+#include "../common/context.h"\r
+#include "../common/object.h"\r
+\r
+#include "util.h"\r
+\r
+\r
+void ui_container_begin_close(UiObject* obj) {\r
+ UiContainer* ct = uic_get_current_container(obj);\r
+ ct->close = 1;\r
+}\r
+\r
+int ui_container_finish(UiObject* obj) {\r
+ UiContainer* ct = uic_get_current_container(obj);\r
+ if (ct->close) {\r
+ ui_end(obj);\r
+ return 0;\r
+ }\r
+ return 1;\r
+}\r
+\r
+\r
+// --------------------- UiBoxContainer ---------------------\r
+\r
+static UIWIDGET ui_box(UiObject* obj, UiContainerArgs args, UiBoxContainerType type) {\r
+ UiObject* current = uic_current_obj(obj);\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ Grid grid = Grid();\r
+ current->container->Add(grid, true);\r
+\r
+ UIElement elm = grid;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+ UiObject* newobj = uic_object_new(obj, widget);\r
+ newobj->container = new UiBoxContainer(grid, type, args.margin, args.spacing);\r
+ ui_context_add_container_destructor(current->ctx, newobj->container);\r
+ uic_obj_add(obj, newobj);\r
+\r
+ return widget;\r
+}\r
+\r
+UIWIDGET ui_vbox_create(UiObject* obj, UiContainerArgs args) {\r
+ return ui_box(obj, args, UI_BOX_CONTAINER_VBOX);\r
+}\r
+\r
+UIWIDGET ui_hbox_create(UiObject* obj, UiContainerArgs args) {\r
+ return ui_box(obj, args, UI_BOX_CONTAINER_HBOX);\r
+}\r
+\r
+UiBoxContainer::UiBoxContainer(Grid grid, enum UiBoxContainerType type, int margin, int spacing) {\r
+ this->grid = grid;\r
+ this->type = type;\r
+\r
+ Thickness t = { (double)margin, (double)margin, (double)margin, (double)margin };\r
+ grid.Margin(t);\r
+ grid.ColumnSpacing((double)spacing);\r
+ grid.RowSpacing((double)spacing);\r
+\r
+ GridLength gl;\r
+ gl.Value = 1;\r
+ gl.GridUnitType = GridUnitType::Star;\r
+\r
+ // hbox needs one row def, vbox needs one col def\r
+ // all other col/row defs are created when elements are added\r
+ if (type == UI_BOX_CONTAINER_HBOX) {\r
+ boxRowDef = RowDefinition();\r
+ boxRowDef.Height(gl);\r
+ grid.RowDefinitions().Append(boxRowDef);\r
+ } else {\r
+ boxColDef = ColumnDefinition();\r
+ boxColDef.Width(gl);\r
+ grid.ColumnDefinitions().Append(boxColDef);\r
+ }\r
+\r
+ ui_reset_layout(layout);\r
+}\r
+\r
+void UiBoxContainer::Add(FrameworkElement control, UiBool fill) {\r
+ if (this->layout.fill != UI_LAYOUT_UNDEFINED) {\r
+ fill = ui_lb2bool(this->layout.fill);\r
+ }\r
+\r
+ GridLength gl;\r
+ if (fill) {\r
+ gl.Value = 1;\r
+ gl.GridUnitType = GridUnitType::Star;\r
+ }\r
+ else {\r
+ gl.Value = 0;\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+ }\r
+\r
+ control.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+ control.VerticalAlignment(VerticalAlignment::Stretch);\r
+\r
+ if (type == UI_CONTAINER_HBOX) {\r
+ ColumnDefinition coldef = ColumnDefinition();\r
+ coldef.Width(gl);\r
+ grid.ColumnDefinitions().Append(coldef);\r
+ grid.SetColumn(control, grid.Children().Size());\r
+ grid.SetRow(control, 0);\r
+ } else {\r
+ RowDefinition rowdef = RowDefinition();\r
+ rowdef.Height(gl);\r
+ grid.RowDefinitions().Append(rowdef);\r
+ grid.SetRow(control, grid.Children().Size());\r
+ grid.SetColumn(control, 0);\r
+ }\r
+\r
+ grid.Children().Append(control);\r
+\r
+ ui_reset_layout(layout);\r
+}\r
+\r
+\r
+// --------------------- UiGridContainer ---------------------\r
+\r
+UIWIDGET ui_grid_create(UiObject* obj, UiContainerArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ Grid grid = Grid();\r
+ current->container->Add(grid, true);\r
+\r
+ UIElement elm = grid;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+ UiObject* newobj = uic_object_new(obj, widget);\r
+ newobj->container = new UiGridContainer(grid, args.margin, args.columnspacing, args.rowspacing);\r
+ ui_context_add_container_destructor(current->ctx, newobj->container);\r
+ uic_obj_add(obj, newobj);\r
+\r
+ return widget;\r
+}\r
+\r
+UiGridContainer::UiGridContainer(Grid grid, int margin, int columnspacing, int rowspacing) {\r
+ this->grid = grid;\r
+ Thickness t = { (double)margin, (double)margin, (double)margin, (double)margin };\r
+ grid.Margin(t);\r
+ grid.ColumnSpacing((double)columnspacing);\r
+ grid.RowSpacing((double)rowspacing);\r
+ ui_reset_layout(layout);\r
+}\r
+\r
+void UiGridContainer::Add(FrameworkElement control, UiBool fill) {\r
+ GridLength gl;\r
+\r
+ bool hexpand = false;\r
+ bool vexpand = false;\r
+ bool hfill = false;\r
+ bool vfill = false;\r
+ if(layout.fill != UI_LAYOUT_UNDEFINED) {\r
+ fill = ui_lb2bool(layout.fill);\r
+ }\r
+ if (layout.hexpand != UI_LAYOUT_UNDEFINED) {\r
+ hexpand = layout.hexpand;\r
+ hfill = true;\r
+ }\r
+ if (layout.vexpand != UI_LAYOUT_UNDEFINED) {\r
+ vexpand = layout.vexpand;\r
+ vfill = true;\r
+ }\r
+ if (fill) {\r
+ hfill = true;\r
+ vfill = true;\r
+ }\r
+\r
+ // create new RowDefinition for the new line\r
+ if (layout.newline || y == -1) {\r
+ x = 0;\r
+ y++;\r
+ RowDefinition rowdef = RowDefinition();\r
+ if (vexpand) {\r
+ gl.GridUnitType = GridUnitType::Star;\r
+ gl.Value = 1;\r
+ }\r
+ else {\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+ gl.Value = 0;\r
+ }\r
+ rowdef.Height(gl);\r
+ grid.RowDefinitions().Append(rowdef);\r
+ } else if (vexpand) {\r
+ // adjust row\r
+ gl.GridUnitType = GridUnitType::Star;\r
+ gl.Value = 1;\r
+ grid.RowDefinitions().GetAt(y).Height(gl);\r
+ }\r
+\r
+ // create new columndefinition, if a new column is added\r
+ if (x == cols) {\r
+ if (hexpand) {\r
+ gl.GridUnitType = GridUnitType::Star;\r
+ gl.Value = 1;\r
+ }\r
+ else {\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+ gl.Value = 0;\r
+ }\r
+ ColumnDefinition coldef = ColumnDefinition();\r
+ coldef.Width(gl);\r
+ grid.ColumnDefinitions().Append(coldef);\r
+ cols++;\r
+ } else if(hexpand) {\r
+ // adjust column\r
+ if (layout.colspan == 0) {\r
+ gl.GridUnitType = GridUnitType::Star;\r
+ gl.Value = 1;\r
+ grid.ColumnDefinitions().GetAt(x).Width(gl);\r
+ } else {\r
+ int adjust_col = x;\r
+ bool adjust = true;\r
+ for (int i = 0; i < layout.colspan; i++) {\r
+ if (grid.ColumnDefinitions().Size() == x + i) {\r
+ break;\r
+ }\r
+ adjust_col = x + i;\r
+ GridLength w = grid.ColumnDefinitions().GetAt(adjust_col).Width();\r
+ if (w.GridUnitType == GridUnitType::Star) {\r
+ adjust = false;\r
+ break;\r
+ }\r
+ }\r
+\r
+ if (adjust) {\r
+ gl.GridUnitType = GridUnitType::Star;\r
+ gl.Value = 1;\r
+ grid.ColumnDefinitions().GetAt(adjust_col).Width(gl);\r
+ }\r
+ }\r
+ }\r
+\r
+ // add control\r
+ if (hfill) {\r
+ control.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+ }\r
+ if (vfill) {\r
+ control.VerticalAlignment(VerticalAlignment::Stretch);\r
+ }\r
+\r
+ if (layout.colspan > 0) {\r
+ grid.SetColumnSpan(control, layout.colspan);\r
+ }\r
+ if (layout.rowspan > 0) {\r
+ grid.SetRowSpan(control, layout.rowspan);\r
+ }\r
+\r
+ grid.SetRow(control, y);\r
+ grid.SetColumn(control, x);\r
+ grid.Children().Append(control);\r
+\r
+ x++;\r
+\r
+ ui_reset_layout(layout);\r
+}\r
+\r
+// --------------------- UI Frame ---------------------\r
+\r
+UIWIDGET ui_frame_create(UiObject* obj, UiFrameArgs args) {\r
+ // create a grid for the frame, that contains the label and a sub-frame\r
+ Grid frame = Grid();\r
+\r
+ GridLength gl;\r
+ gl.GridUnitType = GridUnitType::Star;\r
+ gl.Value = 1;\r
+\r
+ ColumnDefinition coldef = ColumnDefinition();\r
+ coldef.Width(gl);\r
+ frame.ColumnDefinitions().Append(coldef);\r
+\r
+ RowDefinition rowdefFrame = RowDefinition();\r
+ rowdefFrame.Height(gl);\r
+\r
+ // label\r
+ int row = 0;\r
+ if (args.label) {\r
+ RowDefinition rowdefLabel = RowDefinition();\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+ gl.Value = 0;\r
+ rowdefLabel.Height(gl);\r
+ frame.RowDefinitions().Append(rowdefLabel);\r
+\r
+ TextBlock label = TextBlock();\r
+ wchar_t* wlabel = str2wstr(args.label, nullptr);\r
+ winrt::hstring hstr(wlabel);\r
+ label.Text(hstr);\r
+ free(wlabel);\r
+\r
+ frame.SetRow(label, row++);\r
+ frame.SetColumn(label, 0);\r
+ frame.Children().Append(label);\r
+ }\r
+\r
+ // workarea frame\r
+ frame.RowDefinitions().Append(rowdefFrame);\r
+\r
+ Grid workarea = Grid();\r
+ frame.SetRow(workarea, row);\r
+ frame.SetColumn(workarea, 0);\r
+ frame.Children().Append(workarea);\r
+\r
+ // some styling for the workarea\r
+ winrt::Microsoft::UI::Xaml::Media::SolidColorBrush brush{ winrt::Microsoft::UI::ColorHelper::FromArgb(150, 150, 150, 150) };\r
+ workarea.BorderBrush(brush);\r
+ CornerRadius radius{ 8, 8, 8, 8 };\r
+ Thickness t = { 1, 1, 1, 1 };\r
+ workarea.CornerRadius(radius);\r
+ workarea.BorderThickness(t);\r
+\r
+ Thickness padding = { 10, 10, 10, 10 };\r
+ workarea.Padding(padding);\r
+\r
+ // add frame to the parent container\r
+ UiObject* current = uic_current_obj(obj);\r
+ UI_APPLY_LAYOUT1(current, args);\r
+ current->container->Add(frame, true);\r
+\r
+ UIElement elm = frame;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+ // sub container\r
+ UiContainer* ctn = nullptr;\r
+ switch (args.subcontainer) {\r
+ default:\r
+ case UI_CONTAINER_VBOX: {\r
+ ctn = new UiBoxContainer(workarea, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);\r
+ break;\r
+ }\r
+ case UI_CONTAINER_HBOX: {\r
+ ctn = new UiBoxContainer(workarea, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);\r
+ break;\r
+ }\r
+ case UI_CONTAINER_GRID: {\r
+ ctn = new UiGridContainer(workarea, args.margin, args.columnspacing, args.rowspacing);\r
+ break;\r
+ }\r
+ }\r
+ ui_context_add_container_destructor(current->ctx, ctn);\r
+\r
+ UiObject* newobj = uic_object_new(obj, widget);\r
+ newobj->container = ctn;\r
+ uic_obj_add(obj, newobj);\r
+\r
+ return widget;\r
+}\r
+\r
+// --------------------- UI Expander ---------------------\r
+\r
+UIWIDGET ui_expander_create(UiObject* obj, UiFrameArgs args) {\r
+ Expander expander = Expander();\r
+ if (args.label) {\r
+ wchar_t* wlabel = str2wstr(args.label, nullptr);\r
+ expander.Header(box_value(wlabel));\r
+ free(wlabel);\r
+ }\r
+ expander.IsExpanded(args.isexpanded);\r
+\r
+ // add frame to the parent container\r
+ UiObject* current = uic_current_obj(obj);\r
+ UI_APPLY_LAYOUT1(current, args);\r
+ current->container->Add(expander, true);\r
+\r
+ UIElement elm = expander;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+ Grid content = Grid();\r
+ expander.Content(content);\r
+\r
+ UiContainer* ctn = nullptr;\r
+ switch (args.subcontainer) {\r
+ default: \r
+ case UI_CONTAINER_VBOX: {\r
+ ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);\r
+ break;\r
+ }\r
+ case UI_CONTAINER_HBOX: {\r
+ ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);\r
+ break;\r
+ }\r
+ case UI_CONTAINER_GRID: {\r
+ ctn = new UiGridContainer(content, args.margin, args.columnspacing, args.rowspacing);\r
+ break;\r
+ }\r
+ }\r
+ ui_context_add_container_destructor(current->ctx, ctn);\r
+\r
+ UiObject* newobj = uic_object_new(obj, widget);\r
+ newobj->container = ctn;\r
+ uic_obj_add(obj, newobj);\r
+\r
+ return widget;\r
+}\r
+\r
+// --------------------- UI ScrolledWindow ---------------------\r
+\r
+UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args) {\r
+ ScrollViewer scrollW = ScrollViewer();\r
+\r
+ // add frame to the parent container\r
+ UiObject* current = uic_current_obj(obj);\r
+ UI_APPLY_LAYOUT1(current, args);\r
+ current->container->Add(scrollW, true);\r
+\r
+ UIElement elm = scrollW;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+ // create child container\r
+ Grid content = Grid();\r
+ scrollW.Content(content);\r
+\r
+ UiContainer* ctn = nullptr;\r
+ switch (args.subcontainer) {\r
+ default:\r
+ case UI_CONTAINER_VBOX: {\r
+ ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);\r
+ break;\r
+ }\r
+ case UI_CONTAINER_HBOX: {\r
+ ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);\r
+ break;\r
+ }\r
+ case UI_CONTAINER_GRID: {\r
+ ctn = new UiGridContainer(content, args.margin, args.columnspacing, args.rowspacing);\r
+ break;\r
+ }\r
+ }\r
+ ui_context_add_container_destructor(current->ctx, ctn);\r
+\r
+ UiObject* newobj = uic_object_new(obj, widget);\r
+ newobj->container = ctn;\r
+ uic_obj_add(obj, newobj);\r
+\r
+ return widget;\r
+}\r
+\r
+// --------------------- UI TabView ---------------------\r
+\r
+UiTabViewContainer::UiTabViewContainer(UiTabView* tabview) {\r
+ this->tabview = tabview;\r
+}\r
+\r
+void UiTabViewContainer::Add(FrameworkElement control, UiBool fill) {\r
+ // noop\r
+}\r
+\r
+static UiObject* create_subcontainer_obj(UiObject* current, Grid subcontainer, UiSubContainerType type, int margin, int spacing, int columnspacing, int rowspacing) {\r
+ UiContainer* ctn = nullptr;\r
+ switch (type) {\r
+ default:\r
+ case UI_CONTAINER_VBOX: {\r
+ ctn = new UiBoxContainer(subcontainer, UI_BOX_CONTAINER_VBOX, margin, spacing);\r
+ break;\r
+ }\r
+ case UI_CONTAINER_HBOX: {\r
+ ctn = new UiBoxContainer(subcontainer, UI_BOX_CONTAINER_HBOX, margin, spacing);\r
+ break;\r
+ }\r
+ case UI_CONTAINER_GRID: {\r
+ ctn = new UiGridContainer(subcontainer, margin, columnspacing, rowspacing);\r
+ break;\r
+ }\r
+ }\r
+ ui_context_add_container_destructor(current->ctx, ctn);\r
+\r
+ UIElement elm = subcontainer;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ UiObject* newobj = uic_object_new(current, widget);\r
+ newobj->container = ctn;\r
+ return newobj;\r
+}\r
+\r
+static UiTabView* tabview_pivot_create(UiObject* obj, UiTabViewArgs args) {\r
+ Pivot pivot = Pivot();\r
+ UiPivotTabView* tabview = new UiPivotTabView(obj, pivot, args);\r
+\r
+ return tabview;\r
+}\r
+\r
+UiPivotTabView::UiPivotTabView(UiObject* obj, Pivot pivot, UiTabViewArgs args) {\r
+ this->current = obj;\r
+ this->pivot = pivot;\r
+ this->subcontainer = args.subcontainer;\r
+ this->margin = args.margin;\r
+ this->spacing = args.spacing;\r
+ this->columnspacing = args.columnspacing;\r
+ this->rowspacing = args.rowspacing;\r
+}\r
+\r
+UiObject* UiPivotTabView::AddTab(const char* label, int index) {\r
+ TextBlock text = TextBlock();\r
+ wchar_t* wlabel = str2wstr(label, nullptr);\r
+ winrt::hstring hstr(wlabel);\r
+ text.Text(hstr);\r
+ free(wlabel);\r
+\r
+ PivotItem item = PivotItem();\r
+ item.Header(text);\r
+\r
+ // sub container\r
+ Grid subcontainer = Grid();\r
+ item.Content(subcontainer);\r
+ pivot.Items().Append(item);\r
+\r
+ return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);\r
+}\r
+\r
+void UiPivotTabView::Remove(int index) {\r
+ pivot.Items().RemoveAt(index);\r
+}\r
+\r
+void UiPivotTabView::Select(int index) {\r
+ \r
+}\r
+\r
+FrameworkElement UiPivotTabView::GetFrameworkElement() {\r
+ return pivot;\r
+}\r
+\r
+\r
+static UiTabView* tabview_invisible_create(UiObject *obj, UiTabViewArgs args) {\r
+ Grid container = Grid();\r
+ container.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+ container.VerticalAlignment(VerticalAlignment::Stretch);\r
+ UiInvisibleTabView *tabview = new UiInvisibleTabView(obj, container, args);\r
+ return tabview;\r
+}\r
+\r
+UiInvisibleTabView::UiInvisibleTabView(UiObject* obj, Grid container, UiTabViewArgs args) {\r
+ this->current = obj;\r
+ this->container = container;\r
+ this->subcontainer = args.subcontainer;\r
+ this->margin = args.margin;\r
+ this->spacing = args.spacing;\r
+ this->columnspacing = args.columnspacing;\r
+ this->rowspacing = args.rowspacing;\r
+ this->currentIndex = -1;\r
+\r
+ GridLength gl;\r
+ gl.GridUnitType = GridUnitType::Star;\r
+ gl.Value = 1;\r
+\r
+ ColumnDefinition coldef = ColumnDefinition();\r
+ coldef.Width(gl);\r
+ container.ColumnDefinitions().Append(coldef);\r
+\r
+ RowDefinition rowdef = RowDefinition();\r
+ rowdef.Height(gl);\r
+ container.RowDefinitions().Append(rowdef);\r
+}\r
+\r
+UiObject* UiInvisibleTabView::AddTab(const char* label, int index) {\r
+ Grid subcontainer = Grid();\r
+ subcontainer.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+ subcontainer.VerticalAlignment(VerticalAlignment::Stretch);\r
+ \r
+ if (pages.size() == 0) {\r
+ container.Children().Append(subcontainer);\r
+ currentIndex = 0;\r
+ }\r
+\r
+ if (index < 0) {\r
+ pages.push_back(subcontainer);\r
+ } else {\r
+ pages.insert(pages.begin() + index, subcontainer);\r
+ }\r
+\r
+ // sub container\r
+ return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);\r
+}\r
+\r
+void UiInvisibleTabView::Remove(int index) {\r
+ \r
+}\r
+\r
+void UiInvisibleTabView::Select(int index) {\r
+ if (index >= 0 && index < pages.size()) {\r
+ if (currentIndex != -1) {\r
+ container.Children().RemoveAt(0);\r
+ }\r
+ \r
+ container.Children().Append(pages.at(index));\r
+ }\r
+}\r
+\r
+FrameworkElement UiInvisibleTabView::GetFrameworkElement() {\r
+ return container;\r
+}\r
+\r
+\r
+static UiTabView* tabview_main_create(UiObject* obj, UiTabViewArgs args) {\r
+ TabView tabview = TabView();\r
+ tabview.IsAddTabButtonVisible(false);\r
+ //tabview.CanDragTabs(false);\r
+ //tabview.CanReorderTabs(false);\r
+ UiMainTabView* uitabview = new UiMainTabView(obj, tabview, args);\r
+\r
+ return uitabview;\r
+}\r
+\r
+UiMainTabView::UiMainTabView(UiObject* obj, TabView tabview, UiTabViewArgs args) {\r
+ this->current = obj;\r
+ this->tabview = tabview;\r
+ this->subcontainer = args.subcontainer;\r
+ this->margin = args.margin;\r
+ this->spacing = args.spacing;\r
+ this->columnspacing = args.columnspacing;\r
+ this->rowspacing = args.rowspacing;\r
+}\r
+\r
+UiObject* UiMainTabView::AddTab(const char* label, int index) {\r
+ TextBlock text = TextBlock();\r
+ wchar_t* wlabel = str2wstr(label, nullptr);\r
+ winrt::hstring hstr(wlabel);\r
+ text.Text(hstr);\r
+ free(wlabel);\r
+\r
+ TabViewItem item = TabViewItem();\r
+ item.Header(text);\r
+ item.CanDrag(false);\r
+ item.IsClosable(false);\r
+\r
+ // sub container\r
+ Grid subcontainer = Grid();\r
+ item.Content(subcontainer);\r
+ tabview.TabItems().Append(item);\r
+\r
+ return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);\r
+}\r
+\r
+void UiMainTabView::Remove(int index) {\r
+ this->tabview.TabItems().RemoveAt(index);\r
+}\r
+\r
+void UiMainTabView::Select(int index) {\r
+\r
+}\r
+\r
+FrameworkElement UiMainTabView::GetFrameworkElement() {\r
+ return tabview;\r
+}\r
+\r
+\r
+static UiTabView* tabview_navigationview_create(UiObject* obj, UiTabViewArgs args, UiTabViewType type) {\r
+ NavigationView navigationview = NavigationView();\r
+ UiNavigationTabView* tabview = new UiNavigationTabView(obj, navigationview, args, type);\r
+ navigationview.IsBackButtonVisible(NavigationViewBackButtonVisible::Collapsed);\r
+ navigationview.IsSettingsVisible(false);\r
+\r
+ return tabview;\r
+}\r
+\r
+UiNavigationTabView::UiNavigationTabView(UiObject* obj, NavigationView navigationview, UiTabViewArgs args, UiTabViewType type) {\r
+ this->current = obj;\r
+ this->navigationview = navigationview;\r
+ this->type = type;\r
+ this->margin = args.margin;\r
+ this->spacing = args.spacing;\r
+ this->columnspacing = args.columnspacing;\r
+ this->rowspacing = args.rowspacing;\r
+\r
+ if (type == UI_TABVIEW_NAVIGATION_TOP) {\r
+ navigationview.PaneDisplayMode(NavigationViewPaneDisplayMode::Top);\r
+ }\r
+\r
+ navigationview.SelectionChanged({ this, &UiNavigationTabView::SelectionChanged });\r
+}\r
+\r
+UiObject* UiNavigationTabView::AddTab(const char* label, int index1) {\r
+ TextBlock text = TextBlock();\r
+ wchar_t* wlabel = str2wstr(label, nullptr);\r
+ winrt::hstring hstr(wlabel);\r
+ text.Text(hstr);\r
+ free(wlabel);\r
+\r
+ NavigationViewItem item = NavigationViewItem();\r
+ item.Content(text);\r
+\r
+ // sub container\r
+ Grid subcontainer = Grid();\r
+ if (pages.size() == 0) {\r
+ navigationview.Content(subcontainer);\r
+ navigationview.SelectedItem(item);\r
+ }\r
+\r
+ navigationview.MenuItems().Append(item);\r
+ auto page = std::tuple<NavigationViewItem, FrameworkElement>{ item, subcontainer };\r
+ pages.push_back(page);\r
+\r
+ return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);\r
+}\r
+\r
+void UiNavigationTabView::Remove(int index) {\r
+ navigationview.MenuItems().RemoveAt(index);\r
+ pages.erase(pages.begin() + index);\r
+}\r
+\r
+void UiNavigationTabView::Select(int index) {\r
+\r
+}\r
+\r
+FrameworkElement UiNavigationTabView::GetFrameworkElement() {\r
+ return navigationview;\r
+}\r
+\r
+void UiNavigationTabView::SelectionChanged(NavigationView const& sender, NavigationViewSelectionChangedEventArgs const& args) {\r
+ for (auto page : pages) {\r
+ NavigationViewItem item = std::get<0>(page);\r
+ FrameworkElement elm = std::get<1>(page);\r
+ if (item == navigationview.SelectedItem()) {\r
+ navigationview.Content(elm);\r
+ break;\r
+ }\r
+ }\r
+}\r
+\r
+static int64_t ui_tabview_get(UiInteger *i) {\r
+ return 0;\r
+}\r
+\r
+static void ui_tabview_set(UiInteger *i, int64_t value) {\r
+ UiTabView *tabview = (UiTabView*)i->obj;\r
+ tabview->Select(value);\r
+}\r
+\r
+UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs args) {\r
+ UiTabViewType type = args.tabview == UI_TABVIEW_DEFAULT ? UI_TABVIEW_NAVIGATION_TOP2 : args.tabview;\r
+ UiTabView* tabview = nullptr;\r
+ switch (type) {\r
+ default: {\r
+ tabview = tabview_pivot_create(obj, args);\r
+ break;\r
+ }\r
+ case UI_TABVIEW_DOC: { \r
+ tabview = tabview_main_create(obj, args);\r
+ break;\r
+ }\r
+ case UI_TABVIEW_NAVIGATION_SIDE: { \r
+ tabview = tabview_navigationview_create(obj, args, type);\r
+ break;\r
+ }\r
+ case UI_TABVIEW_NAVIGATION_TOP: { \r
+ tabview = tabview_navigationview_create(obj, args, type);\r
+ break;\r
+ }\r
+ case UI_TABVIEW_NAVIGATION_TOP2: { \r
+ tabview = tabview_pivot_create(obj, args);\r
+ break;\r
+ }\r
+ case UI_TABVIEW_INVISIBLE: {\r
+ tabview = tabview_invisible_create(obj, args);\r
+ break;\r
+ }\r
+ }\r
+ UiTabViewContainer* ctn = new UiTabViewContainer(tabview);\r
+\r
+ // add frame to the parent container\r
+ UiObject* current = uic_current_obj(obj);\r
+ UI_APPLY_LAYOUT1(current, args);\r
+ current->container->Add(tabview->GetFrameworkElement(), true);\r
+\r
+ UIElement elm = tabview->GetFrameworkElement();\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ widget->data1 = tabview;\r
+\r
+ // TODO: add tabview destructor\r
+\r
+ // bind variable\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);\r
+ if (var) {\r
+ UiInteger *i = (UiInteger*)var->value;\r
+ i->obj = tabview;\r
+ i->get = ui_tabview_get;\r
+ i->set = ui_tabview_set;\r
+ }\r
+\r
+ UiObject* newobj = uic_object_new(obj, widget);\r
+ newobj->container = ctn;\r
+ uic_obj_add(obj, newobj);\r
+\r
+ return widget;\r
+}\r
+\r
+void ui_tab_create(UiObject* obj, const char* title) {\r
+ UiObject* current = uic_current_obj(obj);\r
+ UiTabView* tabview = (UiTabView*)current->widget->data1;\r
+ UiObject* newobj = tabview->AddTab(title);\r
+ uic_obj_add(current, newobj);\r
+}\r
+\r
+UIEXPORT void ui_tabview_select(UIWIDGET tabview, int tab) {\r
+ UiTabView* t = (UiTabView*)tabview->data1;\r
+ t->Select(tab);\r
+}\r
+\r
+UIEXPORT void ui_tabview_remove(UIWIDGET tabview, int tab) {\r
+ UiTabView* t = (UiTabView*)tabview->data1;\r
+ t->Remove(tab);\r
+}\r
+\r
+UIEXPORT UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) {\r
+ UiTabView* t = (UiTabView*)tabview->data1;\r
+ UiObject* newobj = t->AddTab(name, tab_index);\r
+ return newobj;\r
+}\r
+\r
+\r
+\r
+// --------------------- UI Headerbar ---------------------\r
+\r
+// TODO: replace placeholder implementation\r
+\r
+UIEXPORT UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) {\r
+ UiContainerArgs boxargs = { };\r
+ boxargs.fill = UI_OFF;\r
+ return ui_hbox_create(obj, boxargs);\r
+}\r
+\r
+UIEXPORT void ui_headerbar_start_create(UiObject *obj) {\r
+ UiContainerArgs boxargs = { };\r
+ boxargs.fill = UI_OFF;\r
+ ui_hbox_create(obj, boxargs);\r
+}\r
+\r
+UIEXPORT void ui_headerbar_center_create(UiObject *obj) {\r
+ UiContainerArgs boxargs = { };\r
+ boxargs.fill = UI_OFF;\r
+ ui_hbox_create(obj, boxargs);\r
+}\r
+\r
+UIEXPORT void ui_headerbar_end_create(UiObject *obj) {\r
+ UiContainerArgs boxargs = { };\r
+ boxargs.fill = UI_OFF;\r
+ ui_hbox_create(obj, boxargs);\r
+}\r
+\r
+\r
+/*\r
+* -------------------- Layout Functions --------------------\r
+*\r
+* functions for setting layout attributes for the current container\r
+*\r
+*/\r
+\r
+void ui_layout_fill(UiObject* obj, UiBool fill) {\r
+ UiContainer* ct = uic_get_current_container(obj);\r
+ ct->layout.fill = ui_bool2lb(fill);\r
+}\r
+\r
+void ui_layout_hexpand(UiObject* obj, UiBool expand) {\r
+ UiContainer* ct = uic_get_current_container(obj);\r
+ ct->layout.hexpand = expand;\r
+}\r
+\r
+void ui_layout_vexpand(UiObject* obj, UiBool expand) {\r
+ UiContainer* ct = uic_get_current_container(obj);\r
+ ct->layout.vexpand = expand;\r
+}\r
+\r
+void ui_layout_hfill(UiObject* obj, UiBool fill) {\r
+ UiContainer* ct = uic_get_current_container(obj);\r
+ ct->layout.hfill = fill;\r
+}\r
+\r
+void ui_layout_vfill(UiObject* obj, UiBool fill) {\r
+ UiContainer* ct = uic_get_current_container(obj);\r
+ ct->layout.vfill = fill;\r
+}\r
+\r
+void ui_layout_width(UiObject* obj, int width) {\r
+ UiContainer* ct = uic_get_current_container(obj);\r
+ ct->layout.width = width;\r
+}\r
+\r
+void ui_layout_height(UiObject* obj, int height) {\r
+ UiContainer* ct = uic_get_current_container(obj);\r
+ ct->layout.height = height;\r
+}\r
+\r
+void ui_layout_colspan(UiObject* obj, int cols) {\r
+ UiContainer* ct = uic_get_current_container(obj);\r
+ ct->layout.colspan = cols;\r
+}\r
+\r
+void ui_layout_rowspan(UiObject* obj, int rows) {\r
+ UiContainer* ct = uic_get_current_container(obj);\r
+ ct->layout.rowspan = rows;\r
+}\r
+\r
+void ui_newline(UiObject* obj) {\r
+ UiContainer* ct = uic_get_current_container(obj);\r
+ ct->layout.newline = TRUE;\r
+}\r
+\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "toolkit.h"\r
+#include "../ui/container.h"\r
+\r
+#include <Windows.h>\r
+#undef GetCurrentTime\r
+#include <winrt/Windows.Foundation.Collections.h>\r
+#include <winrt/Windows.UI.Xaml.Interop.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>\r
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>\r
+#include <winrt/Microsoft.UI.Xaml.Markup.h>\r
+\r
+\r
+#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout))\r
+#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)\r
+#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)\r
+\r
+typedef struct UiLayout UiLayout;\r
+typedef enum UiLayoutBool UiLayoutBool;\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Windows::UI::Xaml::Interop;\r
+\r
+enum UiLayoutBool {\r
+ UI_LAYOUT_UNDEFINED = 0,\r
+ UI_LAYOUT_TRUE,\r
+ UI_LAYOUT_FALSE,\r
+};\r
+\r
+struct UiLayout {\r
+ UiLayoutBool fill;\r
+ UiBool newline;\r
+ char* label;\r
+ UiBool hexpand;\r
+ UiBool vexpand;\r
+ UiBool hfill;\r
+ UiBool vfill;\r
+ int width;\r
+ int height;\r
+ int colspan;\r
+ int rowspan;\r
+};\r
+\r
+struct UiContainer {\r
+ UiLayout layout;\r
+ int close = 0;\r
+\r
+ virtual void Add(FrameworkElement control, UiBool fill) = 0;\r
+};\r
+\r
+enum UiBoxContainerType {\r
+ UI_BOX_CONTAINER_VBOX = 0,\r
+ UI_BOX_CONTAINER_HBOX\r
+};\r
+\r
+enum UiNavigationViewType {\r
+ UI_NAVIGATIONVIEW_TOP = 0,\r
+ UI_NAVIGATIONVIEW_SIDE\r
+};\r
+\r
+struct UiBoxContainer : UiContainer {\r
+ Grid grid;\r
+ enum UiBoxContainerType type;\r
+ RowDefinition boxRowDef;\r
+ ColumnDefinition boxColDef;\r
+\r
+ UiBoxContainer(Grid grid, enum UiBoxContainerType type, int margin, int spacing);\r
+\r
+ void Add(FrameworkElement control, UiBool fill);\r
+};\r
+\r
+struct UiGridContainer : UiContainer {\r
+ Grid grid;\r
+ int x = 0;\r
+ int y = -1;\r
+ int cols = 0;\r
+\r
+ UiGridContainer(Grid grid, int margin, int columnspacing, int rowspacing);\r
+\r
+ void Add(FrameworkElement control, UiBool fill);\r
+};\r
+\r
+struct UiTabView {\r
+ UiObject* current;\r
+ UiSubContainerType subcontainer;\r
+ int margin;\r
+ int spacing;\r
+ int columnspacing;\r
+ int rowspacing;\r
+\r
+ virtual UiObject* AddTab(const char* label, int index = -1) = 0;\r
+ virtual void Remove(int index) = 0;\r
+ virtual void Select(int index) = 0;\r
+ virtual FrameworkElement GetFrameworkElement() = 0;\r
+};\r
+\r
+struct UiTabViewContainer : UiContainer {\r
+ UiTabView* tabview;\r
+\r
+ UiTabViewContainer(UiTabView* tabview);\r
+\r
+ void Add(FrameworkElement control, UiBool fill);\r
+};\r
+\r
+struct UiPivotTabView : UiTabView {\r
+ Pivot pivot;\r
+\r
+ UiPivotTabView(UiObject *obj, Pivot pivot, UiTabViewArgs args);\r
+\r
+ UiObject* AddTab(const char* label, int index = -1);\r
+ void Remove(int index);\r
+ void Select(int index);\r
+ FrameworkElement GetFrameworkElement();\r
+};\r
+\r
+struct UiMainTabView : UiTabView {\r
+ TabView tabview;\r
+\r
+ UiMainTabView(UiObject* obj, TabView tabview, UiTabViewArgs args);\r
+\r
+ UiObject* AddTab(const char* label, int index = -1);\r
+ void Remove(int index);\r
+ void Select(int index);\r
+ FrameworkElement GetFrameworkElement();\r
+};\r
+\r
+struct UiNavigationTabView : UiTabView {\r
+ NavigationView navigationview;\r
+ UiTabViewType type;\r
+ std::vector<std::tuple<NavigationViewItem, FrameworkElement> > pages;\r
+\r
+ UiNavigationTabView(UiObject* obj, NavigationView navigationview, UiTabViewArgs args, UiTabViewType type);\r
+\r
+ UiObject* AddTab(const char* label, int index = -1);\r
+ void Remove(int index);\r
+ void Select(int index);\r
+ FrameworkElement GetFrameworkElement();\r
+\r
+ void SelectionChanged(NavigationView const& sender, NavigationViewSelectionChangedEventArgs const& args);\r
+};\r
+\r
+struct UiInvisibleTabView : UiTabView {\r
+ Grid container;\r
+ std::vector<FrameworkElement> pages;\r
+ int currentIndex;\r
+\r
+ UiInvisibleTabView(UiObject *obj, Grid container, UiTabViewArgs args);\r
+\r
+ UiObject* AddTab(const char* label, int index = -1);\r
+ void Remove(int index);\r
+ void Select(int index);\r
+ FrameworkElement GetFrameworkElement();\r
+};
\ No newline at end of file
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "dnd.h"\r
+#include "util.h"\r
+\r
+#include <thread>\r
+\r
+using namespace winrt;\r
+using namespace Windows::ApplicationModel::DataTransfer;\r
+using namespace Windows::Storage;\r
+using namespace Windows::Storage::Streams;\r
+\r
+UIEXPORT void ui_selection_settext(UiDnD* dnd, char* str, int len) {\r
+ if (dnd->data) {\r
+ if (len < 0) {\r
+ len = strlen(str);\r
+ }\r
+ wchar_t *wstr = str2wstr_len(str, len, nullptr);\r
+\r
+ dnd->data.SetText(wstr);\r
+\r
+ free(wstr);\r
+\r
+ }\r
+}\r
+\r
+UIEXPORT void ui_selection_seturis(UiDnD* dnd, char** uris, int nelm) {\r
+\r
+}\r
+\r
+\r
+UIEXPORT char* ui_selection_gettext(UiDnD* dnd) {\r
+ return nullptr;\r
+}\r
+\r
+\r
+UIEXPORT UiFileList ui_selection_geturis(UiDnD *dnd) {\r
+ UiFileList flist;\r
+ flist.files = nullptr;\r
+ flist.nfiles = 0;\r
+\r
+ if (dnd->dataview.Contains(StandardDataFormats::StorageItems())) {\r
+ UiFileList *flist_ptr = &flist;\r
+\r
+ // we need to execute this in a different thread\r
+ // this could block the main gui thread, but shouldn't happen with a simple uri list\r
+ std::thread getDataThread([dnd, flist_ptr]() {\r
+ auto items = dnd->dataview.GetStorageItemsAsync().get();\r
+\r
+ char **uris = (char**)calloc(items.Size(), sizeof(char*));\r
+ flist_ptr->files = uris;\r
+ flist_ptr->nfiles = items.Size();\r
+\r
+ int i = 0;\r
+ for (IStorageItem const& item : items) {\r
+ winrt::hstring path = item.Path();\r
+ uris[i++] = wchar2utf8(path.c_str(), path.size());\r
+ }\r
+ });\r
+ getDataThread.join();\r
+ }\r
+ return flist;\r
+}\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/dnd.h"\r
+\r
+struct UiDnD {\r
+ int evttype = 0;\r
+ winrt::Microsoft::UI::Xaml::DragStartingEventArgs dndstartargs = { nullptr };\r
+ winrt::Microsoft::UI::Xaml::DropCompletedEventArgs dndcompletedargs = { nullptr };\r
+ winrt::Microsoft::UI::Xaml::DragEventArgs drageventargs = { nullptr };\r
+ winrt::Windows::ApplicationModel::DataTransfer::DataPackage data = { nullptr };\r
+ winrt::Windows::ApplicationModel::DataTransfer::DataPackageView dataview = { nullptr };\r
+};\r
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "pch.h"
+
+#include "icons.h"
+#include "../ui/icons.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+#include <Windows.h>
+#include <Shellapi.h>
+
+
+using namespace winrt;
+using namespace Microsoft::UI::Xaml;
+using namespace Microsoft::UI::Xaml::Controls;
+using namespace Windows::UI::Xaml::Interop;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;
+using namespace winrt::Microsoft::UI::Xaml::Media::Imaging;
+//using namespace Windows::Storage::Streams;
+
+static UiIcon* sys_folder_icon16;
+static UiIcon* sys_file_icon16;
+
+static UiIcon* sys_folder_icon32;
+static UiIcon* sys_file_icon32;
+
+std::unordered_map<std::string, Symbol> ui_symbol_icons = {
+ {"Accept", Symbol::Accept },
+ {"Account", Symbol::Account },
+ {"Add", Symbol::Add },
+ {"AddFriend", Symbol::AddFriend },
+ {"Admin", Symbol::Admin },
+ {"AlignCenter", Symbol::AlignCenter },
+ {"AlignLeft", Symbol::AlignLeft },
+ {"AlignRight", Symbol::AlignRight },
+ {"AllApps", Symbol::AllApps },
+ {"Attach", Symbol::Attach },
+ {"AttachCamera", Symbol::AttachCamera },
+ {"Audio", Symbol::Audio },
+ {"Back", Symbol::Back },
+ {"BackToWindow", Symbol::BackToWindow },
+ {"BlockContact", Symbol::BlockContact },
+ {"Bold", Symbol::Bold },
+ {"Bookmarks", Symbol::Bookmarks },
+ {"BrowsePhotos", Symbol::BrowsePhotos },
+ {"Bullets", Symbol::Bullets },
+ {"Calculator", Symbol::Calculator },
+ {"Calendar", Symbol::Calendar },
+ {"CalendarDay", Symbol::CalendarDay },
+ {"CalendarReply", Symbol::CalendarReply },
+ {"CalendarWeek", Symbol::CalendarWeek },
+ {"Camera", Symbol::Camera },
+ {"Cancel", Symbol::Cancel },
+ {"Caption", Symbol::Caption },
+ {"CellPhone", Symbol::CellPhone },
+ {"Character", Symbol::Character },
+ {"Clear", Symbol::Clear },
+ {"ClearSelection", Symbol::ClearSelection },
+ {"Clock", Symbol::Clock },
+ {"ClosedCaption", Symbol::ClosedCaption },
+ {"ClosePane", Symbol::ClosePane },
+ {"Comment", Symbol::Comment },
+ {"Contact", Symbol::Contact },
+ {"Contact2", Symbol::Contact2 },
+ {"ContactInfo", Symbol::ContactInfo },
+ {"ContactPresence", Symbol::ContactPresence },
+ {"Copy", Symbol::Copy },
+ {"Crop", Symbol::Crop },
+ {"Cut", Symbol::Cut },
+ {"Delete", Symbol::Delete },
+ {"Directions", Symbol::Directions },
+ {"DisableUpdates", Symbol::DisableUpdates },
+ {"DisconnectDrive", Symbol::DisconnectDrive },
+ {"Dislike", Symbol::Dislike },
+ {"DockBottom", Symbol::DockBottom },
+ {"DockLeft", Symbol::DockLeft },
+ {"DockRight", Symbol::DockRight },
+ {"Document", Symbol::Document },
+ {"Download", Symbol::Download },
+ {"Edit", Symbol::Edit },
+ {"Emoji", Symbol::Emoji },
+ {"Emoji2", Symbol::Emoji2 },
+ {"Favorite", Symbol::Favorite },
+ {"Filter", Symbol::Filter },
+ {"Find", Symbol::Find },
+ {"Flag", Symbol::Flag },
+ {"Folder", Symbol::Folder },
+ {"Font", Symbol::Font },
+ {"FontColor", Symbol::FontColor },
+ {"FontDecrease", Symbol::FontDecrease },
+ {"FontIncrease", Symbol::FontIncrease },
+ {"FontSize", Symbol::FontSize },
+ {"Forward", Symbol::Forward },
+ {"FourBars", Symbol::FourBars },
+ {"FullScreen", Symbol::FullScreen },
+ {"GlobalNavigationButton", Symbol::GlobalNavigationButton },
+ {"Globe", Symbol::Globe },
+ {"Go", Symbol::Go },
+ {"GoToStart", Symbol::GoToStart },
+ {"GoToToday", Symbol::GoToToday },
+ {"HangUp", Symbol::HangUp },
+ {"Help", Symbol::Help },
+ {"HideBcc", Symbol::HideBcc },
+ {"Highlight", Symbol::Highlight },
+ {"Home", Symbol::Home },
+ {"Import", Symbol::Import },
+ {"ImportAll", Symbol::ImportAll },
+ {"Important", Symbol::Important },
+ {"Italic", Symbol::Italic },
+ {"Keyboard", Symbol::Keyboard },
+ {"LeaveChat", Symbol::LeaveChat },
+ {"Library", Symbol::Library },
+ {"Like", Symbol::Like },
+ {"LikeDislike", Symbol::LikeDislike },
+ {"Link", Symbol::Link },
+ {"List", Symbol::List },
+ {"Mail", Symbol::Mail },
+ {"MailFilled", Symbol::MailFilled },
+ {"MailForward", Symbol::MailForward },
+ {"MailReply", Symbol::MailReply },
+ {"MailReplyAll", Symbol::MailReplyAll },
+ {"Manage", Symbol::Manage },
+ {"Map", Symbol::Map },
+ {"MapDrive", Symbol::MapDrive },
+ {"MapPin", Symbol::MapPin },
+ {"Memo", Symbol::Memo },
+ {"Message", Symbol::Message },
+ {"Microphone", Symbol::Microphone },
+ {"More", Symbol::More },
+ {"MoveToFolder", Symbol::MoveToFolder },
+ {"MusicInfo", Symbol::MusicInfo },
+ {"Mute", Symbol::Mute },
+ {"NewFolder", Symbol::NewFolder },
+ {"NewWindow", Symbol::NewWindow },
+ {"Next", Symbol::Next },
+ {"OneBar", Symbol::OneBar },
+ {"OpenFile", Symbol::OpenFile },
+ {"OpenLocal", Symbol::OpenLocal },
+ {"OpenPane", Symbol::OpenPane },
+ {"OpenWith", Symbol::OpenWith },
+ {"Orientation", Symbol::Orientation },
+ {"OtherUser", Symbol::OtherUser },
+ {"OutlineStar", Symbol::OutlineStar },
+ {"Page", Symbol::Page },
+ {"Page2", Symbol::Page2 },
+ {"Paste", Symbol::Paste },
+ {"Pause", Symbol::Pause },
+ {"People", Symbol::People },
+ {"Permissions", Symbol::Permissions },
+ {"Phone", Symbol::Phone },
+ {"PhoneBook", Symbol::PhoneBook },
+ {"Pictures", Symbol::Pictures },
+ {"Pin", Symbol::Pin },
+ {"Placeholder", Symbol::Placeholder },
+ {"Play", Symbol::Play },
+ {"PostUpdate", Symbol::PostUpdate },
+ {"Preview", Symbol::Preview },
+ {"PreviewLink", Symbol::PreviewLink },
+ {"Previous", Symbol::Previous },
+ {"Print", Symbol::Print },
+ {"Priority", Symbol::Priority },
+ {"ProtectedDocument", Symbol::ProtectedDocument },
+ {"Read", Symbol::Read },
+ {"Redo", Symbol::Redo },
+ {"Refresh", Symbol::Refresh },
+ {"Remote", Symbol::Remote },
+ {"Remove", Symbol::Remove },
+ {"Rename", Symbol::Rename },
+ {"Repair", Symbol::Repair },
+ {"RepeatAll", Symbol::RepeatAll },
+ {"RepeatOne", Symbol::RepeatOne },
+ {"ReportHacked", Symbol::ReportHacked },
+ {"ReShare", Symbol::ReShare },
+ {"Rotate", Symbol::Rotate },
+ {"RotateCamera", Symbol::RotateCamera },
+ {"Save", Symbol::Save },
+ {"SaveLocal", Symbol::SaveLocal },
+ {"Scan", Symbol::Scan },
+ {"SelectAll", Symbol::SelectAll },
+ {"Send", Symbol::Send },
+ {"SetLockScreen", Symbol::SetLockScreen },
+ {"SetTile", Symbol::SetTile },
+ {"Setting", Symbol::Setting },
+ {"Share", Symbol::Share },
+ {"Shop", Symbol::Shop },
+ {"ShowBcc", Symbol::ShowBcc },
+ {"ShowResults", Symbol::ShowResults },
+ {"Shuffle", Symbol::Shuffle },
+ {"SlideShow", Symbol::SlideShow },
+ {"SolidStar", Symbol::SolidStar },
+ {"Sort", Symbol::Sort },
+ {"Stop", Symbol::Stop },
+ {"StopSlideShow", Symbol::StopSlideShow },
+ {"Street", Symbol::Street },
+ {"Switch", Symbol::Switch },
+ {"SwitchApps", Symbol::SwitchApps },
+ {"Sync", Symbol::Sync },
+ {"SyncFolder", Symbol::SyncFolder },
+ {"Tag", Symbol::Tag },
+ {"Target", Symbol::Target },
+ {"ThreeBars", Symbol::ThreeBars },
+ {"TouchPointer", Symbol::TouchPointer },
+ {"Trim", Symbol::Trim },
+ {"TwoBars", Symbol::TwoBars },
+ {"TwoPage", Symbol::TwoPage },
+ {"Underline", Symbol::Underline },
+ {"Undo", Symbol::Undo },
+ {"UnFavorite", Symbol::UnFavorite },
+ {"UnPin", Symbol::UnPin },
+ {"UnSyncFolder", Symbol::UnSyncFolder },
+ {"Up", Symbol::Up },
+ {"Upload", Symbol::Upload },
+ {"Video", Symbol::Video },
+ {"VideoChat", Symbol::VideoChat },
+ {"View", Symbol::View },
+ {"ViewAll", Symbol::ViewAll },
+ {"Volume", Symbol::Volume },
+ {"WebCam", Symbol::WebCam },
+ {"World", Symbol::World },
+ {"XboxOneConsole", Symbol::XboxOneConsole },
+ {"ZeroBars", Symbol::ZeroBars },
+ {"Zoom", Symbol::Zoom },
+ {"ZoomIn", Symbol::ZoomIn },
+ {"ZoomOut", Symbol::ZoomOut }
+};
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement ui_get_icon(const char* name) {
+ if (ui_symbol_icons.find(name) == ui_symbol_icons.end()) {
+ SymbolIcon no_icon = { nullptr };
+ return no_icon;
+ }
+
+ Symbol symbol = ui_symbol_icons[name];
+ SymbolIcon icon = SymbolIcon(symbol);
+ return icon;
+}
+
+
+// symbol icon implementation
+UiSymbolIcon::UiSymbolIcon(winrt::Microsoft::UI::Xaml::Controls::Symbol sym) {
+ symbol = sym;
+}
+
+UiSymbolIcon::~UiSymbolIcon() {
+
+}
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement UiSymbolIcon::getIcon() {
+ return SymbolIcon(symbol);
+}
+
+// image icon implementation
+UiImageIcon::UiImageIcon(const char* uristr) {
+ wchar_t* wuri = str2wstr(uristr, nullptr);
+ Windows::Foundation::Uri uri{ wuri };
+ this->uri = uri;
+ free(wuri);
+}
+
+UiImageIcon::~UiImageIcon() {
+
+}
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement UiImageIcon::getIcon() {
+ BitmapIcon icon = BitmapIcon();
+ icon.UriSource(uri);
+ ImageIcon img = ImageIcon();
+ img.Source();
+ return icon;
+}
+
+// bitmap icon implementation
+UiBitmapIcon::UiBitmapIcon(winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap) {
+ this->bitmap = bitmap;
+}
+
+UiBitmapIcon::~UiBitmapIcon() {
+
+}
+
+winrt::Microsoft::UI::Xaml::Controls::IconElement UiBitmapIcon::getIcon() {
+ ImageIcon icon = ImageIcon();
+ icon.Source(bitmap);
+ return icon;
+}
+
+UIEXPORT UiIcon* ui_icon(const char* name, size_t size) {
+ Symbol symbol = ui_symbol_icons[name];
+ UiSymbolIcon* icon = new UiSymbolIcon(symbol);
+ return icon;
+}
+
+
+UIEXPORT UiIcon* ui_imageicon(const char* file) {
+ return new UiImageIcon(file);
+}
+
+UIEXPORT void ui_icon_free(UiIcon* icon) {
+ delete icon;
+}
+
+
+struct __declspec(uuid("905a0fef-bc53-11df-8c49-001e4fc686da")) IBufferByteAccess : ::IUnknown
+{
+ virtual HRESULT __stdcall Buffer(uint8_t** value) = 0;
+};
+
+
+
+winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap ui_dllicon2bitmap(const char* dll, int iconindex, bool large) {
+ WriteableBitmap wbitmap = { nullptr };
+
+ // get the icon from the dll
+ HICON hicon_small;
+ HICON hicon_large;
+ if (ExtractIconExA(dll, iconindex, &hicon_large, &hicon_small, 1) > 0) {
+ HICON hicon = large ? hicon_large : hicon_small;
+
+ // convert icon to (gdi) bitmap
+ ICONINFO info;
+ info.hbmColor = nullptr;
+ info.hbmMask = nullptr;
+ if (GetIconInfo(hicon, &info)) {
+ BITMAP bitmap;
+ if (GetObjectW(info.hbmColor, sizeof(BITMAP), &bitmap) != 0) {
+ size_t bitmap_size = bitmap.bmWidthBytes * bitmap.bmHeight;
+ char *bitmap_data = (char*)malloc(bitmap_size);
+
+ // get the pixel data
+ if (GetBitmapBits(info.hbmColor, bitmap_size, bitmap_data) != 0) {
+ WriteableBitmap wb = WriteableBitmap(bitmap.bmWidth, bitmap.bmHeight);
+ void *wb_data = wb.PixelBuffer().data();
+ memcpy(wb_data, bitmap_data, bitmap_size);
+ wbitmap = wb;
+ }
+ free(bitmap_data);
+ }
+ if (info.hbmMask) {
+ DeleteObject(info.hbmMask);
+ }
+ if (info.hbmColor) {
+ DeleteObject(info.hbmColor);
+ }
+ }
+
+ DestroyIcon(hicon_small);
+ DestroyIcon(hicon_large);
+ }
+
+ return wbitmap;
+}
+
+UiIcon* ui_dllicon(const char* dll, int iconindex, bool large) {
+ WriteableBitmap wbitmap = ui_dllicon2bitmap(dll, iconindex, large);
+ return new UiBitmapIcon(wbitmap);
+}
+
+UIEXPORT UiIcon* ui_foldericon(size_t size) {
+ bool large = true;
+ UiIcon** sys_folder_icon = &sys_folder_icon32;
+ if (size <= 24) {
+ large = false;
+ sys_folder_icon = &sys_folder_icon16;
+ }
+
+ if (*sys_folder_icon) {
+ return *sys_folder_icon;
+ }
+
+ UiIcon* icon = ui_dllicon("shell32.dll", 3, large);
+ *sys_folder_icon = icon;
+ return icon;
+}
+
+UIEXPORT UiIcon* ui_fileicon(size_t size) {
+ bool large = true;
+ UiIcon** sys_folder_icon = &sys_file_icon32;
+ if (size <= 24) {
+ large = false;
+ sys_folder_icon = &sys_file_icon16;
+ }
+
+ if (*sys_folder_icon) {
+ return *sys_folder_icon;
+ }
+
+ UiIcon* icon = ui_dllicon("shell32.dll", 0, large);
+ *sys_folder_icon = icon;
+ return icon;
+}
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2017 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/toolkit.h"\r
+\r
+\r
+\r
+struct UiIcon {\r
+ //virtual ~UiIcon() = 0;\r
+ \r
+ virtual winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon() = 0;\r
+};\r
+\r
+struct UiSymbolIcon : UiIcon {\r
+ winrt::Microsoft::UI::Xaml::Controls::Symbol symbol;\r
+\r
+ UiSymbolIcon(winrt::Microsoft::UI::Xaml::Controls::Symbol sym);\r
+\r
+ ~UiSymbolIcon();\r
+\r
+ winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon();\r
+};\r
+\r
+struct UiImageIcon : UiIcon {\r
+ winrt::Windows::Foundation::Uri uri{ nullptr };\r
+\r
+ UiImageIcon(const char* uristr);\r
+\r
+ ~UiImageIcon();\r
+\r
+ winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon();\r
+};\r
+\r
+struct UiBitmapIcon : UiIcon {\r
+ winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap{ nullptr };\r
+\r
+ UiBitmapIcon(winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap);\r
+\r
+ ~UiBitmapIcon();\r
+\r
+ winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon();\r
+};\r
+\r
+\r
+winrt::Microsoft::UI::Xaml::Controls::IconElement ui_get_icon(const char* name);\r
+\r
+winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap ui_dllicon2bitmap(const char* dll, int iconindex, bool large);\r
+\r
+UiIcon* ui_dllicon(const char* dll, int iconindex, bool large);\r
--- /dev/null
+/*\r
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+*\r
+* Copyright 2024 Olaf Wintermann. All rights reserved.\r
+*\r
+* Redistribution and use in source and binary forms, with or without\r
+* modification, are permitted provided that the following conditions are met:\r
+*\r
+* 1. Redistributions of source code must retain the above copyright\r
+* notice, this list of conditions and the following disclaimer.\r
+*\r
+* 2. Redistributions in binary form must reproduce the above copyright\r
+* notice, this list of conditions and the following disclaimer in the\r
+* documentation and/or other materials provided with the distribution.\r
+*\r
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+* POSSIBILITY OF SUCH DAMAGE.\r
+*/\r
+\r
+#include "pch.h"\r
+\r
+#include "image.h"\r
+\r
+#include "toolkit.h"\r
+#include "container.h"\r
+#include "../common/object.h"\r
+#include "../common/context.h"\r
+#include "util.h"\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;\r
+using namespace winrt::Microsoft::UI::Xaml::Media::Imaging;\r
+using namespace winrt::Microsoft::UI::Xaml::Media;\r
+\r
+UiImageSource::UiImageSource(winrt::Microsoft::UI::Xaml::Media::ImageSource& src) : imgsrc(src) {}\r
+\r
+UIEXPORT UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+ \r
+ Image image = Image();\r
+ FrameworkElement elm = image;\r
+ if (args.scrollarea) {\r
+ ScrollViewer scroll = ScrollViewer();\r
+ scroll.Content(image);\r
+ elm = scroll;\r
+ }\r
+\r
+ // create toolkit wrapper object and register destructor\r
+ UIElement uielm = image;\r
+ UiWidget* widget = new UiWidget(uielm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+ // bind variable\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_GENERIC);\r
+ if (var) {\r
+ UiGeneric *value = (UiGeneric*)var->value;\r
+ value->obj = widget;\r
+ value->get = ui_image_get;\r
+ value->set = ui_image_set;\r
+ }\r
+\r
+ // add button to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(elm, true);\r
+\r
+ return widget;\r
+}\r
+\r
+extern "C" void* ui_image_get(UiGeneric *g) {\r
+\r
+\r
+ return NULL;\r
+}\r
+\r
+extern "C" int ui_image_set(UiGeneric *g, void *data, const char *type) {\r
+ if(!type || strcmp(type, UI_IMAGE_OBJECT_TYPE)) {\r
+ return 1;\r
+ }\r
+\r
+ UiImageSource *imgdata = (UiImageSource*)data;\r
+ if (g->value) {\r
+ UiImageSource *prevData = (UiImageSource*)g->value;\r
+ delete prevData;\r
+ }\r
+ g->value = imgdata;\r
+\r
+ UiWidget* widget = (UiWidget*)g->obj;\r
+ Image image = widget->uielement.as<Image>();\r
+ image.Source(imgdata->imgsrc);\r
+\r
+ return 0;\r
+}\r
+\r
+UIEXPORT int ui_image_load_file(UiGeneric *obj, const char *path) {\r
+ wchar_t* wpath = str2wstr(path, nullptr);\r
+ std::wstring wPath = wpath;\r
+ std::wstring uriPath = L"file:///" + wPath;\r
+ Uri uri{ uriPath };\r
+ \r
+ BitmapImage bitmapImage = BitmapImage();\r
+ bitmapImage.UriSource(uri);\r
+ ImageSource src = bitmapImage;\r
+\r
+ UiImageSource *imgdata = new UiImageSource(src);\r
+ obj->set(obj, imgdata, UI_IMAGE_OBJECT_TYPE);\r
+\r
+ free(wpath);\r
+\r
+ return 0;\r
+}\r
--- /dev/null
+/*\r
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+*\r
+* Copyright 2024 Olaf Wintermann. All rights reserved.\r
+*\r
+* Redistribution and use in source and binary forms, with or without\r
+* modification, are permitted provided that the following conditions are met:\r
+*\r
+* 1. Redistributions of source code must retain the above copyright\r
+* notice, this list of conditions and the following disclaimer.\r
+*\r
+* 2. Redistributions in binary form must reproduce the above copyright\r
+* notice, this list of conditions and the following disclaimer in the\r
+* documentation and/or other materials provided with the distribution.\r
+*\r
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+* POSSIBILITY OF SUCH DAMAGE.\r
+*/\r
+\r
+#pragma once\r
+\r
+#include "../ui/toolkit.h"\r
+#include "../ui/image.h"\r
+\r
+class UiImageSource {\r
+public:\r
+ winrt::Microsoft::UI::Xaml::Media::ImageSource imgsrc { nullptr };\r
+\r
+ UiImageSource(winrt::Microsoft::UI::Xaml::Media::ImageSource& src);\r
+};\r
+\r
+\r
+extern "C" void* ui_image_get(UiGeneric *g);\r
+extern "C" int ui_image_set(UiGeneric *g, void *data, const char *type);\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "label.h"\r
+#include "text.h"\r
+#include "util.h"\r
+\r
+#include "toolkit.h"\r
+#include "container.h"\r
+#include "../common/object.h"\r
+#include "../common/context.h"\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;\r
+\r
+UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create textbox and toolkit wrapper\r
+ TextBlock label = TextBlock();\r
+ if (args.label) {\r
+ wchar_t* wlabel = str2wstr(args.label, nullptr);\r
+ label.Text(wlabel);\r
+ free(wlabel);\r
+ }\r
+\r
+ UIElement elm = label;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ \r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);\r
+ if (var) {\r
+ UiString* value = (UiString*)var->value;\r
+ value->obj = widget;\r
+ value->get = ui_label_get;\r
+ value->set = ui_label_set;\r
+\r
+ // listener for notifying observers\r
+ // TODO:\r
+ }\r
+\r
+ // add label to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(label, false);\r
+\r
+ return widget;\r
+}\r
+\r
+UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs args) {\r
+ args.align = UI_ALIGN_LEFT;\r
+ return ui_label_create(obj, args);\r
+}\r
+\r
+UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs args) {\r
+ args.align = UI_ALIGN_RIGHT;\r
+ return ui_label_create(obj, args);\r
+}\r
+\r
+\r
+\r
+char* ui_label_get(UiString* str) {\r
+ UiWidget* widget = (UiWidget*)str->obj;\r
+ TextBlock box = widget->uielement.as<TextBlock>();\r
+ std::wstring wstr(box.Text());\r
+ return ui_wstring_get(str, wstr);\r
+}\r
+\r
+void ui_label_set(UiString* str, const char* newvalue) {\r
+ UiWidget* widget = (UiWidget*)str->obj;\r
+ TextBlock box = widget->uielement.as<TextBlock>();\r
+ box.Text(ui_wstring_set(str, newvalue));\r
+}\r
+\r
+\r
+// -------------------- progressbar -------------------------\r
+\r
+UIWIDGET ui_progressbar_create(UiObject* obj, UiProgressbarArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create textbox and toolkit wrapper\r
+ ProgressBar progressbar = ProgressBar();\r
+ progressbar.Minimum(args.min);\r
+ progressbar.Maximum(args.max == 0 ? 100 : args.max);\r
+ if (args.width > 0) {\r
+ progressbar.Width(args.width);\r
+ }\r
+\r
+ UIElement elm = progressbar;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE);\r
+ if (var) {\r
+ UiDouble* value = (UiDouble*)var->value;\r
+ value->obj = widget;\r
+ value->get = ui_progressbar_get;\r
+ value->set = ui_progressbar_set;\r
+\r
+ // listener for notifying observers\r
+ // TODO:\r
+ }\r
+\r
+ // add button to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(progressbar, false);\r
+\r
+ return widget;\r
+}\r
+\r
+double ui_progressbar_get(UiDouble * d) {\r
+ UiWidget* widget = (UiWidget*)d->obj;\r
+ ProgressBar progressbar = widget->uielement.as<ProgressBar>();\r
+ d->value = progressbar.Value();\r
+ return d->value;\r
+}\r
+\r
+void ui_progressbar_set(UiDouble * d, double newvalue) {\r
+ UiWidget* widget = (UiWidget*)d->obj;\r
+ ProgressBar progressbar = widget->uielement.as<ProgressBar>();\r
+ d->value = newvalue;\r
+ progressbar.Value(newvalue);\r
+}\r
+\r
+UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create textbox and toolkit wrapper\r
+ ProgressRing spinner = ProgressRing();\r
+ spinner.IsActive(false);\r
+\r
+ UIElement elm = spinner;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE);\r
+ if (var) {\r
+ UiInteger* value = (UiInteger*)var->value;\r
+ value->obj = widget;\r
+ value->get = ui_progressspinner_get;\r
+ value->set = ui_progressspinner_set;\r
+\r
+ // listener for notifying observers\r
+ // TODO:\r
+ }\r
+\r
+ // add button to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(spinner, false);\r
+\r
+ return widget;\r
+}\r
+\r
+int64_t ui_progressspinner_get(UiInteger * i) {\r
+ UiWidget* widget = (UiWidget*)i->obj;\r
+ ProgressRing spinner = widget->uielement.as<ProgressRing>();\r
+ i->value = spinner.IsActive();\r
+ return i->value;\r
+}\r
+\r
+void ui_progressspinner_set(UiInteger * i, int64_t newvalue) {\r
+ UiWidget* widget = (UiWidget*)i->obj;\r
+ ProgressRing spinner = widget->uielement.as<ProgressRing>();\r
+ i->value = newvalue != 0 ? 1 : 0;\r
+ spinner.IsActive(i->value);\r
+}\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+\r
+#pragma once\r
+\r
+#include "../ui/toolkit.h"\r
+#include "../ui/display.h"\r
+\r
+extern "C" char* ui_label_get(UiString * str);\r
+extern "C" void ui_label_set(UiString * str, const char* newvalue);\r
+\r
+extern "C" double ui_progressbar_get(UiDouble *d);\r
+extern "C" void ui_progressbar_set(UiDouble *d, double newvalue);\r
+\r
+extern "C" int64_t ui_progressspinner_get(UiInteger * i);\r
+extern "C" void ui_progressspinner_set(UiInteger * i, int64_t newvalue);\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "list.h"\r
+#include "container.h"\r
+#include "util.h"\r
+\r
+#include "../common/context.h"\r
+#include "../common/object.h"\r
+\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Microsoft::UI::Xaml::Media;\r
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;\r
+\r
+\r
+UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create listview and toolkit wrapper\r
+ ListView listview = ListView();\r
+ if (args.multiselection) {\r
+ listview.SelectionMode(ListViewSelectionMode::Extended);\r
+ }\r
+\r
+ bool clickEnabled = listview.IsItemClickEnabled();\r
+ listview.IsItemClickEnabled(true);\r
+ \r
+\r
+ UIElement elm = listview;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ widget->data1 = args.model;\r
+ widget->data2 = args.getvalue;\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+ // bind var\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);\r
+ if (var) {\r
+ UiList* list = (UiList*)var->value;\r
+ list->update = ui_simple_list_update;\r
+ list->getselection = ui_listview_getselection;\r
+ list->setselection = ui_listview_setselection;\r
+ list->obj = widget;\r
+\r
+ ui_simple_list_update(list, 0);\r
+ }\r
+\r
+ if (args.onselection) {\r
+ ui_callback onselection = args.onselection;\r
+ void* cbdata = args.onselectiondata;\r
+ listview.SelectionChanged([onselection, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) {\r
+ std::vector<int> selectedRows = ui_create_listview_selection(sender.as<ListView>());\r
+\r
+ UiListSelection selection;\r
+ selection.rows = selectedRows.data();\r
+ selection.count = selectedRows.size();\r
+\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = &selection;\r
+ evt.intval = 0;\r
+ onselection(&evt, cbdata);\r
+ });\r
+ }\r
+ if (args.onactivate) {\r
+ ui_callback cb = args.onactivate;\r
+ void* cbdata = args.onactivatedata;\r
+ listview.ItemClick([cb, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) {\r
+ std::vector<int> selectedRows = ui_create_listview_selection(sender.as<ListView>());\r
+ UiListSelection selection;\r
+ selection.rows = selectedRows.data();\r
+ selection.count = selectedRows.size();\r
+\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = &selection;\r
+ evt.intval = 0;\r
+ cb(&evt, cbdata);\r
+ });\r
+ }\r
+\r
+ // add listview to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(listview, false);\r
+\r
+ return widget;\r
+}\r
+\r
+\r
+UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create listview and toolkit wrapper\r
+ ComboBox combobox = ComboBox();\r
+\r
+ UIElement elm = combobox;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ widget->data1 = args.model;\r
+ widget->data2 = args.getvalue;\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+ // bind var\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);\r
+ if (var) {\r
+ UiList* list = (UiList*)var->value;\r
+ list->update = ui_simple_list_update;\r
+ list->getselection = ui_dropdown_getselection;\r
+ list->setselection = ui_dropdown_setselection;\r
+ list->obj = widget;\r
+ ui_simple_list_update(list, 0);\r
+ }\r
+\r
+ if (args.onactivate) {\r
+ ui_callback cb = args.onactivate;\r
+ void* cbdata = args.onactivatedata;\r
+ combobox.SelectionChanged([cb, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) {\r
+ int selectedrow = sender.as<ComboBox>().SelectedIndex();\r
+ UiListSelection selection;\r
+ selection.count = 1;\r
+ selection.rows = &selectedrow;\r
+\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = &selection;\r
+ evt.intval = selectedrow;\r
+ cb(&evt, cbdata);\r
+ });\r
+ }\r
+\r
+ // add listview to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(combobox, false);\r
+\r
+ return widget;\r
+}\r
+\r
+UiListSelection ui_listview_getselection(UiList *list) {\r
+ UiWidget *widget = (UiWidget*)list->obj;\r
+ ListView listview = widget->uielement.as<ListView>();\r
+ std::vector<int> selectedRows = ui_create_listview_selection(listview);\r
+\r
+ UiListSelection selection = { NULL, 0 };\r
+ if (selectedRows.size() > 0) {\r
+ selection.count = selectedRows.size();\r
+ int *data = selectedRows.data();\r
+ selection.rows = (int*)calloc(selection.count, sizeof(int));\r
+ memcpy(selection.rows, data, selection.count);\r
+ }\r
+\r
+ return selection;\r
+}\r
+\r
+void ui_listview_setselection(UiList *list, UiListSelection selection) {\r
+ UiWidget* widget = (UiWidget*)list->obj;\r
+ if (selection.count > 0) {\r
+ ListView listview = widget->uielement.as<ListView>();\r
+ listview.SelectedIndex(selection.rows[0]);\r
+ }\r
+}\r
+\r
+UiListSelection ui_dropdown_getselection(UiList *list) {\r
+ UiWidget* widget = (UiWidget*)list->obj;\r
+ ComboBox cb = widget->uielement.as<ComboBox>();\r
+ int index = cb.SelectedIndex();\r
+ UiListSelection selection = { NULL, 0 };\r
+ if (index >= 0) {\r
+ selection.rows = (int*)calloc(1, sizeof(int));\r
+ selection.count = 1;\r
+ selection.rows[0] = index;\r
+ }\r
+ return selection;\r
+}\r
+\r
+void ui_dropdown_setselection(UiList *list, UiListSelection selection) {\r
+ UiWidget* widget = (UiWidget*)list->obj;\r
+ if (selection.count > 0) {\r
+ ComboBox cb = widget->uielement.as<ComboBox>();\r
+ cb.SelectedIndex(selection.rows[0]);\r
+ }\r
+}\r
+\r
+UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create listview and toolkit wrapper\r
+ BreadcrumbBar bcbar = BreadcrumbBar();\r
+\r
+ UIElement elm = bcbar;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ widget->data1 = args.model;\r
+ widget->data2 = args.getvalue;\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+ // bind var\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);\r
+ if (var) {\r
+ UiList* list = (UiList*)var->value;\r
+ list->update = ui_breadcrumbbar_update;\r
+ list->obj = widget;\r
+ ui_breadcrumbbar_update(list, 0);\r
+ }\r
+\r
+ if (args.onactivate) {\r
+ ui_callback cb = args.onactivate;\r
+ void* cbdata = args.onactivatedata;\r
+ bcbar.ItemClicked([cb, cbdata, obj](IInspectable const& sender, BreadcrumbBarItemClickedEventArgs evtargs) {\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = nullptr;\r
+ evt.intval = evtargs.Index();\r
+ cb(&evt, cbdata);\r
+ });\r
+ }\r
+\r
+ // add listview to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(bcbar, false);\r
+\r
+ return widget;\r
+}\r
+\r
+static void* getstrvalue(void* elm, int ignore) {\r
+ return elm;\r
+}\r
+\r
+void ui_simple_list_update(UiList* list, int i) {\r
+ UiWidget* widget = (UiWidget*)list->obj;\r
+ UiModel* model = (UiModel*)widget->data1;\r
+ ui_getvaluefunc getvalue = (ui_getvaluefunc)widget->data2;\r
+ ItemsControl listview = widget->uielement.as<ItemsControl>();\r
+ auto items = listview.Items();\r
+\r
+ // priority: getvalue, model.getvalue, getstrvalue (fallback)\r
+ if (getvalue == nullptr) {\r
+ if (model && model->getvalue) {\r
+ getvalue = model->getvalue;\r
+ } else {\r
+ getvalue = getstrvalue;\r
+ }\r
+ }\r
+\r
+ // add list elements to listview.Items\r
+ items.Clear();\r
+ void* elm = list->first(list);\r
+ while (elm) {\r
+ char* value = (char*)getvalue(elm, 0);\r
+ wchar_t* wstr = str2wstr(value, nullptr);\r
+ items.Append(box_value(wstr));\r
+ free(wstr);\r
+\r
+ elm = list->next(list);\r
+ }\r
+}\r
+\r
+extern "C" void ui_breadcrumbbar_update(UiList * list, int i) {\r
+ UiWidget* widget = (UiWidget*)list->obj;\r
+ UiModel* model = (UiModel*)widget->data1;\r
+ ui_getvaluefunc getvalue = (ui_getvaluefunc)widget->data2;\r
+\r
+ // priority: getvalue, model.getvalue, getstrvalue (fallback)\r
+ if (getvalue == nullptr) {\r
+ if (model && model->getvalue) {\r
+ getvalue = model->getvalue;\r
+ }\r
+ else {\r
+ getvalue = getstrvalue;\r
+ }\r
+ }\r
+\r
+ BreadcrumbBar bar = widget->uielement.as<BreadcrumbBar>();\r
+ \r
+ Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> items { winrt::single_threaded_vector<Windows::Foundation::IInspectable>() };\r
+ void* elm = list->first(list);\r
+ while (elm) {\r
+ char* value = (char*)getvalue(elm, 0);\r
+ wchar_t* wstr = str2wstr(value, nullptr);\r
+ items.Append(box_value(wstr));\r
+ free(wstr);\r
+\r
+ elm = list->next(list);\r
+ }\r
+\r
+ bar.ItemsSource(items);\r
+}\r
+\r
+\r
+std::vector<int> ui_create_listview_selection(ListView listview) {\r
+ std::vector<int> selection;\r
+ int p = 0;\r
+ auto ranges = listview.SelectedRanges();\r
+ for (auto range : ranges) {\r
+ int begin = range.FirstIndex();\r
+ int end = range.LastIndex();\r
+ for (int i = begin; i <= end; i++) {\r
+ selection.push_back(i);\r
+ }\r
+ }\r
+ return selection;\r
+}\r
+\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/tree.h"\r
+#include "toolkit.h"\r
+\r
+#include "../ui/container.h"\r
+\r
+\r
+extern "C" void ui_simple_list_update(UiList * list, int i);\r
+\r
+extern "C" void ui_breadcrumbbar_update(UiList * list, int i);\r
+\r
+std::vector<int> ui_create_listview_selection(winrt::Microsoft::UI::Xaml::Controls::ListView listview);\r
+\r
+extern "C" UiListSelection ui_listview_getselection(UiList *list);\r
+extern "C" void ui_listview_setselection(UiList *list, UiListSelection selection);\r
+\r
+extern "C" UiListSelection ui_dropdown_getselection(UiList *list);\r
+extern "C" void ui_dropdown_setselection(UiList *list, UiListSelection selection);\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<packages>\r
+ <package id="Microsoft.Windows.CppWinRT" version="2.0.240405.15" targetFramework="native" />\r
+ <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.240803.1" targetFramework="native" />\r
+ <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.1742" targetFramework="native" />\r
+ <package id="Microsoft.WindowsAppSDK" version="1.5.241001000" targetFramework="native" />\r
+</packages>
\ No newline at end of file
--- /dev/null
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+#include "pch.h"\r
--- /dev/null
+// Copyright (c) Microsoft Corporation and Contributors.\r
+// Licensed under the MIT License.\r
+\r
+#pragma once\r
+#include <windows.h>\r
+#include <unknwn.h>\r
+#include <restrictederrorinfo.h>\r
+#include <hstring.h>\r
+\r
+// Undefine GetCurrentTime macro to prevent\r
+// conflict with Storyboard::GetCurrentTime\r
+#undef GetCurrentTime\r
+\r
+#include <winrt/Windows.Foundation.h>\r
+#include <winrt/Windows.Foundation.Collections.h>\r
+#include <winrt/Windows.ApplicationModel.Activation.h>\r
+#include <winrt/Microsoft.UI.Composition.h>\r
+#include <winrt/Microsoft.UI.Windowing.h>\r
+#include <winrt/Microsoft.UI.Xaml.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>\r
+#include <winrt/Microsoft.UI.Xaml.Data.h>\r
+#include <winrt/Microsoft.UI.Xaml.Interop.h>\r
+#include <winrt/Microsoft.UI.Xaml.Markup.h>\r
+#include <winrt/Microsoft.UI.Xaml.Media.h>\r
+#include <winrt/Microsoft.UI.Xaml.Media.Imaging.h>\r
+#include <winrt/Microsoft.UI.Xaml.Navigation.h>\r
+#include <winrt/Microsoft.UI.Xaml.Shapes.h>\r
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>\r
+#include <winrt/Microsoft.UI.Dispatching.h>\r
+#include <winrt/Windows.ApplicationModel.DataTransfer.h>\r
+#include <wil/cppwinrt_helpers.h>\r
+#include <winrt/Microsoft.UI.Xaml.Input.h>\r
+#include <winrt/Microsoft.UI.Input.h>\r
+#include <winrt/Windows.UI.Core.h>\r
+#include <winrt/Windows.ApplicationModel.h>\r
+#include <winrt/Windows.Storage.Pickers.h>\r
+\r
+#include <winrt\Microsoft.UI.Dispatching.h>\r
+\r
+#include <winrt/Windows.Storage.Streams.h>\r
+\r
+#include <Microsoft.UI.Xaml.Window.h>\r
+\r
+#include <shobjidl_core.h>\r
--- /dev/null
+========================================================================\r
+ winui Project Overview\r
+========================================================================\r
+\r
+This project demonstrates how to get started writing WinUI3 apps directly\r
+with standard C++, using the Windows App SDK and C++/WinRT packages and\r
+XAML compiler support to generate implementation headers from interface\r
+(IDL) files. These headers can then be used to implement the local\r
+Windows Runtime classes referenced in the app's XAML pages.\r
+\r
+Steps:\r
+1. Create an interface (IDL) file to define any local Windows Runtime\r
+ classes referenced in the app's XAML pages.\r
+2. Build the project once to generate implementation templates under\r
+ the "Generated Files" folder, as well as skeleton class definitions\r
+ under "Generated Files\sources".\r
+3. Use the skeleton class definitions for reference to implement your\r
+ Windows Runtime classes.\r
+\r
+========================================================================\r
+Learn more about Windows App SDK here:\r
+https://docs.microsoft.com/windows/apps/windows-app-sdk/\r
+Learn more about WinUI3 here:\r
+https://docs.microsoft.com/windows/apps/winui/winui3/\r
+Learn more about C++/WinRT here:\r
+http://aka.ms/cppwinrt/\r
+========================================================================\r
--- /dev/null
+\r
+\r
+#include "pch.h"\r
+\r
+#include "stock.h"
\ No newline at end of file
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2017 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/stock.h"\r
+\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "table.h"\r
+#include "container.h"\r
+#include "util.h"\r
+#include "icons.h"\r
+\r
+#include "../common/context.h"\r
+#include "../common/object.h"\r
+#include "../common/types.h"\r
+\r
+#include <winrt/Microsoft.UI.Xaml.Data.h>\r
+#include <winrt/Microsoft.UI.Xaml.Media.h>\r
+#include <winrt/Microsoft.UI.Xaml.Input.h>\r
+#include <winrt/Windows.UI.Core.h>\r
+#include <winrt/Windows.ApplicationModel.h>\r
+#include <winrt/Windows.ApplicationModel.DataTransfer.h>\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;\r
+using namespace winrt::Microsoft::UI::Xaml::Media;\r
+using namespace winrt::Windows::UI::Xaml::Input;\r
+\r
+static UINT ui_double_click_time = GetDoubleClickTime();\r
+\r
+extern "C" void reg_table_destructor(UiContext * ctx, UiTable * table) {\r
+ // TODO:\r
+}\r
+\r
+static void textblock_set_str(TextBlock& t, const char* str) {\r
+ if (str) {\r
+ wchar_t* wstr = str2wstr(str, nullptr);\r
+ t.Text(winrt::hstring(wstr));\r
+ free(wstr);\r
+ }\r
+}\r
+\r
+static void textblock_set_int(TextBlock& t, int i) {\r
+ wchar_t buf[16];\r
+ swprintf(buf, 16, L"%d", i);\r
+ t.Text(winrt::hstring(buf));\r
+}\r
+\r
+UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs args) {\r
+ if (!args.model) {\r
+ return nullptr;\r
+ }\r
+\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create widgets and wrapper obj\r
+ ScrollViewer scrollW = ScrollViewer();\r
+ Grid grid = Grid();\r
+ scrollW.Content(grid);\r
+ UiTable* uitable = new UiTable(obj, scrollW, grid);\r
+ reg_table_destructor(current->ctx, uitable);\r
+ \r
+ uitable->getvalue = args.model->getvalue ? args.model->getvalue : args.getvalue;\r
+ uitable->onselection = args.onselection;\r
+ uitable->onselectiondata = args.onselectiondata;\r
+ uitable->onactivate = args.onactivate;\r
+ uitable->onactivatedata = args.onactivatedata;\r
+ uitable->ondragstart = args.ondragstart;\r
+ uitable->ondragstartdata = args.ondragstartdata;\r
+ uitable->ondragcomplete = args.ondragcomplete;\r
+ uitable->ondrop = args.ondrop;\r
+ uitable->ondropdata = args.ondropsdata;\r
+\r
+ // grid styling\r
+ winrt::Windows::UI::Color bg = { 255, 255, 255, 255 }; // test color\r
+ SolidColorBrush brush = SolidColorBrush(bg);\r
+ grid.Background(brush);\r
+\r
+ // add columns from args.model\r
+ uitable->add_header(args.model);\r
+ \r
+ // bind var\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);\r
+ if (var) {\r
+ UiList* list = (UiList*)var->value;\r
+ list->update = ui_table_update;\r
+ list->getselection = ui_table_selection;\r
+ list->obj = uitable;\r
+ uitable->update(list, 0);\r
+ }\r
+\r
+ // create toolkit wrapper object and register destructor\r
+ UIElement elm = scrollW;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+ // add scrollW to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(scrollW, false);\r
+\r
+ return widget;\r
+}\r
+\r
+extern "C" void ui_table_update(UiList * list, int i) {\r
+ UiTable* table = (UiTable*)list->obj;\r
+ table->clear();\r
+ table->update(list, i);\r
+}\r
+\r
+extern "C" UiListSelection ui_table_selection(UiList * list) {\r
+ UiTable* table = (UiTable*)list->obj;\r
+ return table->uiselection();\r
+}\r
+\r
+UiTable::UiTable(UiObject *obj, winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollW, winrt::Microsoft::UI::Xaml::Controls::Grid grid) {\r
+ this->obj = obj;\r
+\r
+ this->scrollw = scrollw;\r
+ this->grid = grid;\r
+\r
+ winrt::Windows::UI::Color highlightBg = { 255, 234, 234, 234 };\r
+ highlightBrush = SolidColorBrush(highlightBg);\r
+\r
+ winrt::Windows::UI::Color defaultBg = { 0, 0, 0, 0 }; // default\r
+ defaultBrush = SolidColorBrush(defaultBg);\r
+\r
+ winrt::Windows::UI::Color selectedBg = { 255, 204, 232, 255 }; // test color\r
+ selectedBrush = SolidColorBrush(selectedBg);\r
+\r
+ winrt::Windows::UI::Color selectedFg = { 255, 0, 90, 158 }; // test color\r
+ selectedBorderBrush = SolidColorBrush(selectedFg);\r
+\r
+ grid.KeyDown(\r
+ winrt::Microsoft::UI::Xaml::Input::KeyEventHandler(\r
+ [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& args) {\r
+ // key event for hanling the table cursor or enter\r
+ })\r
+ );\r
+}\r
+\r
+UiTable::~UiTable() {\r
+ ui_model_free(NULL, model);\r
+}\r
+\r
+void UiTable::add_header(UiModel* model) {\r
+ this->model = ui_model_copy(NULL, model);\r
+\r
+ GridLength gl;\r
+ gl.Value = 0;\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+\r
+ // add header row definition\r
+ auto headerRowDef = RowDefinition();\r
+ headerRowDef.Height(gl);\r
+ grid.RowDefinitions().Append(headerRowDef);\r
+\r
+ winrt::Windows::UI::Color borderColor = { 63, 0, 0, 0 };\r
+ SolidColorBrush borderBrush = SolidColorBrush(borderColor);\r
+\r
+\r
+ for (int i = 0; i < model->columns;i++) {\r
+ char* title = model->titles[i];\r
+ UiModelType type = model->types[i];\r
+\r
+ // add grid column definition\r
+ auto colDef = ColumnDefinition();\r
+ colDef.Width(gl);\r
+ grid.ColumnDefinitions().Append(colDef);\r
+\r
+ // header column border\r
+ Border headerBorder = Border();\r
+ Thickness border = { 0,0,1,0 };\r
+ headerBorder.BorderThickness(border);\r
+ headerBorder.BorderBrush(borderBrush);\r
+\r
+ // add text\r
+ auto hLabel = TextBlock();\r
+ textblock_set_str(hLabel, title);\r
+ Thickness cellpadding = { 10,4,4,4 };\r
+ hLabel.Padding(cellpadding);\r
+ hLabel.VerticalAlignment(VerticalAlignment::Stretch);\r
+\r
+ // event handler for highlighting and column resizing\r
+ headerBorder.PointerPressed(\r
+ winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+ [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+ // the last column doesn't need resize capabilities\r
+ if (i + 1 < model->columns) {\r
+ double width = headerBorder.ActualWidth();\r
+ auto point = args.GetCurrentPoint(headerBorder);\r
+ auto position = point.Position();\r
+ if (position.X + 4 >= width) {\r
+ this->resize = true;\r
+ this->resizedCol = headerBorder;\r
+ }\r
+ }\r
+ })\r
+ );\r
+ headerBorder.PointerReleased(\r
+ winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+ [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+ this->resize = false;\r
+ })\r
+ );\r
+ headerBorder.PointerMoved(\r
+ winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+ [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+ if (this->resize) {\r
+ auto point = args.GetCurrentPoint(this->resizedCol);\r
+ auto position = point.Position();\r
+ if (position.X > 1) {\r
+ this->resizedCol.Width(position.X);\r
+ }\r
+ }\r
+ })\r
+ );\r
+ headerBorder.PointerEntered(\r
+ winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+ [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+ // TODO: background\r
+ })\r
+ );\r
+ headerBorder.PointerExited(\r
+ winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+ [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+ // TODO: background\r
+ })\r
+ );\r
+\r
+\r
+\r
+ // add controls\r
+ headerBorder.Child(hLabel);\r
+\r
+ grid.SetColumn(headerBorder, i);\r
+ grid.SetRow(headerBorder, 0);\r
+ grid.Children().Append(headerBorder);\r
+\r
+ UiTableColumn h;\r
+ h.header = headerBorder;\r
+ header.push_back(h);\r
+ }\r
+\r
+ maxrows = 1;\r
+}\r
+\r
+static ULONG64 getsystime() {\r
+ SYSTEMTIME st;\r
+ GetSystemTime(&st);\r
+ return st.wYear * 10000000000000 +\r
+ st.wMonth * 100000000000 +\r
+ st.wDay * 1000000000 +\r
+ st.wHour * 10000000 +\r
+ st.wMinute * 100000 +\r
+ st.wSecond * 1000 +\r
+ st.wMilliseconds;\r
+}\r
+\r
+void UiTable::update(UiList* list, int i) {\r
+ if (getvalue == nullptr) {\r
+ return;\r
+ }\r
+\r
+ Thickness b1 = { 1, 1, 0, 1 }; // first col\r
+ Thickness b2 = { 0, 1, 0, 1 }; // middle\r
+ Thickness b3 = { 0, 1, 1, 1 }; // last col\r
+\r
+ GridLength gl;\r
+ gl.Value = 0;\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+\r
+ // iterate model\r
+ int row = 1;\r
+ void* elm = list->first(list);\r
+ while (elm) {\r
+ if (row >= maxrows) {\r
+ auto rowdef = RowDefinition();\r
+ rowdef.Height(gl);\r
+ grid.RowDefinitions().Append(rowdef);\r
+ maxrows = row;\r
+ }\r
+\r
+ Thickness cellpadding = { 10,0,4,0 };\r
+\r
+ // model column, usually the same as col, however UI_ICON_TEXT uses two columns in the model\r
+ int model_col = 0;\r
+ for (int col = 0; col < header.size(); col++, model_col++) {\r
+ // create ui elements with the correct cell border\r
+ // dependeing on the column\r
+ Border cellBorder = Border();\r
+ cellBorder.Background(defaultBrush);\r
+ cellBorder.BorderBrush(defaultBrush);\r
+ if (col == 0) {\r
+ cellBorder.BorderThickness(b1);\r
+ }\r
+ else if (col + 1 == header.size()) {\r
+ cellBorder.BorderThickness(b3);\r
+ }\r
+ else {\r
+ cellBorder.BorderThickness(b2);\r
+ }\r
+\r
+ // dnd\r
+ if (ondragstart) {\r
+ cellBorder.CanDrag(true);\r
+ cellBorder.DragStarting([this](IInspectable const& sender, DragStartingEventArgs args) {\r
+ UiDnD dnd;\r
+ dnd.evttype = 0;\r
+ dnd.dndstartargs = args;\r
+ dnd.dndcompletedargs = { nullptr };\r
+ dnd.drageventargs = { nullptr };\r
+ dnd.data = args.Data();\r
+\r
+ UiEvent evt;\r
+ evt.obj = this->obj;\r
+ evt.window = evt.obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = &dnd;\r
+ evt.intval = 0;\r
+ \r
+ this->ondragstart(&evt, this->ondragstartdata);\r
+ });\r
+ cellBorder.DropCompleted([this](IInspectable const& sender, DropCompletedEventArgs args) {\r
+ UiDnD dnd;\r
+ dnd.evttype = 1;\r
+ dnd.dndstartargs = { nullptr };\r
+ dnd.dndcompletedargs = args;\r
+ dnd.drageventargs = { nullptr };\r
+ dnd.data = { nullptr };\r
+\r
+ UiEvent evt;\r
+ evt.obj = this->obj;\r
+ evt.window = evt.obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = &dnd;\r
+ evt.intval = 0;\r
+\r
+ if (this->ondragcomplete) {\r
+ this->ondragcomplete(&evt, this->ondragcompletedata);\r
+ }\r
+ });\r
+ }\r
+ if (ondrop) {\r
+ cellBorder.AllowDrop(true);\r
+ cellBorder.Drop(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){\r
+ UiDnD dnd;\r
+ dnd.evttype = 2;\r
+ dnd.dndstartargs = { nullptr };\r
+ dnd.dndcompletedargs = { nullptr };\r
+ dnd.drageventargs = args;\r
+ dnd.dataview = args.DataView();\r
+\r
+ UiEvent evt;\r
+ evt.obj = this->obj;\r
+ evt.window = evt.obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = &dnd;\r
+ evt.intval = 0;\r
+\r
+ this->ondrop(&evt, this->ondropdata);\r
+ }));\r
+ cellBorder.DragOver(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){\r
+ args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Copy);\r
+ }));\r
+ }\r
+\r
+ // set the cell value\r
+ // depending on the type, we create different cell controls\r
+ UiModelType type = model->types[col];\r
+ switch (type) {\r
+ case UI_STRING_FREE:\r
+ case UI_STRING: {\r
+ TextBlock cell = TextBlock();\r
+ cell.Padding(cellpadding);\r
+ cell.VerticalAlignment(VerticalAlignment::Stretch);\r
+ char *val = (char*)getvalue(elm, model_col);\r
+ textblock_set_str(cell, val);\r
+ cellBorder.Child(cell);\r
+ if (type == UI_STRING_FREE && val) {\r
+ free(val);\r
+ }\r
+\r
+ break;\r
+ }\r
+ case UI_INTEGER: {\r
+ TextBlock cell = TextBlock();\r
+ cell.Padding(cellpadding);\r
+ cell.VerticalAlignment(VerticalAlignment::Stretch);\r
+ int *value = (int*)getvalue(elm, model_col);\r
+ if (value) {\r
+ textblock_set_int(cell, *value);\r
+ }\r
+ cellBorder.Child(cell);\r
+ break;\r
+ }\r
+ case UI_ICON: {\r
+ UiIcon* iconConstr = (UiIcon*)getvalue(elm, model_col);\r
+ if (iconConstr) {\r
+ IconElement icon = iconConstr->getIcon();\r
+ cellBorder.Child(icon);\r
+ }\r
+ break;\r
+ }\r
+ case UI_ICON_TEXT_FREE:\r
+ case UI_ICON_TEXT: {\r
+ StackPanel cellPanel = StackPanel();\r
+ cellPanel.Spacing(2);\r
+ cellPanel.Padding(cellpadding);\r
+ cellPanel.VerticalAlignment(VerticalAlignment::Stretch);\r
+\r
+ cellPanel.Orientation(Orientation::Horizontal);\r
+ UiIcon* iconConstr = (UiIcon*)getvalue(elm, model_col++);\r
+ char* str = (char*)getvalue(elm, model_col);\r
+ if (iconConstr) {\r
+ IconElement icon = iconConstr->getIcon();\r
+ cellPanel.Children().Append(icon);\r
+ }\r
+ TextBlock cell = TextBlock();\r
+ textblock_set_str(cell, str);\r
+ cellPanel.Children().Append(cell);\r
+ cellBorder.Child(cellPanel);\r
+ if (type == UI_ICON_TEXT_FREE && str) {\r
+ free(str);\r
+ }\r
+ break;\r
+ }\r
+ }\r
+\r
+ // event handler\r
+ cellBorder.PointerPressed(\r
+ winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+ [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+ winrt::Windows::System::VirtualKeyModifiers modifiers = args.KeyModifiers();\r
+ bool update_selection = true;\r
+\r
+ if (modifiers == winrt::Windows::System::VirtualKeyModifiers::Control) {\r
+ // add/remove current row\r
+ if (!is_row_selected(row)) {\r
+ row_background(row, selectedBrush, selectedBorderBrush);\r
+ selection.push_back(row);\r
+ }\r
+ else {\r
+ row_background(row, highlightBrush, highlightBrush);\r
+ remove_from_selection(row);\r
+ }\r
+ }\r
+ else if (modifiers == winrt::Windows::System::VirtualKeyModifiers::None || selection.size() == 0) {\r
+ // no modifier or shift is pressed but there is no selection\r
+ if (selection.size() > 0) {\r
+ change_rows_bg(selection, defaultBrush, defaultBrush);\r
+ }\r
+ \r
+ row_background(row, selectedBrush, selectedBorderBrush);\r
+ selection = { row };\r
+ if (modifiers == winrt::Windows::System::VirtualKeyModifiers::None) {\r
+ SYSTEMTIME st;\r
+ GetSystemTime(&st);\r
+ \r
+ ULONG64 now = getsystime();\r
+ ULONG64 tdiff = now - lastPointerPress;\r
+ if (tdiff < ui_double_click_time && onactivate != nullptr) {\r
+ // two pointer presse events in short time and we have an onactivate handler\r
+ update_selection = false; // we don't want an additional selection event\r
+ lastPointerPress = 0; // reset double-click\r
+\r
+ int selectedrow = row - 1; // subtract header row\r
+\r
+ UiListSelection selection;\r
+ selection.count = 1;\r
+ selection.rows = &selectedrow;\r
+\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = &selection;\r
+ evt.intval = selectedrow;\r
+ onactivate(&evt, onactivatedata);\r
+ }\r
+ else {\r
+ lastPointerPress = now;\r
+ }\r
+ }\r
+ }\r
+ else if (modifiers == winrt::Windows::System::VirtualKeyModifiers::Shift) {\r
+ // select everything between the first selection and the current row\r
+ std::sort(selection.begin(), selection.end());\r
+ int first = selection.front();\r
+ int last = row;\r
+ if (first > row) {\r
+ last = first;\r
+ first = row;\r
+ }\r
+\r
+ // clear previous selection\r
+ change_rows_bg(selection, defaultBrush, defaultBrush);\r
+\r
+ // create new selection\r
+ std::vector<int> newselection;\r
+ for (int s = first; s <= last; s++) {\r
+ newselection.push_back(s);\r
+ }\r
+ selection = newselection;\r
+ change_rows_bg(selection, selectedBrush, selectedBorderBrush);\r
+ }\r
+\r
+ if (update_selection) {\r
+ call_handler(onselection, onselectiondata);\r
+ }\r
+ })\r
+ );\r
+ cellBorder.PointerReleased(\r
+ winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+ [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+\r
+ })\r
+ );\r
+ cellBorder.PointerEntered(\r
+ winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+ [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+ if (!is_row_selected(row)) {\r
+ row_background(row, highlightBrush, highlightBrush);\r
+ }\r
+ })\r
+ );\r
+ cellBorder.PointerExited(\r
+ winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+ [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+ if (!is_row_selected(row)) {\r
+ row_background(row, defaultBrush, defaultBrush);\r
+ }\r
+ })\r
+ );\r
+\r
+ grid.SetColumn(cellBorder, col);\r
+ grid.SetRow(cellBorder, row);\r
+ grid.Children().Append(cellBorder);\r
+ }\r
+\r
+ row++;\r
+ elm = list->next(list);\r
+ }\r
+}\r
+\r
+void UiTable::clear() {\r
+ for (int i = grid.Children().Size()-1; i >= 0; i--) {\r
+ FrameworkElement elm = grid.Children().GetAt(i).as<FrameworkElement>();\r
+ int child_row = grid.GetRow(elm);\r
+ if (child_row > 0) {\r
+ grid.Children().RemoveAt(i);\r
+ }\r
+ }\r
+\r
+ // TODO: should we clean row definitions?\r
+}\r
+\r
+void UiTable::row_background(int row, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush) {\r
+ Thickness b1 = { 1, 1, 0, 1 }; // first col\r
+ Thickness b2 = { 0, 1, 0, 1 }; // middle\r
+ Thickness b3 = { 0, 1, 1, 1 }; // last col\r
+ \r
+ for (auto child : grid.Children()) {\r
+ FrameworkElement elm = child.as<FrameworkElement>();\r
+ int child_row = grid.GetRow(elm);\r
+ if (child_row == row) {\r
+ Border b = elm.as<Border>();\r
+ b.Background(brush);\r
+ b.BorderBrush(borderBrush);\r
+ }\r
+ }\r
+}\r
+\r
+void UiTable::change_rows_bg(std::vector<int> rows, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush) {\r
+ std::for_each(rows.cbegin(), rows.cend(), [&](const int& row) {row_background(row, brush, borderBrush); });\r
+}\r
+\r
+bool UiTable::is_row_selected(int row) {\r
+ return std::find(selection.begin(), selection.end(), row) != selection.end() ? true : false;\r
+}\r
+\r
+void UiTable::remove_from_selection(int row) {\r
+ selection.erase(std::remove(selection.begin(), selection.end(), row), selection.end());\r
+ selection.shrink_to_fit();\r
+}\r
+\r
+UiListSelection UiTable::uiselection() {\r
+ std::sort(selection.begin(), selection.end());\r
+\r
+ UiListSelection selobj;\r
+ selobj.count = selection.size();\r
+ selobj.rows = nullptr;\r
+ if (selobj.count > 0) {\r
+ selobj.rows = (int*)calloc(selobj.count, sizeof(int));\r
+ memcpy(selobj.rows, selection.data(), selobj.count * sizeof(int));\r
+ for (int i = 0; i < selobj.count; i++) {\r
+ selobj.rows[i]--;\r
+ }\r
+ }\r
+ return selobj;\r
+}\r
+\r
+void UiTable::call_handler(ui_callback cb, void* cbdata) {\r
+ if (!cb) {\r
+ return;\r
+ }\r
+\r
+ UiListSelection selobj = uiselection();\r
+\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = &selobj;\r
+ evt.intval = 0;\r
+ cb(&evt, cbdata);\r
+\r
+ if (selobj.rows) {\r
+ free(selobj.rows);\r
+ }\r
+}\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/tree.h"\r
+#include "toolkit.h"\r
+#include "dnd.h"\r
+\r
+#include "../ui/container.h"\r
+\r
+\r
+typedef struct UiTableColumn {\r
+ winrt::Microsoft::UI::Xaml::Controls::Border header;\r
+\r
+} UiTableColumn;\r
+\r
+typedef struct UiTable {\r
+ winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollw;\r
+ winrt::Microsoft::UI::Xaml::Controls::Grid grid;\r
+ winrt::Microsoft::UI::Xaml::Media::SolidColorBrush defaultBrush;\r
+ winrt::Microsoft::UI::Xaml::Media::SolidColorBrush highlightBrush;\r
+ winrt::Microsoft::UI::Xaml::Media::SolidColorBrush selectedBrush;\r
+ winrt::Microsoft::UI::Xaml::Media::SolidColorBrush selectedBorderBrush;\r
+\r
+ winrt::Microsoft::UI::Xaml::Controls::Border resizedCol{ nullptr };\r
+ bool resize = false;\r
+\r
+ UiObject* obj;\r
+ ui_callback onactivate;\r
+ void* onactivatedata;\r
+ ui_callback onselection;\r
+ void* onselectiondata;\r
+ ui_callback ondragstart;\r
+ void* ondragstartdata;\r
+ ui_callback ondragcomplete;\r
+ void* ondragcompletedata;\r
+ ui_callback ondrop;\r
+ void* ondropdata;\r
+ UiModel* model = nullptr;\r
+ std::vector<UiTableColumn> header;\r
+ ui_getvaluefunc getvalue = nullptr;\r
+ int maxrows = 0;\r
+ int lastSelection = 0;\r
+ ULONG64 lastPointerPress = 0;\r
+ std::vector<int> selection;\r
+\r
+ UiTable(UiObject *obj, winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollW, winrt::Microsoft::UI::Xaml::Controls::Grid grid);\r
+\r
+ ~UiTable();\r
+ \r
+ void add_header(UiModel* model);\r
+\r
+ void update(UiList* list, int i);\r
+\r
+ void clear();\r
+\r
+ void row_background(int row, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush);\r
+\r
+ void change_rows_bg(std::vector<int> rows, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush);\r
+\r
+ bool is_row_selected(int row);\r
+\r
+ void remove_from_selection(int row);\r
+\r
+ UiListSelection uiselection();\r
+\r
+ void call_handler(ui_callback cb, void *cbdata);\r
+} UiTable;\r
+\r
+extern "C" void ui_table_update(UiList * list, int i);\r
+\r
+extern "C" UiListSelection ui_table_selection(UiList * list);\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "text.h"\r
+\r
+#include "../common/context.h"\r
+#include "../common/object.h"\r
+\r
+#include <cx/string.h>\r
+#include <cx/allocator.h>\r
+\r
+#include "util.h"\r
+#include "container.h"\r
+\r
+\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Microsoft::UI::Xaml::Media;\r
+using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives;\r
+using namespace winrt::Windows::UI::Xaml::Input;\r
+\r
+\r
+UIEXPORT UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create textarea and toolkit wrapper\r
+ TextBox textarea = TextBox();\r
+ textarea.AcceptsReturn(true);\r
+ ScrollViewer::SetVerticalScrollBarVisibility(textarea, ScrollBarVisibility::Auto);\r
+ UIElement elm = textarea;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_TEXT);\r
+ if (var) {\r
+ UiText* value = (UiText*)var->value;\r
+ value->obj = widget;\r
+ value->undomgr = NULL;\r
+ value->set = ui_textarea_set;\r
+ value->get = ui_textarea_get;\r
+ value->getsubstr = ui_textarea_getsubstr;\r
+ value->insert = ui_textarea_insert;\r
+ value->setposition = ui_textarea_setposition;\r
+ value->position = ui_textarea_position;\r
+ value->selection = ui_textarea_selection;\r
+ value->length = ui_textarea_length;\r
+ value->remove = ui_textarea_remove;\r
+ }\r
+\r
+ // add textarea to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(textarea, true);\r
+\r
+ return widget;\r
+}\r
+\r
+UIEXPORT UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) {\r
+ return textarea;\r
+}\r
+\r
+UIEXPORT void ui_text_undo(UiText *value) {\r
+\r
+}\r
+\r
+UIEXPORT void ui_text_redo(UiText *value) {\r
+\r
+}\r
+\r
+// -------------------------- getter/setter for textarea UiText --------------------------\r
+\r
+char* ui_wtext_get(UiText *text, std::wstring &value) {\r
+ if (text->value.ptr) {\r
+ text->value.free(text->value.ptr);\r
+ }\r
+\r
+ text->value.ptr = wchar2utf8(value.c_str(), value.length());\r
+ text->value.free = free;\r
+\r
+ return text->value.ptr;\r
+}\r
+\r
+std::wstring ui_wtext_set(UiText *text, const char* value) {\r
+ if (text->value.ptr) {\r
+ text->value.free(text->value.ptr);\r
+ }\r
+\r
+ text->value.ptr = _strdup(value);\r
+ text->value.free = free;\r
+\r
+ int len;\r
+ wchar_t* wstr = str2wstr(value, &len);\r
+ std::wstring s(wstr);\r
+ free(wstr);\r
+\r
+ return s;\r
+}\r
+\r
+extern "C" char* ui_textarea_get(UiText *text) {\r
+ UiWidget* widget = (UiWidget*)text->obj;\r
+ TextBox box = widget->uielement.as<TextBox>();\r
+ std::wstring wstr(box.Text());\r
+ return ui_wtext_get(text, wstr);\r
+}\r
+\r
+extern "C" void ui_textarea_set(UiText *text, const char *newvalue) {\r
+ UiWidget* widget = (UiWidget*)text->obj;\r
+ TextBox box = widget->uielement.as<TextBox>();\r
+ box.Text(ui_wtext_set(text, newvalue));\r
+}\r
+\r
+extern "C" char* ui_textarea_getsubstr(UiText *text, int begin, int end) {\r
+ return NULL;\r
+}\r
+\r
+extern "C" void ui_textarea_insert(UiText *text, int pos, char *str) {\r
+\r
+}\r
+\r
+extern "C" void ui_textarea_setposition(UiText *text, int pos) {\r
+\r
+}\r
+\r
+extern "C" int ui_textarea_position(UiText *text) {\r
+ return 0;\r
+}\r
+\r
+extern "C" void ui_textarea_selection(UiText *text, int *begin, int *end) {\r
+\r
+}\r
+\r
+extern "C" int ui_textarea_length(UiText *text) {\r
+ return 0;\r
+}\r
+\r
+extern "C" void ui_textarea_remove(UiText *text, int begin, int end) {\r
+\r
+}\r
+\r
+\r
+\r
+\r
+UIWIDGET ui_textfield_create(UiObject* obj, UiTextFieldArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create textbox and toolkit wrapper\r
+ TextBox textfield = TextBox();\r
+ UIElement elm = textfield;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);\r
+ if (var) {\r
+ UiString* value = (UiString*)var->value;\r
+ value->obj = widget;\r
+ value->get = ui_textfield_get;\r
+ value->set = ui_textfield_set;\r
+\r
+ // listener for notifying observers\r
+ // TODO:\r
+ }\r
+ \r
+ // add textfield to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(textfield, false);\r
+\r
+ return widget;\r
+}\r
+\r
+UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {\r
+ return ui_textfield_create(obj, args);\r
+}\r
+\r
+UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create textbox and toolkit wrapper\r
+ PasswordBox textfield = PasswordBox();\r
+ UIElement elm = textfield;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+ ui_set_widget_groups(current->ctx, widget, args.groups);\r
+\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);\r
+ if (var) {\r
+ UiString* value = (UiString*)var->value;\r
+ value->obj = widget;\r
+ value->get = ui_passwordfield_get;\r
+ value->set = ui_passwordfield_set;\r
+\r
+ // listener for notifying observers\r
+ // TODO:\r
+ }\r
+\r
+ // add textfield to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(textfield, false);\r
+\r
+ return widget;\r
+}\r
+\r
+\r
+// -------------------------- getter/setter for textfield UiString --------------------------\r
+\r
+char* ui_wstring_get(UiString* str, std::wstring &value) {\r
+ if (str->value.ptr) {\r
+ str->value.free(str->value.ptr);\r
+ }\r
+\r
+ str->value.ptr = wchar2utf8(value.c_str(), value.length());\r
+ str->value.free = free;\r
+\r
+ return str->value.ptr;\r
+}\r
+\r
+std::wstring ui_wstring_set(UiString* str, const char* value) {\r
+ if (str->value.ptr) {\r
+ str->value.free(str->value.ptr);\r
+ }\r
+\r
+ str->value.ptr = _strdup(value);\r
+ str->value.free = free;\r
+\r
+ int len;\r
+ wchar_t* wstr = str2wstr(value, &len);\r
+ std::wstring s(wstr);\r
+ free(wstr);\r
+\r
+ return s;\r
+}\r
+\r
+char* ui_textfield_get(UiString * str) {\r
+ UiWidget* widget = (UiWidget*)str->obj;\r
+ TextBox box = widget->uielement.as<TextBox>();\r
+ std::wstring wstr(box.Text());\r
+ return ui_wstring_get(str, wstr);\r
+}\r
+\r
+void ui_textfield_set(UiString * str, const char* newvalue) {\r
+ UiWidget* widget = (UiWidget*)str->obj;\r
+ TextBox box = widget->uielement.as<TextBox>();\r
+ box.Text(ui_wstring_set(str, newvalue));\r
+}\r
+\r
+\r
+char* ui_passwordfield_get(UiString * str) {\r
+ UiWidget* widget = (UiWidget*)str->obj;\r
+ PasswordBox box = widget->uielement.as<PasswordBox>();\r
+ std::wstring wstr(box.Password());\r
+ return ui_wstring_get(str, wstr);\r
+}\r
+\r
+void ui_passwordfield_set(UiString * str, const char* newvalue) {\r
+ UiWidget* widget = (UiWidget*)str->obj;\r
+ PasswordBox box = widget->uielement.as<PasswordBox>();\r
+ box.Password(ui_wstring_set(str, newvalue));\r
+}\r
+\r
+\r
+// ------------------------ path textfield --------------------------------------\r
+\r
+extern "C" static void destroy_ui_pathtextfield(void* ptr) {\r
+ UiPathTextField* pb = (UiPathTextField*)ptr;\r
+ delete pb;\r
+}\r
+\r
+static void ui_context_add_pathtextfield_destructor(UiContext* ctx, UiPathTextField* pb) {\r
+ cxMempoolRegister(ctx->mp, pb, destroy_ui_pathtextfield);\r
+}\r
+\r
+static void ui_pathtextfield_clear(StackPanel& buttons) {\r
+ for (int i = buttons.Children().Size() - 1; i >= 0; i--) {\r
+ buttons.Children().RemoveAt(i);\r
+ }\r
+}\r
+\r
+static void ui_pathfield_free_pathelms(UiPathElm* elms, size_t nelm) {\r
+ if (!elms) {\r
+ return;\r
+ }\r
+ for (int i = 0; i < nelm; i++) {\r
+ UiPathElm e = elms[i];\r
+ free(e.name);\r
+ free(e.path);\r
+ }\r
+ free(elms);\r
+}\r
+\r
+UiPathTextField::~UiPathTextField() {\r
+ ui_pathfield_free_pathelms(this->current_path, this->current_path_nelms);\r
+}\r
+\r
+static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) {\r
+ cxstring *pathelms;\r
+ size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms);\r
+\r
+ if (nelm == 0) {\r
+ *ret_nelm = 0;\r
+ return nullptr;\r
+ }\r
+\r
+ UiPathElm* elms = (UiPathElm*)calloc(nelm, sizeof(UiPathElm));\r
+ size_t n = nelm;\r
+ int j = 0;\r
+ for (int i = 0; i < nelm; i++) {\r
+ cxstring c = pathelms[i];\r
+ if (c.length == 0) {\r
+ if (i == 0) {\r
+ c.length = 1;\r
+ }\r
+ else {\r
+ n--;\r
+ continue;\r
+ }\r
+ }\r
+\r
+ cxmutstr m = cx_strdup(c);\r
+ elms[j].name = m.ptr;\r
+ elms[j].name_len = m.length;\r
+ \r
+ size_t elm_path_len = c.ptr + c.length - full_path;\r
+ cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len));\r
+ elms[j].path = elm_path.ptr;\r
+ elms[j].path_len = elm_path.length;\r
+\r
+ j++;\r
+ }\r
+ *ret_nelm = n;\r
+\r
+ return elms;\r
+}\r
+\r
+int ui_pathtextfield_update(UiPathTextField* pb, const char *full_path) {\r
+ Grid grid = pb->grid;\r
+\r
+ ui_pathelm_func getpathelm = pb->getpathelm;\r
+ void* getpathelmdata = pb->getpathelmdata;\r
+\r
+ size_t full_path_len = full_path ? strlen(full_path) : 0;\r
+\r
+ size_t nelm = 0;\r
+ UiPathElm* path_elm = getpathelm(full_path, full_path_len, &nelm, getpathelmdata);\r
+ if (!path_elm) {\r
+ return 1;\r
+ }\r
+\r
+ // hide textbox, show button panel\r
+ pb->textbox.Visibility(Visibility::Collapsed);\r
+ pb->buttons.Visibility(Visibility::Visible);\r
+\r
+ // clear old buttons\r
+ ui_pathtextfield_clear(pb->buttons); \r
+\r
+ ui_pathfield_free_pathelms(pb->current_path, pb->current_path_nelms);\r
+ pb->current_path = path_elm;\r
+ pb->current_path_nelms = nelm;\r
+\r
+ // add new buttons\r
+ int j = 0;\r
+ for (int i = 0; i < nelm;i++) {\r
+ UiPathElm elm = path_elm[i];\r
+ wchar_t* wstr = str2wstr_len(elm.name, elm.name_len, nullptr);\r
+ Button button = Button();\r
+ button.Content(box_value(wstr));\r
+ free(wstr);\r
+\r
+ if (pb->onactivate) {\r
+ button.Click([pb, j, elm](IInspectable const& sender, RoutedEventArgs) {\r
+ // copy elm.path because it could be a non-terminated string\r
+ cxmutstr elmpath = cx_strdup(cx_strn(elm.path, elm.path_len));\r
+\r
+ UiEvent evt;\r
+ evt.obj = pb->obj;\r
+ evt.window = evt.obj->window;\r
+ evt.document = evt.obj->ctx->document;\r
+ evt.eventdata = elmpath.ptr;\r
+ evt.intval = j;\r
+ pb->onactivate(&evt, pb->onactivatedata);\r
+\r
+ free(elmpath.ptr);\r
+ });\r
+ }\r
+\r
+ Thickness t = { 0, 0, 1, 0 };\r
+ CornerRadius c = { 0 ,0, 0, 0 };\r
+ button.BorderThickness(t);\r
+ button.CornerRadius(c);\r
+\r
+ pb->buttons.Children().Append(button);\r
+\r
+ j++;\r
+ }\r
+\r
+ return 0;\r
+}\r
+\r
+char* ui_path_textfield_get(UiString * str) {\r
+ UiPathTextField* widget = (UiPathTextField*)str->obj;\r
+ TextBox box = widget->textbox;\r
+ std::wstring wstr(box.Text());\r
+ return ui_wstring_get(str, wstr);\r
+}\r
+\r
+void ui_path_textfield_set(UiString* str, const char* newvalue) {\r
+ UiPathTextField* widget = (UiPathTextField*)str->obj;\r
+ TextBox box = widget->textbox;\r
+ box.Text(ui_wstring_set(str, newvalue));\r
+ ui_pathtextfield_update(widget, newvalue);\r
+}\r
+\r
+UIEXPORT UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+\r
+ // create view and toolkit wrapper\r
+ Border pathbar = Border();\r
+\r
+ IInspectable bgRes = Application::Current().Resources().Lookup(box_value(L"TextControlBackground"));\r
+ IInspectable borderThicknessRes = Application::Current().Resources().Lookup(box_value(L"TextControlBorderThemeThickness"));\r
+ IInspectable borderBrushRes = Application::Current().Resources().Lookup(box_value(L"TextControlBorderBrush"));\r
+ // IInspectable cornerRes = Application::Current().Resources().Lookup(box_value(L"TextControlCornerRadius"));\r
+\r
+ Brush bgBrush = unbox_value<Brush>(bgRes);\r
+ Thickness border = unbox_value<Thickness>(borderThicknessRes);\r
+ Brush borderBrush = unbox_value<Brush>(borderBrushRes);\r
+ CornerRadius cornerRadius = { 4, 4, 4, 4 }; //unbox_value<CornerRadius>(cornerRes);\r
+\r
+ pathbar.Background(bgBrush);\r
+ pathbar.BorderBrush(borderBrush);\r
+ pathbar.BorderThickness(border);\r
+ pathbar.CornerRadius(cornerRadius);\r
+\r
+ Grid content = Grid();\r
+ pathbar.Child(content);\r
+\r
+ GridLength gl;\r
+ gl.Value = 0;\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+\r
+ ColumnDefinition coldef = ColumnDefinition();\r
+ coldef.Width(gl);\r
+ content.ColumnDefinitions().Append(coldef);\r
+\r
+ gl.Value = 1;\r
+ gl.GridUnitType = GridUnitType::Star;\r
+\r
+ ColumnDefinition coldef2 = ColumnDefinition();\r
+ coldef2.Width(gl);\r
+ content.ColumnDefinitions().Append(coldef2);\r
+\r
+ TextBox pathTextBox = TextBox();\r
+ Thickness t = { 0, 0, 0, 0 };\r
+ CornerRadius c = { 0 ,0, 0, 0 };\r
+ pathTextBox.BorderThickness(t);\r
+ //pathTextBox.CornerRadius(c);\r
+\r
+\r
+ pathTextBox.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+ content.SetColumn(pathTextBox, 0);\r
+ content.SetColumnSpan(pathTextBox, 2);\r
+\r
+ content.Children().Append(pathTextBox);\r
+\r
+ // stackpanel for buttons\r
+ StackPanel buttons = StackPanel();\r
+ buttons.Orientation(Orientation::Horizontal);\r
+ buttons.Visibility(Visibility::Collapsed);\r
+ content.SetColumn(buttons, 0);\r
+ content.Children().Append(buttons);\r
+\r
+ TextBlock filler = TextBlock();\r
+ filler.VerticalAlignment(VerticalAlignment::Stretch);\r
+ //filler.Text(winrt::hstring(L"hello filler"));\r
+\r
+ filler.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+ filler.VerticalAlignment(VerticalAlignment::Stretch);\r
+ content.SetColumn(filler, 1);\r
+ content.Children().Append(filler);\r
+\r
+ filler.PointerPressed(\r
+ winrt::Microsoft::UI::Xaml::Input::PointerEventHandler(\r
+ [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) {\r
+ pathTextBox.Visibility(Visibility::Visible);\r
+ buttons.Visibility(Visibility::Collapsed);\r
+ filler.Visibility(Visibility::Collapsed);\r
+ pathTextBox.SelectionStart(pathTextBox.Text().size());\r
+ pathTextBox.SelectionLength(0);\r
+ pathTextBox.Focus(FocusState::Keyboard);\r
+ })\r
+ );\r
+\r
+ //pathTextBox.Visibility(Visibility::Collapsed);\r
+\r
+ UiPathTextField* uipathbar = new UiPathTextField;\r
+ ui_context_add_pathtextfield_destructor(current->ctx, uipathbar);\r
+ uipathbar->grid = content;\r
+ uipathbar->buttons = buttons;\r
+ uipathbar->textbox = pathTextBox;\r
+ uipathbar->filler = filler;\r
+ uipathbar->obj = obj;\r
+ uipathbar->getpathelm = args.getpathelm ? args.getpathelm : default_pathelm_func;\r
+ uipathbar->getpathelmdata = args.getpathelmdata;\r
+ uipathbar->onactivate = args.onactivate;\r
+ uipathbar->onactivatedata = args.onactivatedata;\r
+ uipathbar->ondragstart = args.ondragstart;\r
+ uipathbar->ondragstartdata = args.ondragstartdata;\r
+ uipathbar->ondragcomplete = args.ondragcomplete;\r
+ uipathbar->ondragcompletedata = args.ondragcompletedata;\r
+ uipathbar->ondrop = args.ondrop;\r
+ uipathbar->ondropdata = args.ondropsdata;\r
+\r
+\r
+ pathTextBox.KeyDown(\r
+ winrt::Microsoft::UI::Xaml::Input::KeyEventHandler(\r
+ [=](winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) {\r
+ auto key = e.Key();\r
+ bool showButtons = false;\r
+ bool update = false;\r
+ if (key == Windows::System::VirtualKey::Escape) {\r
+ showButtons = true;\r
+ }\r
+ else if (key == Windows::System::VirtualKey::Enter) {\r
+ showButtons = true;\r
+ update = true;\r
+ }\r
+\r
+ if (showButtons) {\r
+ pathTextBox.Visibility(Visibility::Collapsed);\r
+ buttons.Visibility(Visibility::Visible);\r
+ filler.Visibility(Visibility::Visible);\r
+ if (update) {\r
+ std::wstring value(pathTextBox.Text());\r
+ char* full_path = wchar2utf8(value.c_str(), value.length());\r
+\r
+ if (!ui_pathtextfield_update(uipathbar, full_path)) {\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = full_path;\r
+ evt.intval = -1;\r
+ args.onactivate(&evt, args.onactivatedata);\r
+ } \r
+\r
+ free(full_path);\r
+ }\r
+\r
+ //buttons.Focus(FocusState::Keyboard);\r
+ }\r
+ })\r
+ );\r
+\r
+\r
+ UIElement elm = pathbar;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ widget->data1 = uipathbar;\r
+ ui_context_add_widget_destructor(current->ctx, widget);\r
+\r
+ // bind var\r
+ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);\r
+ if (var) {\r
+ UiString* value = (UiString*)var->value;\r
+ value->obj = uipathbar;\r
+ value->get = ui_path_textfield_get;\r
+ value->set = ui_path_textfield_set;\r
+ }\r
+\r
+ // add listview to current container\r
+ UI_APPLY_LAYOUT1(current, args);\r
+\r
+ current->container->Add(pathbar, false);\r
+\r
+ return widget;\r
+}\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/text.h"\r
+#include "toolkit.h"\r
+\r
+#include "../ui/container.h"\r
+\r
+struct UiPathTextField {\r
+ winrt::Microsoft::UI::Xaml::Controls::Grid grid = { nullptr };\r
+ winrt::Microsoft::UI::Xaml::Controls::StackPanel buttons = { nullptr };\r
+ winrt::Microsoft::UI::Xaml::Controls::TextBox textbox = { nullptr };\r
+ winrt::Microsoft::UI::Xaml::Controls::TextBlock filler = { nullptr };\r
+\r
+ ~UiPathTextField();\r
+\r
+ UiPathElm* current_path = nullptr;\r
+ size_t current_path_nelms = 0;\r
+\r
+ UiObject* obj;\r
+\r
+ ui_pathelm_func getpathelm;\r
+ void* getpathelmdata;\r
+ \r
+ ui_callback onactivate;\r
+ void* onactivatedata;\r
+\r
+ ui_callback ondragstart;\r
+ void* ondragstartdata;\r
+ ui_callback ondragcomplete;\r
+ void* ondragcompletedata;\r
+ ui_callback ondrop;\r
+ void* ondropdata;\r
+};\r
+\r
+char* ui_wtext_get(UiText *text, std::wstring &value);\r
+std::wstring ui_wtext_set(UiText *text, const char* value);\r
+\r
+char* ui_wstring_get(UiString* str, std::wstring& value);\r
+std::wstring ui_wstring_set(UiString* str, const char* value);\r
+\r
+extern "C" char* ui_textarea_get(UiText *text);\r
+extern "C" void ui_textarea_set(UiText *text, const char *newvalue);\r
+extern "C" char* ui_textarea_getsubstr(UiText*, int, int);\r
+extern "C" void ui_textarea_insert(UiText*, int, char*);\r
+extern "C" void ui_textarea_setposition(UiText*,int);\r
+extern "C" int ui_textarea_position(UiText*);\r
+extern "C" void ui_textarea_selection(UiText*, int*, int*);\r
+extern "C" int ui_textarea_length(UiText*);\r
+extern "C" void ui_textarea_remove(UiText*, int, int);\r
+\r
+extern "C" char* ui_textfield_get(UiString *str);\r
+extern "C" void ui_textfield_set(UiString *str, const char *newvalue);\r
+\r
+extern "C" char* ui_passwordfield_get(UiString * str);\r
+extern "C" void ui_passwordfield_set(UiString * str, const char* newvalue);\r
+\r
+extern "C" char* ui_path_textfield_get(UiString * str);\r
+extern "C" void ui_path_textfield_set(UiString * str, const char* newvalue);\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+#include "toolkit.h"\r
+\r
+#include <cx/allocator.h>\r
+#include <cx/mempool.h>\r
+\r
+#include "../common/context.h"\r
+#include "../common/document.h"\r
+#include "../common/toolbar.h"\r
+#include "../common/properties.h"\r
+\r
+#include "icons.h"\r
+\r
+#include "MainWindow.xaml.h"\r
+\r
+#include "App.xaml.h"\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace Windows::UI::Core;\r
+\r
+static const char* application_name;\r
+\r
+static ui_callback startup_func;\r
+static void* startup_data;\r
+\r
+static ui_callback open_func;\r
+void* open_data;\r
+\r
+static ui_callback exit_func;\r
+void* exit_data;\r
+\r
+static ui_callback appclose_fnc;\r
+\r
+static void* appclose_udata;\r
+\r
+\r
+static UiObject* active_window;\r
+\r
+static winrt::Microsoft::UI::Dispatching::DispatcherQueue uiDispatcherQueue = { nullptr };\r
+\r
+void ui_app_run_startup() {\r
+ uiDispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread();\r
+ \r
+ if (startup_func) {\r
+ startup_func(NULL, startup_data);\r
+ }\r
+}\r
+\r
+class App : public ApplicationT<App, IXamlMetadataProvider> {\r
+public:\r
+ void OnLaunched(LaunchActivatedEventArgs const&) {\r
+ Resources().MergedDictionaries().Append(XamlControlsResources());\r
+ if (startup_func) {\r
+ startup_func(NULL, startup_data);\r
+ }\r
+\r
+ //auto window = make<winui::implementation::MainWindow>();\r
+ //window.Activate();\r
+ }\r
+ IXamlType GetXamlType(TypeName const& type) {\r
+ return provider.GetXamlType(type);\r
+ }\r
+ IXamlType GetXamlType(hstring const& fullname) {\r
+ return provider.GetXamlType(fullname);\r
+ }\r
+ com_array<XmlnsDefinition> GetXmlnsDefinitions() {\r
+ return provider.GetXmlnsDefinitions();\r
+ }\r
+private:\r
+ XamlControlsXamlMetaDataProvider provider;\r
+};\r
+\r
+UiWidget::UiWidget(winrt::Microsoft::UI::Xaml::UIElement& elm) : uielement(elm) {}\r
+\r
+extern "C" void destroy_ui_window_wrapper(void* ptr) {\r
+ UiWindow* win = (UiWindow*)ptr;\r
+ delete win;\r
+}\r
+\r
+extern "C" void destroy_ui_widget_wrapper(void* ptr) {\r
+ UiWidget* widget = (UiWidget*)ptr;\r
+ delete widget;\r
+}\r
+\r
+extern "C" void destroy_ui_container_wrapper(void* ptr) {\r
+ UiContainer* ctn = (UiContainer*)ptr;\r
+ delete ctn;\r
+}\r
+\r
+void ui_context_add_window_destructor(UiContext* ctx, UiWindow* win) {\r
+ cxMempoolRegister(ctx->mp, win, destroy_ui_window_wrapper);\r
+}\r
+\r
+void ui_context_add_widget_destructor(UiContext* ctx, UiWidget* widget) {\r
+ cxMempoolRegister(ctx->mp, widget, destroy_ui_widget_wrapper);\r
+}\r
+\r
+void ui_context_add_container_destructor(UiContext* ctx, UiContainer *container) {\r
+ cxMempoolRegister(ctx->mp, container, destroy_ui_container_wrapper);\r
+}\r
+\r
+\r
+UiEvent ui_create_int_event(UiObject* obj, int64_t i) {\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = nullptr;\r
+ evt.intval = i;\r
+ return evt;\r
+}\r
+\r
+\r
+#include <MddBootstrap.h>\r
+\r
+void ui_appsdk_bootstrap(void) {\r
+ const UINT32 majorMinorVersion{ 0x00010002 };\r
+ PCWSTR versionTag{ L"" };\r
+ const PACKAGE_VERSION minVersion{};\r
+\r
+ const HRESULT hr = MddBootstrapInitialize(majorMinorVersion, versionTag, minVersion);\r
+ if (FAILED(hr)) {\r
+ exit(102);\r
+ }\r
+}\r
+\r
+void ui_init(const char* appname, int argc, char** argv) {\r
+ application_name = appname;\r
+\r
+ //ui_appsdk_bootstrap();\r
+\r
+ uic_init_global_context();\r
+ uic_docmgr_init();\r
+ uic_menu_init();\r
+ uic_toolbar_init();\r
+ \r
+ uic_load_app_properties();\r
+}\r
+\r
+const char* ui_appname() {\r
+ return application_name;\r
+}\r
+\r
+void ui_onstartup(ui_callback f, void* userdata) {\r
+ startup_func = f;\r
+ startup_data = userdata;\r
+}\r
+\r
+void ui_onopen(ui_callback f, void* userdata) {\r
+ open_func = f;\r
+ open_data = userdata;\r
+}\r
+\r
+void ui_onexit(ui_callback f, void* userdata) {\r
+ exit_func = f;\r
+ exit_data = userdata;\r
+}\r
+\r
+void ui_main() {\r
+ /*\r
+ init_apartment();\r
+ //Application::Start([](auto&&) {make<App>(); });\r
+\r
+ ::winrt::Microsoft::UI::Xaml::Application::Start(\r
+ [](auto&&)\r
+ {\r
+ ::winrt::make<::winrt::winui::implementation::App>();\r
+ });\r
+ */\r
+ {\r
+ void (WINAPI * pfnXamlCheckProcessRequirements)();\r
+ auto module = ::LoadLibrary(L"Microsoft.ui.xaml.dll");\r
+ if (module)\r
+ {\r
+ pfnXamlCheckProcessRequirements = reinterpret_cast<decltype(pfnXamlCheckProcessRequirements)>(GetProcAddress(module, "XamlCheckProcessRequirements"));\r
+ if (pfnXamlCheckProcessRequirements)\r
+ {\r
+ (*pfnXamlCheckProcessRequirements)();\r
+ }\r
+\r
+ ::FreeLibrary(module);\r
+ }\r
+ }\r
+\r
+ winrt::init_apartment(winrt::apartment_type::single_threaded);\r
+ ::winrt::Microsoft::UI::Xaml::Application::Start(\r
+ [](auto&&)\r
+ {\r
+ ::winrt::make<::winrt::winui::implementation::App>();\r
+ });\r
+}\r
+\r
+class UiWin {\r
+public:\r
+ Window window;\r
+};\r
+\r
+void ui_show(UiObject* obj) {\r
+ if (obj->wobj) {\r
+ obj->wobj->window.Activate();\r
+ } else if(obj->widget && obj->widget->Show) {\r
+ obj->widget->Show();\r
+ }\r
+}\r
+\r
+void ui_close(UiObject* obj) {\r
+ if (obj->wobj) {\r
+ obj->wobj->window.Close();\r
+ }\r
+}\r
+\r
+static void ui_job_thread(UiJob* job) {\r
+ if (!job->job_func(job->job_data) && job->finish_callback) {\r
+ bool isQueued = uiDispatcherQueue.TryEnqueue([job]()\r
+ {\r
+ UiEvent event;\r
+ event.obj = job->obj;\r
+ event.window = job->obj->window;\r
+ event.document = job->obj->ctx->document;\r
+ event.intval = 0;\r
+ event.eventdata = NULL;\r
+ job->finish_callback(&event, job->finish_data);\r
+ delete job;\r
+ });\r
+ if (!isQueued) {\r
+ // TODO: error or try again?\r
+ exit(-1);\r
+ }\r
+ }\r
+ else {\r
+ delete job;\r
+ }\r
+}\r
+\r
+UIEXPORT void ui_job(UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd) {\r
+ UiJob* job = new UiJob;\r
+ job->obj = obj;\r
+ job->job_func = tf;\r
+ job->job_data = td;\r
+ job->finish_callback = f;\r
+ job->finish_data = fd;\r
+\r
+ std::thread jobThread(ui_job_thread, job);\r
+ jobThread.detach();\r
+}\r
+\r
+UIEXPORT void ui_call_mainthread(ui_threadfunc tf, void* td) {\r
+ bool isQueued = uiDispatcherQueue.TryEnqueue([tf, td]()\r
+ {\r
+ (void)tf(td);\r
+ });\r
+ if (!isQueued) {\r
+ // TODO: error or try again?\r
+ exit(-1);\r
+ }\r
+}\r
+\r
+static UiJob kill_job; // &kill_job indicates to stop the thread\r
+\r
+static void ui_threadpool_run(UiThreadpool* pool) {\r
+ for (;;) {\r
+ UiJob* job = pool->GetJob();\r
+ if (job == &kill_job) {\r
+ return;\r
+ }\r
+ else if (job) {\r
+ ui_job_thread(job);\r
+ }\r
+ }\r
+}\r
+\r
+UiThreadpool::UiThreadpool(int nthreads) {\r
+ for (int i = 0; i < nthreads; i++) {\r
+ std::thread thread(ui_threadpool_run, this);\r
+ thread.detach();\r
+ }\r
+}\r
+\r
+void UiThreadpool::EnqueueJob(UiJob* job)\r
+{\r
+ std::unique_lock<std::mutex> lock(mutex);\r
+ queue.push(job);\r
+ lock.unlock();\r
+ condition.notify_one();\r
+}\r
+\r
+UiJob* UiThreadpool::GetJob() {\r
+ std::unique_lock<std::mutex> lock(mutex);\r
+\r
+ UiJob* job = nullptr;\r
+ while (!job) {\r
+ if (queue.empty()) {\r
+ condition.wait(lock);\r
+ continue;\r
+ }\r
+ else\r
+ {\r
+ job = queue.front();\r
+ queue.pop();\r
+ }\r
+ }\r
+\r
+ return job;\r
+}\r
+\r
+UIEXPORT UiThreadpool* ui_threadpool_create(int nthreads) {\r
+ return new UiThreadpool(nthreads);\r
+}\r
+\r
+UIEXPORT void ui_threadpool_destroy(UiThreadpool* pool) {\r
+ // TODO\r
+}\r
+\r
+UIEXPORT void ui_threadpool_job(UiThreadpool* pool, UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd) {\r
+ UiJob* job = new UiJob;\r
+ job->obj = obj;\r
+ job->job_func = tf;\r
+ job->job_data = td;\r
+ job->finish_callback = f;\r
+ job->finish_data = fd;\r
+ pool->EnqueueJob(job);\r
+}\r
+\r
+\r
+\r
+void ui_set_widget_groups(UiContext *ctx, UIWIDGET widget, const int *groups) {\r
+ if(!groups) {\r
+ return;\r
+ }\r
+ size_t ngroups = uic_group_array_size(groups);\r
+ ui_set_widget_ngroups(ctx, widget, groups, ngroups);\r
+}\r
+\r
+void ui_set_widget_ngroups(UiContext *ctx, UIWIDGET widget, const int *groups, size_t ngroups) {\r
+ if(ngroups > 0) {\r
+ uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups);\r
+ ui_set_enabled(widget, FALSE);\r
+ }\r
+}\r
+\r
+\r
+UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled) {\r
+ Control ctrl = widget->uielement.as<Control>();\r
+ if (ctrl) {\r
+ ctrl.IsEnabled(enabled);\r
+ }\r
+}\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "../ui/toolkit.h"\r
+\r
+#include <queue>\r
+#include <mutex>\r
+#include <condition_variable>\r
+\r
+typedef struct UiJob {\r
+ UiObject* obj;\r
+ ui_threadfunc job_func;\r
+ void* job_data;\r
+ ui_callback finish_callback;\r
+ void* finish_data;\r
+} UiJob;\r
+\r
+struct UiThreadpool\r
+{\r
+ std::queue<UiJob*> queue;\r
+ std::mutex mutex;\r
+ std::condition_variable condition;\r
+\r
+ UiThreadpool(int nthreads);\r
+\r
+ void EnqueueJob(UiJob* job);\r
+\r
+ UiJob* GetJob();\r
+};\r
+\r
+typedef void(*ui_eventfunc)(void*, void*);\r
+\r
+void ui_app_run_startup();\r
+\r
+extern "C" void destroy_ui_window_wrapper(void* ptr);\r
+extern "C" void destroy_ui_widget_wrapper(void* ptr);\r
+\r
+void ui_context_add_window_destructor(UiContext* ctx, UiWindow* win);\r
+void ui_context_add_widget_destructor(UiContext* ctx, UiWidget* widget);\r
+void ui_context_add_container_destructor(UiContext* ctx, UiContainer *container);\r
+\r
+UiEvent ui_create_int_event(UiObject* obj, int64_t i);\r
+\r
+void ui_set_widget_groups(UiContext *ctx, UIWIDGET widget, const int *groups);\r
+void ui_set_widget_ngroups(UiContext *ctx, UIWIDGET widget, const int *groups, size_t ngroups);\r
--- /dev/null
+#include "pch.h"\r
+\r
+#include "util.h"\r
+\r
+#include <stdlib.h>\r
+\r
+\r
+wchar_t* str2wstr(const char* str, int* newlen) {\r
+ size_t len = strlen(str);\r
+\r
+ return str2wstr_len(str, len, newlen);\r
+}\r
+\r
+wchar_t* str2wstr_len(const char* str, size_t len, int* newlen) {\r
+ wchar_t* wstr = (wchar_t*)calloc(len + 1, sizeof(wchar_t));\r
+ int wlen = MultiByteToWideChar(\r
+ CP_UTF8,\r
+ 0,\r
+ str,\r
+ len,\r
+ wstr,\r
+ len + 1\r
+ );\r
+ if (newlen) {\r
+ *newlen = wlen;\r
+ }\r
+ wstr[wlen] = 0;\r
+\r
+ return wstr;\r
+}\r
+\r
+char* wchar2utf8(const wchar_t* wstr, size_t wlen) {\r
+ size_t maxlen = wlen * 4;\r
+ char* ret = (char*)malloc(maxlen + 1);\r
+ int ret_len = WideCharToMultiByte(\r
+ CP_UTF8,\r
+ 0,\r
+ wstr,\r
+ wlen,\r
+ ret,\r
+ maxlen,\r
+ NULL,\r
+ NULL);\r
+ ret[ret_len] = 0;\r
+ return ret;\r
+}\r
--- /dev/null
+#pragma once\r
+\r
+#include <stdlib.h>\r
+\r
+wchar_t* str2wstr(const char* str, int* newlen);\r
+\r
+wchar_t* str2wstr_len(const char* str, size_t len, int* newlen);\r
+\r
+char* wchar2utf8(const wchar_t* wstr, size_t wlen);\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#include "pch.h"\r
+\r
+\r
+#include "window.h"\r
+\r
+#include "appmenu.h"\r
+#include "commandbar.h"\r
+#include "container.h"\r
+#include "util.h"\r
+#include "button.h"\r
+\r
+#include "../common/context.h"\r
+#include "../common/object.h"\r
+\r
+#include <stdlib.h>\r
+\r
+#include <cx/mempool.h>\r
+\r
+#include "MainWindow.xaml.h"\r
+\r
+\r
+#include <Windows.h>\r
+#include <shobjidl.h>\r
+#include <iostream>\r
+\r
+using namespace winrt;\r
+using namespace Microsoft::UI::Xaml;\r
+using namespace Microsoft::UI::Xaml::Controls;\r
+using namespace Microsoft::UI::Xaml::Controls::Primitives;\r
+using namespace Microsoft::UI::Xaml::XamlTypeInfo;\r
+using namespace Microsoft::UI::Xaml::Markup;\r
+using namespace Windows::UI::Xaml::Interop;\r
+using namespace winrt::Windows::Foundation;\r
+using namespace winrt::Windows::Storage::Pickers;\r
+\r
+UiWindow::UiWindow(winrt::Microsoft::UI::Xaml::Window& win) : window(win) {}\r
+\r
+UiObject* ui_window(const char* title, void* window_data) {\r
+ UiObject* obj = ui_simple_window(title, window_data);\r
+\r
+ /*\r
+ if (uic_get_menu_list()) {\r
+ // create/add menubar\r
+ MenuBar mb = ui_create_menubar(obj);\r
+ mb.VerticalAlignment(VerticalAlignment::Top);\r
+ obj->container->Add(mb, false);\r
+ }\r
+ */\r
+\r
+ if (uic_toolbar_isenabled()) {\r
+ // create a grid for the toolbar: ColumnDefinitions="Auto, *, Auto"\r
+ Grid toolbar_grid = Grid();\r
+ GridLength gl;\r
+ gl.Value = 0;\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+\r
+ ColumnDefinition coldef0 = ColumnDefinition();\r
+ coldef0.Width(gl);\r
+ toolbar_grid.ColumnDefinitions().Append(coldef0);\r
+\r
+ gl.Value = 1;\r
+ gl.GridUnitType = GridUnitType::Star;\r
+ ColumnDefinition coldef1 = ColumnDefinition();\r
+ coldef1.Width(gl);\r
+ toolbar_grid.ColumnDefinitions().Append(coldef1);\r
+\r
+ gl.Value = 0;\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+ ColumnDefinition coldef2 = ColumnDefinition();\r
+ coldef2.Width(gl);\r
+ toolbar_grid.ColumnDefinitions().Append(coldef2);\r
+\r
+ // rowdef\r
+ gl.Value = 0;\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+ RowDefinition rowdef = RowDefinition();\r
+ rowdef.Height(gl);\r
+ toolbar_grid.RowDefinitions().Append(rowdef);\r
+\r
+\r
+ // create commandbar\r
+ CxList* def_l = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);\r
+ CxList* def_c = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);\r
+ CxList* def_r = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);\r
+\r
+ bool addappmenu = true;\r
+ if (cxListSize(def_r) > 0) {\r
+ CommandBar toolbar_r = ui_create_toolbar(obj, def_r, addappmenu);\r
+ toolbar_grid.SetColumn(toolbar_r, 2);\r
+ toolbar_grid.SetRow(toolbar_r, 0);\r
+ toolbar_grid.Children().Append(toolbar_r);\r
+ addappmenu = false;\r
+ }\r
+ if (cxListSize(def_c) > 0) {\r
+ CommandBar toolbar_c = ui_create_toolbar(obj, def_c, addappmenu);\r
+ toolbar_c.HorizontalAlignment(HorizontalAlignment::Center);\r
+ toolbar_grid.SetColumn(toolbar_c, 1);\r
+ toolbar_grid.SetRow(toolbar_c, 0);\r
+ toolbar_grid.Children().Append(toolbar_c);\r
+ addappmenu = false;\r
+ }\r
+ if (cxListSize(def_l) > 0) {\r
+ CommandBar toolbar_l = ui_create_toolbar(obj, def_l, addappmenu);\r
+ toolbar_grid.SetColumn(toolbar_l, 0);\r
+ toolbar_grid.SetRow(toolbar_l, 0);\r
+ toolbar_grid.Children().Append(toolbar_l);\r
+ }\r
+\r
+ toolbar_grid.VerticalAlignment(VerticalAlignment::Top);\r
+ obj->container->Add(toolbar_grid, false);\r
+ }\r
+\r
+ return obj;\r
+}\r
+\r
+UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data) {\r
+ CxMempool* mp = cxBasicMempoolCreate(256);\r
+ UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject));\r
+\r
+ obj->ctx = uic_context(obj, mp);\r
+ obj->window = window_data;\r
+\r
+ Window window = Window();\r
+ //Window window = make<winui::implementation::MainWindow>();\r
+\r
+ winrt::Windows::Foundation::Uri resourceLocator{ L"ms-appx:///MainWindow.xaml" };\r
+ Application::LoadComponent(window, resourceLocator, ComponentResourceLocation::Nested);\r
+\r
+ window.ExtendsContentIntoTitleBar(true);\r
+\r
+ Grid grid = Grid();\r
+ window.Content(grid);\r
+\r
+ StackPanel titleBar = StackPanel();\r
+ Thickness titleBarPadding = { 10, 5, 5, 10 };\r
+ titleBar.Padding(titleBarPadding);\r
+ titleBar.Orientation(Orientation::Horizontal);\r
+ TextBlock titleLabel = TextBlock();\r
+ titleBar.Children().Append(titleLabel);\r
+\r
+ if (title) {\r
+ wchar_t* wtitle = str2wstr(title, nullptr);\r
+ window.Title(wtitle);\r
+ titleLabel.Text(hstring(wtitle));\r
+ free(wtitle);\r
+ }\r
+\r
+ window.SetTitleBar(titleBar);\r
+\r
+ obj->wobj = new UiWindow(window);\r
+ ui_context_add_window_destructor(obj->ctx, obj->wobj);\r
+\r
+ window.Closed([obj](IInspectable const& sender, WindowEventArgs) {\r
+ if (obj->ctx->close_callback) {\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.document = obj->ctx->document;\r
+ evt.window = obj->window;\r
+ evt.eventdata = NULL;\r
+ evt.intval = 0;\r
+ obj->ctx->close_callback(&evt, obj->ctx->close_data);\r
+ } else {\r
+ ui_context_destroy(obj->ctx);\r
+ }\r
+ });\r
+\r
+ obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0);\r
+\r
+ titleBar.VerticalAlignment(VerticalAlignment::Top);\r
+ obj->container->Add(titleBar, false);\r
+\r
+ obj->window = window_data;\r
+\r
+ return obj;\r
+}\r
+\r
+static void dialog_button_add_callback(ContentDialog dialog, Button button, int num, UiObject *obj, ui_callback onclick, void *onclickdata) {\r
+ button.Click([dialog, num, obj, onclick, onclickdata](IInspectable const& sender, RoutedEventArgs) {\r
+ if (onclick) {\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.window = obj->window;\r
+ evt.document = obj->ctx->document;\r
+ evt.eventdata = nullptr;\r
+ evt.intval = num;\r
+ onclick(&evt, onclickdata);\r
+ }\r
+ dialog.Hide();\r
+ });\r
+}\r
+\r
+UIEXPORT UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) {\r
+ UiWindow *window = parent->wobj;\r
+ if (!window) {\r
+ return NULL;\r
+ }\r
+ \r
+ CxMempool* mp = cxBasicMempoolCreate(256);\r
+ UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject));\r
+ \r
+ obj->ctx = uic_context(obj, mp);\r
+ \r
+ ContentDialog dialog = ContentDialog();\r
+ UIElement elm = dialog;\r
+ UiWidget* widget = new UiWidget(elm);\r
+ ui_context_add_widget_destructor(obj->ctx, widget);\r
+ obj->widget = widget;\r
+\r
+ if (args.title) {\r
+ wchar_t* wtitle = str2wstr(args.title, nullptr);\r
+ dialog.Title(box_value(wtitle));\r
+ free(wtitle);\r
+ }\r
+\r
+ \r
+ Grid dialogContent = Grid();\r
+ GridLength gl;\r
+\r
+ // content row\r
+ gl.Value = 1;\r
+ gl.GridUnitType = GridUnitType::Star;\r
+ RowDefinition rowdef0 = RowDefinition();\r
+ rowdef0.Height(gl);\r
+ dialogContent.RowDefinitions().Append(rowdef0);\r
+\r
+ // button row\r
+ gl.Value = 0;\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+ RowDefinition rowdef1 = RowDefinition();\r
+ rowdef1.Height(gl);\r
+ dialogContent.RowDefinitions().Append(rowdef1);\r
+\r
+ // coldef\r
+ gl.Value = 1;\r
+ gl.GridUnitType = GridUnitType::Star;\r
+ ColumnDefinition coldef = ColumnDefinition();\r
+ coldef.Width(gl);\r
+ dialogContent.ColumnDefinitions().Append(coldef);\r
+\r
+ // content\r
+ Grid grid = Grid();\r
+ grid.SetRow(grid, 0);\r
+ grid.SetColumn(grid, 0);\r
+ dialogContent.Children().Append(grid);\r
+ obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0);\r
+\r
+ // buttons\r
+ Grid buttons = Grid();\r
+ Thickness btnsMargin = { (double)0, (double)10, (double)0, (double)0 };\r
+ buttons.Margin(btnsMargin);\r
+\r
+ RowDefinition btnrowdef = RowDefinition();\r
+ gl.Value = 0;\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+ btnrowdef.Height(gl);\r
+ buttons.RowDefinitions().Append(btnrowdef);\r
+\r
+ gl.Value = 1;\r
+ gl.GridUnitType = GridUnitType::Auto;\r
+ int c = 0;\r
+ if (args.lbutton1) {\r
+ ColumnDefinition bcoldef = ColumnDefinition();\r
+ bcoldef.Width(gl);\r
+ buttons.ColumnDefinitions().Append(bcoldef);\r
+\r
+ Button btn = Button();\r
+ ui_set_button_label(btn, args.lbutton1, NULL, NULL, UI_LABEL_TEXT);\r
+ Thickness margin = { (double)5, (double)5, (double)5, (double)5 };\r
+ btn.Margin(margin);\r
+ btn.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+ dialog_button_add_callback(dialog, btn, 1, obj, args.onclick, args.onclickdata);\r
+\r
+ buttons.SetRow(btn, 0);\r
+ buttons.SetColumn(btn, c++);\r
+ buttons.Children().Append(btn);\r
+ }\r
+ if (args.lbutton2) {\r
+ ColumnDefinition bcoldef = ColumnDefinition();\r
+ bcoldef.Width(gl);\r
+ buttons.ColumnDefinitions().Append(bcoldef);\r
+\r
+ Button btn = Button();\r
+ ui_set_button_label(btn, args.lbutton2, NULL, NULL, UI_LABEL_TEXT);\r
+ Thickness margin = { (double)5, (double)5, (double)5, (double)5 };\r
+ btn.Margin(margin);\r
+ btn.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+ dialog_button_add_callback(dialog, btn, 2, obj, args.onclick, args.onclickdata);\r
+\r
+ buttons.SetRow(btn, 0);\r
+ buttons.SetColumn(btn, c++);\r
+ buttons.Children().Append(btn);\r
+ }\r
+ if (args.rbutton3) {\r
+ ColumnDefinition bcoldef = ColumnDefinition();\r
+ bcoldef.Width(gl);\r
+ buttons.ColumnDefinitions().Append(bcoldef);\r
+\r
+ Button btn = Button();\r
+ ui_set_button_label(btn, args.rbutton3, NULL, NULL, UI_LABEL_TEXT);\r
+ Thickness margin = { (double)5, (double)5, (double)5, (double)5 };\r
+ btn.Margin(margin);\r
+ btn.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+ dialog_button_add_callback(dialog, btn, 3, obj, args.onclick, args.onclickdata);\r
+\r
+ buttons.SetRow(btn, 0);\r
+ buttons.SetColumn(btn, c++);\r
+ buttons.Children().Append(btn);\r
+ }\r
+ if (args.rbutton4) {\r
+ ColumnDefinition bcoldef = ColumnDefinition();\r
+ bcoldef.Width(gl);\r
+ buttons.ColumnDefinitions().Append(bcoldef);\r
+\r
+ Button btn = Button();\r
+ ui_set_button_label(btn, args.rbutton4, NULL, NULL, UI_LABEL_TEXT);\r
+ Thickness margin = { (double)5, (double)5, (double)5, (double)5 };\r
+ btn.Margin(margin);\r
+ btn.HorizontalAlignment(HorizontalAlignment::Stretch);\r
+ dialog_button_add_callback(dialog, btn, 4, obj, args.onclick, args.onclickdata);\r
+\r
+ buttons.SetRow(btn, 0);\r
+ buttons.SetColumn(btn, c++);\r
+ buttons.Children().Append(btn);\r
+ }\r
+\r
+ dialogContent.SetRow(buttons, 1);\r
+ dialogContent.SetColumn(buttons, 0);\r
+ dialogContent.Children().Append(buttons);\r
+\r
+\r
+ dialog.Content(dialogContent);\r
+ dialog.XamlRoot(window->window.Content().XamlRoot());\r
+\r
+ obj->widget->Show = [dialog]() {\r
+ dialog.ShowAsync();\r
+ };\r
+\r
+ return obj;\r
+}\r
+\r
+void ui_window_size(UiObject *obj, int width, int height) {\r
+ UIWINDOW win = obj->wobj;\r
+ if (win) {\r
+ winrt::Windows::Graphics::SizeInt32 wsize;\r
+ wsize.Width = width;\r
+ wsize.Height = height;\r
+ win->window.AppWindow().Resize(wsize);\r
+ }\r
+}\r
+\r
+\r
+\r
+\r
+static Windows::Foundation::IAsyncAction create_dialog_async(UiObject *obj, UiDialogArgs args) {\r
+ UiObject* current = uic_current_obj(obj);\r
+ Window parentWindow = current->wobj->window;\r
+\r
+ ContentDialog dialog = ContentDialog();\r
+ dialog.XamlRoot(parentWindow.Content().XamlRoot());\r
+\r
+ if (args.title) {\r
+ wchar_t *str = str2wstr(args.title, nullptr);\r
+ dialog.Title(winrt::box_value(str));\r
+ free(str);\r
+ }\r
+\r
+ TextBox textfield{ nullptr };\r
+ PasswordBox password{ nullptr };\r
+ if(args.input || args.password) {\r
+ StackPanel panel = StackPanel();\r
+ panel.Orientation(Orientation::Vertical);\r
+ if (args.content) {\r
+ wchar_t *str = str2wstr(args.content, nullptr);\r
+ TextBlock label = TextBlock();\r
+ label.Text(str);\r
+ panel.Children().Append(label);\r
+ free(str);\r
+ }\r
+\r
+ Thickness margin = { 0, 5, 0, 0 };\r
+ if (args.password) {\r
+ password = PasswordBox();\r
+ password.Margin(margin);\r
+ panel.Children().Append(password);\r
+ } else {\r
+ textfield = TextBox();\r
+ textfield.Margin(margin);\r
+ panel.Children().Append(textfield);\r
+ }\r
+ \r
+ panel.Margin(margin);\r
+\r
+ dialog.Content(panel);\r
+\r
+ } else {\r
+ if (args.content) {\r
+ wchar_t *str = str2wstr(args.content, nullptr);\r
+ dialog.Content(winrt::box_value(str));\r
+ free(str);\r
+ }\r
+ }\r
+\r
+ if (args.button1_label) {\r
+ wchar_t *str = str2wstr(args.button1_label, nullptr);\r
+ dialog.PrimaryButtonText(winrt::hstring(str));\r
+ free(str);\r
+ dialog.DefaultButton(ContentDialogButton::Primary);\r
+ }\r
+ if (args.button2_label) {\r
+ wchar_t *str = str2wstr(args.button2_label, nullptr);\r
+ dialog.SecondaryButtonText(winrt::hstring(str));\r
+ free(str);\r
+ }\r
+ if (args.closebutton_label) {\r
+ wchar_t *str = str2wstr(args.closebutton_label, nullptr);\r
+ dialog.CloseButtonText(winrt::hstring(str));\r
+ free(str);\r
+ }\r
+\r
+ ContentDialogResult result = co_await dialog.ShowAsync();\r
+\r
+ if (args.result) {\r
+ UiEvent evt;\r
+ evt.obj = current;\r
+ evt.document = current->ctx->document;\r
+ evt.window = current->window;\r
+ evt.eventdata = NULL;\r
+ evt.intval = 0;\r
+ if (result == ContentDialogResult::Primary) {\r
+ evt.intval = 1;\r
+ } else if (result == ContentDialogResult::Secondary) {\r
+ evt.intval = 2;\r
+ }\r
+\r
+ if (args.password) {\r
+ std::wstring wstr(password.Password());\r
+ char *text = wchar2utf8(wstr.c_str(), wstr.length());\r
+ evt.eventdata = text;\r
+ } else if (args.input) {\r
+ std::wstring wstr(textfield.Text());\r
+ char *text = wchar2utf8(wstr.c_str(), wstr.length());\r
+ evt.eventdata = text;\r
+ }\r
+\r
+ args.result(&evt, args.resultdata);\r
+\r
+ if (evt.eventdata) {\r
+ free(evt.eventdata);\r
+ }\r
+ }\r
+}\r
+\r
+UIEXPORT void ui_dialog_create(UiObject *obj, UiDialogArgs args) {\r
+ create_dialog_async(obj, args);\r
+}\r
+\r
+\r
+\r
+// --------------------------------------- File Dialog ---------------------------------------\r
+\r
+static void filedialog_callback(\r
+ UiObject *obj,\r
+ ui_callback file_selected_callback,\r
+ void *cbdata,\r
+ winrt::Windows::Foundation::Collections::IVectorView<winrt::Windows::Storage::StorageFile> result)\r
+{\r
+ UiFileList flist;\r
+ flist.nfiles = result.Size();\r
+ flist.files = new char*[flist.nfiles];\r
+\r
+ int i = 0;\r
+ for (auto const& file : result) {\r
+ winrt::hstring path = file.Path();\r
+ flist.files[i++] = wchar2utf8(path.c_str(), path.size());\r
+ }\r
+\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.document = obj->ctx->document;\r
+ evt.window = obj->window;\r
+ evt.eventdata = &flist;\r
+ evt.intval = 0;\r
+ file_selected_callback(&evt, cbdata);\r
+\r
+ for (int i = 0; i < flist.nfiles;i++) {\r
+ free(flist.files[i]);\r
+ }\r
+ delete[] flist.files;\r
+}\r
+\r
+static Windows::Foundation::IAsyncAction open_filedialog_async(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {\r
+ FileOpenPicker openFileDialog = FileOpenPicker();\r
+ auto initializeWithWindow { openFileDialog.as<::IInitializeWithWindow>()\r
+ };\r
+\r
+ HWND hwnd{ nullptr };\r
+ winrt::check_hresult(obj->wobj->window.as<IWindowNative>()->get_WindowHandle(&hwnd));\r
+\r
+ initializeWithWindow->Initialize(hwnd);\r
+\r
+ openFileDialog.FileTypeFilter().Append(L"*");\r
+\r
+ if ((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) {\r
+ auto files = co_await openFileDialog.PickMultipleFilesAsync();\r
+ filedialog_callback(obj, file_selected_callback, cbdata, files);\r
+ } else {\r
+ auto file = co_await openFileDialog.PickSingleFileAsync();\r
+ auto files = single_threaded_vector<winrt::Windows::Storage::StorageFile>();\r
+ files.Append(file);\r
+ filedialog_callback(obj, file_selected_callback, cbdata, files.GetView());\r
+ }\r
+}\r
+\r
+static Windows::Foundation::IAsyncAction save_filedialog_async(UiObject *obj, char *name, ui_callback file_selected_callback, void *cbdata) {\r
+ IFileSaveDialog *saveFileDialog;\r
+\r
+ HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL, IID_IFileSaveDialog, reinterpret_cast<void**>(&saveFileDialog));\r
+ if (FAILED(hr))\r
+ {\r
+ co_return;\r
+ }\r
+\r
+ if (name) {\r
+ wchar_t *wname = str2wstr(name, NULL);\r
+ saveFileDialog->SetFileName(wname);\r
+ free(wname);\r
+ free(name);\r
+ }\r
+ \r
+\r
+ hr = saveFileDialog->Show(NULL);\r
+ if (SUCCEEDED(hr)) {\r
+ IShellItem *item;\r
+ hr = saveFileDialog->GetResult(&item);\r
+ if (SUCCEEDED(hr)) {\r
+ PWSTR wpath;\r
+ hr = item->GetDisplayName(SIGDN_FILESYSPATH, &wpath);\r
+\r
+ if (SUCCEEDED(hr)) {\r
+ char *path = wchar2utf8(wpath, lstrlen(wpath));\r
+ CoTaskMemFree(wpath);\r
+\r
+ UiFileList flist;\r
+ flist.nfiles = 1;\r
+ flist.files = new char*[1];\r
+ flist.files[0] = path;\r
+\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.document = obj->ctx->document;\r
+ evt.window = obj->window;\r
+ evt.eventdata = &flist;\r
+ evt.intval = 0;\r
+ file_selected_callback(&evt, cbdata);\r
+\r
+ free(path);\r
+ delete[] flist.files;\r
+ }\r
+ item->Release();\r
+ }\r
+ }\r
+\r
+ // cleanup\r
+ saveFileDialog->Release();\r
+}\r
+\r
+static Windows::Foundation::IAsyncAction folderdialog_async(UiObject *obj, ui_callback file_selected_callback, void *cbdata) {\r
+ FolderPicker folderPicker = FolderPicker();\r
+ auto initializeWithWindow { folderPicker.as<::IInitializeWithWindow>()\r
+ };\r
+\r
+ HWND hwnd{ nullptr };\r
+ winrt::check_hresult(obj->wobj->window.as<IWindowNative>()->get_WindowHandle(&hwnd));\r
+\r
+ initializeWithWindow->Initialize(hwnd);\r
+\r
+ folderPicker.FileTypeFilter().Append(L"*");\r
+\r
+ auto folder = co_await folderPicker.PickSingleFolderAsync();\r
+ if (folder) {\r
+ winrt::hstring hpath = folder.Path();\r
+ char *cpath = wchar2utf8(hpath.c_str(), hpath.size());\r
+ \r
+ UiFileList flist;\r
+ flist.nfiles = 1;\r
+ flist.files = &cpath;\r
+\r
+ UiEvent evt;\r
+ evt.obj = obj;\r
+ evt.document = obj->ctx->document;\r
+ evt.window = obj->window;\r
+ evt.eventdata = &flist;\r
+ evt.intval = 0;\r
+ file_selected_callback(&evt, cbdata);\r
+ }\r
+}\r
+\r
+UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {\r
+ if ((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) {\r
+ folderdialog_async(obj, file_selected_callback, cbdata);\r
+ } else {\r
+ open_filedialog_async(obj, mode, file_selected_callback, cbdata);\r
+ }\r
+}\r
+\r
+UIEXPORT void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) {\r
+ char *n = name ? _strdup(name) : NULL;\r
+ save_filedialog_async(obj, n, file_selected_callback, cbdata);\r
+}\r
--- /dev/null
+/*\r
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.\r
+ *\r
+ * Copyright 2023 Olaf Wintermann. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ *\r
+ * 1. Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * 2. Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+#pragma once\r
+\r
+#include "toolkit.h"\r
+\r
+#include "../ui/window.h"\r
+\r
+#include <Windows.h>\r
+#undef GetCurrentTime\r
+#include <winrt/Windows.Foundation.Collections.h>\r
+#include <winrt/Windows.UI.Xaml.Interop.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.h>\r
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>\r
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>\r
+#include <winrt/Microsoft.UI.Xaml.Markup.h>\r
+\r
+\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+ <Import Project="..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.props')" />\r
+ <Import Project="..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props')" />\r
+ <Import Project="..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props')" />\r
+ <PropertyGroup Label="Globals">\r
+ <CppWinRTOptimized>true</CppWinRTOptimized>\r
+ <CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>\r
+ <MinimalCoreWin>true</MinimalCoreWin>\r
+ <ProjectGuid>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</ProjectGuid>\r
+ <ProjectName>winui</ProjectName>\r
+ <RootNamespace>winui</RootNamespace>\r
+ <!--\r
+ $(TargetName) should be same as $(RootNamespace) so that the produced binaries (.exe/.pri/etc.)\r
+ have a name that matches the .winmd\r
+ -->\r
+ <TargetName>$(RootNamespace)</TargetName>\r
+ <DefaultLanguage>de-DE</DefaultLanguage>\r
+ <MinimumVisualStudioVersion>16.0</MinimumVisualStudioVersion>\r
+ <AppContainerApplication>false</AppContainerApplication>\r
+ <AppxPackage>false</AppxPackage>\r
+ <ApplicationType>Windows Store</ApplicationType>\r
+ <ApplicationTypeRevision>10.0</ApplicationTypeRevision>\r
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r
+ <WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>\r
+ <UseWinUI>true</UseWinUI>\r
+ <EnableMsixTooling>true</EnableMsixTooling>\r
+ <WindowsPackageType>None</WindowsPackageType>\r
+ <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+ <ItemGroup Label="ProjectConfigurations">\r
+ <ProjectConfiguration Include="Debug|Win32">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>Win32</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Debug|x64">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>x64</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Debug|ARM64">\r
+ <Configuration>Debug</Configuration>\r
+ <Platform>ARM64</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|Win32">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>Win32</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|x64">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>x64</Platform>\r
+ </ProjectConfiguration>\r
+ <ProjectConfiguration Include="Release|ARM64">\r
+ <Configuration>Release</Configuration>\r
+ <Platform>ARM64</Platform>\r
+ </ProjectConfiguration>\r
+ </ItemGroup>\r
+ <PropertyGroup Label="Configuration">\r
+ <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+ <PlatformToolset>v143</PlatformToolset>\r
+ <CharacterSet>Unicode</CharacterSet>\r
+ <DesktopCompatible>true</DesktopCompatible>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">\r
+ <UseDebugLibraries>true</UseDebugLibraries>\r
+ <LinkIncremental>true</LinkIncremental>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">\r
+ <UseDebugLibraries>false</UseDebugLibraries>\r
+ <WholeProgramOptimization>true</WholeProgramOptimization>\r
+ <LinkIncremental>true</LinkIncremental>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+ <ImportGroup Label="ExtensionSettings">\r
+ </ImportGroup>\r
+ <ImportGroup Label="PropertySheets">\r
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+ </ImportGroup>\r
+ <PropertyGroup Label="UserMacros" />\r
+ <ItemDefinitionGroup>\r
+ <ClCompile>\r
+ <PrecompiledHeader>Use</PrecompiledHeader>\r
+ <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>\r
+ <PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>\r
+ <WarningLevel>Level4</WarningLevel>\r
+ <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>\r
+ </ClCompile>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">\r
+ <ClCompile>\r
+ <PreprocessorDefinitions>_DEBUG;DISABLE_XAML_GENERATED_MAIN__;UI_WINUI;UI_WINUI_PCH;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+ <LanguageStandard_C Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">stdc17</LanguageStandard_C>\r
+ <RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MultiThreadedDebugDLL</RuntimeLibrary>\r
+ </ClCompile>\r
+ <Link>\r
+ <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">shell32.lib;gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">\r
+ <ClCompile>\r
+ <PreprocessorDefinitions>DISABLE_XAML_GENERATED_MAIN__;UI_WINUI;UI_WINUI_PCH;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+ <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+ <SDLCheck Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</SDLCheck>\r
+ <LanguageStandard_C Condition="'$(Configuration)|$(Platform)'=='Release|x64'">stdc17</LanguageStandard_C>\r
+ </ClCompile>\r
+ <Link>\r
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+ <OptimizeReferences>true</OptimizeReferences>\r
+ <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|x64'">shell32.lib;gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+ </Link>\r
+ </ItemDefinitionGroup>\r
+ <ItemGroup Condition="'$(WindowsPackageType)'!='None' and Exists('Package.appxmanifest')">\r
+ <AppxManifest Include="Package.appxmanifest">\r
+ <SubType>Designer</SubType>\r
+ </AppxManifest>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Manifest Include="app.manifest" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClInclude Include="..\common\context.h" />\r
+ <ClInclude Include="..\common\document.h" />\r
+ <ClInclude Include="..\common\menu.h" />\r
+ <ClInclude Include="..\common\object.h" />\r
+ <ClInclude Include="..\common\properties.h" />\r
+ <ClInclude Include="..\common\toolbar.h" />\r
+ <ClInclude Include="..\common\types.h" />\r
+ <ClInclude Include="..\common\ucx_properties.h" />\r
+ <ClInclude Include="..\ui\button.h" />\r
+ <ClInclude Include="..\ui\container.h" />\r
+ <ClInclude Include="..\ui\display.h" />\r
+ <ClInclude Include="..\ui\dnd.h" />\r
+ <ClInclude Include="..\ui\entry.h" />\r
+ <ClInclude Include="..\ui\graphics.h" />\r
+ <ClInclude Include="..\ui\icons.h" />\r
+ <ClInclude Include="..\ui\image.h" />\r
+ <ClInclude Include="..\ui\menu.h" />\r
+ <ClInclude Include="..\ui\properties.h" />\r
+ <ClInclude Include="..\ui\range.h" />\r
+ <ClInclude Include="..\ui\stock.h" />\r
+ <ClInclude Include="..\ui\text.h" />\r
+ <ClInclude Include="..\ui\toolbar.h" />\r
+ <ClInclude Include="..\ui\toolkit.h" />\r
+ <ClInclude Include="..\ui\tree.h" />\r
+ <ClInclude Include="..\ui\ui.h" />\r
+ <ClInclude Include="..\ui\window.h" />\r
+ <ClInclude Include="appmenu.h" />\r
+ <ClInclude Include="button.h" />\r
+ <ClInclude Include="commandbar.h" />\r
+ <ClInclude Include="condvar.h" />\r
+ <ClInclude Include="container.h" />\r
+ <ClInclude Include="dnd.h" />\r
+ <ClInclude Include="icons.h" />\r
+ <ClInclude Include="image.h" />\r
+ <ClInclude Include="label.h" />\r
+ <ClInclude Include="list.h" />\r
+ <ClInclude Include="pch.h" />\r
+ <ClInclude Include="App.xaml.h">\r
+ <DependentUpon>App.xaml</DependentUpon>\r
+ </ClInclude>\r
+ <ClInclude Include="MainWindow.xaml.h">\r
+ <DependentUpon>MainWindow.xaml</DependentUpon>\r
+ </ClInclude>\r
+ <ClInclude Include="stock.h" />\r
+ <ClInclude Include="table.h" />\r
+ <ClInclude Include="text.h" />\r
+ <ClInclude Include="toolkit.h" />\r
+ <ClInclude Include="util.h" />\r
+ <ClInclude Include="window.h" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ApplicationDefinition Include="App.xaml" />\r
+ <Page Include="MainWindow.xaml" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClCompile Include="..\common\context.c">\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\document.c">\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\menu.c">\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\object.c">\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\properties.c">\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\toolbar.c">\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\types.c">\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\ucx_properties.c">\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>\r
+ </ClCompile>\r
+ <ClCompile Include="appmenu.cpp" />\r
+ <ClCompile Include="button.cpp" />\r
+ <ClCompile Include="commandbar.cpp" />\r
+ <ClCompile Include="condvar.cpp" />\r
+ <ClCompile Include="container.cpp" />\r
+ <ClCompile Include="dnd.cpp" />\r
+ <ClCompile Include="icons.cpp" />\r
+ <ClCompile Include="image.cpp" />\r
+ <ClCompile Include="label.cpp" />\r
+ <ClCompile Include="list.cpp" />\r
+ <ClCompile Include="pch.cpp">\r
+ <PrecompiledHeader>Create</PrecompiledHeader>\r
+ </ClCompile>\r
+ <ClCompile Include="App.xaml.cpp">\r
+ <DependentUpon>App.xaml</DependentUpon>\r
+ </ClCompile>\r
+ <ClCompile Include="MainWindow.xaml.cpp">\r
+ <DependentUpon>MainWindow.xaml</DependentUpon>\r
+ </ClCompile>\r
+ <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />\r
+ <ClCompile Include="stock.cpp" />\r
+ <ClCompile Include="table.cpp" />\r
+ <ClCompile Include="text.cpp" />\r
+ <ClCompile Include="toolkit.cpp" />\r
+ <ClCompile Include="util.cpp" />\r
+ <ClCompile Include="window.cpp" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Midl Include="App.idl">\r
+ <SubType>Code</SubType>\r
+ <DependentUpon>App.xaml</DependentUpon>\r
+ </Midl>\r
+ <Midl Include="MainWindow.idl">\r
+ <SubType>Code</SubType>\r
+ <DependentUpon>MainWindow.xaml</DependentUpon>\r
+ </Midl>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Text Include="readme.txt">\r
+ <DeploymentContent>false</DeploymentContent>\r
+ </Text>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Image Include="Assets\LockScreenLogo.scale-200.png" />\r
+ <Image Include="Assets\SplashScreen.scale-200.png" />\r
+ <Image Include="Assets\Square150x150Logo.scale-200.png" />\r
+ <Image Include="Assets\Square44x44Logo.scale-200.png" />\r
+ <Image Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />\r
+ <Image Include="Assets\StoreLogo.png" />\r
+ <Image Include="Assets\Wide310x150Logo.scale-200.png" />\r
+ </ItemGroup>\r
+ <!--\r
+ Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging\r
+ Tools extension to be activated for this project even if the Windows App SDK Nuget\r
+ package has not yet been restored.\r
+ -->\r
+ <ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">\r
+ <ProjectCapability Include="Msix" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <None Include="packages.config" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ProjectReference Include="..\..\make\vs\ucx\ucx.vcxproj">\r
+ <Project>{27da0164-3475-43e2-a1a4-a5d07d305749}</Project>\r
+ </ProjectReference>\r
+ </ItemGroup>\r
+ <!--\r
+ Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution\r
+ Explorer "Package and Publish" context menu entry to be enabled for this project even if\r
+ the Windows App SDK Nuget package has not yet been restored.\r
+ -->\r
+ <PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">\r
+ <HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+ <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+ <IntDir>..\..\build\vs\winui\$(Platform)\$(Configuration)\</IntDir>\r
+ </PropertyGroup>\r
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+ <OutDir>$(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\</OutDir>\r
+ <IntDir>..\..\build\vs\winui\$(Platform)\$(Configuration)\</IntDir>\r
+ </PropertyGroup>\r
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+ <ImportGroup Label="ExtensionTargets">\r
+ <Import Project="..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets')" />\r
+ <Import Project="..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets')" />\r
+ <Import Project="..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />\r
+ <Import Project="..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.targets')" />\r
+ </ImportGroup>\r
+ <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">\r
+ <PropertyGroup>\r
+ <ErrorText>Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}".</ErrorText>\r
+ </PropertyGroup>\r
+ <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.props'))" />\r
+ <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.WindowsAppSDK.1.5.241001000\build\native\Microsoft.WindowsAppSDK.targets'))" />\r
+ <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props'))" />\r
+ <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets'))" />\r
+ <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.ImplementationLibrary.1.0.240803.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />\r
+ <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.props'))" />\r
+ <Error Condition="!Exists('..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\make\vs\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.1742\build\Microsoft.Windows.SDK.BuildTools.targets'))" />\r
+ </Target>\r
+</Project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+ <ItemGroup>\r
+ <ApplicationDefinition Include="App.xaml" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Page Include="MainWindow.xaml" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Midl Include="App.idl" />\r
+ <Midl Include="MainWindow.idl" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClCompile Include="pch.cpp" />\r
+ <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />\r
+ <ClCompile Include="appmenu.cpp" />\r
+ <ClCompile Include="button.cpp" />\r
+ <ClCompile Include="commandbar.cpp" />\r
+ <ClCompile Include="container.cpp" />\r
+ <ClCompile Include="list.cpp" />\r
+ <ClCompile Include="table.cpp" />\r
+ <ClCompile Include="text.cpp" />\r
+ <ClCompile Include="toolkit.cpp" />\r
+ <ClCompile Include="util.cpp" />\r
+ <ClCompile Include="window.cpp" />\r
+ <ClCompile Include="stock.cpp" />\r
+ <ClCompile Include="icons.cpp" />\r
+ <ClCompile Include="label.cpp" />\r
+ <ClCompile Include="dnd.cpp" />\r
+ <ClCompile Include="condvar.cpp" />\r
+ <ClCompile Include="image.cpp" />\r
+ <ClCompile Include="..\common\context.c">\r
+ <Filter>common</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\document.c">\r
+ <Filter>common</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\menu.c">\r
+ <Filter>common</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\object.c">\r
+ <Filter>common</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\properties.c">\r
+ <Filter>common</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\toolbar.c">\r
+ <Filter>common</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\types.c">\r
+ <Filter>common</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="..\common\ucx_properties.c">\r
+ <Filter>common</Filter>\r
+ </ClCompile>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <ClInclude Include="pch.h" />\r
+ <ClInclude Include="appmenu.h" />\r
+ <ClInclude Include="button.h" />\r
+ <ClInclude Include="commandbar.h" />\r
+ <ClInclude Include="container.h" />\r
+ <ClInclude Include="list.h" />\r
+ <ClInclude Include="table.h" />\r
+ <ClInclude Include="text.h" />\r
+ <ClInclude Include="toolkit.h" />\r
+ <ClInclude Include="util.h" />\r
+ <ClInclude Include="window.h" />\r
+ <ClInclude Include="..\ui\button.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\container.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\display.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\dnd.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\entry.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\graphics.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\image.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\menu.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\properties.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\range.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\stock.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\text.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\toolbar.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\toolkit.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\tree.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\ui.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\ui\window.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="stock.h" />\r
+ <ClInclude Include="icons.h" />\r
+ <ClInclude Include="label.h" />\r
+ <ClInclude Include="dnd.h" />\r
+ <ClInclude Include="condvar.h" />\r
+ <ClInclude Include="image.h" />\r
+ <ClInclude Include="..\ui\icons.h">\r
+ <Filter>public</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\common\context.h">\r
+ <Filter>common</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\common\document.h">\r
+ <Filter>common</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\common\menu.h">\r
+ <Filter>common</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\common\object.h">\r
+ <Filter>common</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\common\properties.h">\r
+ <Filter>common</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\common\toolbar.h">\r
+ <Filter>common</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\common\types.h">\r
+ <Filter>common</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\common\ucx_properties.h">\r
+ <Filter>common</Filter>\r
+ </ClInclude>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Image Include="Assets\Wide310x150Logo.scale-200.png">\r
+ <Filter>Assets</Filter>\r
+ </Image>\r
+ <Image Include="Assets\StoreLogo.png">\r
+ <Filter>Assets</Filter>\r
+ </Image>\r
+ <Image Include="Assets\Square150x150Logo.scale-200.png">\r
+ <Filter>Assets</Filter>\r
+ </Image>\r
+ <Image Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png">\r
+ <Filter>Assets</Filter>\r
+ </Image>\r
+ <Image Include="Assets\Square44x44Logo.scale-200.png">\r
+ <Filter>Assets</Filter>\r
+ </Image>\r
+ <Image Include="Assets\SplashScreen.scale-200.png">\r
+ <Filter>Assets</Filter>\r
+ </Image>\r
+ <Image Include="Assets\LockScreenLogo.scale-200.png">\r
+ <Filter>Assets</Filter>\r
+ </Image>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Filter Include="Assets">\r
+ <UniqueIdentifier>{59f97886-bf49-4b3f-9ef6-fa7a84f3ab56}</UniqueIdentifier>\r
+ </Filter>\r
+ <Filter Include="public">\r
+ <UniqueIdentifier>{2b58fe46-d27b-4335-b63c-13ec2204fa24}</UniqueIdentifier>\r
+ </Filter>\r
+ <Filter Include="common">\r
+ <UniqueIdentifier>{35c88d5c-b36f-41b3-86c0-784227845ae9}</UniqueIdentifier>\r
+ </Filter>\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Text Include="readme.txt" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Manifest Include="app.manifest" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <None Include="packages.config" />\r
+ </ItemGroup>\r
+ <ItemGroup>\r
+ <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />\r
+ </ItemGroup>\r
+</Project>
\ No newline at end of file