From: Olaf Wintermann Date: Thu, 5 Dec 2024 09:22:08 +0000 (+0100) Subject: add ucx and toolkit code X-Git-Url: https://uap-core.de/gitweb/?a=commitdiff_plain;h=dc301960ffbb47e582c7e38861ccb5fbed089ca9;p=note.git add ucx and toolkit code --- dc301960ffbb47e582c7e38861ccb5fbed089ca9 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8bfba4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +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 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7162bba --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +all: config.mk + $(MAKE) -f make/Makefile.mk + +config.mk: + ./configure + +clean: FORCE + rm -fR build/* + +FORCE: diff --git a/application/Makefile b/application/Makefile new file mode 100644 index 0000000..1ab56e2 --- /dev/null +++ b/application/Makefile @@ -0,0 +1,45 @@ +# +# 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 $< + diff --git a/application/main.c b/application/main.c new file mode 100644 index 0000000..45d80ef --- /dev/null +++ b/application/main.c @@ -0,0 +1,55 @@ +/* + * 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 +#include + +#ifdef _WIN32 +#include +#endif + +#include + +int app_main(int argc, char **argv) { + ui_init("notes", argc, argv); + ui_main(); + return 0; +} + +#ifndef _WIN32 + +int main(int argc, char** argv) { + return app_main(argc, argv); +} +#else + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { + return app_main(__argc, __argv); +} +#endif + diff --git a/configure b/configure new file mode 100755 index 0000000..4fe9469 --- /dev/null +++ b/configure @@ -0,0 +1,1147 @@ +#!/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" diff --git a/libidav/Makefile b/libidav/Makefile new file mode 100644 index 0000000..83cf2d6 --- /dev/null +++ b/libidav/Makefile @@ -0,0 +1,60 @@ +# +# 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) + diff --git a/libidav/config.c b/libidav/config.c new file mode 100644 index 0000000..9cbb902 --- /dev/null +++ b/libidav/config.c @@ -0,0 +1,1033 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +#define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) +#define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b) + +#define print_error(lineno, ...) \ + do {\ + fprintf(stderr, "Error (config.xml line %u): ", lineno); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "Abort.\n"); \ + } while(0); +#define print_warning(lineno, ...) \ + do {\ + fprintf(stderr, "Warning (config.xml line %u): ", lineno); \ + fprintf(stderr, __VA_ARGS__); \ + } while(0); + +#ifdef _WIN32 +#define ENV_HOME getenv("USERPROFILE") +#else +#define ENV_HOME getenv("HOME") +#endif /* _WIN32 */ + + +static int load_repository( + DavConfig *config, + DavCfgRepository **list_begin, + DavCfgRepository **list_end, + xmlNode *reponode); +static int load_key( + DavConfig *config, + DavCfgKey **list_begin, + DavCfgKey **list_end, + xmlNode *keynode); +static int load_proxy( + DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode, int type); +static int load_namespace( + DavConfig *config, + DavCfgNamespace **list_begin, + DavCfgNamespace **list_end, + xmlNode *node); +static int load_secretstore(DavConfig *config, xmlNode *node); + + +int dav_cfg_string_set_node_value(DavConfig *config, CfgString *str, xmlNode *node) { + str->node = node; + char *value = util_xml_get_text(node); + if(value) { + str->value = cx_strdup_a(config->mp->allocator, cx_str(value)); + return 0; + } else { + str->value = (cxmutstr){NULL, 0}; + return 1; + } +} + +void dav_cfg_bool_set_node_value(DavConfig *config, CfgBool *cbool, xmlNode *node) { + cbool->node = node; + char *value = util_xml_get_text(node); + cbool->value = util_getboolean(value); +} + +static void set_xml_content(xmlNode *node, const char *content) { + xmlNode *child = node->children; + while(child) { + xmlNode *next = child->next; + xmlUnlinkNode(child); + xmlFreeNode(child); + child = next; + } + + if(content) { + xmlChar *encoded = xmlEncodeSpecialChars(node->doc, (const xmlChar*)content); + if(encoded) { + xmlNodeSetContent(node, encoded); + xmlFree(encoded); + } + } +} + +void dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *parent, cxstring new_value, const char *nodename) { + if(str->value.ptr) { + cxFree(config->mp->allocator, str->value.ptr); + } + if(new_value.ptr) { + str->value = cx_strdup_a(config->mp->allocator, new_value); + } else { + str->value = cx_mutstrn(NULL, 0); + } + + if(!str->node) { + str->node = xmlNewNode(NULL, (const xmlChar*) nodename); + xmlAddChild(parent, str->node); + } + set_xml_content(str->node, new_value.ptr); +} + +void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *parent, DavBool new_value, const char *nodename) { + const char *content = new_value ? "true" : "false"; + cbool->value = new_value; + if(!cbool->node) { + cbool->node = xmlNewNode(NULL, (const xmlChar*) nodename); + xmlAddChild(parent, cbool->node); + } + set_xml_content(cbool->node, content); +} + +void dav_cfg_int_set_value(DavConfig *config, CfgInt *cint, xmlNode *parent, int64_t new_value, const char *nodename) { + char content[32]; + snprintf(content, 32, "%" PRId64, new_value); + cint->value = new_value; + if(!cint->node) { + cint->node = xmlNewNode(NULL, (const xmlChar*) nodename); + xmlAddChild(parent, cint->node); + } + set_xml_content(cint->node, content); +} + +void dav_cfg_uint_set_value(DavConfig *config, CfgUInt *cint, xmlNode *parent, uint64_t new_value, const char *nodename) { + char content[32]; + snprintf(content, 32, "%" PRIu64, new_value); + cint->value = new_value; + if(!cint->node) { + cint->node = xmlNewNode(NULL, (const xmlChar*) nodename); + xmlAddChild(parent, cint->node); + } + set_xml_content(cint->node, content); +} + +void dav_cfg_string_remove(CfgString *str) { + if(str->node) { + xmlUnlinkNode(str->node); + xmlFreeNode(str->node); + str->node = NULL; + } +} + +void dav_cfg_bool_remove(CfgBool *cbool) { + if(cbool->node) { + xmlUnlinkNode(cbool->node); + xmlFreeNode(cbool->node); + cbool->node = NULL; + } +} + +void dav_cfg_int_remove(CfgInt *cint) { + if(cint->node) { + xmlUnlinkNode(cint->node); + xmlFreeNode(cint->node); + cint->node = NULL; + } +} + +void dav_cfg_uint_remove(CfgUInt *cint) { + if(cint->node) { + xmlUnlinkNode(cint->node); + xmlFreeNode(cint->node); + cint->node = NULL; + } +} + + +DavConfig* dav_config_new(xmlDoc *doc) { + CxMempool *cfg_mp = cxMempoolCreate(128, NULL); + DavConfig *config = cxMalloc(cfg_mp->allocator, sizeof(DavConfig)); + memset(config, 0, sizeof(DavConfig)); + config->mp = cfg_mp; + + if(!doc) { + doc = xmlNewDoc(BAD_CAST "1.0"); + xmlNode *root = xmlNewNode(NULL, BAD_CAST "configuration"); + xmlDocSetRootElement(doc, root); + } + config->doc = doc; + + return config; +} + +DavConfig* dav_config_load(cxmutstr xmlfilecontent, int *error) { + xmlDoc *doc = xmlReadMemory(xmlfilecontent.ptr, xmlfilecontent.length, NULL, NULL, 0); + if(!doc) { + if(error) { + *error = DAV_CONFIG_ERROR_XML; + } + return NULL; + } + + DavConfig *config = dav_config_new(doc); + CxMempool *cfg_mp = config->mp; + cxMempoolRegister(cfg_mp, doc, (cx_destructor_func)xmlFreeDoc); + + DavCfgRepository *repos_begin = NULL; + DavCfgRepository *repos_end = NULL; + DavCfgKey *keys_begin = NULL; + DavCfgKey *keys_end = NULL; + DavCfgNamespace *namespaces_begin = NULL; + DavCfgNamespace *namespaces_end = NULL; + + xmlNode *xml_root = xmlDocGetRootElement(doc); + xmlNode *node = xml_root->children; + int ret = 0; + while(node && !ret) { + if(node->type == XML_ELEMENT_NODE) { + if(xstreq(node->name, "repository")) { + ret = load_repository(config, &repos_begin, &repos_end, node); + } else if(xstreq(node->name, "key")) { + ret = load_key(config, &keys_begin, &keys_end, node); + } else if (xstreq(node->name, "http-proxy")) { + config->http_proxy = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgProxy)); + ret = load_proxy(config, config->http_proxy, node, DAV_HTTP_PROXY); + } else if (xstreq(node->name, "https-proxy")) { + config->https_proxy = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgProxy)); + ret = load_proxy(config, config->https_proxy, node, DAV_HTTPS_PROXY); + } else if (xstreq(node->name, "namespace")) { + ret = load_namespace(config, &namespaces_begin, &namespaces_end, node); + } else if (xstreq(node->name, "secretstore")) { + ret = load_secretstore(config, node); + } else { + fprintf(stderr, "Unknown config element: %s\n", node->name); + ret = 1; + } + } + node = node->next; + } + + config->repositories = repos_begin; + config->keys = keys_begin; + config->namespaces = namespaces_begin; + + if(ret != 0 && error) { + *error = ret; + 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; +} diff --git a/libidav/config.h b/libidav/config.h new file mode 100644 index 0000000..f85c097 --- /dev/null +++ b/libidav/config.h @@ -0,0 +1,218 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIBIDAV_CONFIG_H +#define LIBIDAV_CONFIG_H + +#include "webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DavConfig DavConfig; +typedef struct DavCfgRepository DavCfgRepository; +typedef struct DavCfgProxy DavCfgProxy; +typedef struct DavCfgKey DavCfgKey; +typedef struct DavCfgNamespace DavCfgNamespace; +typedef struct DavCfgSecretStore DavCfgSecretStore; + +typedef struct CfgString CfgString; +typedef struct CfgInt CfgInt; +typedef struct CfgUInt CfgUInt; +typedef struct CfgBool CfgBool; + +typedef enum dav_cfg_key_type DavCfgKeyType; + +typedef cxmutstr (*dav_loadkeyfile_func)(const char *filename); + +#define DAV_HTTP_PROXY 1 +#define DAV_HTTPS_PROXY 2 + +enum dav_cfg_key_type { + DAV_KEY_TYPE_AES256 = 0, + DAV_KEY_TYPE_AES128, + DAV_KEY_TYPE_UNKNOWN +}; + +struct DavConfig { + CxMempool *mp; + + DavCfgRepository *repositories; + DavCfgKey *keys; + DavCfgNamespace *namespaces; + DavCfgProxy *http_proxy; + DavCfgProxy *https_proxy; + DavCfgSecretStore *secretstore; + + xmlDoc *doc; +}; + +struct CfgString { + cxmutstr value; + xmlNode *node; +}; + +struct CfgInt { + int64_t value; + xmlNode *node; +}; + +struct CfgUInt { + uint64_t value; + xmlNode *node; +}; + +struct CfgBool { + bool value; + xmlNode *node; +}; + + +struct DavCfgRepository { + xmlNode *node; + + CfgString name; + CfgString url; + CfgString user; + CfgString password; + CfgString stored_user; + CfgString default_key; + CfgString cert; + CfgBool verification; + + CfgBool full_encryption; + CfgBool content_encryption; + CfgBool decrypt_content; + CfgBool decrypt_name; + CfgBool decrypt_properties; + + CfgInt ssl_version; + CfgUInt authmethods; + + int unknown_elements; + + DavCfgRepository *prev; + DavCfgRepository *next; +}; + +struct DavCfgProxy { + CfgString url; + CfgString user; + CfgString password; + CfgString noproxy; + + int unknown_elements; +}; + +struct DavCfgKey { + xmlNode *node; + + CfgString name; + CfgString file; + DavCfgKeyType type; + xmlNode *type_node; + + DavCfgKey *prev; + DavCfgKey *next; + + int unknown_elements; +}; + +struct DavCfgNamespace { + xmlNode *node; + cxmutstr prefix; + cxmutstr uri; + + DavCfgNamespace *prev; + DavCfgNamespace *next; +}; + +struct DavCfgSecretStore { + CfgString unlock_cmd; + CfgString lock_cmd; +}; + +enum DavConfigError { + DAV_CONFIG_ERROR_XML = 0 +}; + +DavConfig* dav_config_new(xmlDoc *doc); + +DavConfig* dav_config_load(cxmutstr xmlfilecontent, int *error); + +void dav_config_free(DavConfig *config); + +CxBuffer* dav_config2buf(DavConfig *config); + +void dav_config_add_repository(DavConfig *config, DavCfgRepository *repo); + +DavCfgRepository* dav_repository_new(DavConfig *config); +void dav_repository_free(DavConfig *config, DavCfgRepository *repo); +void dav_repository_remove_and_free(DavConfig *config, DavCfgRepository *repo); +int dav_repository_get_flags(DavCfgRepository *repo); +void dav_repository_set_url(DavConfig *config, DavCfgRepository *repo, cxstring newurl); +void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password); +cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo); + +void dav_config_add_key(DavConfig *config, DavCfgKey *key); + +DavCfgKey* dav_key_new(DavConfig *config); +void dav_key_remove_and_free(DavConfig *config, DavCfgKey *key); + +int dav_str2ssl_version(const char *str); + +int dav_cfg_string_set_node_value(DavConfig *config, CfgString *str, xmlNode *node); +void dav_cfg_bool_set_node_value(DavConfig *config, CfgBool *cbool, xmlNode *node); + +void dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *parent, cxstring new_value, const char *nodename); +void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *parent, DavBool new_value, const char *nodename); +void dav_cfg_int_set_value(DavConfig *config, CfgInt *cint, xmlNode *parent, int64_t new_value, const char *nodename); +void dav_cfg_uint_set_value(DavConfig *config, CfgUInt *cint, xmlNode *parent, uint64_t new_value, const char *nodename); + +void dav_cfg_string_remove(CfgString *str); +void dav_cfg_bool_remove(CfgBool *cbool); +void dav_cfg_int_remove(CfgInt *cint); +void dav_cfg_uint_remove(CfgUInt *cint); + +DavCfgRepository* dav_config_get_repository(DavConfig *config, cxstring name); +DavCfgRepository* dav_config_url2repo(DavConfig *config, const char *url, char **path); +DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path); + +int dav_config_keytype(DavCfgKeyType type); +const char* dav_config_keytype_str(DavCfgKeyType type); +int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey); + +int dav_config_register_namespaces(DavConfig *config, DavContext *ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBIDAV_CONFIG_H */ + diff --git a/libidav/crypto.c b/libidav/crypto.c new file mode 100644 index 0000000..d52e845 --- /dev/null +++ b/libidav/crypto.c @@ -0,0 +1,1542 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#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; +} diff --git a/libidav/crypto.h b/libidav/crypto.h new file mode 100644 index 0000000..b058488 --- /dev/null +++ b/libidav/crypto.h @@ -0,0 +1,166 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DAV_CRYPTO_H +#define DAV_CRYPTO_H + +#include "webdav.h" +#include + +#ifdef __APPLE__ +/* macos */ + +#define DAV_CRYPTO_COMMON_CRYPTO + +#define DAV_AES_CTX CCCryptorRef +#define DAV_SHA_CTX CC_SHA256_CTX +#define DAV_SHA256_DIGEST_LENGTH 32 + +#include +#include + +#elif defined(_WIN32) + +#define DAV_CRYPTO_CNG + +#include +#include + +typedef struct WinBCryptCTX { + BCRYPT_ALG_HANDLE hAlg; + BCRYPT_KEY_HANDLE hKey; + void *pbKeyObject; + unsigned char pbIV[16]; + + unsigned char buf[16]; + ULONG buflen; +} WinBCryptCTX; + +typedef struct WinBCryptSHACTX { + BCRYPT_ALG_HANDLE hAlg; + BCRYPT_HASH_HANDLE hHash; + void *pbHashObject; +} WinBCryptSHACTX; + +#define DAV_AES_CTX WinBCryptCTX +#define DAV_SHA_CTX WinBCryptSHACTX +#define DAV_SHA256_DIGEST_LENGTH 32 + +#else +/* unix/linux */ + +#define DAV_USE_OPENSSL + +#define DAV_AES_CTX EVP_CIPHER_CTX* +#define DAV_SHA_CTX SHA256_CTX +#define DAV_SHA256_DIGEST_LENGTH 32 + +#include +#include + +#if defined(__sun) && defined(__SunOS_5_10) +#include +#define SHA256_Init SHA256Init +#define SHA256_Update SHA256Update +#define SHA256_Final SHA256Final +#else +#include +#endif + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define DAV_PWFUNC_PBKDF2_SHA256 0 +#define DAV_PWFUNC_PBKDF2_SHA512 1 + +#define DAV_CRYPTO_ITERATION_COUNT 4000 + +typedef struct { + DAV_AES_CTX ctx; + DAV_SHA_CTX sha256; + void *stream; + dav_write_func write; + DavKey *key; + int init; + unsigned char ivtmp[16]; + size_t ivpos; +} AESDecrypter; + +typedef struct { + DAV_AES_CTX ctx; + DAV_SHA_CTX sha256; + void *iv; + size_t ivlen; + void *stream; + dav_read_func read; + dav_seek_func seek; + char *tmp; + size_t tmplen; + size_t tmpoff; + int end; +} AESEncrypter; + +typedef struct DavHashContext DavHashContext; + +int dav_rand_bytes(unsigned char *buf, size_t len); + +AESDecrypter* aes_decrypter_new(DavKey *key, void *stream, dav_write_func write_func); +size_t aes_write(const void *buf, size_t s, size_t n, AESDecrypter *dec); +void aes_decrypter_shutdown(AESDecrypter *dec); +void aes_decrypter_close(AESDecrypter *dec); + +AESEncrypter* aes_encrypter_new(DavKey *key, void *stream, dav_read_func read_func, dav_seek_func seek_func); +size_t aes_read(void *buf, size_t s, size_t n, AESEncrypter *enc); +void aes_encrypter_close(AESEncrypter *enc); +int aes_encrypter_reset(AESEncrypter *enc, curl_off_t offset, int origin); + +char* aes_encrypt(const char *in, size_t len, DavKey *key); +char* aes_decrypt(const char *in, size_t *len, DavKey *key); + +void dav_get_hash(DAV_SHA_CTX *sha256, unsigned char *buf); + +char* dav_create_hash(const char *data, size_t len); + +DAV_SHA_CTX* dav_hash_init(void); +void dav_hash_update(DAV_SHA_CTX *ctx, const char *data, size_t len); +void dav_hash_final(DAV_SHA_CTX *ctx, unsigned char *buf); + +DavKey* dav_pw2key(const char *password, const unsigned char *salt, int saltlen, int pwfunc, int enc); + +CxBuffer* aes_encrypt_buffer(CxBuffer *in, DavKey *key); +CxBuffer* aes_decrypt_buffer(CxBuffer *in, DavKey *key); + +#ifdef __cplusplus +} +#endif + +#endif /* DAV_CRYPTO_H */ + diff --git a/libidav/davqlexec.c b/libidav/davqlexec.c new file mode 100644 index 0000000..6811ded --- /dev/null +++ b/libidav/davqlexec.c @@ -0,0 +1,1485 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "davqlexec.h" +#include "utils.h" +#include "methods.h" +#include "session.h" +#include "resource.h" + +DavQLArgList* dav_ql_get_args(DavQLStatement *st, va_list ap) { + DavQLArgList *args = malloc(sizeof(DavQLArgList)); + if(!args) { + return NULL; + } + args->first = NULL; + + 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;itype == 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;iname); + obj.data.string = res->name; + break; + } + case DAVQL_RES_PATH: { + obj.type = 1; + obj.length = strlen(res->path); + obj.data.string = res->path; + break; + } + case DAVQL_RES_HREF: { + obj.type = 1; + obj.length = strlen(res->href); + obj.data.string = res->href; + break; + } + case DAVQL_RES_CONTENTLENGTH: { + obj.type = 0; + obj.length = 0; + obj.data.integer = res->contentlength; + break; + } + case DAVQL_RES_CONTENTTYPE: { + obj.type = 1; + obj.length = strlen(res->contenttype); + obj.data.string = res->contenttype; + break; + } + case DAVQL_RES_CREATIONDATE: { + obj.type = 0; + obj.length = 0; + obj.data.integer = res->creationdate; + break; + } + case DAVQL_RES_LASTMODIFIED: { + obj.type = 0; + obj.length = 0; + obj.data.integer = res->lastmodified; + break; + } + case DAVQL_RES_ISCOLLECTION: { + obj.type = 0; + obj.length = 0; + obj.data.integer = res->iscollection; + break; + } + } + DAVQL_PUSH(obj); + break; + } + case DAVQL_CMD_PROP_IDENTIFIER: { + //printf("property %s:%s\n", cmd.data.property.ns, cmd.data.property.name); + //char *value = dav_get_string_property_ns(res, cmd.data.property.ns, cmd.data.property.name); + DavXmlNode *value = dav_get_property_ns(res, cmd.data.property.ns, cmd.data.property.name); + if(dav_xml_isstring(value)) { + obj.type = 1; + obj.length = (uint32_t)value->contentlength; + obj.data.string = value->content; + } else { + obj.type = 2; + obj.length = 0; + obj.data.node = value; + } + DAVQL_PUSH(obj); + break; + } + //case DAVQL_CMD_OP_UNARY_ADD: { + // printf("uadd\n"); + // break; + //} + case DAVQL_CMD_OP_UNARY_SUB: { + //printf("usub\n"); + obj = DAVQL_POP(); + if(obj.type == 0) { + obj.data.integer = -obj.data.integer; + DAVQL_PUSH(obj); + } else { + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_UNARY_NEG: { + //printf("uneg\n"); + obj = DAVQL_POP(); + if(obj.type == 0) { + obj.data.integer = obj.data.integer == 0 ? 1 : 0; + DAVQL_PUSH(obj); + } else { + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_BINARY_ADD: { + //printf("add\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer + obj2.data.integer); + } else { + // TODO: string concat + } + break; + } + case DAVQL_CMD_OP_BINARY_SUB: { + //printf("sub\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer - obj2.data.integer); + } else { + // error + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_BINARY_MUL: { + //printf("mul\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer * obj2.data.integer); + } else { + // error + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_BINARY_DIV: { + //printf("div\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer / obj2.data.integer); + } else { + // error + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_BINARY_AND: { + //printf("and\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer & obj2.data.integer); + } else { + // error + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_BINARY_OR: { + //printf("or\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer | obj2.data.integer); + } else { + // error + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_BINARY_XOR: { + //printf("xor\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer ^ obj2.data.integer); + } else { + // error + ret = -1; + i = count; // end loop + } + break; + } + case DAVQL_CMD_OP_LOGICAL_NOT: { + //printf("not\n"); + break; + } + case DAVQL_CMD_OP_LOGICAL_AND: { + //printf("land\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0); + int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0); + DAVQL_PUSH_INT(v1 && v2); + break; + } + case DAVQL_CMD_OP_LOGICAL_OR_L: { + //printf("or_l %d\n", cmd.data.integer); + DavQLStackObj obj1 = stack[stpos]; + if((obj1.type == 0 && obj1.data.integer) || (obj1.type == 1 && obj1.data.string)) { + stpos--; + DAVQL_PUSH_INT(1); + i += cmd.data.integer; // jump, skip right subtree of 'or' + } + break; + } + case DAVQL_CMD_OP_LOGICAL_OR: { + //printf("or\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0); + int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0); + DAVQL_PUSH_INT(v1 || v2); + break; + } + case DAVQL_CMD_OP_LOGICAL_XOR: { + //printf("lxor\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + int v1 = obj1.type == 0 ? (int)obj1.data.integer : (obj1.data.string ? 1 : 0); + int v2 = obj2.type == 0 ? (int)obj2.data.integer : (obj2.data.string ? 1 : 0); + DAVQL_PUSH_INT(!v1 != !v2); + break; + } + case DAVQL_CMD_OP_EQ: { + //printf("eq\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer == obj2.data.integer); + } else { + DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type)); + } + break; + } + case DAVQL_CMD_OP_NEQ: { + //printf("neq\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer != obj2.data.integer); + } else { + DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type)); + } + break; + } + case DAVQL_CMD_OP_LT: { + //printf("lt\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer < obj2.data.integer); + } else { + DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type)); + } + break; + } + case DAVQL_CMD_OP_GT: { + //printf("gt\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer > obj2.data.integer); + } else { + DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type)); + } + break; + } + case DAVQL_CMD_OP_LE: { + //printf("le\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer <= obj2.data.integer); + } else { + DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type)); + } + break; + } + case DAVQL_CMD_OP_GE: { + //printf("ge\n"); + DavQLStackObj obj2 = DAVQL_POP(); + DavQLStackObj obj1 = DAVQL_POP(); + if(obj1.type == 0 && obj2.type == 0) { + DAVQL_PUSH_INT(obj1.data.integer >= obj2.data.integer); + } else { + DAVQL_PUSH_INT(cmd_str_cmp(obj1, obj2, cmd.type)); + } + break; + } + case DAVQL_CMD_OP_LIKE: { + //printf("like\n"); + break; + } + case DAVQL_CMD_OP_UNLIKE: { + //printf("unlike\n"); + break; + } + case DAVQL_CMD_CALL: { + //printf("call %x\n", cmd.data.func); + break; + } + } + } + + if(stpos == 1) { + *result = stack[0]; + } else { + ret = -1; + } + free(stack); + + return ret; +} diff --git a/libidav/davqlexec.h b/libidav/davqlexec.h new file mode 100644 index 0000000..48511fc --- /dev/null +++ b/libidav/davqlexec.h @@ -0,0 +1,187 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef DAVQLEXEC_H +#define DAVQLEXEC_H + +#include +#include "davqlparser.h" +#include "webdav.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DavQLCmd DavQLCmd; +typedef struct DavQLStackObj DavQLStackObj; +typedef struct DavQLRes DavQLRes; + +typedef struct DavQLArg DavQLArg; +typedef struct DavQLArgList DavQLArgList; + +typedef void*(*davql_func)(); // TODO: interface? + +struct DavQLArg { + int type; + union DavQLArgValue{ + int d; + unsigned int u; + char *s; + time_t t; + } value; + DavQLArg *next; +}; + +struct DavQLArgList { + DavQLArg *first; + DavQLArg *current; +}; + +typedef enum { + DAVQL_OK = 0, + DAVQL_UNSUPPORTED_FORMATCHAR, + DAVQL_UNKNOWN_FORMATCHAR, + 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 */ + diff --git a/libidav/davqlparser.c b/libidav/davqlparser.c new file mode 100644 index 0000000..c18ab97 --- /dev/null +++ b/libidav/davqlparser.c @@ -0,0 +1,1860 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "davqlparser.h" +#include +#include +#include +#include +#include +#include + +#define sfmtarg(s) ((int)(s).length), (s).ptr + +// ------------------------------------------------------------------------ +// D E B U G E R +// ------------------------------------------------------------------------ + +static const char* _map_querytype(davqltype_t type) { + switch(type) { + case DAVQL_ERROR: return "ERROR"; + case DAVQL_SELECT: return "SELECT"; + case DAVQL_SET: return "SET"; + default: return "unknown"; + } +} + +static const char* _map_exprtype(davqlexprtype_t type) { + switch(type) { + case DAVQL_UNDEFINED_TYPE: return "undefined"; + case DAVQL_NUMBER: return "NUMBER"; + case DAVQL_STRING: return "STRING"; + case DAVQL_TIMESTAMP: return "TIMESTAMP"; + case DAVQL_IDENTIFIER: return "IDENTIFIER"; + case DAVQL_UNARY: return "UNARY"; + case DAVQL_BINARY: return "BINARY"; + case DAVQL_LOGICAL: return "LOGICAL"; + case DAVQL_FUNCCALL: return "FUNCCALL"; + default: return "unknown"; + } +} + +static const char* _map_specialfield(int info) { + switch(info) { + case 0: return ""; + case 1: return "with wildcard"; + case 2: return "(resource data only)"; + default: return "with mysterious identifier"; + } +} + +static const char* _map_operator(davqloperator_t op) { + // don't use string array, because enum values may change + switch(op) { + case DAVQL_NOOP: return "no operator"; + case DAVQL_CALL: return "function call"; case DAVQL_ARGLIST: return ","; + case DAVQL_ADD: return "+"; case DAVQL_SUB: return "-"; + case DAVQL_MUL: return "*"; case DAVQL_DIV: return "/"; + case DAVQL_AND: return "&"; case DAVQL_OR: return "|"; + case DAVQL_XOR: return "^"; case DAVQL_NEG: return "~"; + case DAVQL_NOT: return "NOT"; case DAVQL_LAND: return "AND"; + case DAVQL_LOR: return "OR"; case DAVQL_LXOR: return "XOR"; + case DAVQL_EQ: return "="; case DAVQL_NEQ: return "!="; + case DAVQL_LT: return "<"; case DAVQL_GT: return ">"; + case DAVQL_LE: return "<="; case DAVQL_GE: return ">="; + case DAVQL_LIKE: return "LIKE"; case DAVQL_UNLIKE: return "UNLIKE"; + default: return "unknown"; + } +} + +static void dav_debug_ql_fnames_print(DavQLStatement *stmt) { + if (stmt->fields) { + printf("Field names: "); + 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 ') " _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;isrctext = 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); +} diff --git a/libidav/davqlparser.h b/libidav/davqlparser.h new file mode 100644 index 0000000..7640b26 --- /dev/null +++ b/libidav/davqlparser.h @@ -0,0 +1,374 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DAVQLPARSER_H +#define DAVQLPARSER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/** + * 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. + * NULL for literals or identifiers. + */ + DavQLExpression *left; + /** + * Right operand. + * NULL for literals, identifiers or unary expressions. + */ + DavQLExpression *right; +}; + +/** + * A tuple representing an order criterion. + */ +typedef struct { + /** + * The column. + */ + DavQLExpression *column; + /** + * True, if the result shall be sorted descending, false otherwise. + * Default is false (ascending). + */ + _Bool descending; +} DavQLOrderCriterion; + +/** + * A tuple representing a field. + */ +typedef struct { + /** + * The field name. + *
    + *
  • SELECT: the identifier or an alias name
  • + *
  • SET: the identifier
  • + *
+ */ + cxstring name; + /** + * The field expression. + *
    + *
  • SELECT: the queried property (identifier) or an expression
  • + *
  • SET: the expression for the value to be set
  • + *
+ */ + DavQLExpression *expr; +} DavQLField; + +/** + * Query statement object. + * Contains the binary information about the parsed query. + * + * The grammar for a DavQLStatement is: + * + *
+ * Keyword = "select" | "set" | "from" | "at" | "as"
+ *         | "where" | "anywhere" | "like" | "unlike"
+ *         | "and" | "or" | "not" | "xor" | "with" | "infinity"
+ *         | "order" | "by" | "asc" | "desc";
+ * 
+ * Expression        = AddExpression;
+ * AddExpression     = MultExpression, [AddOperator, AddExpression];
+ * MultExpression    = BitwiseExpression, [MultOperator, MultExpression];
+ * BitwiseExpression = UnaryExpression, [BitwiseOperator, BitwiseExpression];
+ * UnaryExpression   = [UnaryOperator], (ParExpression | AtomicExpression);
+ * AtomicExpression  = FunctionCall | Identifier | Literal;
+ * ParExpression     = "(", Expression, ")";
+ * 
+ * BitwiseOperator = "&" | "|" | "^";
+ * MultOperator    = "*" | "/";
+ * AddOperator     = "+" | "-";
+ * UnaryOperator   = "+" | "-" | "~";
+ * 
+ * FunctionCall    = Identifier, "(", [ArgumentList], ")";
+ * ArgumentList    = Expression, {",", Expression};
+ * Identifier      = IdentifierChar - ?Digit?, {IdentifierChar}
+ *                 | "`", ?Character? - "`", {?Character? - "`"}, "`";
+ * IdentifierChar  = ?Character? - (" "|",");
+ * Literal         = Number | String | Timestamp;
+ * Number          = ?Digit?, {?Digit?} | "%d";
+ * String          = "'", {?Character? - "'" | "'''"} , "'" | "%s";
+ * Timestamp       = "%t"; // TODO: maybe introduce a real literal 
+ * 
+ * LogicalExpression = BooleanExpression, [LogicalOperator, LogicalExpression];
+ * BooleanExpression = "not ", BooleanExpression
+ *                   | "(", LogicalExpression, ")"
+ *                   | BooleanPrimary;
+ * BooleanPrimary    = Expression, (" like " | " unlike "), String
+ *                   | Expression, Comparison, Expression
+ *                   | FunctionCall | Identifier;
+ * 
+ * LogicalOperator = " and " | " or " | " xor ";
+ * Comparison      = | "=" | "<" | ">" | "<=" | ">=" | "!=";
+ * 
+ * FieldExpressions = "-"
+ *                  | "*", {",", NamedField}
+ *                  | FieldExpression, {",", FieldExpression};
+ * FieldExpression  = NamedField | Identifier;
+ * NamedField       = Expression, " as ", Identifier;
+ * 
+ * Assignments   = Assignment, {",", Assignment};
+ * Assignment    = Identifier, "=", Expression;
+ * 
+ * Path     = String
+ *          | "/", [PathNode, {"/", PathNode}], ["/"];
+ * PathNode = {{?Character? - "/"} - Keyword};
+ * 
+ * WithClause = "depth", "=", (Number | "infinity");
+ * 
+ * OrderByClause    = OrderByCriterion, {",", OrderByCriterion};
+ * OrderByCriterion = (Identifier | Number), [" asc"|" desc"];
+ * 
+ * 
+ * + * Note: mandatory spaces are part of the grammar. But you may also insert an + * arbitrary amount of optional spaces between two symbols if they are not part + * of an literal, identifier or the path. + * + * SELECT: + *
+ * SelectStatement = "select ", FieldExpressions,
+ * " from ", Path,
+ * [" with ", WithClause],
+ * [(" where ", LogicalExpression) | " anywhere"],
+ * [" order by ", OrderByClause];
+  * 
+ * + * SET: + *
+ * SetStatement = "set ",Assignments,
+ * " at ", Path,
+ * [" with ", WithClause],
+ * (" where ", LogicalExpression) | " anywhere";
+ * 
+ * + */ +typedef struct { + /** + * The original query text. + */ + 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. + * NULL, if there is no where clause. + */ + DavQLExpression* where; + /** + * The list of DavQLOrderCriterions. + * This is NULL for SET queries and may be NULL + * if the result doesn't need to be sorted. + */ + 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 */ + diff --git a/libidav/methods.c b/libidav/methods.c new file mode 100644 index 0000000..0a436c1 --- /dev/null +++ b/libidav/methods.c @@ -0,0 +1,1367 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "utils.h" +#include "methods.h" +#include "crypto.h" +#include "session.h" +#include "xml.h" + +#include +#include +#include + +#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;ipos = 0; + response->size = response->pos = 0; + ret = dav_session_curl_perform_buf(sn, request, response, NULL); + curl_slist_free_all(headers); + headers = NULL; + + /* + * Handle two cases: + * 1. We communicate with IIS and get a X-MSDAVEXT_Error: 589831 + * => try with depth 0 next time, it's not a collection + * 2. Other cases + * => the server handled our request and we can stop requesting + */ + char *msdavexterror; + msdavexterror = 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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + 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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + 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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + // write root element and namespaces + cx_bprintf(buf, "prefix); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("=\""); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = cx_str(ns->name); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\""); + cxBufferWrite(s.ptr, 1, s.length, buf); + } + s = CX_STR(">\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + // default properties + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + // crypto properties + if(DAV_CRYPTO(sn) && !nocrypt) { + if(add_crypto_name) { + 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, "\n\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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + // properties + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + // end + s = CX_STR("\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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + // write root element and namespaces + s = CX_STR("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("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + CxIterator i = cxListIterator(data->set); + cx_foreach(DavProperty*, property, i) { + char *prefix = cxMapGet(namespaces, cx_hash_key_str(property->ns->name)); + if(!prefix) { + prefix = "D"; + } + + // begin tag + s = 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("name); + cxBufferWrite(s.ptr, 1, s.length, buf); + s = CX_STR(">\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + } + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + } + if(data->remove) { + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + CxIterator i = cxListIterator(data->remove); + cx_foreach(DavProperty*, property, i) { + char *prefix = cxMapGet(namespaces, cx_hash_key_str(property->ns->name)); + + s = 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("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + } + + s = CX_STR("\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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + if(DAV_ENCRYPT_NAME(sn)) { + s = CX_STR(""); + cxBufferWrite(s.ptr, 1, s.length, buf); + char *crname = aes_encrypt(name, strlen(name), key); + cxBufferPutString(buf, crname); + free(crname); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + } + + s = CX_STR(""); + cxBufferWrite(s.ptr, 1, s.length, buf); + cxBufferPutString(buf, key->name); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + if(hash) { + s = CX_STR(""); + cxBufferWrite(s.ptr, 1, s.length, buf); + cxBufferPutString(buf, hash); + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + } + + s = CX_STR("\n\n\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("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n" + "\n" + "\n" + "http://davutils.org/libidav/\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + s = CX_STR("\n"); + cxBufferWrite(s.ptr, 1, s.length, buf); + + return buf; +} + +int parse_lock_response(DavSession *sn, 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; +} diff --git a/libidav/methods.h b/libidav/methods.h new file mode 100644 index 0000000..399ce1c --- /dev/null +++ b/libidav/methods.h @@ -0,0 +1,134 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef METHODS_H +#define METHODS_H + +#include "webdav.h" +#include "resource.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct PropfindParser PropfindParser; +typedef struct ResponseTag ResponseTag; +typedef struct LockDiscovery LockDiscovery; + +struct PropfindParser { + xmlDoc *document; + xmlNode *current; +}; + +struct ResponseTag { + 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 */ + diff --git a/libidav/pwdstore.c b/libidav/pwdstore.c new file mode 100644 index 0000000..466226b --- /dev/null +++ b/libidav/pwdstore.c @@ -0,0 +1,585 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pwdstore.h" + +#include "utils.h" + +#include +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#pragma comment(lib, "Ws2_32.lib") +#else +#include +#endif + + +static pwdstore_pwinput_func pw_input = (pwdstore_pwinput_func)pwdstore_default_pwinput; +static void *pw_input_data = "Master password: "; + +char * pwdstore_default_pwinput(char *prompt) { + return util_password_input(prompt); +} + +void pwdstore_set_pwinput_func(pwdstore_pwinput_func func, void *userdata) { + pw_input = func; + pw_input_data = userdata; +} + +PwdStore* pwdstore_open(const char *file) { + FILE *in = fopen(file, "r"); + if(!in) { + return NULL; + } + + CxBuffer *buf = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cx_stream_copy(in, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite); + fclose(in); + + if(buf->size < PWDS_HEADER_SIZE || buf->space[0] != PWDS_MAGIC_CHAR) { + cxBufferFree(buf); + return NULL; + } + + PwdStore *p = malloc(sizeof(PwdStore)); + p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS); + p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + p->content = buf; + p->key = NULL; + p->unlock_cmd = NULL; + p->lock_cmd = NULL; + p->encoffset = PWDS_HEADER_SIZE; + p->isdecrypted = 0; + + if(pwdstore_getindex(p)) { + pwdstore_free(p); + return NULL; + } + + return p; +} + +PwdStore* pwdstore_new(void) { + PwdStore *p = calloc(1, sizeof(PwdStore)); + p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS); + p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + p->content = cxBufferCreate(NULL, PWDS_HEADER_SIZE, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + PWDS_MAGIC(p) = PWDS_MAGIC_CHAR; + PWDS_VERSION(p) = 1; + PWDS_ENC(p) = DAV_KEY_AES256; + PWDS_PWFUNC(p) = DAV_PWFUNC_PBKDF2_SHA256; + dav_rand_bytes((unsigned char*)p->content->space+4, 16); + p->isdecrypted = 1; + p->encoffset = PWDS_HEADER_SIZE; + return p; +} + +PwdStore* pwdstore_clone(PwdStore *p) { + CxBuffer *newbuffer = calloc(1, sizeof(CxBuffer)); + *newbuffer = *p->content; + newbuffer->space = malloc(p->content->capacity); + memcpy(newbuffer->space, p->content->space, p->content->capacity); + + DavKey *key = NULL; + if(p->key) { + key = malloc(sizeof(DavKey)); + key->data = malloc(p->key->length); + memcpy(key->data, p->key->data, p->key->length); + key->length = p->key->length; + key->type = p->key->type; + key->name = NULL; + } + + PwdStore *newp = calloc(1, sizeof(PwdStore)); + newp->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + newp->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + newp->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS); + newp->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + newp->content = newbuffer; + newp->key = key; + newp->unlock_cmd = p->unlock_cmd ? strdup(p->unlock_cmd) : NULL; + newp->lock_cmd = p->lock_cmd ? strdup(p->lock_cmd) : NULL; + newp->encoffset = p->encoffset; + newp->isdecrypted = p->isdecrypted; + + CxIterator i = cxMapIterator(p->ids); + cx_foreach(CxMapEntry *, e, i) { + PwdEntry *entry = e->value; + pwdstore_put(newp, entry->id, entry->user, entry->password); + } + + i = cxMapIterator(p->index); + cx_foreach(CxMapEntry *, e, i) { + PwdIndexEntry *entry = e->value; + CxList *locations = NULL; + if(entry->locations) { + locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + CxIterator li = cxListIterator(entry->locations); + cx_foreach(char *, location, li) { + cxListAdd(locations, strdup(location)); + } + } + pwdstore_put_index(newp, strdup(entry->id), locations); + } + + return newp; +} + +static int readval(CxBuffer *in, char **val, int allowzero) { + // value = length string + // length = uint32 + // string = bytes + + *val = NULL; + + // get length + uint32_t length = 0; + if(cxBufferRead(&length, 1, sizeof(uint32_t), in) != sizeof(uint32_t)) { + return 0; + } + length = ntohl(length); // convert from BE to host byte order + if(length == 0) { + if(allowzero) { + return 1; + } else { + return 0; + } + } + if(length > PWDSTORE_MAX_LEN) { + return 0; + } + + // get value + char *value = malloc(length + 1); + value[length] = 0; + if(cxBufferRead(value, 1, length, in) != length) { + free(value); + return 0; + } + + *val = value; + return 1; +} + +static int read_indexentry(PwdStore *p, CxBuffer *in) { + // read type of index element + int type = cxBufferGet(in); + if(type == EOF || type != 0) { + // only type 0 supported yet + return 0; + } + + char *id = NULL; + CxList *locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + cxDefineDestructor(locations, free); + + // get id (required) + int ret = 0; + if(readval(in, &id, FALSE)) { + // get locations + char *location = NULL; + while((ret = readval(in, &location, TRUE)) == 1) { + if(!location) { + break; + } + cxListAdd(locations, location); + } + } + + if(ret) { + pwdstore_put_index(p, id, locations); + if(cxListSize(locations) == 0) { + 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; +} diff --git a/libidav/pwdstore.h b/libidav/pwdstore.h new file mode 100644 index 0000000..d865039 --- /dev/null +++ b/libidav/pwdstore.h @@ -0,0 +1,218 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIBIDAV_PWDSTORE_H +#define LIBIDAV_PWDSTORE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include +#include +#include "crypto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PWDSTORE_MAX_LEN 4096 + +/* + * File Format: + * + * file = header, index, enc_content + * header = magic, version, enc, pwfunc, salt, indexlen + * magic = 1 byte + * version = 1 byte + * enc = 1 byte + * pwfunc = 1 byte + * salt = 16 bytes + * indexlen = uint32 + * index = { itype length id locations zero } + * enc_content = iv bytes + * iv = 16 bytes + * content = { entry } + * entry = itype length id length username length password + * length = uint32 + * zero = 4 zero bytes + * itype = 1 byte + * id = string + * locations = { length string } + * username = string + * password = string + * + * The content is AES encrypted with a key derived from a password + * and the salt. The first 16 bytes are the aes iv. + * + * All integers are big endian + */ + +#define PWDS_HEADER_SIZE 24 + +typedef struct PwdStore PwdStore; +typedef struct PwdEntry PwdEntry; +typedef struct PwdIndexEntry PwdIndexEntry; + +struct PwdStore { + /* + * map of all credentials + * key is the username + * value is PwdEntry* + */ + CxMap *ids; + + /* + * list of all credentials with location + * value is PwdIndexEntry* + */ + CxList *locations; + + /* + * list of all credentials without location + * value is PwdIndexEntry* + */ + CxList *noloc; + + /* + * index map that contains all elements from the lists + * 'locations' and 'noloc' + */ + CxMap *index; + + /* + * a buffer containing the complete file content + */ + CxBuffer *content; + + /* + * key used for encryption/decryption + */ + DavKey *key; + + /* + * optional shell command, that is used for getting the master password + */ + char *unlock_cmd; + + /* + * optional shell command, that is exected when the secretstore is closed + */ + char *lock_cmd; + + /* + * start offset of the encrypted buffer + */ + uint32_t encoffset; + + /* + * indicates if the PwdStore is decrypted with pwdstore_decrypt + */ + uint8_t isdecrypted; +}; + +#define PWDS_MAGIC(p) (p)->content->space[0] +#define PWDS_VERSION(p) (p)->content->space[1] +#define PWDS_ENC(p) (p)->content->space[2] +#define PWDS_PWFUNC(p) (p)->content->space[3] + +#define PWDS_MAGIC_CHAR 'P' + +struct PwdEntry { + char *id; + char *user; + char *password; +}; + +struct PwdIndexEntry { + char *id; + CxList *locations; +}; + +/* + * opens the password store + * the content is still encrypted and must be decrypted using pwdstore_decrypt + */ +PwdStore* pwdstore_open(const char *file); + +PwdStore* pwdstore_new(void); + +PwdStore* pwdstore_clone(PwdStore *p); + +/* + * decrypts the password store with the previously set password + */ +int pwdstore_decrypt(PwdStore *p); + +int pwdstore_setpassword(PwdStore *p, const char *password); + +void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc); + +void pwdstore_free_entry(PwdEntry *e); +void pwdstore_free(PwdStore* p); + +int pwdstore_has_id(PwdStore *s, const char *id); + +PwdEntry* pwdstore_get(PwdStore *p, const char *id); + +void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password); +void pwdstore_put_index(PwdStore *p, char *id, CxList *locations); + +void pwdstore_remove_entry(PwdStore *s, const char *id); + +int pwdstore_store(PwdStore *p, const char *file); + + +int pwdstore_decrypt_secrets(PwdStore *secrets); + + + + + + +typedef char*(*pwdstore_pwinput_func)(void *userdata); + +void pwdstore_set_pwinput_func(pwdstore_pwinput_func func, void *userdata); + +char * pwdstore_default_pwinput(char *prompt); + + + +/* private */ +int pwdstore_getindex(PwdStore *s); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBIDAV_PWDSTORE_H */ + diff --git a/libidav/resource.c b/libidav/resource.c new file mode 100644 index 0000000..305358f --- /dev/null +++ b/libidav/resource.c @@ -0,0 +1,1882 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include "utils.h" +#include "session.h" +#include "methods.h" +#include "crypto.h" +#include +#include +#include +#include +#include +#include + +#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 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;iname = 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, "\n"); + cxBufferPutString(content, "\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, ""); + + 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; +} + +*/ diff --git a/libidav/resource.h b/libidav/resource.h new file mode 100644 index 0000000..37f2033 --- /dev/null +++ b/libidav/resource.h @@ -0,0 +1,142 @@ +/* + * 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 +#include + +#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 */ + diff --git a/libidav/session.c b/libidav/session.c new file mode 100644 index 0000000..17806b3 --- /dev/null +++ b/libidav/session.c @@ -0,0 +1,640 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include + +#include "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); +} diff --git a/libidav/session.h b/libidav/session.h new file mode 100644 index 0000000..6759da4 --- /dev/null +++ b/libidav/session.h @@ -0,0 +1,120 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DAV_SESSION_H +#define DAV_SESSION_H + +#include +#include "webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// initial size of the session mempool +#define DAV_SESSION_MEMPOOL_SIZE 1024 +// initial size of the path cache map +#define DAV_PATH_CACHE_SIZE 32 + +#define DAV_ENCRYPT_NAME(sn) \ + (((sn)->flags & DAV_SESSION_ENCRYPT_NAME) == DAV_SESSION_ENCRYPT_NAME) + +#define DAV_DECRYPT_NAME(sn) \ + (((sn)->flags & DAV_SESSION_DECRYPT_NAME) == DAV_SESSION_DECRYPT_NAME) + +#define DAV_ENCRYPT_CONTENT(sn) \ + (((sn)->flags & DAV_SESSION_ENCRYPT_CONTENT) == DAV_SESSION_ENCRYPT_CONTENT) + +#define DAV_DECRYPT_CONTENT(sn) \ + (((sn)->flags & DAV_SESSION_DECRYPT_CONTENT) == DAV_SESSION_DECRYPT_CONTENT) + +#define DAV_IS_ENCRYPTED(sn) \ + (DAV_ENCRYPT_NAME(sn) || DAV_ENCRYPT_CONTENT(sn)) + +#define DAV_CRYPTO(sn) \ + (DAV_ENCRYPT_NAME(sn) || DAV_DECRYPT_NAME(sn) || \ + DAV_ENCRYPT_CONTENT(sn) || DAV_DECRYPT_CONTENT(sn)) + +#define DAV_ENCRYPT_PROPERTIES(sn) \ + (((sn)->flags & DAV_SESSION_ENCRYPT_PROPERTIES) == DAV_SESSION_ENCRYPT_PROPERTIES) + +#define DAV_DECRYPT_PROPERTIES(sn) \ + (((sn)->flags & DAV_SESSION_DECRYPT_PROPERTIES) == DAV_SESSION_DECRYPT_PROPERTIES) + +/* +typedef struct DavPathCacheElement { + char *name; + char *encrypted_name; + int exists; +} DavPathCacheElement; +*/ + +typedef struct DavLock 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 */ + diff --git a/libidav/utils.c b/libidav/utils.c new file mode 100644 index 0000000..d1f3776 --- /dev/null +++ b/libidav/utils.c @@ -0,0 +1,1306 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#define getpasswordchar() getch() +#define IS_PATH_SEPARATOR(c) (c == '/' || c == '\\') +#define PATH_SEPARATOR '\\' +#else +#include +#include +#include +#include +#define getpasswordchar() getchar() +#define IS_PATH_SEPARATOR(c) (c == '/') +#define PATH_SEPARATOR '/' +#endif + +#include "webdav.h" +#include "utils.h" +#include "crypto.h" +#include "session.h" + +/* +#include +#include +#include +#include +#include +*/ + +static size_t extractval(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 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;ihandle, url, strlen(url), NULL); + char *ret = strdup(unesc); + curl_free(unesc); + return ret; +} + +static size_t util_header_callback(char *buffer, size_t size, + size_t nitems, void *data) { + + 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 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> 18) & 63]; + out[pos++] = b64enctable[(inb >> 12) & 63]; + out[pos++] = b64enctable[(inb >> 6) & 63]; + out[pos++] = b64enctable[(inb) & 63]; + } + + // encode last bytes + if(pad > 0) { + char p[3] = {0, 0, 0}; + for(int j=0;i> 18) & 63]; + out[pos++] = b64enctable[(inb >> 12) & 63]; + out[pos++] = b64enctable[(inb >> 6) & 63]; + out[pos++] = b64enctable[(inb) & 63]; + for(int k=outlen-1;k>=outlen-(3-pad);k--) { + out[k] = '='; + } + } + + return out; +} + +char* util_encrypt_str(DavSession *sn, 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 0) { + dav_hash_update(sha, buf, r); + } + + unsigned char hash[DAV_SHA256_DIGEST_LENGTH]; + dav_hash_final(sha, hash); + free(buf); + fclose(in); + + return util_hexstr(hash, DAV_SHA256_DIGEST_LENGTH); +} + diff --git a/libidav/utils.h b/libidav/utils.h new file mode 100644 index 0000000..8952c76 --- /dev/null +++ b/libidav/utils.h @@ -0,0 +1,150 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UTILS_H +#define UTILS_H + +#ifdef _WIN32 +#include +#include +#endif /* _WIN32 */ + +#include +#include +#include +#include +#include +#include + +#include +#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 */ + diff --git a/libidav/versioning.c b/libidav/versioning.c new file mode 100644 index 0000000..e0a4514 --- /dev/null +++ b/libidav/versioning.c @@ -0,0 +1,175 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "versioning.h" + +#include "methods.h" +#include "utils.h" +#include "session.h" + +static int basic_deltav_op(DavResource *res, char *method) { + DavSession *sn = res->session; + CURL *handle = sn->handle; + util_set_url(sn, dav_resource_get_href(res)); + + DavLock *lock = dav_get_lock(res->session, res->path); + char *locktoken = lock ? lock->token : NULL; + + CURLcode ret = do_simple_request(sn, method, locktoken); + long status = 0; + curl_easy_getinfo (handle, CURLINFO_RESPONSE_CODE, &status); + if(!(ret == CURLE_OK && (status >= 200 && status < 300))) { + dav_session_set_error(sn, ret, status); + return 1; + } + return 0; +} + +int dav_versioncontrol(DavResource *res) { + return basic_deltav_op(res, "VERSION-CONTROL"); +} + +int dav_checkout(DavResource *res) { + return basic_deltav_op(res, "CHECKOUT"); +} + +int dav_checkin(DavResource *res) { + return basic_deltav_op(res, "CHECKIN"); +} + +int dav_uncheckout(DavResource *res) { + return basic_deltav_op(res, "UNCHECKOUT"); +} + +DavResource* dav_versiontree(DavResource *res, char *properties) { + DavSession *sn = res->session; + util_set_url(sn, dav_resource_get_href(res)); + + 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; +} diff --git a/libidav/versioning.h b/libidav/versioning.h new file mode 100644 index 0000000..1cf2649 --- /dev/null +++ b/libidav/versioning.h @@ -0,0 +1,45 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef VERSIONING_H +#define VERSIONING_H + +#include "webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + +#ifdef __cplusplus +} +#endif + +#endif /* VERSIONING_H */ + diff --git a/libidav/webdav.c b/libidav/webdav.c new file mode 100644 index 0000000..bbc3ef2 --- /dev/null +++ b/libidav/webdav.c @@ -0,0 +1,530 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "utils.h" +#include "webdav.h" +#include "session.h" +#include "methods.h" +#include +#include +#include +#include +#include +#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); +} diff --git a/libidav/webdav.h b/libidav/webdav.h new file mode 100644 index 0000000..3bd60ac --- /dev/null +++ b/libidav/webdav.h @@ -0,0 +1,438 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WEBDAV_H +#define WEBDAV_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#else +#include +#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 */ + diff --git a/libidav/xml.c b/libidav/xml.c new file mode 100644 index 0000000..32221ad --- /dev/null +++ b/libidav/xml.c @@ -0,0 +1,438 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include + +#include "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("", 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, "", prefix, node->name); + } else { + cx_fprintf(stream, writef, "", node->name); + } + } + + if(prefix_fr) { + free(prefix_fr); + } + } else if(node->type == DAV_XML_TEXT) { + writef(node->content, 1, node->contentlength, stream); + } + + 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; +} diff --git a/libidav/xml.h b/libidav/xml.h new file mode 100644 index 0000000..834440b --- /dev/null +++ b/libidav/xml.h @@ -0,0 +1,49 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef DAV_XML_H +#define DAV_XML_H + +#include "webdav.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DavXmlNode* dav_convert_xml(DavSession *sn, xmlNode *node); + +void dav_print_xml(DavXmlNode *node); + +void dav_print_node(void *stream, cx_write_func writef, CxMap *nsmap, DavXmlNode *node); + + +#ifdef __cplusplus +} +#endif + +#endif /* DAV_XML_H */ + diff --git a/make/Makefile.mk b/make/Makefile.mk new file mode 100644 index 0000000..f41b7f7 --- /dev/null +++ b/make/Makefile.mk @@ -0,0 +1,57 @@ +# +# 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: + diff --git a/make/cc.mk b/make/cc.mk new file mode 100644 index 0000000..149bde6 --- /dev/null +++ b/make/cc.mk @@ -0,0 +1,14 @@ +# +# cc toolchain config +# + +CFLAGS = +CXXFLAGS = +DEBUG_CC_FLAGS = -g +DEBUG_CXX_FLAGS = -g +RELEASE_CC_FLAGS = -O3 -DNDEBUG +RELEASE_CXX_FLAGS = -O3 -DNDEBUG +LDFLAGS = + +SHLIB_CFLAGS = -fPIC +SHLIB_LDFLAGS = -shared \ No newline at end of file diff --git a/make/clang.mk b/make/clang.mk new file mode 100644 index 0000000..772ebb7 --- /dev/null +++ b/make/clang.mk @@ -0,0 +1,14 @@ +# +# 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 diff --git a/make/configure.vm b/make/configure.vm new file mode 100644 index 0000000..b8acad3 --- /dev/null +++ b/make/configure.vm @@ -0,0 +1,673 @@ +#!/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" diff --git a/make/gcc.mk b/make/gcc.mk new file mode 100644 index 0000000..5eea8e1 --- /dev/null +++ b/make/gcc.mk @@ -0,0 +1,15 @@ +# +# 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 + diff --git a/make/mingw.mk b/make/mingw.mk new file mode 100644 index 0000000..340102e --- /dev/null +++ b/make/mingw.mk @@ -0,0 +1,46 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +CC = gcc +LD = gcc +AR = ar +RM = rm +MSBUILD = MSBuild.exe + +CFLAGS = -std=gnu99 -c -O2 -m64 +COFLAGS = -o +LDFLAGS = +LOFLAGS = -o +ARFLAGS = -r +RMFLAGS = -f + +OBJ_EXT = o +LIB_EXT = a +APP_EXT = .exe + +PACKAGE_SCRIPT = package_windows.sh \ No newline at end of file diff --git a/make/osx.mk b/make/osx.mk new file mode 100644 index 0000000..0db5e1c --- /dev/null +++ b/make/osx.mk @@ -0,0 +1,43 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +CC = gcc +LD = gcc +AR = ar +RM = rm + +CFLAGS += -std=gnu99 -g -I/usr/include/libxml2 +LDFLAGS += -lxml2 -lz -lpthread -licucore -lm +ARFLAGS = -r +RMFLAGS = -f + +OBJ_EXT = o +LIB_EXT = a +APP_EXT = + +PACKAGE_SCRIPT = package_osx.sh diff --git a/make/package_osx.sh b/make/package_osx.sh new file mode 100644 index 0000000..b6ecb00 --- /dev/null +++ b/make/package_osx.sh @@ -0,0 +1,12 @@ +#!/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/ + diff --git a/make/package_unix.sh b/make/package_unix.sh new file mode 100644 index 0000000..13f4793 --- /dev/null +++ b/make/package_unix.sh @@ -0,0 +1,2 @@ +#!/bin/sh + diff --git a/make/package_windows.sh b/make/package_windows.sh new file mode 100644 index 0000000..13f4793 --- /dev/null +++ b/make/package_windows.sh @@ -0,0 +1,2 @@ +#!/bin/sh + diff --git a/make/project.xml b/make/project.xml new file mode 100644 index 0000000..ff244ec --- /dev/null +++ b/make/project.xml @@ -0,0 +1,180 @@ + + + + c + + + + curl-config --cflags + curl-config --ldflags + + + libcurl + + + curl-config --cflags + curl-config --libs + + + + xml2-config --cflags + xml2-config --libs + + + xml2-config --cflags + xml2-config --libs + + + libxml-2.0 + + + xml2-config --cflags + xml2-config --libs + + + + -lssl -lcrypto + + + -framework CoreFoundation + + + -lssl -lcrypto + + + openssl + + + + libadwaita-1 + -DUI_GTK4 -DUI_LIBADWAITA + -lpthread + + + gtk4 + -DUI_GTK3 + -lpthread + + + gtk+-3.0 + -DUI_GTK3 + -lpthread + + + pkg-config --atleast-version=2.20 gtk+-2.0 + gtk+-2.0 + -DUI_GTK2 + -lpthread + + + gtk+-2.0 + -DUI_GTK2 -DUI_GTK2LEGACY + -lpthread + + + -DUI_WINUI + + + + + -DUI_COCOA + -lobjc -framework Cocoa + + + + -DUI_MOTIF -I/usr/local/include/X11 + -lXm -lXt -lX11 -lpthread + + + + -DUI_MOTIF + -lXm -lXt -lX11 -lpthread + + + + OBJ_EXT = .o + LIB_EXT = .a + PACKAGE_SCRIPT = package_osx.sh + + + OBJ_EXT = .o + LIB_EXT = .a + PACKAGE_SCRIPT = package_unix.sh + + + + -I/usr/local/include + -L/usr/local/lib + + + + curl,libxml2,openssl + + + + + + + diff --git a/make/suncc.mk b/make/suncc.mk new file mode 100644 index 0000000..38cbb87 --- /dev/null +++ b/make/suncc.mk @@ -0,0 +1,15 @@ +# +# 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 + diff --git a/make/toolchain.sh b/make/toolchain.sh new file mode 100644 index 0000000..ac5fb68 --- /dev/null +++ b/make/toolchain.sh @@ -0,0 +1,200 @@ +#!/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 +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 +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 +} diff --git a/make/uwproj.xsd b/make/uwproj.xsd new file mode 100644 index 0000000..f702701 --- /dev/null +++ b/make/uwproj.xsd @@ -0,0 +1,287 @@ + + + + + + + + The root element of an uwproj project. + Consists of an optional config element + and an arbitrary number of dependency + and target elements. + + + + + + + + + + + + + The configuration section. + Consists of an arbitrary number of var elements. + + + + + + + + + + + The definition of a configuration variable. +

+ 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 name attribute is mandatory, the value is defined by the text body of the element. + The optional Boolean exec attribute (false by default) controls, whether the entire + definition is automatically executed under command substitution. +

+
+
+ + + + + + +
+ + + + + Instructs configure to invoke pkg-config, 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 atleast, exact, max. + + + + + + + + + + + + + + + Requests a compiler for the specified language. Allowed values are + c, cpp. + + + + + + + + + + + + Declares a dependency. +

+ If the optional name attribute is omitted, the dependency is global + and must be satisfied, otherwise configuration shall fail. + A named dependency 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. +

+

+ The optional platform attribute may specify a single platform identifier and + the optional toolchain attribute may specify a single toolchain. + The optional not 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. +

+
+
+ + + + + + + + + + Specifies a custom command that shall be executed to test whether this dependency is satisfied. + + + + + + + + + +
+ + + + + Instructs configure to append the contents of the element's body to the respective flags variable. + If the optional exec flag is set to true, the contents are supposed to be + executed under command substitution at configuration time before they are applied. + + + + + + + + + + + + + Declares a build target that is supposed to be configured. +

+ If no build target is declared explicitly, an implicit default + target is generated, which has the alldependencies + flag set. +

+

+ The optional name 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 feature, + option, and define elements. + Named dependencies can be listed (separated by comma) in the dependencies + element. If this target shall use all available named dependencies, the empty + element alldependencies can be used as a shortcut. +

+
+
+ + + + + + + + + + +
+ + + + + Declares an optional feature, that can be enabled during configuration, if all + dependencies are satisfied. + If a feature is enabled, all define and make definitions are + supposed to be applied to the config file. + In case the optional default attribute is set to true, the feature is enabled by default + and is supposed to be automatically disabled (without error) when the dependencies are not satisfied. + The name that is supposed to be used for the --enable and --disable arguments can be optionally + specified with the arg attribute. Otherwise, the name is used by default. + Optionally, a description for the help text of the resulting configure script can be specified by + adding a desc element. + + + + + + + + + + + + + + + Declares a configuration option. + The option argument name is specified with the arg attribute. + Then, the children of this element specify possible values by defining the conditions + (in terms of dependencies) and effects (in terms of defines and make variables) of each value. + Finally, a set of defaults 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). + + + + + + + + + + + + + Declares a possible value for the option (in the str attribute) and + the conditions (dependencies) and effects, the value has. + + + + + + + + + + + + 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 platform attribute, + the default value can be constrained to a single specific platform and is supposed to be + skipped by configure, when this platform is not detected. + + + + + + + + + + + + + + + + + + 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) + + + + + + + + + A comma-separated list of named dependencies. + + + + + + + + The text contents in the body of this element are supposed to be appended literally + to the config file without prior processing. + + + + +
diff --git a/make/vs/idav.sln b/make/vs/idav.sln new file mode 100644 index 0000000..d861c89 --- /dev/null +++ b/make/vs/idav.sln @@ -0,0 +1,85 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33530.505 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ucx", "ucx\ucx.vcxproj", "{27DA0164-3475-43E2-A1A4-A5D07D305749}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libidav", "libidav\libidav.vcxproj", "{C29C0378-6548-48E8-9426-31922515212A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winui", "..\..\ui\winui\winui.vcxproj", "{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "idav", "idav\idav.vcxproj", "{F975D652-779E-4945-BFC2-8C649C6F9938}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|ARM64.ActiveCfg = Debug|x64 + {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|ARM64.Build.0 = Debug|x64 + {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x64.ActiveCfg = Debug|x64 + {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x64.Build.0 = Debug|x64 + {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x86.ActiveCfg = Debug|Win32 + {27DA0164-3475-43E2-A1A4-A5D07D305749}.Debug|x86.Build.0 = Debug|Win32 + {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|ARM64.ActiveCfg = Release|x64 + {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|ARM64.Build.0 = Release|x64 + {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x64.ActiveCfg = Release|x64 + {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x64.Build.0 = Release|x64 + {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x86.ActiveCfg = Release|Win32 + {27DA0164-3475-43E2-A1A4-A5D07D305749}.Release|x86.Build.0 = Release|Win32 + {C29C0378-6548-48E8-9426-31922515212A}.Debug|ARM64.ActiveCfg = Debug|x64 + {C29C0378-6548-48E8-9426-31922515212A}.Debug|ARM64.Build.0 = Debug|x64 + {C29C0378-6548-48E8-9426-31922515212A}.Debug|x64.ActiveCfg = Debug|x64 + {C29C0378-6548-48E8-9426-31922515212A}.Debug|x64.Build.0 = Debug|x64 + {C29C0378-6548-48E8-9426-31922515212A}.Debug|x86.ActiveCfg = Debug|Win32 + {C29C0378-6548-48E8-9426-31922515212A}.Debug|x86.Build.0 = Debug|Win32 + {C29C0378-6548-48E8-9426-31922515212A}.Release|ARM64.ActiveCfg = Release|x64 + {C29C0378-6548-48E8-9426-31922515212A}.Release|ARM64.Build.0 = Release|x64 + {C29C0378-6548-48E8-9426-31922515212A}.Release|x64.ActiveCfg = Release|x64 + {C29C0378-6548-48E8-9426-31922515212A}.Release|x64.Build.0 = Release|x64 + {C29C0378-6548-48E8-9426-31922515212A}.Release|x86.ActiveCfg = Release|Win32 + {C29C0378-6548-48E8-9426-31922515212A}.Release|x86.Build.0 = Release|Win32 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.Build.0 = Debug|ARM64 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.ActiveCfg = Debug|x64 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.Build.0 = Debug|x64 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x64.Deploy.0 = Debug|x64 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.ActiveCfg = Debug|Win32 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.Build.0 = Debug|Win32 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Debug|x86.Deploy.0 = Debug|Win32 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.ActiveCfg = Release|ARM64 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.Build.0 = Release|ARM64 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|ARM64.Deploy.0 = Release|ARM64 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.ActiveCfg = Release|x64 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.Build.0 = Release|x64 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x64.Deploy.0 = Release|x64 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.ActiveCfg = Release|Win32 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.Build.0 = Release|Win32 + {59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}.Release|x86.Deploy.0 = Release|Win32 + {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|ARM64.ActiveCfg = Debug|x64 + {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|ARM64.Build.0 = Debug|x64 + {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x64.ActiveCfg = Debug|x64 + {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x64.Build.0 = Debug|x64 + {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x86.ActiveCfg = Debug|Win32 + {F975D652-779E-4945-BFC2-8C649C6F9938}.Debug|x86.Build.0 = Debug|Win32 + {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|ARM64.ActiveCfg = Release|x64 + {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|ARM64.Build.0 = Release|x64 + {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x64.ActiveCfg = Release|x64 + {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x64.Build.0 = Release|x64 + {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x86.ActiveCfg = Release|Win32 + {F975D652-779E-4945-BFC2-8C649C6F9938}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {141CA624-F556-4BE7-9218-8D6EEAB95C95} + EndGlobalSection +EndGlobal diff --git a/make/vs/idav/app.manifest b/make/vs/idav/app.manifest new file mode 100644 index 0000000..b204072 --- /dev/null +++ b/make/vs/idav/app.manifest @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + PerMonitorV2 + + + \ No newline at end of file diff --git a/make/vs/idav/idav.vcxproj b/make/vs/idav/idav.vcxproj new file mode 100644 index 0000000..f6b6887 --- /dev/null +++ b/make/vs/idav/idav.vcxproj @@ -0,0 +1,208 @@ + + + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {F975D652-779E-4945-BFC2-8C649C6F9938} + testapp + 10.0 + true + None + false + idav + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\ + ..\..\..\build\vs\idav\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\ + ..\..\..\build\vs\idav\$(Platform)\$(Configuration)\ + + + true + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + false + _DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions) + true + ..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include;..\..\..\ui\;..\..\..;%(AdditionalIncludeDirectories) + stdc17 + + + Windows + true + + + + + Level3 + true + true + false + _DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions) + true + stdc17 + ..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include;..\..\..\ui\;..\..\..;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + + + + + + + + {59f97886-bf49-4b3f-9ef6-fa7a84f3ab56} + + + {c29c0378-6548-48e8-9426-31922515212a} + + + {27da0164-3475-43e2-a1a4-a5d07d305749} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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}. + + + + + + + + + + diff --git a/make/vs/idav/idav.vcxproj.filters b/make/vs/idav/idav.vcxproj.filters new file mode 100644 index 0000000..8a5b438 --- /dev/null +++ b/make/vs/idav/idav.vcxproj.filters @@ -0,0 +1,76 @@ + + + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {46525c70-7cfc-45fe-ae78-54123ad9bd7e} + + + + + + + + + + + + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + src + + + diff --git a/make/vs/idav/packages.config b/make/vs/idav/packages.config new file mode 100644 index 0000000..ece9b57 --- /dev/null +++ b/make/vs/idav/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/make/vs/libidav/libidav.vcxproj b/make/vs/libidav/libidav.vcxproj new file mode 100644 index 0000000..41bebe2 --- /dev/null +++ b/make/vs/libidav/libidav.vcxproj @@ -0,0 +1,188 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {c29c0378-6548-48e8-9426-31922515212a} + libidav + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + .dll + $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\ + ..\..\..\build\vs\libidav\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\ + ..\..\..\build\vs\libidav\$(Platform)\$(Configuration)\ + + + true + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + false + _DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + stdc11 + ..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include + + + + + Console + true + ..\vcpkg_installed\x64-windows\x64-windows\lib;%(AdditionalLibraryDirectories) + charset.lib;iconv.lib;libcurl.lib;libxml2.lib;lzma.lib;zlib.lib;bcrypt.lib;%(AdditionalDependencies) + + + xcopy /y $(SolutionDir)vcpkg_installed\x64-windows\x64-windows\bin\*.dll $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\ + + + + + Level3 + true + true + true + _DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + stdc17 + ..\..\..\ucx;..\vcpkg_installed\x64-windows\x64-windows\include + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {27da0164-3475-43e2-a1a4-a5d07d305749} + + + + + + \ No newline at end of file diff --git a/make/vs/libidav/libidav.vcxproj.filters b/make/vs/libidav/libidav.vcxproj.filters new file mode 100644 index 0000000..8d308cd --- /dev/null +++ b/make/vs/libidav/libidav.vcxproj.filters @@ -0,0 +1,93 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Quelldateien + + + Headerdateien + + + \ No newline at end of file diff --git a/make/vs/testapp/app.manifest b/make/vs/testapp/app.manifest new file mode 100644 index 0000000..b204072 --- /dev/null +++ b/make/vs/testapp/app.manifest @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + PerMonitorV2 + + + \ No newline at end of file diff --git a/make/vs/testapp/main.c b/make/vs/testapp/main.c new file mode 100644 index 0000000..aac5b3d --- /dev/null +++ b/make/vs/testapp/main.c @@ -0,0 +1,378 @@ +/* + * 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 + +#include +#include +#include + +#include + +typedef struct WindowData { + UiInteger* check; + UiInteger* toggle; + UiInteger* radio; + UiString* text; + UiString* password; + UiList* list; + UiString* t1; + UiString* t2; + UiString* t3; + UiString* path; + UiList* list2; + UiList* list3; + UiDouble* progress; + UiInteger* spinner; +} WindowData; + +static UiIcon* folder_icon; + +void action1(UiEvent* event, void* data) { + char* action = data; + + WindowData* wdata = event->window; + int64_t is_checked = wdata->check->get(wdata->check); + int64_t radio = wdata->radio->get(wdata->radio); + + printf("data: %s %d\n", data, is_checked); + + double d = wdata->progress->get(wdata->progress); + wdata->progress->set(wdata->progress, d + 1); + + int spinner_active = wdata->spinner->get(wdata->spinner); + wdata->spinner->set(wdata->spinner, !spinner_active); +} + +void action_set_checkbox(UiEvent* event, void* data) { + char* action = data; + + WindowData* wdata = event->window; + wdata->check->set(wdata->check, 1); +} + +void action_onchange(UiEvent* event, void* data) { + printf("onchange: %d\n", event->intval); +} + +void action_switch(UiEvent* event, void* data) { + printf("onchange: %d\n", event->intval); +} + +void action_toolbar_button(UiEvent* event, void *data) { + printf("toolbar action\n"); +} + + +void action_listselection_changed(UiEvent* event, void* data) { + printf("selection changed\n"); + UiListSelection* sel = event->eventdata; + for (int i = 0; i < sel->count; i++) { + int row = sel->rows[i]; + printf("row: %d\n", row); + } +} + +void action_onactivate(UiEvent* event, void* Data) { + printf("activate\n"); + UiListSelection* sel = event->eventdata; + for (int i = 0; i < sel->count; i++) { + int row = sel->rows[i]; + printf("row: %d\n", row); + } +} + +typedef struct TableData { + char* col1; + char* col2; + char* col3; +} TableData; + +void* table_getvalue(void* data, int i) { + TableData* t = data; + switch (i) { + case 0: return folder_icon; + case 1: return t->col1; + case 2: return t->col2; + case 3: return t->col3; + } + return NULL; +} + +void action_add(UiEvent* event, void* data) { + WindowData* wdata = event->window; + char* t1 = wdata->t1->get(wdata->t1); + char* t2 = wdata->t2->get(wdata->t2); + char* t3 = wdata->t3->get(wdata->t3); + + TableData* tdat = malloc(sizeof(TableData)); + tdat->col1 = _strdup(t1); + tdat->col2 = _strdup(t2); + tdat->col3 = _strdup(t3); + ui_list_append(wdata->list2, tdat); + wdata->list2->update(wdata->list2, 0); + +} + +void action_breadcrumb(UiEvent* event, void* data) { + int i = event->intval; + char* c = event->eventdata; + printf("index: %d\n", i); +} + +void dragstart(UiEvent* event, void* data) { + UiListDnd* ldnd = event->eventdata; + ui_selection_settext(ldnd->dnd, "Hello World!", -1); +} + +void dragcomplete(UiEvent* event, void* data) { + +} + +void dragover(UiEvent* event, void* data) { + +} + +void drop(UiEvent* event, void* data) { + +} + +void application_startup(UiEvent* event, void* data) { + UiObject* obj = ui_window("Test", NULL); + WindowData* wdata = ui_malloc(obj->ctx, sizeof(WindowData)); + obj->window = wdata; + wdata->check = ui_int_new(obj->ctx, "check"); + wdata->toggle = ui_int_new(obj->ctx, "toggle"); + wdata->radio = ui_int_new(obj->ctx, "radio"); + wdata->text = ui_string_new(obj->ctx, "text"); + wdata->password = ui_string_new(obj->ctx, "password"); + wdata->list = ui_list_new(obj->ctx, "list"); + wdata->list2 = ui_list_new(obj->ctx, "list2"); + wdata->list3 = ui_list_new(obj->ctx, "list3"); + wdata->t1 = ui_string_new(obj->ctx, "t1"); + wdata->t2 = ui_string_new(obj->ctx, "t2"); + wdata->t3 = ui_string_new(obj->ctx, "t3"); + wdata->path = ui_string_new(obj->ctx, "path"); + wdata->progress = ui_double_new(obj->ctx, "progress"); + wdata->spinner = ui_int_new(obj->ctx, "spinner"); + + ui_list_append(wdata->list, "Hello"); + ui_list_append(wdata->list, "World"); + ui_list_append(wdata->list, "Item3"); + ui_list_append(wdata->list, "Item4"); + ui_list_append(wdata->list, "Item5"); + ui_list_append(wdata->list, "Item6"); + + ui_list_append(wdata->list3, "usr"); + ui_list_append(wdata->list3, "share"); + ui_list_append(wdata->list3, "test"); + ui_list_append(wdata->list3, "dir"); + + //folder_icon = ui_icon("Folder", 32); + folder_icon = ui_foldericon(16); + + TableData* td1 = malloc(sizeof(TableData)); + TableData* td2 = malloc(sizeof(TableData)); + TableData* td3 = malloc(sizeof(TableData)); + TableData* td4 = malloc(sizeof(TableData)); + TableData* td5 = malloc(sizeof(TableData)); + TableData* td6 = malloc(sizeof(TableData)); + td1->col1 = "a1"; + td1->col2 = "b1"; + td1->col3 = "c1"; + td2->col1 = "a2"; + td2->col2 = "b2"; + td2->col3 = "b3"; + td3->col1 = "a3"; + td3->col2 = "b3"; + td3->col3 = "c3"; + td4->col1 = "a3"; + td4->col2 = "b3"; + td4->col3 = "c3"; + td5->col1 = "a3"; + td5->col2 = "b3"; + td5->col3 = "c3"; + td6->col1 = "a3"; + td6->col2 = "b3"; + td6->col3 = "c3"; + + ui_list_append(wdata->list2, td1); + ui_list_append(wdata->list2, td2); + ui_list_append(wdata->list2, td3); + ui_list_append(wdata->list2, td4); + ui_list_append(wdata->list2, td5); + ui_list_append(wdata->list2, td6); + + ui_scrolledwindow0(obj) { + ui_grid(obj, .margin = 10, .columnspacing = 5, .rowspacing = 20) { + ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1"); + ui_button(obj, .label = "Button2", .onclick = action1, .onclickdata = "action2"); + ui_button(obj, .label = "Button3", .onclick = action1, .onclickdata = "action3", .hexpand = true); + ui_newline(obj); + + ui_button(obj, .label = "Button4", .onclick = action1, .onclickdata = "action4"); + ui_button(obj, .label = "Button5", .onclick = action1, .onclickdata = "action5", .colspan = 2); + ui_newline(obj); + + ui_button(obj, .label = "Very Long Button Label Text ____________ Test", .onclick = action_set_checkbox); + ui_newline(obj); + + ui_checkbox(obj, .label = "Option 1", .value = wdata->check, .onchange = action_onchange); + ui_togglebutton(obj, .label = "Option 2", .value = wdata->toggle); + ui_newline(obj); + + ui_label(obj, .label = "Progress"); + ui_progressspinner(obj, .value = wdata->spinner); + ui_newline(obj); + + ui_hbox(obj, .colspan = 3) { + ui_radiobutton(obj, .label = "Radio 1", .value = wdata->radio); + ui_radiobutton(obj, .label = "Radio 2", .value = wdata->radio); + ui_radiobutton(obj, .label = "Radio 3", .value = wdata->radio); + } + ui_newline(obj); + ui_radiobutton(obj, .label = "Radio 4", .value = wdata->radio); + ui_switch(obj, .label = "test", .onchange = action_switch); + ui_newline(obj); + + //ui_breadcrumbbar(obj, .list = wdata->list3, .onactivate=action_breadcrumb); + ui_textfield(obj, .varname = "newtext"); + ui_path_textfield(obj, .colspan = 2, .value=wdata->path, .onactivate = action_breadcrumb); + ui_newline(obj); + wdata->path->set(wdata->path, "/usr/path/test"); + + ui_textfield(obj, .value = wdata->text); + ui_passwordfield(obj, .value = wdata->password); + ui_newline(obj); + + ui_frame(obj, .label = "Test", .colspan = 3) { + ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1"); + } + ui_newline(obj); + + ui_expander(obj, .label = "Expand", .colspan = 3, .margin = 10, .spacing = 5, .isexpanded = false) { + ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1"); + ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1"); + ui_button(obj, .label = "Button1", .onclick = action1, .onclickdata = "action1"); + } + ui_newline(obj); + + ui_combobox(obj, .list = wdata->list, .onselection= action_listselection_changed, .onactivate= action_onactivate); + ui_newline(obj); + + ui_tabview(obj, .colspan = 3, .vexpand = true, .hexpand = true, .tabview = UI_TABVIEW_NAVIGATION_SIDE) { + ui_tab(obj, "Tab 1") { + ui_button(obj, .label = "Tab 1 Button"); + } + ui_tab(obj, "Tab 2") { + ui_button(obj, .label = "Tab 2 Button"); + } + ui_tab(obj, "Tab 3") { + + } + } + ui_newline(obj); + + ui_label(obj, .label = "Test Label"); + ui_progressbar(obj, .value = wdata->progress, .colspan = 2); + ui_newline(obj); + + ui_newline(obj); + ui_textfield(obj, .value = wdata->t1); + ui_textfield(obj, .value = wdata->t2); + ui_textfield(obj, .value = wdata->t3); + ui_newline(obj); + ui_button(obj, .label = "Add", .onclick = action_add); + ui_newline(obj); + + + ui_newline(obj); + + UiModel* model = ui_model(obj->ctx, UI_ICON_TEXT, "Col 1", UI_STRING, "Col 2", UI_STRING, "Col 3", -1); + model->getvalue = table_getvalue; + ui_table(obj, .colspan = 3, .model = model, .list = wdata->list2, .onactivate = action_onactivate, + .onselection = action_listselection_changed, + .ondragstart = dragstart, .ondragcomplete = dragcomplete, .ondrop = drop); + ui_model_free(obj->ctx, model); + } + } + + ui_show(obj); +} + + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) +{ + ui_init("app1", 0, NULL); + ui_onstartup(application_startup, NULL); + + ui_menu("File") { + ui_menuitem(.label = "Item 1"); + ui_menuitem(.label = "Item 2"); + ui_menuseparator(); + ui_menu("File Sub") { + ui_menuitem(.label = "Sub Item"); + } + + ui_menuitem(.label = "Exit"); + } + + ui_toolbar_item("Test", .label = "Home", .icon = "Home", .onclick = action_toolbar_button); + ui_toolbar_toggleitem("Toggle", .label = "Toggle", .onchange = action_toolbar_button); + ui_toolbar_toggleitem("Toggle2", .label = "Toggle2", .onchange = action_toolbar_button); + ui_toolbar_toggleitem("Toggle3", .label = "Toggle3", .onchange = action_toolbar_button); + + ui_toolbar_menu("Menu", .label = "Menu") { + + ui_menuitem(.label = "x", NULL, NULL); + ui_menuitem(.label = "x", NULL, NULL); + ui_menuitem(.label = "x", NULL, NULL); + ui_menuitem(.label = "x", NULL, NULL); + ui_menuitem(.label = "x", NULL, NULL); + ui_menu("TB Sub") { + ui_menuitem("TB subitem", NULL, NULL); + } + } + + ui_toolbar_menu(NULL, .label = "Menu") { + ui_menuitem("Secondary Test", NULL, NULL); + ui_menu("Secondary Sub") { + ui_menuitem("Secondary subitem", NULL, NULL); + } + } + + ui_toolbar_add_default("Test", UI_TOOLBAR_LEFT); + ui_toolbar_add_default("Toggle", UI_TOOLBAR_LEFT); + ui_toolbar_add_default("Toggle2", UI_TOOLBAR_CENTER); + ui_toolbar_add_default("Toggle3", UI_TOOLBAR_CENTER); + ui_toolbar_add_default("Menu", UI_TOOLBAR_RIGHT); + + ui_main(); + + return (EXIT_SUCCESS); +} diff --git a/make/vs/testapp/packages.config b/make/vs/testapp/packages.config new file mode 100644 index 0000000..ece9b57 --- /dev/null +++ b/make/vs/testapp/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/make/vs/testapp/testapp.vcxproj b/make/vs/testapp/testapp.vcxproj new file mode 100644 index 0000000..386f861 --- /dev/null +++ b/make/vs/testapp/testapp.vcxproj @@ -0,0 +1,178 @@ + + + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + 16.0 + Win32Proj + {3541f08b-e6cc-4c23-a0d3-51983aab33c6} + testapp + 10.0 + true + None + false + idav + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\ + ..\..\..\build\vs\testapp\$(Platform)\$(Configuration)\ + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;UI_WINUI;%(PreprocessorDefinitions) + true + C:\Users\Olaf\Projekte\toolkit\ui;%(AdditionalIncludeDirectories) + + + Windows + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + {59f97886-bf49-4b3f-9ef6-fa7a84f3ab56} + + + {27da0164-3475-43e2-a1a4-a5d07d305749} + + + + + + + + + + + + + + + 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}. + + + + + + + + + + \ No newline at end of file diff --git a/make/vs/testapp/testapp.vcxproj.filters b/make/vs/testapp/testapp.vcxproj.filters new file mode 100644 index 0000000..5ed7d83 --- /dev/null +++ b/make/vs/testapp/testapp.vcxproj.filters @@ -0,0 +1,31 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Quelldateien + + + + + + + + + + + + \ No newline at end of file diff --git a/make/vs/ucx/ucx.vcxproj b/make/vs/ucx/ucx.vcxproj new file mode 100644 index 0000000..eadb7c2 --- /dev/null +++ b/make/vs/ucx/ucx.vcxproj @@ -0,0 +1,172 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {27da0164-3475-43e2-a1a4-a5d07d305749} + ucx + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\ + ..\..\..\build\vs\ucx\$(Platform)\$(Configuration)\ + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + false + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdc17 + MultiThreadedDebugDLL + + + Console + true + + + + + Level3 + true + true + false + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdc17 + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/make/vs/ucx/ucx.vcxproj.filters b/make/vs/ucx/ucx.vcxproj.filters new file mode 100644 index 0000000..52256da --- /dev/null +++ b/make/vs/ucx/ucx.vcxproj.filters @@ -0,0 +1,108 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + \ No newline at end of file diff --git a/make/vs/uicommon/uicommon.vcxproj b/make/vs/uicommon/uicommon.vcxproj new file mode 100644 index 0000000..6f8b494 --- /dev/null +++ b/make/vs/uicommon/uicommon.vcxproj @@ -0,0 +1,158 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {8b88698e-c185-4383-99fe-0c34d6deed2e} + uicommon + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + StaticLibrary + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\ + ..\..\..\build\vs\uicommon\$(Platform)\$(Configuration)\ + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + false + _DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;UI_WINUI;%(PreprocessorDefinitions) + true + stdc17 + $(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories) + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/make/vs/uicommon/uicommon.vcxproj.filters b/make/vs/uicommon/uicommon.vcxproj.filters new file mode 100644 index 0000000..3322c9f --- /dev/null +++ b/make/vs/uicommon/uicommon.vcxproj.filters @@ -0,0 +1,65 @@ + + + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + Ressourcendateien\Source + + + \ No newline at end of file diff --git a/make/vs/vcpkg.json b/make/vs/vcpkg.json new file mode 100644 index 0000000..e6408b6 --- /dev/null +++ b/make/vs/vcpkg.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "name": "libidav", + "dependencies": [ + "libxml2", + "curl", + "pcre" + ], + "builtin-baseline": "2c401863dd54a640aeb26ed736c55489c079323b" +} diff --git a/resource/.DS_Store b/resource/.DS_Store new file mode 100644 index 0000000..2bdfb30 Binary files /dev/null and b/resource/.DS_Store differ diff --git a/resource/locales/de_DE.properties b/resource/locales/de_DE.properties new file mode 100644 index 0000000..7ab93f2 --- /dev/null +++ b/resource/locales/de_DE.properties @@ -0,0 +1 @@ +hello = HALLO WELT! diff --git a/resource/locales/en_EN.properties b/resource/locales/en_EN.properties new file mode 100644 index 0000000..3e033cf --- /dev/null +++ b/resource/locales/en_EN.properties @@ -0,0 +1 @@ +hello = HELLO WORLD! diff --git a/resource/template.app/Contents/Info.plist b/resource/template.app/Contents/Info.plist new file mode 100644 index 0000000..d23a9e1 --- /dev/null +++ b/resource/template.app/Contents/Info.plist @@ -0,0 +1,71 @@ + + + + + BuildMachineOSBuild + 10K549 + CFBundleDevelopmentRegion + de_DE + CFBundleExecutable + mk12 + CFBundleIdentifier + com.yourcompany.toolkit + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + toolkit + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + DTCompiler + + DTPlatformBuild + 10M2518 + DTPlatformVersion + PG + DTSDKBuild + 10M2518 + DTSDKName + macosx10.6 + DTXcode + 0400 + DTXcodeBuild + 10M2518 + LSMinimumSystemVersion + 10.7 + NSMainNibFile + MainMenu + CFBundleDisplayName + + CFBundleGetInfoString + + LSApplicationCategoryType + + CFBundleDocumentTypes + + + LSItemContentTypes + + public.data + + CFBundleTypeIconFile + + CFBundleTypeName + DocumentType + CFBundleTypeRole + Editor + + + + NSPrincipalClass + NSApplication + + diff --git a/resource/template.app/Contents/PkgInfo b/resource/template.app/Contents/PkgInfo new file mode 100644 index 0000000..bd04210 --- /dev/null +++ b/resource/template.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings b/resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings new file mode 100644 index 0000000..dea12de Binary files /dev/null and b/resource/template.app/Contents/Resources/English.lproj/InfoPlist.strings differ diff --git a/resource/template.app/Contents/Resources/English.lproj/MainMenu.nib b/resource/template.app/Contents/Resources/English.lproj/MainMenu.nib new file mode 100644 index 0000000..69e866a Binary files /dev/null and b/resource/template.app/Contents/Resources/English.lproj/MainMenu.nib differ diff --git a/ucx/Makefile b/ucx/Makefile new file mode 100644 index 0000000..3259fc8 --- /dev/null +++ b/ucx/Makefile @@ -0,0 +1,63 @@ +# +# 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 $< + diff --git a/ucx/allocator.c b/ucx/allocator.c new file mode 100644 index 0000000..f5f37c9 --- /dev/null +++ b/ucx/allocator.c @@ -0,0 +1,136 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#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); +} diff --git a/ucx/array_list.c b/ucx/array_list.c new file mode 100644 index 0000000..2a0823b --- /dev/null +++ b/ucx/array_list.c @@ -0,0 +1,767 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/array_list.h" +#include "cx/compare.h" +#include +#include + +// 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; +} diff --git a/ucx/buffer.c b/ucx/buffer.c new file mode 100644 index 0000000..f03e513 --- /dev/null +++ b/ucx/buffer.c @@ -0,0 +1,420 @@ +/* + * 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 +#include + +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; + } +} diff --git a/ucx/compare.c b/ucx/compare.c new file mode 100644 index 0000000..8d2e4f0 --- /dev/null +++ b/ucx/compare.c @@ -0,0 +1,213 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/compare.h" + +#include + +int cx_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; + } +} diff --git a/ucx/cx/allocator.h b/ucx/cx/allocator.h new file mode 100644 index 0000000..aef3e18 --- /dev/null +++ b/ucx/cx/allocator.h @@ -0,0 +1,242 @@ +/* + * 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 diff --git a/ucx/cx/array_list.h b/ucx/cx/array_list.h new file mode 100644 index 0000000..89cbacf --- /dev/null +++ b/ucx/cx/array_list.h @@ -0,0 +1,461 @@ +/* + * 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 diff --git a/ucx/cx/buffer.h b/ucx/cx/buffer.h new file mode 100644 index 0000000..3dbbc87 --- /dev/null +++ b/ucx/cx/buffer.h @@ -0,0 +1,464 @@ +/* + * 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 + * memset(buffer->bytes, 0, shift) for a right shift or + * memset(buffer->bytes + buffer->size, 0, buffer->capacity - buffer->size) + * for a left shift. + * + * @param buffer the buffer + * @param shift the shift offset (negative means left shift) + * @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 diff --git a/ucx/cx/collection.h b/ucx/cx/collection.h new file mode 100644 index 0000000..7c8ae4b --- /dev/null +++ b/ucx/cx/collection.h @@ -0,0 +1,164 @@ +/* + * 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 diff --git a/ucx/cx/common.h b/ucx/cx/common.h new file mode 100644 index 0000000..754dfc6 --- /dev/null +++ b/ucx/cx/common.h @@ -0,0 +1,142 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file common.h + * + * \brief Common definitions and feature checks. + * + * \author Mike Becker + * \author Olaf Wintermann + * \copyright 2-Clause BSD License + * + * \mainpage UAP Common Extensions + * Library with common and useful functions, macros and data structures. + *

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

+ * + *

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

+ * + *

LICENCE

+ * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UCX_COMMON_H +#define UCX_COMMON_H + +/** Major UCX version as integer constant. */ +#define UCX_VERSION_MAJOR 3 + +/** Minor UCX version as integer constant. */ +#define UCX_VERSION_MINOR 1 + +/** Version constant which ensures to increase monotonically. */ +#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) + +// Common Includes + +#include +#include +#include +#include +#include + +#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 +typedef SSIZE_T ssize_t; + +// fix missing _Thread_local support +#define _Thread_local __declspec(thread) + +#endif + +#endif // UCX_COMMON_H diff --git a/ucx/cx/common.h.orig b/ucx/cx/common.h.orig new file mode 100644 index 0000000..e6ce33b --- /dev/null +++ b/ucx/cx/common.h.orig @@ -0,0 +1,138 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file common.h + * + * \brief Common definitions and feature checks. + * + * \author Mike Becker + * \author Olaf Wintermann + * \version 3.0 + * \copyright 2-Clause BSD License + * + * \mainpage UAP Common Extensions + * Library with common and useful functions, macros and data structures. + *

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

+ * + *

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

+ * + *

LICENCE

+ * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UCX_COMMON_H +#define UCX_COMMON_H + +/** Major UCX version as integer constant. */ +#define UCX_VERSION_MAJOR 3 + +/** Minor UCX version as integer constant. */ +#define UCX_VERSION_MINOR 0 + +/** Version constant which ensures to increase monotonically. */ +#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) + +#define __attribute__(...) + +#include +#include +#include +#include + +/** + * Function pointer compatible with fwrite-like functions. + */ +typedef size_t (*cx_write_func)( + void const *, + size_t, + size_t, + void * +); + +/** + * Function pointer compatible with fread-like functions. + */ +typedef size_t (*cx_read_func)( + void *, + size_t, + size_t, + void * +); + +#ifdef _WIN32 + +#ifdef __MINGW32__ +#include +#endif // __MINGW32__ + +#else // !_WIN32 + +#include + +#endif // _WIN32 + +#ifndef __GNUC__ +/** + * Removes GNU C attributes where they are not supported. + */ +#define __attribute__(x) +#endif + +#endif // UCX_COMMON_H diff --git a/ucx/cx/compare.h b/ucx/cx/compare.h new file mode 100644 index 0000000..652bb0e --- /dev/null +++ b/ucx/cx/compare.h @@ -0,0 +1,243 @@ +/* + * 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 diff --git a/ucx/cx/hash_key.h b/ucx/cx/hash_key.h new file mode 100644 index 0000000..9c48342 --- /dev/null +++ b/ucx/cx/hash_key.h @@ -0,0 +1,128 @@ +/* + * 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 diff --git a/ucx/cx/hash_map.h b/ucx/cx/hash_map.h new file mode 100644 index 0000000..c1cfc9f --- /dev/null +++ b/ucx/cx/hash_map.h @@ -0,0 +1,133 @@ +/* + * 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 diff --git a/ucx/cx/iterator.h b/ucx/cx/iterator.h new file mode 100644 index 0000000..c9fb73e --- /dev/null +++ b/ucx/cx/iterator.h @@ -0,0 +1,265 @@ +/* + * 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 diff --git a/ucx/cx/linked_list.h b/ucx/cx/linked_list.h new file mode 100644 index 0000000..23b37a7 --- /dev/null +++ b/ucx/cx/linked_list.h @@ -0,0 +1,506 @@ +/* + * 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 diff --git a/ucx/cx/list.h b/ucx/cx/list.h new file mode 100644 index 0000000..4807624 --- /dev/null +++ b/ucx/cx/list.h @@ -0,0 +1,800 @@ +/* + * 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 diff --git a/ucx/cx/map.h b/ucx/cx/map.h new file mode 100644 index 0000000..2fde575 --- /dev/null +++ b/ucx/cx/map.h @@ -0,0 +1,1052 @@ +/* + * 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 diff --git a/ucx/cx/mempool.h b/ucx/cx/mempool.h new file mode 100644 index 0000000..6214d8a --- /dev/null +++ b/ucx/cx/mempool.h @@ -0,0 +1,148 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \file 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 diff --git a/ucx/cx/printf.h b/ucx/cx/printf.h new file mode 100644 index 0000000..fad4a7e --- /dev/null +++ b/ucx/cx/printf.h @@ -0,0 +1,335 @@ +/* + * 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 + +#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 diff --git a/ucx/cx/string.h b/ucx/cx/string.h new file mode 100644 index 0000000..a55e6a7 --- /dev/null +++ b/ucx/cx/string.h @@ -0,0 +1,1082 @@ +/* + * 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 const char* you are really supposed to free. + * If you encounter such situation, you should double-check your code. + * + * @param str the string to free + */ +__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 const char* you are really supposed to free. + * If you encounter such situation, you should double-check your code. + * + * @param alloc the allocator + * @param str the string to free + */ +__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 diff --git a/ucx/cx/test.h b/ucx/cx/test.h new file mode 100644 index 0000000..62f11ad --- /dev/null +++ b/ucx/cx/test.h @@ -0,0 +1,330 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file: test.h + * + * UCX Test Framework. + * + * Usage of this test framework: + * + * **** IN HEADER FILE: **** + * + *
+ * CX_TEST(function_name);
+ * CX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
+ * 
+ * + * **** IN SOURCE FILE: **** + *
+ * CX_TEST_SUBROUTINE(subroutine_name, paramlist) {
+ *   // tests with CX_TEST_ASSERT()
+ * }
+ * 
+ * CX_TEST(function_name) {
+ *   // memory allocation and other stuff here
+ *   #CX_TEST_DO {
+ *     // tests with CX_TEST_ASSERT() and/or
+ *     // calls with CX_TEST_CALL_SUBROUTINE() here
+ *   }
+ *   // cleanup of memory here
+ * }
+ * 
+ * + * @attention Do not call own functions within a test, that use + * CX_TEST_ASSERT() macros and are not defined by using CX_TEST_SUBROUTINE(). + * + * @author Mike Becker + * @author Olaf Wintermann + * + */ + +#ifndef UCX_TEST_H +#define UCX_TEST_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __FUNCTION__ +/** + * Alias for the __func__ preprocessor macro. + * Some compilers use __func__ and others use __FUNCTION__. + * We use __FUNCTION__ so we define it for those compilers which use + * __func__. + */ +#define __FUNCTION__ __func__ +#endif + +// +#if !defined(__clang__) && __GNUC__ > 3 +#pragma GCC diagnostic ignored "-Wclobbered" +#endif + +#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 only call subroutines within a #CX_TEST_DO block. + * + * @param name the name of the subroutine + * @param ... the argument list + * + * @see CX_TEST_SUBROUTINE() + */ +#define CX_TEST_CALL_SUBROUTINE(name,...) \ + name(_suite_,_output_,_writefnc_,_env_,__VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_TEST_H */ + diff --git a/ucx/cx/tree.h b/ucx/cx/tree.h new file mode 100644 index 0000000..7abb6fc --- /dev/null +++ b/ucx/cx/tree.h @@ -0,0 +1,1254 @@ +/* + * 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 diff --git a/ucx/cx/utils.h b/ucx/cx/utils.h new file mode 100644 index 0000000..be359ea --- /dev/null +++ b/ucx/cx/utils.h @@ -0,0 +1,194 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file utils.h + * + * \brief General purpose utility functions. + * + * \author Mike Becker + * \author Olaf Wintermann + * \copyright 2-Clause BSD License + */ + +#ifndef UCX_UTILS_H +#define UCX_UTILS_H + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Convenience macro for a for loop that counts from zero to n-1. + */ +#define cx_for_n(varname, n) for (size_t varname = 0 ; (varname) < (n) ; (varname)++) + +/** + * Convenience macro for swapping two pointers. + */ +#ifdef __cplusplus +#define cx_swap_ptr(left, right) do {auto cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0) +#else +#define cx_swap_ptr(left, right) do {void *cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0) +#endif + +// cx_szmul() definition + +#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN) +#define CX_SZMUL_BUILTIN + +/** + * Alias for \c __builtin_mul_overflow. + * + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result) + +#else // no GNUC or clang bultin + +/** + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result) + +/** + * Performs a multiplication of size_t values and checks for overflow. + * + * This is a custom implementation in case there is no compiler builtin + * available. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t where the result should be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +int cx_szmul_impl(size_t a, size_t b, size_t *result); + +#endif // cx_szmul + + +/** + * Reads data from a stream and writes it to another stream. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param buf a pointer to the copy buffer or \c NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can + * set this to zero to let the implementation decide + * @param n the maximum number of bytes that shall be copied. + * If this is larger than \p bufsize, the content is copied over multiple + * iterations. + * @return the total number of bytes copied + */ +__attribute__((__nonnull__(1, 2, 3, 4))) +size_t cx_stream_bncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + char *buf, + size_t bufsize, + size_t n +); + +/** + * Reads data from a stream and writes it to another stream. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param buf a pointer to the copy buffer or \c NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can + * set this to zero to let the implementation decide + * @return total number of bytes copied + */ +#define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \ + cx_stream_bncopy(src, dest, rfnc, wfnc, buf, bufsize, SIZE_MAX) + +/** + * Reads data from a stream and writes it to another stream. + * + * The data is temporarily stored in a stack allocated buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param n the maximum number of bytes that shall be copied. + * @return total number of bytes copied + */ +__attribute__((__nonnull__)) +size_t cx_stream_ncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + size_t n +); + +/** + * Reads data from a stream and writes it to another stream. + * + * The data is temporarily stored in a stack allocated buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @return total number of bytes copied + */ +#define cx_stream_copy(src, dest, rfnc, wfnc) \ + cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX) + +#ifdef __cplusplus +} +#endif + +#endif // UCX_UTILS_H diff --git a/ucx/hash_key.c b/ucx/hash_key.c new file mode 100644 index 0000000..a2840b4 --- /dev/null +++ b/ucx/hash_key.c @@ -0,0 +1,112 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/hash_key.h" +#include + +void cx_hash_murmur(CxHashKey *key) { + const unsigned char *data = key->data; + if (data == NULL) { + // extension: special value for NULL + key->hash = 1574210520u; + return; + } + size_t len = key->len; + + unsigned m = 0x5bd1e995; + unsigned r = 24; + unsigned h = 25 ^ len; + unsigned i = 0; + while (len >= 4) { + unsigned k = data[i + 0] & 0xFF; + k |= (data[i + 1] & 0xFF) << 8; + k |= (data[i + 2] & 0xFF) << 16; + k |= (data[i + 3] & 0xFF) << 24; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + i += 4; + len -= 4; + } + + switch (len) { + case 3: + h ^= (data[i + 2] & 0xFF) << 16; + __attribute__((__fallthrough__)); + case 2: + h ^= (data[i + 1] & 0xFF) << 8; + __attribute__((__fallthrough__)); + case 1: + h ^= (data[i + 0] & 0xFF); + h *= m; + __attribute__((__fallthrough__)); + default: // do nothing + ; + } + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + key->hash = h; +} + +CxHashKey cx_hash_key_str(const char *str) { + CxHashKey key; + key.data = str; + key.len = str == NULL ? 0 : strlen(str); + cx_hash_murmur(&key); + return key; +} + +CxHashKey cx_hash_key_bytes( + const unsigned char *bytes, + size_t len +) { + CxHashKey key; + key.data = bytes; + key.len = len; + cx_hash_murmur(&key); + return key; +} + +CxHashKey cx_hash_key( + const void *obj, + size_t len +) { + CxHashKey key; + key.data = obj; + key.len = len; + cx_hash_murmur(&key); + return key; +} diff --git a/ucx/hash_map.c b/ucx/hash_map.c new file mode 100644 index 0000000..f5608a7 --- /dev/null +++ b/ucx/hash_map.c @@ -0,0 +1,479 @@ +/* + * 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 +#include + +struct cx_hash_map_element_s { + /** A pointer to the next element in the current bucket. */ + struct cx_hash_map_element_s *next; + + /** The corresponding key. */ + CxHashKey key; + + /** The value data. */ + char data[]; +}; + +static void cx_hash_map_clear(struct cx_map_s *map) { + struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; + 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; +} diff --git a/ucx/iterator.c b/ucx/iterator.c new file mode 100644 index 0000000..0dae173 --- /dev/null +++ b/ucx/iterator.c @@ -0,0 +1,112 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/iterator.h" + +#include + +static bool cx_iter_valid(const void *it) { + const struct cx_iterator_s *iter = it; + return iter->index < iter->elem_count; +} + +static void *cx_iter_current(const void *it) { + const struct cx_iterator_s *iter = it; + return iter->elem_handle; +} + +static void cx_iter_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; +} diff --git a/ucx/linked_list.c b/ucx/linked_list.c new file mode 100644 index 0000000..5bc1cb1 --- /dev/null +++ b/ucx/linked_list.c @@ -0,0 +1,1117 @@ +/* + * 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 +#include + +// LOW LEVEL LINKED LIST FUNCTIONS + +#define CX_LL_PTR(cur, off) (*(void**)(((char*)(cur))+(off))) +#define ll_prev(node) CX_LL_PTR(node, loc_prev) +#define ll_next(node) CX_LL_PTR(node, loc_next) +#define ll_advance(node) CX_LL_PTR(node, loc_advance) +#define ll_data(node) (((char*)(node))+loc_data) + +void *cx_linked_list_at( + const void *start, + size_t start_index, + ptrdiff_t loc_advance, + size_t index +) { + assert(start != NULL); + assert(loc_advance >= 0); + size_t i = start_index; + const void *cur = start; + while (i != index && cur != NULL) { + cur = ll_advance(cur); + i < index ? i++ : i--; + } + return (void *) cur; +} + +ssize_t cx_linked_list_find( + const void *start, + ptrdiff_t loc_advance, + ptrdiff_t loc_data, + cx_compare_func cmp_func, + const void *elem +) { + void *dummy; + return cx_linked_list_find_node( + &dummy, start, + loc_advance, loc_data, + cmp_func, elem + ); +} + +ssize_t cx_linked_list_find_node( + void **result, + const void *start, + ptrdiff_t loc_advance, + ptrdiff_t loc_data, + cx_compare_func cmp_func, + const void *elem +) { + assert(result != NULL); + assert(start != NULL); + assert(loc_advance >= 0); + assert(loc_data >= 0); + assert(cmp_func); + + const void *node = start; + ssize_t index = 0; + do { + void *current = ll_data(node); + if (cmp_func(current, elem) == 0) { + *result = (void*) node; + return index; + } + node = ll_advance(node); + index++; + } while (node != NULL); + *result = NULL; + return -1; +} + +void *cx_linked_list_first( + const void *node, + ptrdiff_t loc_prev +) { + return cx_linked_list_last(node, loc_prev); +} + +void *cx_linked_list_last( + const void *node, + ptrdiff_t loc_next +) { + assert(node != NULL); + assert(loc_next >= 0); + + const void *cur = node; + const void *last; + do { + last = cur; + } while ((cur = ll_next(cur)) != NULL); + + return (void *) last; +} + +void *cx_linked_list_prev( + const void *begin, + ptrdiff_t loc_next, + const void *node +) { + assert(begin != NULL); + assert(node != NULL); + assert(loc_next >= 0); + if (begin == node) return NULL; + const void *cur = begin; + const void *next; + while (1) { + next = ll_next(cur); + if (next == node) return (void *) cur; + cur = next; + } +} + +void cx_linked_list_link( + void *left, + void *right, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + assert(loc_next >= 0); + ll_next(left) = right; + if (loc_prev >= 0) { + ll_prev(right) = left; + } +} + +void cx_linked_list_unlink( + void *left, + void *right, + ptrdiff_t loc_prev, + ptrdiff_t loc_next +) { + assert (loc_next >= 0); + assert(ll_next(left) == right); + ll_next(left) = NULL; + if (loc_prev >= 0) { + assert(ll_prev(right) == left); + ll_prev(right) = NULL; + } +} + +void cx_linked_list_add( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node +) { + void *last; + if (end == NULL) { + assert(begin != NULL); + last = *begin == NULL ? NULL : cx_linked_list_last(*begin, loc_next); + } else { + last = *end; + } + cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, last, new_node, new_node); +} + +void cx_linked_list_prepend( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node +) { + cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, NULL, new_node, new_node); +} + +void cx_linked_list_insert( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node, + void *new_node +) { + cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, node, new_node, new_node); +} + +void cx_linked_list_insert_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node, + void *insert_begin, + void *insert_end +) { + // find the end of the chain, if not specified + if (insert_end == NULL) { + insert_end = cx_linked_list_last(insert_begin, loc_next); + } + + // determine the successor + void *successor; + if (node == NULL) { + assert(begin != NULL || (end != NULL && loc_prev >= 0)); + if (begin != NULL) { + successor = *begin; + *begin = insert_begin; + } else { + successor = *end == NULL ? NULL : cx_linked_list_first(*end, loc_prev); + } + } else { + successor = ll_next(node); + cx_linked_list_link(node, insert_begin, loc_prev, loc_next); + } + + if (successor == NULL) { + // the list ends with the new chain + if (end != NULL) { + *end = insert_end; + } + } else { + cx_linked_list_link(insert_end, successor, loc_prev, loc_next); + } +} + +void cx_linked_list_insert_sorted( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node, + cx_compare_func cmp_func +) { + assert(ll_next(new_node) == NULL); + cx_linked_list_insert_sorted_chain( + begin, end, loc_prev, loc_next, new_node, cmp_func); +} + +void cx_linked_list_insert_sorted_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *insert_begin, + cx_compare_func cmp_func +) { + assert(begin != NULL); + assert(loc_next >= 0); + assert(insert_begin != NULL); + + // track currently observed nodes + void *dest_prev = NULL; + void *dest = *begin; + void *src = insert_begin; + + // special case: list is empty + if (dest == NULL) { + *begin = src; + if (end != NULL) { + *end = cx_linked_list_last(src, loc_next); + } + return; + } + + // search the list for insertion points + while (dest != NULL && src != NULL) { + // compare current list node with source node + // if less or equal, skip + if (cmp_func(dest, src) <= 0) { + dest_prev = dest; + dest = ll_next(dest); + continue; + } + + // determine chain of elements that can be inserted + void *end_of_chain = src; + void *next_in_chain = ll_next(src); + while (next_in_chain != NULL) { + // once we become larger than the list elem, break + if (cmp_func(dest, next_in_chain) <= 0) { + break; + } + // otherwise, we can insert one more + end_of_chain = next_in_chain; + next_in_chain = ll_next(next_in_chain); + } + + // insert the elements + if (dest_prev == NULL) { + // new begin + *begin = src; + } else { + cx_linked_list_link(dest_prev, src, loc_prev, loc_next); + } + cx_linked_list_link(end_of_chain, dest, loc_prev, loc_next); + + // continue with next + src = next_in_chain; + dest_prev = dest; + dest = ll_next(dest); + } + + // insert remaining items + if (src != NULL) { + cx_linked_list_link(dest_prev, src, loc_prev, loc_next); + } + + // determine new end of list, if requested + if (end != NULL) { + *end = cx_linked_list_last( + dest != NULL ? dest : dest_prev, loc_next); + } +} + +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; +} diff --git a/ucx/list.c b/ucx/list.c new file mode 100644 index 0000000..6e3d4bb --- /dev/null +++ b/ucx/list.c @@ -0,0 +1,487 @@ +/* + * 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 + +// + +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; +} + +// + +// + +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; + +// + +#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; +} diff --git a/ucx/map.c b/ucx/map.c new file mode 100644 index 0000000..dc2dc9d --- /dev/null +++ b/ucx/map.c @@ -0,0 +1,102 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/map.h" +#include + +// + +static void cx_empty_map_noop(__attribute__((__unused__)) CxMap *map) { + // this is a noop, but MUST be implemented +} + +static void *cx_empty_map_get( + __attribute__((__unused__)) const CxMap *map, + __attribute__((__unused__)) CxHashKey key +) { + return NULL; +} + +static bool cx_empty_map_iter_valid(__attribute__((__unused__)) const void *iter) { + return false; +} + +static CxIterator cx_empty_map_iterator( + const struct cx_map_s *map, + __attribute__((__unused__)) enum cx_map_iterator_type type +) { + CxIterator iter = {0}; + iter.src_handle.c = map; + iter.base.valid = cx_empty_map_iter_valid; + return iter; +} + +static struct cx_map_class_s cx_empty_map_class = { + cx_empty_map_noop, + cx_empty_map_noop, + NULL, + cx_empty_map_get, + NULL, + cx_empty_map_iterator +}; + +CxMap cx_empty_map = { + { + NULL, + NULL, + 0, + 0, + NULL, + NULL, + NULL, + false + }, + &cx_empty_map_class +}; + +CxMap *const cxEmptyMap = &cx_empty_map; + +// + +CxIterator cxMapMutIteratorValues(CxMap *map) { + CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); + it.base.mutating = true; + return it; +} + +CxIterator cxMapMutIteratorKeys(CxMap *map) { + CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); + it.base.mutating = true; + return it; +} + +CxIterator cxMapMutIterator(CxMap *map) { + CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); + it.base.mutating = true; + return it; +} diff --git a/ucx/mempool.c b/ucx/mempool.c new file mode 100644 index 0000000..972a487 --- /dev/null +++ b/ucx/mempool.c @@ -0,0 +1,233 @@ +/* + * 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 + +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; +} diff --git a/ucx/printf.c b/ucx/printf.c new file mode 100644 index 0000000..dd77e52 --- /dev/null +++ b/ucx/printf.c @@ -0,0 +1,194 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/printf.h" + +#include +#include + +#ifndef CX_PRINTF_SBO_SIZE +#define CX_PRINTF_SBO_SIZE 512 +#endif +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; +} diff --git a/ucx/string.c b/ucx/string.c new file mode 100644 index 0000000..532a40b --- /dev/null +++ b/ucx/string.c @@ -0,0 +1,786 @@ +/* + * 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 +#include +#include + +#ifndef _WIN32 + +#include // 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; +} diff --git a/ucx/szmul.c b/ucx/szmul.c new file mode 100644 index 0000000..64c6a65 --- /dev/null +++ b/ucx/szmul.c @@ -0,0 +1,46 @@ +/* + * 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; + } +} diff --git a/ucx/tree.c b/ucx/tree.c new file mode 100644 index 0000000..150cf57 --- /dev/null +++ b/ucx/tree.c @@ -0,0 +1,958 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cx/tree.h" + +#include "cx/array_list.h" + +#include + +#define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off))) +#define tree_parent(node) CX_TREE_PTR(node, loc_parent) +#define tree_children(node) CX_TREE_PTR(node, loc_children) +#define tree_last_child(node) CX_TREE_PTR(node, loc_last_child) +#define tree_prev(node) CX_TREE_PTR(node, loc_prev) +#define tree_next(node) CX_TREE_PTR(node, loc_next) + +#define cx_tree_ptr_locations \ + loc_parent, loc_children, loc_last_child, loc_prev, loc_next + +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; +} diff --git a/ucx/utils.c b/ucx/utils.c new file mode 100644 index 0000000..c01979b --- /dev/null +++ b/ucx/utils.c @@ -0,0 +1,99 @@ +/* + * 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 diff --git a/ui/Makefile b/ui/Makefile new file mode 100644 index 0000000..a0d1148 --- /dev/null +++ b/ui/Makefile @@ -0,0 +1,47 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2012 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +BUILD_ROOT = ../ +include ../config.mk + +OBJ_DIR = ../build/ + +include common/objs.mk + +UI_LIB = ../build/lib/libuitk$(LIB_EXT) + +include $(TOOLKIT)/objs.mk +OBJ = $(TOOLKITOBJS) $(COMMONOBJS) + +all: $(UI_LIB) + +include $(TOOLKIT)/Makefile + +$(COMMON_OBJPRE)%.o: common/%.c + $(CC) -o $@ -c -I../ucx/ $(CFLAGS) $(TK_CFLAGS) $< + diff --git a/ui/cocoa/Makefile b/ui/cocoa/Makefile new file mode 100644 index 0000000..8c13a4f --- /dev/null +++ b/ui/cocoa/Makefile @@ -0,0 +1,34 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2012 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +$(COCOA_OBJPRE)%.o: cocoa/%.m + $(CC) -o $@ -c $(CFLAGS) $< + +$(UI_LIB): $(OBJ) + $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ) + diff --git a/ui/cocoa/container.h b/ui/cocoa/container.h new file mode 100644 index 0000000..1289b63 --- /dev/null +++ b/ui/cocoa/container.h @@ -0,0 +1,44 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#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); + diff --git a/ui/cocoa/container.m b/ui/cocoa/container.m new file mode 100644 index 0000000..dd6697d --- /dev/null +++ b/ui/cocoa/container.m @@ -0,0 +1,106 @@ +/* + * 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 +#import + +#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]; +} diff --git a/ui/cocoa/graphics.h b/ui/cocoa/graphics.h new file mode 100644 index 0000000..3f593d7 --- /dev/null +++ b/ui/cocoa/graphics.h @@ -0,0 +1,52 @@ +/* + * 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 + diff --git a/ui/cocoa/graphics.m b/ui/cocoa/graphics.m new file mode 100644 index 0000000..062163b --- /dev/null +++ b/ui/cocoa/graphics.m @@ -0,0 +1,124 @@ +/* + * 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 +#import +#import + +#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); + } +} + + + diff --git a/ui/cocoa/menu.h b/ui/cocoa/menu.h new file mode 100644 index 0000000..0f7cb8e --- /dev/null +++ b/ui/cocoa/menu.h @@ -0,0 +1,94 @@ +/* + * 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 + +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 { + 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); diff --git a/ui/cocoa/menu.m b/ui/cocoa/menu.m new file mode 100644 index 0000000..c286ef7 --- /dev/null +++ b/ui/cocoa/menu.m @@ -0,0 +1,319 @@ +/* + * 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 +#import +#import +#import + +#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;idata; + + 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;roldcount;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; +} diff --git a/ui/cocoa/objs.mk b/ui/cocoa/objs.mk new file mode 100644 index 0000000..0a35acd --- /dev/null +++ b/ui/cocoa/objs.mk @@ -0,0 +1,45 @@ +# +# 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) diff --git a/ui/cocoa/resource.h b/ui/cocoa/resource.h new file mode 100644 index 0000000..0dd0c66 --- /dev/null +++ b/ui/cocoa/resource.h @@ -0,0 +1,32 @@ +/* + * 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" + + diff --git a/ui/cocoa/resource.m b/ui/cocoa/resource.m new file mode 100644 index 0000000..afb9410 --- /dev/null +++ b/ui/cocoa/resource.m @@ -0,0 +1,73 @@ +/* + * 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 +#import +#import + +#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 diff --git a/ui/cocoa/stock.h b/ui/cocoa/stock.h new file mode 100644 index 0000000..2568aaf --- /dev/null +++ b/ui/cocoa/stock.h @@ -0,0 +1,43 @@ +/* + * 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 + +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); diff --git a/ui/cocoa/stock.m b/ui/cocoa/stock.m new file mode 100644 index 0000000..227e3bc --- /dev/null +++ b/ui/cocoa/stock.m @@ -0,0 +1,75 @@ +/* + * 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 +#import + +#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; +} diff --git a/ui/cocoa/text.h b/ui/cocoa/text.h new file mode 100644 index 0000000..ba0f1c5 --- /dev/null +++ b/ui/cocoa/text.h @@ -0,0 +1,63 @@ +/* + * 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 + +@interface TextChangeMgr : NSObject { + 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); diff --git a/ui/cocoa/text.m b/ui/cocoa/text.m new file mode 100644 index 0000000..bc92f1a --- /dev/null +++ b/ui/cocoa/text.m @@ -0,0 +1,190 @@ +/* + * 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 +#import +#import + +#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]; +} + diff --git a/ui/cocoa/toolbar.h b/ui/cocoa/toolbar.h new file mode 100644 index 0000000..95fd967 --- /dev/null +++ b/ui/cocoa/toolbar.h @@ -0,0 +1,131 @@ +/* + * 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 + + +@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 { + 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 { + 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 { + NSMutableArray *allowedItems; + NSMutableArray *defaultItems; + NSMutableDictionary *items; +} + +- (UiToolbarDelegate*) init; + +- (void) addDefault:(NSString*)identifier; + +- (void) addItem: (NSString*) identifier + item: (NSObject*) 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); diff --git a/ui/cocoa/toolbar.m b/ui/cocoa/toolbar.m new file mode 100644 index 0000000..4a044ac --- /dev/null +++ b/ui/cocoa/toolbar.m @@ -0,0 +1,358 @@ +/* + * 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 +#import +#import +#import +#import + +#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*) 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; +} + diff --git a/ui/cocoa/toolkit.h b/ui/cocoa/toolkit.h new file mode 100644 index 0000000..a4c73b8 --- /dev/null +++ b/ui/cocoa/toolkit.h @@ -0,0 +1,88 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#include "../ui/toolkit.h" +#include "../common/context.h" +#include "../common/object.h" + + +@interface UiApplicationDelegate : NSObject { + +} + +- (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 + + diff --git a/ui/cocoa/toolkit.m b/ui/cocoa/toolkit.m new file mode 100644 index 0000000..c9f2a72 --- /dev/null +++ b/ui/cocoa/toolkit.m @@ -0,0 +1,346 @@ +/* + * 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 +#import +#import +#import +#import + +#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 + + diff --git a/ui/cocoa/tree.h b/ui/cocoa/tree.h new file mode 100644 index 0000000..66ee899 --- /dev/null +++ b/ui/cocoa/tree.h @@ -0,0 +1,47 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + #import "../ui/tree.h" + #import "toolkit.h" + + +@interface UiTableDataSource : NSObject { + 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); + diff --git a/ui/cocoa/tree.m b/ui/cocoa/tree.m new file mode 100644 index 0000000..63a6525 --- /dev/null +++ b/ui/cocoa/tree.m @@ -0,0 +1,246 @@ +/* + * 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 +#import + +#import "tree.h" +#import "container.h" +#import "window.h" +#import "../common/context.h" +#import + +@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;icolumns;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; +} diff --git a/ui/cocoa/window.h b/ui/cocoa/window.h new file mode 100644 index 0000000..d1cfe46 --- /dev/null +++ b/ui/cocoa/window.h @@ -0,0 +1,52 @@ +/* + * 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 +#import "../ui/window.h" +#import +#import + +#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 diff --git a/ui/cocoa/window.m b/ui/cocoa/window.m new file mode 100644 index 0000000..e7c5ecd --- /dev/null +++ b/ui/cocoa/window.m @@ -0,0 +1,220 @@ +/* + * 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 +#include +#include + +#import "window.h" +#import "menu.h" +#import "toolbar.h" +#import "container.h" +#import +#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;ictx = 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; +} diff --git a/ui/common/condvar.c b/ui/common/condvar.c new file mode 100644 index 0000000..8b405c9 --- /dev/null +++ b/ui/common/condvar.c @@ -0,0 +1,70 @@ +/* + * 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); + +} diff --git a/ui/common/condvar.h b/ui/common/condvar.h new file mode 100644 index 0000000..bf4b8e4 --- /dev/null +++ b/ui/common/condvar.h @@ -0,0 +1,53 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_CONDVAR_H +#define UIC_CONDVAR_H + +#include "../ui/toolkit.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiPosixCondVar { + UiCondVar var; + int set; + pthread_mutex_t lock; + pthread_cond_t cond; +} UiPosixCondVar; + + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_CONDVAR_H */ + diff --git a/ui/common/context.c b/ui/common/context.c new file mode 100644 index 0000000..3480620 --- /dev/null +++ b/ui/common/context.c @@ -0,0 +1,597 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "context.h" +#include "../ui/window.h" +#include "document.h" +#include "types.h" + + +static UiContext* global_context; + +void uic_init_global_context(void) { + 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;inumgroups;k++) { + if(groups[i] == gw->groups[k]) { + check[k] = 1; + } + } + } + + int enable = 1; + for(int i=0;inumgroups;i++) { + if(check[i] == 0) { + enable = 0; + break; + } + } + 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; +} diff --git a/ui/common/context.h b/ui/common/context.h new file mode 100644 index 0000000..fdcd6a8 --- /dev/null +++ b/ui/common/context.h @@ -0,0 +1,150 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_CONTEXT_H +#define UIC_CONTEXT_H + +#include "../ui/toolkit.h" +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiVar UiVar; +typedef struct UiListPtr UiListPtr; +typedef struct UiListVar UiListVar; +typedef struct UiGroupWidget UiGroupWidget; + +typedef enum UiVarType UiVarType; + +enum UiVarType { + UI_VAR_SPECIAL = 0, + UI_VAR_INTEGER, + UI_VAR_DOUBLE, + UI_VAR_STRING, + UI_VAR_TEXT, + UI_VAR_LIST, + UI_VAR_RANGE, + 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 */ + diff --git a/ui/common/document.c b/ui/common/document.c new file mode 100644 index 0000000..237b16f --- /dev/null +++ b/ui/common/document.c @@ -0,0 +1,123 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "document.h" + +#include +#include + + +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; + } +} diff --git a/ui/common/document.h b/ui/common/document.h new file mode 100644 index 0000000..0f439d5 --- /dev/null +++ b/ui/common/document.h @@ -0,0 +1,48 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_DOCUMENT_H +#define UIC_DOCUMENT_H + +#include "../ui/toolkit.h" +#include "context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void uic_docmgr_init(); +void uic_document_addvar(void *doc, char *name, int type, size_t vs); + + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_DOCUMENT_H */ + diff --git a/ui/common/menu.c b/ui/common/menu.c new file mode 100644 index 0000000..eb47d58 --- /dev/null +++ b/ui/common/menu.c @@ -0,0 +1,329 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "menu.h" + +#include +#include + +#include +#include + + +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); +} diff --git a/ui/common/menu.h b/ui/common/menu.h new file mode 100644 index 0000000..63b577d --- /dev/null +++ b/ui/common/menu.h @@ -0,0 +1,139 @@ +/* + * 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 + +#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 */ + diff --git a/ui/common/object.c b/ui/common/object.c new file mode 100644 index 0000000..5fd1130 --- /dev/null +++ b/ui/common/object.c @@ -0,0 +1,111 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "object.h" +#include "context.h" + +void ui_end(UiObject *obj) { + if(!obj->next) { + return; + } + + UiObject *prev = NULL; + while(obj->next) { + prev = obj; + obj = obj->next; + } + + if(prev) { + // TODO: free last obj + prev->next = NULL; + } +} + +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; +} diff --git a/ui/common/object.h b/ui/common/object.h new file mode 100644 index 0000000..bc8bd79 --- /dev/null +++ b/ui/common/object.h @@ -0,0 +1,53 @@ +/* + * 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 */ + diff --git a/ui/common/objs.mk b/ui/common/objs.mk new file mode 100644 index 0000000..b8980af --- /dev/null +++ b/ui/common/objs.mk @@ -0,0 +1,46 @@ +# +# 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) + diff --git a/ui/common/properties.c b/ui/common/properties.c new file mode 100644 index 0000000..45a9b4e --- /dev/null +++ b/ui/common/properties.c @@ -0,0 +1,323 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include "../ui/toolkit.h" + + +#include "properties.h" +#include +#include + +#include +#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); +} diff --git a/ui/common/properties.h b/ui/common/properties.h new file mode 100644 index 0000000..6e11345 --- /dev/null +++ b/ui/common/properties.h @@ -0,0 +1,57 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_PROPERTIES_H +#define UIC_PROPERTIES_H + +#include "../ui/properties.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +#define UI_HOME "USERPROFILE" +#else +#define UI_HOME "HOME" +#endif + +void uic_load_app_properties(); +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 */ + diff --git a/ui/common/threadpool.c b/ui/common/threadpool.c new file mode 100644 index 0000000..71adb9f --- /dev/null +++ b/ui/common/threadpool.c @@ -0,0 +1,193 @@ +/* + * 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 + +#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;imin_threads;i++) { + pthread_t t; + if (pthread_create(&t, NULL, threadpool_func, pool) != 0) { + fprintf(stderr, "uic: threadpool_start: pthread_create failed: %s", strerror(errno)); + return 1; + } + } + return 0; +} + +void* threadpool_func(void *data) { + UiThreadpool *pool = (UiThreadpool*)data; + + for(;;) { + threadpool_job *job = threadpool_get_job(pool); + if(job == &kill_job) { + break; + } + + job->callback(job->data); + + free(job); + } + return NULL; +} + +threadpool_job* threadpool_get_job(UiThreadpool *pool) { + pthread_mutex_lock(&pool->queue_lock); + + threadpool_job *job = NULL; + pool->num_idle++; + while(job == NULL) { + if(pool->queue_len == 0) { + pthread_cond_wait(&pool->available, &pool->queue_lock); + continue; + } else { + pool_queue_t *q = pool->queue; + job = q->job; + pool->queue = q->next; + pool->queue_len--; + free(q); + } + } + pool->num_idle--; + + pthread_mutex_unlock(&pool->queue_lock); + return job; +} + +void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data) { + // TODO: handle errors + + threadpool_job *job = malloc(sizeof(threadpool_job)); + job->callback = func; + job->data = data; + + pthread_mutex_lock(&pool->queue_lock); + threadpool_enqueue_job(pool, job); + + int create_thread = 0; + int destroy_thread = 0; + int diff = pool->queue_len - pool->num_idle; + + //if(pool->queue_len == 1) { + pthread_cond_signal(&pool->available); + //} + + pthread_mutex_unlock(&pool->queue_lock); +} + +void threadpool_enqueue_job(UiThreadpool *pool, threadpool_job *job) { + pool_queue_t *q = malloc(sizeof(pool_queue_t)); + q->job = job; + q->next = NULL; + + if(pool->queue == NULL) { + pool->queue = q; + } else { + pool_queue_t *last_elem = pool->queue; + while(last_elem->next != NULL) { + last_elem = last_elem->next; + } + last_elem->next = q; + } + pool->queue_len++; +} + + + + + + +UiThreadpool* ui_threadpool_create(int nthreads) { + UiThreadpool *pool = threadpool_new(nthreads, nthreads); + threadpool_start(pool); // TODO: check return value + return pool; +} + +void ui_threadpool_destroy(UiThreadpool* pool) { + +} + +static int ui_threadpool_job_finish(void *data) { + UiJob *job = data; + UiEvent event; + event.obj = job->obj; + event.window = job->obj->window; + event.document = job->obj->ctx->document; + event.intval = 0; + event.eventdata = NULL; + job->finish_callback(&event, job->finish_data); + free(job); + return 0; +} + +static void* ui_threadpool_job_func(void *data) { + UiJob *job = data; + if (!job->job_func(job->job_data) && job->finish_callback) { + ui_call_mainthread(ui_threadpool_job_finish, job); + } else { + free(job); + } + return NULL; +} + +void ui_threadpool_job(UiThreadpool* pool, UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd) { + UiJob* job = malloc(sizeof(UiJob)); + job->obj = obj; + job->job_func = tf; + job->job_data = td; + job->finish_callback = f; + job->finish_data = fd; + threadpool_run(pool, ui_threadpool_job_func, job); +} + + +#endif + diff --git a/ui/common/threadpool.h b/ui/common/threadpool.h new file mode 100644 index 0000000..2ab5e35 --- /dev/null +++ b/ui/common/threadpool.h @@ -0,0 +1,85 @@ +/* + * 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 */ + diff --git a/ui/common/toolbar.c b/ui/common/toolbar.c new file mode 100644 index 0000000..0d86f17 --- /dev/null +++ b/ui/common/toolbar.c @@ -0,0 +1,148 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "toolbar.h" +#include "menu.h" + +#include + +static CxMap* toolbar_items; + +static CxList* toolbar_defaults[3]; // 0: left 1: center 2: right + +static UiToolbarMenuItem* ui_appmenu; + + +void uic_toolbar_init(void) { + toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + toolbar_defaults[0] = cxLinkedListCreateSimple(CX_STORE_POINTERS); + toolbar_defaults[1] = cxLinkedListCreateSimple(CX_STORE_POINTERS); + toolbar_defaults[2] = cxLinkedListCreateSimple(CX_STORE_POINTERS); +} + +static char* nl_strdup(const char* str) { + return str ? strdup(str) : NULL; +} + +static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs args, size_t *ngroups) { + UiToolbarItemArgs newargs; + newargs.label = nl_strdup(args.label); + newargs.stockid = nl_strdup(args.stockid); + newargs.icon = nl_strdup(args.icon); + newargs.onclick = args.onclick; + newargs.onclickdata = args.onclickdata; + newargs.groups = uic_copy_groups(args.groups, ngroups); + return newargs; +} + +void ui_toolbar_item_create(const char* name, UiToolbarItemArgs args) { + UiToolbarItem* item = malloc(sizeof(UiToolbarItem)); + item->item.type = UI_TOOLBAR_ITEM; + item->args = itemargs_copy(args, &item->ngroups); + cxMapPut(toolbar_items, name, item); +} + + +static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs args, size_t *ngroups) { + UiToolbarToggleItemArgs newargs; + newargs.label = nl_strdup(args.label); + newargs.stockid = nl_strdup(args.stockid); + newargs.icon = nl_strdup(args.icon); + newargs.varname = nl_strdup(args.varname); + newargs.onchange = args.onchange; + newargs.onchangedata = args.onchangedata; + newargs.groups = uic_copy_groups(args.groups, ngroups); + return newargs; +} + +void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs args) { + UiToolbarToggleItem* item = malloc(sizeof(UiToolbarToggleItem)); + item->item.type = UI_TOOLBAR_TOGGLEITEM; + item->args = toggleitemargs_copy(args, &item->ngroups); + cxMapPut(toolbar_items, name, item); +} + +static UiToolbarMenuArgs menuargs_copy(UiToolbarMenuArgs args) { + UiToolbarMenuArgs newargs; + newargs.label = nl_strdup(args.label); + newargs.stockid = nl_strdup(args.stockid); + newargs.icon = nl_strdup(args.icon); + return newargs; +} + +UIEXPORT void ui_toolbar_menu_create(const char* name, UiToolbarMenuArgs args) { + UiToolbarMenuItem* item = malloc(sizeof(UiToolbarMenuItem)); + item->item.type = UI_TOOLBAR_MENU; + memset(&item->menu, 0, sizeof(UiMenu)); + item->args = menuargs_copy(args); + + item->end = 0; + + if (!name) { + // special appmenu + ui_appmenu = item; + } else { + // toplevel menu + cxMapPut(toolbar_items, name, item); + } + + uic_add_menu_to_stack(&item->menu); +} + + +CxMap* uic_get_toolbar_items(void) { + return toolbar_items; +} + +CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos) { + if (pos >= 0 && pos < 3) { + return toolbar_defaults[pos]; + } + return NULL; +} + +void ui_toolbar_add_default(const char* name, enum UiToolbarPos pos) { + char* cp = strdup(name); + if (pos >= 0 && pos < 3) { + cxListAdd(toolbar_defaults[pos], cp); + } else { + // TODO: error + } +} + +UiBool uic_toolbar_isenabled(void) { + return cxListSize(toolbar_defaults[0]) + cxListSize(toolbar_defaults[1]) + cxListSize(toolbar_defaults[2]) > 0; +} + +UiToolbarItemI* uic_toolbar_get_item(const char* name) { + return cxMapGet(toolbar_items, name); +} + +UiToolbarMenuItem* uic_get_appmenu(void) { + return ui_appmenu; +} diff --git a/ui/common/toolbar.h b/ui/common/toolbar.h new file mode 100644 index 0000000..a13828e --- /dev/null +++ b/ui/common/toolbar.h @@ -0,0 +1,98 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_TOOLBAR_H +#define UIC_TOOLBAR_H + +#include "../ui/toolbar.h" + +#include +#include + +#include "menu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiToolbarItemI UiToolbarItemI; + +typedef struct UiToolbarItem UiToolbarItem; +typedef struct UiToolbarToggleItem UiToolbarToggleItem; + +typedef struct UiToolbarMenuItem UiToolbarMenuItem; + +enum UiToolbarItemType { + UI_TOOLBAR_ITEM = 0, + UI_TOOLBAR_TOGGLEITEM, + UI_TOOLBAR_MENU +}; + +typedef enum UiToolbarItemType UiToolbarItemType; + +struct UiToolbarItemI { + UiToolbarItemType type; +}; + +struct UiToolbarItem { + UiToolbarItemI item; + UiToolbarItemArgs args; + size_t ngroups; +}; + +struct UiToolbarToggleItem { + UiToolbarItemI item; + UiToolbarToggleItemArgs args; + size_t ngroups; +}; + +struct UiToolbarMenuItem { + UiToolbarItemI item; + UiMenu menu; + UiToolbarMenuArgs args; + int end; +}; + + +void uic_toolbar_init(void); + +CxMap* uic_get_toolbar_items(void); +CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos); + +UiBool uic_toolbar_isenabled(void); + +UiToolbarItemI* uic_toolbar_get_item(const char* name); + +UiToolbarMenuItem* uic_get_appmenu(void); + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_TOOLBAR_H */ + diff --git a/ui/common/types.c b/ui/common/types.c new file mode 100644 index 0000000..cbea616 --- /dev/null +++ b/ui/common/types.c @@ -0,0 +1,579 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include +#include +#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); +} diff --git a/ui/common/types.h b/ui/common/types.h new file mode 100644 index 0000000..677199f --- /dev/null +++ b/ui/common/types.h @@ -0,0 +1,69 @@ +/* + * 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 */ + diff --git a/ui/common/ucx_properties.c b/ui/common/ucx_properties.c new file mode 100644 index 0000000..b9374aa --- /dev/null +++ b/ui/common/ucx_properties.c @@ -0,0 +1,263 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx_properties.h" + +#include +#include +#include + +UcxProperties *ucx_properties_new() { + UcxProperties *parser = (UcxProperties*)malloc( + sizeof(UcxProperties)); + if(!parser) { + return NULL; + } + + parser->buffer = NULL; + parser->buflen = 0; + parser->pos = 0; + parser->tmp = NULL; + parser->tmplen = 0; + parser->tmpcap = 0; + parser->error = 0; + parser->delimiter = '='; + parser->comment1 = '#'; + parser->comment2 = 0; + parser->comment3 = 0; + + return parser; +} + +void ucx_properties_free(UcxProperties *parser) { + if(parser->tmp) { + free(parser->tmp); + } + free(parser); +} + +void ucx_properties_fill(UcxProperties *parser, char *buf, size_t len) { + parser->buffer = buf; + parser->buflen = len; + parser->pos = 0; +} + +static void parser_tmp_append(UcxProperties *parser, char *buf, size_t len) { + if(parser->tmpcap - parser->tmplen < len) { + size_t newcap = parser->tmpcap + len + 64; + parser->tmp = (char*)realloc(parser->tmp, newcap); + parser->tmpcap = newcap; + } + memcpy(parser->tmp + parser->tmplen, buf, len); + parser->tmplen += len; +} + +int ucx_properties_next(UcxProperties *parser, cxstring *name, cxstring *value) { + if(parser->tmplen > 0) { + char *buf = parser->buffer + parser->pos; + size_t len = parser->buflen - parser->pos; + cxstring str = cx_strn(buf, len); + cxstring nl = cx_strchr(str, '\n'); + if(nl.ptr) { + size_t newlen = (size_t)(nl.ptr - buf) + 1; + parser_tmp_append(parser, buf, newlen); + // the tmp buffer contains exactly one line now + + char *orig_buf = parser->buffer; + size_t orig_len = parser->buflen; + + parser->buffer = parser->tmp; + parser->buflen = parser->tmplen; + parser->pos = 0; + parser->tmp = NULL; + parser->tmpcap = 0; + parser->tmplen = 0; + // run ucx_properties_next with the tmp buffer as main buffer + int ret = ucx_properties_next(parser, name, value); + + // restore original buffer + parser->tmp = parser->buffer; + parser->buffer = orig_buf; + parser->buflen = orig_len; + parser->pos = newlen; + + /* + * if ret == 0 the tmp buffer contained just space or a comment + * we parse again with the original buffer to get a name/value + * or a new tmp buffer + */ + return ret ? ret : ucx_properties_next(parser, name, value); + } else { + parser_tmp_append(parser, buf, len); + return 0; + } + } else if(parser->tmp) { + free(parser->tmp); + parser->tmp = NULL; + } + + char comment1 = parser->comment1; + char comment2 = parser->comment2; + char comment3 = parser->comment3; + char delimiter = parser->delimiter; + + // get one line and parse it + while(parser->pos < parser->buflen) { + char *buf = parser->buffer + parser->pos; + size_t len = parser->buflen - parser->pos; + + /* + * First we check if we have at least one line. We also get indices of + * delimiter and comment chars + */ + size_t delimiter_index = 0; + size_t comment_index = 0; + int has_comment = 0; + + size_t i = 0; + char c = 0; + for(;itmpcap = len + 128; + parser->tmp = (char*)malloc(parser->tmpcap); + parser->tmplen = len; + memcpy(parser->tmp, buf, len); + return 0; + } + + cxstring line = has_comment ? cx_strn(buf, comment_index) : cx_strn(buf, i); + // check line + if(delimiter_index == 0) { + line = cx_strtrim(line); + if(line.length != 0) { + parser->error = 1; + } + } else { + cxstring n = cx_strn(buf, delimiter_index); + cxstring v = cx_strn( + buf + delimiter_index + 1, + line.length - delimiter_index - 1); + n = cx_strtrim(n); + v = cx_strtrim(v); + if(n.length != 0 || v.length != 0) { + *name = n; + *value = v; + parser->pos += i + 1; + return 1; + } else { + parser->error = 1; + } + } + + parser->pos += i + 1; + } + + return 0; +} + +int ucx_properties2map(UcxProperties *parser, CxMap *map) { + cxstring name; + cxstring value; + while(ucx_properties_next(parser, &name, &value)) { + cxmutstr mutvalue = cx_strdup_a(map->collection.allocator, value); + if(!mutvalue.ptr) { + return 1; + } + if(cxMapPut(map, cx_hash_key_cxstr(name), mutvalue.ptr)) { + cxFree(map->collection.allocator, mutvalue.ptr); + return 1; + } + } + if (parser->error) { + return parser->error; + } else { + return 0; + } +} + +// buffer size is documented - change doc, when you change bufsize! +#define UCX_PROPLOAD_BUFSIZE 1024 +int ucx_properties_load(CxMap *map, FILE *file) { + UcxProperties *parser = ucx_properties_new(); + if(!(parser && map && file)) { + return 1; + } + + int error = 0; + size_t r; + char buf[UCX_PROPLOAD_BUFSIZE]; + while((r = fread(buf, 1, UCX_PROPLOAD_BUFSIZE, file)) != 0) { + ucx_properties_fill(parser, buf, r); + error = ucx_properties2map(parser, map); + if (error) { + break; + } + } + ucx_properties_free(parser); + return error; +} + +int ucx_properties_store(CxMap *map, FILE *file) { + CxIterator iter = cxMapIterator(map); + cxstring value; + size_t written; + + cx_foreach(CxMapEntry *, v, iter) { + value = cx_str(v->value); + + written = 0; + written += fwrite(v->key->data, 1, v->key->len, file); + written += fwrite(" = ", 1, 3, file); + written += fwrite(value.ptr, 1, value.length, file); + written += fwrite("\n", 1, 1, file); + + if (written != v->key->len + value.length + 4) { + return 1; + } + } + + return 0; +} + diff --git a/ui/common/ucx_properties.h b/ui/common/ucx_properties.h new file mode 100644 index 0000000..cfb4359 --- /dev/null +++ b/ui/common/ucx_properties.h @@ -0,0 +1,223 @@ +/* + * 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 +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UcxProperties object for parsing properties data. + * Most of the fields are for internal use only. You may configure the + * properties parser, e.g. by changing the used delimiter or specifying + * up to three different characters that shall introduce comments. + */ +typedef struct { + /** + * Input buffer (don't set manually). + * Automatically set by calls to ucx_properties_fill(). + */ + char *buffer; + + /** + * Length of the input buffer (don't set manually). + * Automatically set by calls to ucx_properties_fill(). + */ + size_t buflen; + + /** + * Current buffer position (don't set manually). + * Used by ucx_properties_next(). + */ + size_t pos; + + /** + * Internal temporary buffer (don't set manually). + * Used by ucx_properties_next(). + */ + char *tmp; + + /** + * Internal temporary buffer length (don't set manually). + * Used by ucx_properties_next(). + */ + size_t tmplen; + + /** + * Internal temporary buffer capacity (don't set manually). + * Used by ucx_properties_next(). + */ + size_t tmpcap; + + /** + * Parser error code. + * This is always 0 on success and a nonzero value on syntax errors. + * The value is set by ucx_properties_next(). + */ + int error; + + /** + * The delimiter that shall be used. + * This is '=' by default. + */ + char delimiter; + + /** + * The first comment character. + * This is '#' by default. + */ + char comment1; + + /** + * The second comment character. + * This is not set by default. + */ + char comment2; + + /** + * The third comment character. + * This is not set by default. + */ + char comment3; +} UcxProperties; + + +/** + * Constructs a new UcxProperties object. + * @return a pointer to the new UcxProperties object + */ +UcxProperties *ucx_properties_new(); + +/** + * Destroys a UcxProperties object. + * @param prop the UcxProperties object to destroy + */ +void ucx_properties_free(UcxProperties *prop); + +/** + * Sets the input buffer for the properties parser. + * + * After calling this function, you may parse the data by calling + * ucx_properties_next() until it returns 0. The function ucx_properties2map() + * is a convenience function that reads as much data as possible by using this + * function. + * + * + * @param prop the UcxProperties object + * @param buf a pointer to the new buffer + * @param len the payload length of the buffer + * @see ucx_properties_next() + * @see ucx_properties2map() + */ +void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len); + +/** + * Retrieves the next key/value-pair. + * + * This function returns a nonzero value as long as there are key/value-pairs + * found. If no more key/value-pairs are found, you may refill the input buffer + * with ucx_properties_fill(). + * + * Attention: the 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 FILE* stream to read from + * @return 0 on success, or a non-zero value on error + * + * @see ucx_properties_fill() + * @see ucx_properties2map() + */ +int ucx_properties_load(CxMap *map, FILE *file); + +/** + * Stores a UcxMap to a file. + * + * The key/value-pairs are written by using the following format: + * + * [key] = [value]\\n + * + * @param map the map to store + * @param file the FILE* stream to write to + * @return 0 on success, or a non-zero value on error + */ +int ucx_properties_store(CxMap *map, FILE *file); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_PROPERTIES_H */ + diff --git a/ui/gtk/Makefile b/ui/gtk/Makefile new file mode 100644 index 0000000..aba263f --- /dev/null +++ b/ui/gtk/Makefile @@ -0,0 +1,34 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2012 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +$(GTK_OBJPRE)%.o: gtk/%.c + $(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $< + +$(UI_LIB): $(OBJ) + $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ) + diff --git a/ui/gtk/button.c b/ui/gtk/button.c new file mode 100644 index 0000000..adbbc43 --- /dev/null +++ b/ui/gtk/button.c @@ -0,0 +1,579 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "button.h" +#include "container.h" +#include +#include "../common/context.h" +#include "../common/object.h" + +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 diff --git a/ui/gtk/button.h b/ui/gtk/button.h new file mode 100644 index 0000000..c855b35 --- /dev/null +++ b/ui/gtk/button.h @@ -0,0 +1,97 @@ +/* + * 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 */ + diff --git a/ui/gtk/container.c b/ui/gtk/container.c new file mode 100644 index 0000000..36e46cd --- /dev/null +++ b/ui/gtk/container.c @@ -0,0 +1,962 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "container.h" +#include "toolkit.h" +#include "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;pvalue = p; + break; + } + } + return i->value; +} + +void ui_stack_set(UiInteger *i, int64_t value) { + GtkWidget *widget = i->obj; + if(stack_set_page(widget, value) >= 0) { + i->value = value; + } +} +#elif GTK_MAJOR_VERSION >= 3 +static GtkWidget* stack_get_child(GtkWidget *stack, int index) { + GList *children = gtk_container_get_children(GTK_CONTAINER(stack)); + if(children) { + return g_list_nth_data(children, index); + } + 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; +} + diff --git a/ui/gtk/container.h b/ui/gtk/container.h new file mode 100644 index 0000000..0a06a9c --- /dev/null +++ b/ui/gtk/container.h @@ -0,0 +1,194 @@ +/* + * 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 + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout)) +#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE) +#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE) + +typedef void (*ui_container_add_f)(UiContainer*, GtkWidget*, UiBool); + +typedef struct UiDocumentView UiDocumentView; + +typedef struct UiLayout UiLayout; +typedef enum UiLayoutBool UiLayoutBool; + +enum UiLayoutBool { + UI_LAYOUT_UNDEFINED = 0, + UI_LAYOUT_TRUE, + UI_LAYOUT_FALSE, +}; + +struct UiLayout { + UiLayoutBool fill; + UiBool newline; + char *label; + UiBool hexpand; + UiBool vexpand; + 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 */ + diff --git a/ui/gtk/display.c b/ui/gtk/display.c new file mode 100644 index 0000000..78ff754 --- /dev/null +++ b/ui/gtk/display.c @@ -0,0 +1,265 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "display.h" +#include "container.h" +#include "../common/context.h" +#include "../common/object.h" +#include "../ui/display.h" + +#include + +static void set_alignment(GtkWidget *widget, float xalign, float yalign) { +#if GTK_MAJOR_VERSION >= 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("%s", args.label); + markup = m.ptr; + args.label = NULL; + } + case UI_LABEL_STYLE_SUBTITLE: { + break; + } + case UI_LABEL_STYLE_DIM: { + break; + } + } +# else + switch(args.style) { + case UI_LABEL_STYLE_DEFAULT: break; + case UI_LABEL_STYLE_TITLE: { + css_class = "ui_label_title"; + break; + } + case UI_LABEL_STYLE_SUBTITLE: { + css_class = "subtitle"; + break; + } + case UI_LABEL_STYLE_DIM: { + css_class = "dim-label"; + break; + } + } +# endif + } + + + GtkWidget *widget = gtk_label_new(args.label); + if(markup) { + gtk_label_set_markup(GTK_LABEL(widget), markup); + free(markup); + } + + if(css_class) { + WIDGET_ADD_CSS_CLASS(widget, css_class); + } + + switch(args.align) { + case UI_ALIGN_DEFAULT: break; + case UI_ALIGN_LEFT: set_alignment(widget, 0, .5); break; + case UI_ALIGN_RIGHT: set_alignment(widget, 1, .5); break; + case UI_ALIGN_CENTER: break; // TODO + } + + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING); + if(var) { + UiString* value = (UiString*)var->value; + value->obj = widget; + value->get = ui_label_get; + value->set = ui_label_set; + } + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, widget, FALSE); + + return widget; +} + +UIWIDGET ui_llabel_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); + } + } +} diff --git a/ui/gtk/display.h b/ui/gtk/display.h new file mode 100644 index 0000000..504c7ad --- /dev/null +++ b/ui/gtk/display.h @@ -0,0 +1,53 @@ +/* + * 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 */ + diff --git a/ui/gtk/dnd.c b/ui/gtk/dnd.c new file mode 100644 index 0000000..e51c981 --- /dev/null +++ b/ui/gtk/dnd.c @@ -0,0 +1,295 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "dnd.h" +#include +#include + +#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;idata, uriarray); +} + +char* ui_selection_gettext(UiSelection *sel) { + guchar *text = gtk_selection_data_get_text(sel->data); + if(text) { + char *textcp = strdup((char*)text); + g_free(text); + return textcp; + } + return NULL; +} + +char** ui_selection_geturis(UiSelection *sel, size_t *nelm) { + gchar **uris = gtk_selection_data_get_uris(sel->data); + if(uris) { + size_t al = 32; + char **array = malloc(al * sizeof(char*)); + size_t i = 0; + while(uris[i] != NULL) { + if(i >= al) { + al *= 2; + array = realloc(array, al * sizeof(char*)); + } + array[i] = strdup((char*)uris[i]); + i++; + } + *nelm = i; + g_strfreev(uris); + return array; + } + return NULL; +} +*/ + +#if GTK_MAJOR_VERSION >= 4 + +void ui_selection_settext(UiDnD *sel, char *str, int len) { + if(!sel->providers) { + return; + } + + if(len == -1) { + len = strlen(str); + } + GBytes *bytes = g_bytes_new(str, len); + GdkContentProvider *provider = gdk_content_provider_new_for_bytes("text/plain;charset=utf-8", bytes); + g_bytes_unref(bytes); + + cxListAdd(sel->providers, &provider); +} + +void ui_selection_seturis(UiDnD *sel, char **uris, int nelm) { + if(!sel->providers) { + return; + } + + GFile **files = calloc(nelm, sizeof(GFile*)); + for(int i=0;iproviders, &provider); + + g_slist_free_full ((GSList*)list, g_object_unref); + free(files); +} + +char* ui_selection_gettext(UiDnD *sel) { + if(!sel->value) { + return NULL; + } + + if(G_VALUE_HOLDS(sel->value, G_TYPE_STRING)) { + const char *str = g_value_get_string(sel->value); + return str ? strdup(str) : NULL; + } + + return NULL; +} + +UiFileList ui_selection_geturis(UiDnD *sel) { + if(!sel->value) { + return (UiFileList){NULL,0}; + } + + if(G_VALUE_HOLDS(sel->value, GDK_TYPE_FILE_LIST)) { + GSList *list = g_value_get_boxed(sel->value); + if(!list) { + return (UiFileList){NULL,0}; + } + guint size = g_slist_length(list); + + UiFileList flist; + flist.nfiles = size; + flist.files = calloc(size, sizeof(char*)); + int i=0; + while(list) { + GFile *file = list->data; + char *uri = g_file_get_uri(file); + flist.files[i++] = strdup(uri); + g_free(uri); + list = list->next; + } + return flist; + } + return (UiFileList){NULL,0}; +} + + +UiDnD* ui_create_dnd(void) { + UiDnD *dnd = malloc(sizeof(UiDnD)); + memset(dnd, 0, sizeof(UiDnD)); + dnd->providers = cxArrayListCreateSimple(sizeof(void*), 16); + dnd->selected_action = 0; + dnd->delete = FALSE; + return dnd; +} + +void ui_dnd_free(UiDnD *dnd) { + 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;idata, uriarray); + free(uriarray); +} + +char* ui_selection_gettext(UiDnD *sel) { + if(!sel->data) { + return NULL; + } + + guchar *text = gtk_selection_data_get_text(sel->data); + if(text) { + char *textcp = strdup((char*)text); + g_free(text); + return textcp; + } + return NULL; +} + +UiFileList ui_selection_geturis(UiDnD *sel) { + if(!sel->data) { + return (UiFileList){NULL,0}; + } + + gchar **uris = gtk_selection_data_get_uris(sel->data); + if(uris) { + size_t al = 32; + char **array = malloc(al * sizeof(char*)); + size_t i = 0; + while(uris[i] != NULL) { + if(i >= al) { + al *= 2; + array = realloc(array, al * sizeof(char*)); + } + array[i] = strdup((char*)uris[i]); + i++; + } + g_strfreev(uris); + return (UiFileList){array,i}; + } + + return (UiFileList){NULL,0}; +} + +UiDnDAction ui_dnd_result(UiDnD *dnd) { + switch(dnd->selected_action) { + case 0: return UI_DND_ACTION_NONE; + case GDK_ACTION_COPY: return UI_DND_ACTION_COPY; + case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE; + case GDK_ACTION_LINK: return UI_DND_ACTION_LINK; + default: break; + } + return UI_DND_ACTION_CUSTOM; +} + + +UiDnD* ui_create_dnd(void) { + UiDnD *dnd = malloc(sizeof(UiDnD)); + memset(dnd, 0, sizeof(UiDnD)); + return dnd; +} + +void ui_dnd_free(UiDnD *dnd) { + free(dnd); +} + +#endif + +UiBool ui_dnd_need_delete(UiDnD *dnd) { + return dnd->delete; +} + +void ui_dnd_accept(UiDnD *dnd, UiBool accept) { + dnd->accept = accept; +} + diff --git a/ui/gtk/dnd.h b/ui/gtk/dnd.h new file mode 100644 index 0000000..e245f99 --- /dev/null +++ b/ui/gtk/dnd.h @@ -0,0 +1,72 @@ +/* + * 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 + +#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 */ + diff --git a/ui/gtk/draw_cairo.c b/ui/gtk/draw_cairo.c new file mode 100644 index 0000000..fc85c32 --- /dev/null +++ b/ui/gtk/draw_cairo.c @@ -0,0 +1,149 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#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); +} + diff --git a/ui/gtk/draw_cairo.h b/ui/gtk/draw_cairo.h new file mode 100644 index 0000000..6be96a7 --- /dev/null +++ b/ui/gtk/draw_cairo.h @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DRAW_CAIRO_H +#define DRAW_CAIRO_H + +#include "graphics.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiCairoGraphics { + UiGraphics g; + GtkWidget *widget; + cairo_t *cr; +} UiCairoGraphics; + +// ui_canvas_expose + +#ifdef __cplusplus +} +#endif + +#endif /* DRAW_CAIRO_H */ + diff --git a/ui/gtk/draw_gdk.c b/ui/gtk/draw_gdk.c new file mode 100644 index 0000000..5e43ed7 --- /dev/null +++ b/ui/gtk/draw_gdk.c @@ -0,0 +1,93 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "container.h" + +#include "draw_gdk.h" + + +gboolean ui_drawingarea_expose(GtkWidget *w, GdkEventExpose *e, void *data) { + UiGdkGraphics g; + g.g.width = w->allocation.width; + g.g.height = w->allocation.height; + g.widget = w; + g.gc = gdk_gc_new(w->window); + + UiDrawEvent *event = data; + UiEvent ev; + ev.obj = event->obj; + ev.window = event->obj->window; + ev.document = event->obj->ctx->document; + + event->callback(&ev, &g.g, event->userdata); + + return FALSE; +} + +// function from graphics.h + +void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) { + g_signal_connect(G_OBJECT(widget), + "expose_event", + G_CALLBACK(ui_drawingarea_expose), + event); +} + +PangoContext *ui_get_pango_context(UiGraphics *g) { + UiGdkGraphics *gr = (UiGdkGraphics*)g; + return gtk_widget_get_pango_context(gr->widget); +} + +// drawing functions +void ui_graphics_color(UiGraphics *g, int red, int green, int blue) { + UiGdkGraphics *gr = (UiGdkGraphics*)g; + GdkColor color; + color.red = red * 257; + color.green = green * 257; + color.blue = blue * 257; + gdk_gc_set_rgb_fg_color(gr->gc, &color); + //gdk_gc_set_rgb_bg_color(g->gc, &color); +} + +void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) { + UiGdkGraphics *gr = (UiGdkGraphics*)g; + gdk_draw_line(gr->widget->window, gr->gc, x1, y1, x2, y2); +} + +void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) { + UiGdkGraphics *gr = (UiGdkGraphics*)g; + gdk_draw_rectangle(gr->widget->window, gr->gc, fill, x, y, w, h); +} + +void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) { + UiGdkGraphics *gr = (UiGdkGraphics*)g; + gdk_draw_layout(gr->widget->window, gr->gc, x, y, text->layout); +} diff --git a/ui/gtk/draw_gdk.h b/ui/gtk/draw_gdk.h new file mode 100644 index 0000000..f4aa821 --- /dev/null +++ b/ui/gtk/draw_gdk.h @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DRAW_GDK_H +#define DRAW_GDK_H + +#include "graphics.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiGdkGraphics { + UiGraphics g; + GtkWidget *widget; + GdkGC *gc; +} UiGdkGraphics; + +gboolean ui_canvas_expose(GtkWidget *w, GdkEventExpose *e, void *data); + +#ifdef __cplusplus +} +#endif + +#endif /* DRAW_GDK_H */ + diff --git a/ui/gtk/entry.c b/ui/gtk/entry.c new file mode 100644 index 0000000..38ebe17 --- /dev/null +++ b/ui/gtk/entry.c @@ -0,0 +1,205 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "../common/context.h" +#include "../common/object.h" +#include "container.h" +#include "entry.h" + + +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; +} diff --git a/ui/gtk/entry.h b/ui/gtk/entry.h new file mode 100644 index 0000000..ae43b0f --- /dev/null +++ b/ui/gtk/entry.h @@ -0,0 +1,44 @@ +/* + * 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 */ + diff --git a/ui/gtk/graphics.c b/ui/gtk/graphics.c new file mode 100644 index 0000000..dcaff88 --- /dev/null +++ b/ui/gtk/graphics.c @@ -0,0 +1,168 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "graphics.h" +#include "container.h" +#include "../common/object.h" + +UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) { + GtkWidget *widget = gtk_drawing_area_new(); + + if(f) { + UiDrawEvent *event = malloc(sizeof(UiDrawEvent)); + event->obj = obj; + event->callback = f; + event->userdata = userdata; + ui_connect_draw_handler(widget, event); + } + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, widget, TRUE); + + return widget; +} + + +#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); +} diff --git a/ui/gtk/graphics.h b/ui/gtk/graphics.h new file mode 100644 index 0000000..67616bd --- /dev/null +++ b/ui/gtk/graphics.h @@ -0,0 +1,58 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DRAWINGAREA_H +#define DRAWINGAREA_H + +#include "../ui/graphics.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiDrawEvent { + ui_drawfunc callback; + UiObject *obj; + void *userdata; +} UiDrawEvent; + +struct UiTextLayout { + PangoLayout *layout; +}; + +// implemented in draw_*.h +void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event); +PangoContext *ui_get_pango_context(UiGraphics *g); + +#ifdef __cplusplus +} +#endif + +#endif /* DRAWINGAREA_H */ + diff --git a/ui/gtk/headerbar.c b/ui/gtk/headerbar.c new file mode 100644 index 0000000..e1b80b5 --- /dev/null +++ b/ui/gtk/headerbar.c @@ -0,0 +1,174 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "headerbar.h" + +#include "button.h" +#include "menu.h" + +#if GTK_CHECK_VERSION(3, 10, 0) + +void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar) { + CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT); + CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER); + CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT); + + ui_headerbar_add_items(obj, headerbar, left_defaults, UI_TOOLBAR_LEFT); + ui_headerbar_add_items(obj, headerbar, center_defaults, UI_TOOLBAR_CENTER); + + UiToolbarMenuItem *appmenu = uic_get_appmenu(); + if(appmenu) { + ui_add_headerbar_menu(headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT); + } + ui_headerbar_add_items(obj, headerbar, right_defaults, UI_TOOLBAR_RIGHT); +} + +static void create_item(UiObject *obj, GtkWidget *headerbar, GtkWidget *box, UiToolbarItemI *i, enum UiToolbarPos pos) { + switch(i->type) { + case UI_TOOLBAR_ITEM: { + ui_add_headerbar_item(headerbar, box, (UiToolbarItem*)i, obj, pos); + break; + } + case UI_TOOLBAR_TOGGLEITEM: { + ui_add_headerbar_toggleitem(headerbar, box, (UiToolbarToggleItem*)i, obj, pos); + break; + } + case UI_TOOLBAR_MENU: { + ui_add_headerbar_menu(headerbar, box, (UiToolbarMenuItem*)i, obj, pos); + break; + } + default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type); + } +} + +static void headerbar_add(GtkWidget *headerbar, GtkWidget *box, GtkWidget *item, enum UiToolbarPos pos) { + switch(pos) { + case UI_TOOLBAR_LEFT: { + UI_HEADERBAR_PACK_START(headerbar, item); + break; + } + case UI_TOOLBAR_CENTER: { + +#if GTK_MAJOR_VERSION >= 4 + gtk_box_append(GTK_BOX(box), item); +#else + gtk_box_pack_start(GTK_BOX(box), item, 0, 0, 0); +#endif + break; + } + case UI_TOOLBAR_RIGHT: { + UI_HEADERBAR_PACK_END(headerbar, item); + break; + } + } +} + +void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos) { + GtkWidget *box = NULL; + + if(pos == UI_TOOLBAR_CENTER && cxListSize(items) > 0) { + box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + UI_HEADERBAR_SET_TITLE_WIDGET(headerbar, box); + } + + CxIterator i = pos == UI_TOOLBAR_RIGHT ? cxListBackwardsIterator(items) : cxListIterator(items); + cx_foreach(char*, def, i) { + UiToolbarItemI* item = uic_toolbar_get_item(def); + if (!item) { + fprintf(stderr, "unknown toolbar item: %s\n", def); + continue; + } + create_item(obj, headerbar, box, item, pos); + } +} + +void ui_add_headerbar_item( + GtkWidget *headerbar, + GtkWidget *box, + UiToolbarItem *item, + UiObject *obj, + enum UiToolbarPos pos) +{ + GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.onclick, item->args.onclickdata, 0, FALSE); + ui_set_widget_groups(obj->ctx, button, item->args.groups); + WIDGET_ADD_CSS_CLASS(button, "flat"); + headerbar_add(headerbar, box, button, pos); +} + +void ui_add_headerbar_toggleitem( + GtkWidget *headerbar, + GtkWidget *box, + UiToolbarToggleItem *item, + UiObject *obj, + enum UiToolbarPos pos) +{ + GtkWidget *button = gtk_toggle_button_new(); + ui_set_widget_groups(obj->ctx, button, item->args.groups); + WIDGET_ADD_CSS_CLASS(button, "flat"); + ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.varname, NULL, item->args.onchange, item->args.onchangedata, 0); + headerbar_add(headerbar, box, button, pos); +} + +void ui_add_headerbar_menu( + GtkWidget *headerbar, + GtkWidget *box, + UiToolbarMenuItem *item, + UiObject *obj, + enum UiToolbarPos pos) +{ + + +#if GTK_MAJOR_VERSION >= 4 + GtkWidget *menubutton = gtk_menu_button_new(); + if(item->args.label) { + gtk_menu_button_set_label(GTK_MENU_BUTTON(menubutton), item->args.label); + } + if(item->args.icon) { + gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(menubutton), item->args.icon); + } + + if(!item->args.label && !item->args.icon) { + gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(menubutton), "open-menu-symbolic"); + } + + GMenu *menu = g_menu_new(); + ui_gmenu_add_menu_items(menu, 0, &item->menu, obj); + + gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(menubutton), G_MENU_MODEL(menu)); +#else + GtkWidget *menubutton = gtk_menu_button_new(); + + // TODO + + +#endif + + headerbar_add(headerbar, box, menubutton, pos); +} + +#endif // GTK_CHECK_VERSION(3, 10, 0) diff --git a/ui/gtk/headerbar.h b/ui/gtk/headerbar.h new file mode 100644 index 0000000..acea9fe --- /dev/null +++ b/ui/gtk/headerbar.h @@ -0,0 +1,93 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HEADERBAR_H +#define HEADERBAR_H + +#include "toolkit.h" +#include "../ui/toolbar.h" +#include "../common/toolbar.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if GTK_CHECK_VERSION(3, 10, 0) + +#ifdef UI_LIBADWAITA +#define UI_HEADERBAR AdwHeaderBar* +#define UI_HEADERBAR_CAST(h) ADW_HEADER_BAR(h) +#define UI_HEADERBAR_PACK_START(h, w) adw_header_bar_pack_start(ADW_HEADER_BAR(h), w) +#define UI_HEADERBAR_PACK_END(h, w) adw_header_bar_pack_end(ADW_HEADER_BAR(h), w) +#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) adw_header_bar_set_title_widget(ADW_HEADER_BAR(h), w) +#else +#define UI_HEADERBAR GtkHeaderBar* +#define UI_HEADERBAR_CAST(h) GTK_HEADER_BAR(h) +#define UI_HEADERBAR_PACK_START(h, w) gtk_header_bar_pack_start(GTK_HEADER_BAR(h), w) +#define UI_HEADERBAR_PACK_END(h, w) gtk_header_bar_pack_end(GTK_HEADER_BAR(h), w) +#if GTK_MAJOR_VERSION >= 4 +#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_title_widget(GTK_HEADER_BAR(h), w) +#else +#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_custom_title(GTK_HEADER_BAR(h), w) +#endif +#endif + +void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar); + +void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos); + +void ui_add_headerbar_item( + GtkWidget *headerbar, + GtkWidget *box, + UiToolbarItem *item, + UiObject *obj, + enum UiToolbarPos pos); + +void ui_add_headerbar_toggleitem( + GtkWidget *headerbar, + GtkWidget *box, + UiToolbarToggleItem *item, + UiObject *obj, + enum UiToolbarPos pos); + +void ui_add_headerbar_menu( + GtkWidget *headerbar, + GtkWidget *box, + UiToolbarMenuItem *item, + UiObject *obj, + enum UiToolbarPos pos); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* HEADERBAR_H */ + diff --git a/ui/gtk/icon.c b/ui/gtk/icon.c new file mode 100644 index 0000000..608ad04 --- /dev/null +++ b/ui/gtk/icon.c @@ -0,0 +1,208 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "toolkit.h" +#include "icon.h" +#include "../common/properties.h" + +static CxMap *image_map; + +static GtkIconTheme *icon_theme; + +#if GTK_MAJOR_VERSION >= 4 +#define ICONTHEME_GET_DEFAULT() gtk_icon_theme_get_for_display(gdk_display_get_default()) +#else +#define ICONTHEME_GET_DEFAULT() gtk_icon_theme_get_default() +#endif + +void ui_image_init(void) { + image_map = cxHashMapCreateSimple(CX_STORE_POINTERS); + + icon_theme = ICONTHEME_GET_DEFAULT(); +} + +// **** deprecated functions **** + +GdkPixbuf* ui_get_image(const char *name) { + UiImage *img = cxMapGet(image_map, name); + if(img) { + return img->pixbuf; + } else { + //ui_add_image(name, name); + //return ucx_map_cstr_get(image_map, name); + // TODO + return NULL; + } +} + +// **** deprecated2**** + +static UiIcon* get_icon(const char *name, int size, int scale) { +#if GTK_MAJOR_VERSION >= 4 + GtkIconPaintable *info = gtk_icon_theme_lookup_icon(icon_theme, name, NULL, size, scale, GTK_TEXT_DIR_LTR, GTK_ICON_LOOKUP_FORCE_REGULAR); +#elif defined(UI_SUPPORTS_SCALE) + GtkIconInfo *info = gtk_icon_theme_lookup_icon_for_scale(icon_theme, name, size, scale, 0); +#else + GtkIconInfo *info = gtk_icon_theme_lookup_icon(icon_theme, name, size, 0); +#endif + if(info) { + UiIcon *icon = malloc(sizeof(UiIcon)); + icon->info = info; + icon->pixbuf = NULL; + return icon; + } + return NULL; +} + +UiIcon* ui_icon(const char* name, size_t size) { + return get_icon(name, size, ui_get_scalefactor()); +} + +UiIcon* ui_imageicon(const char* file) { + GError *error = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(file, &error); + if(!pixbuf) { + fprintf(stderr, "UiError: Cannot load image: %s\n", file); + return NULL; + } + + UiIcon *icon = malloc(sizeof(UiIcon)); + icon->info = NULL; + icon->pixbuf = pixbuf; + return icon; +} + +void ui_icon_free(UiIcon* icon) { + if(icon->info) { + g_object_unref(icon->info); + } + if(icon->pixbuf) { + g_object_unref(icon->pixbuf); + } + free(icon); +} + +UiIcon* ui_foldericon(size_t size) { + return ui_icon("folder", size); +} + +UiIcon* ui_fileicon(size_t size) { + UiIcon *icon = ui_icon("file", size); +#if GTK_MAJOR_VERSION >= 4 + GFile *file = gtk_icon_paintable_get_file(icon->info); + char *path = g_file_get_path(file); + if(!path) { + icon = ui_icon("application-x-generic", size); + } +#else + if(!icon) { + icon = ui_icon("application-x-generic", size); + } +#endif + return icon; +} + +UiIcon* ui_icon_unscaled(const char *name, int size) { + return get_icon(name, size, 1); +} + +#if GTK_MAJOR_VERSION >= 4 +GdkPixbuf* ui_icon_pixbuf(UiIcon *icon) { + if(!icon->pixbuf) { + GFile *file = gtk_icon_paintable_get_file(icon->info); + GError *error = NULL; + char *path = g_file_get_path(file); + icon->pixbuf = gdk_pixbuf_new_from_file(path, &error); + } + return icon->pixbuf; +} +#else +GdkPixbuf* ui_icon_pixbuf(UiIcon *icon) { + if(!icon->pixbuf) { + GError *error = NULL; + icon->pixbuf = gtk_icon_info_load_icon(icon->info, &error); + } + return icon->pixbuf; +} +#endif + +/* +UiImage* ui_icon_image(UiIcon *icon) { + GError *error = NULL; + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); + if(pixbuf) { + UiImage *img = malloc(sizeof(UiImage)); + img->pixbuf = pixbuf; + return img; + } + return NULL; +} +*/ + +/* +UiImage* ui_image(const char *filename) { + return ui_named_image(filename, NULL); +} + +UiImage* ui_named_image(const char *filename, const char *name) { + char *path = uic_get_image_path(filename); + if(!path) { + fprintf(stderr, "UiError: pixmaps directory not set\n"); + return NULL; + } + UiImage *img = ui_load_image_from_path(path, name); + free(path); + return img; +} + +UiImage* ui_load_image_from_path(const char *path, const char *name) { + GError *error = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error); + if(!pixbuf) { + fprintf(stderr, "UiError: Cannot load image: %s\n", path); + return NULL; + } + + UiImage *img = malloc(sizeof(UiImage)); + img->pixbuf = pixbuf; + if(name) { + cxMapPut(image_map, name, img); + } + return img; +} +*/ + +void ui_free_image(UiImage *img) { + g_object_unref(img->pixbuf); + free(img); +} diff --git a/ui/gtk/icon.h b/ui/gtk/icon.h new file mode 100644 index 0000000..d811817 --- /dev/null +++ b/ui/gtk/icon.h @@ -0,0 +1,67 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef 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 */ + diff --git a/ui/gtk/image.c b/ui/gtk/image.c new file mode 100644 index 0000000..3c2a0e6 --- /dev/null +++ b/ui/gtk/image.c @@ -0,0 +1,135 @@ +/* + * 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; +} diff --git a/ui/gtk/image.h b/ui/gtk/image.h new file mode 100644 index 0000000..00c205d --- /dev/null +++ b/ui/gtk/image.h @@ -0,0 +1,49 @@ +/* + * 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 */ + diff --git a/ui/gtk/list.c b/ui/gtk/list.c new file mode 100644 index 0000000..34bf993 --- /dev/null +++ b/ui/gtk/list.c @@ -0,0 +1,984 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "../common/context.h" +#include "../common/object.h" +#include "container.h" + +#include + +#include "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;itypes[i]) { + case UI_STRING: + case UI_STRING_FREE: types[c] = G_TYPE_STRING; break; + case UI_INTEGER: types[c] = G_TYPE_INT; break; + case UI_ICON: types[c] = G_TYPE_OBJECT; break; + case UI_ICON_TEXT: + case UI_ICON_TEXT_FREE: { + types[c] = G_TYPE_OBJECT; + types[++c] = G_TYPE_STRING; + } + } + } + + GtkListStore *store = gtk_list_store_newv(c, types); + + if(list) { + void *elm = list->first(list); + while(elm) { + // insert new row + GtkTreeIter iter; + gtk_list_store_insert (store, &iter, -1); + + // set column values + int c = 0; + for(int i=0;igetvalue(elm, c); + + GValue value = G_VALUE_INIT; + switch(model->types[i]) { + case UI_STRING: + case UI_STRING_FREE: { + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, data); + if(model->types[i] == UI_STRING_FREE) { + free(data); + } + break; + } + case UI_INTEGER: { + g_value_init(&value, G_TYPE_INT); + int *intptr = data; + g_value_set_int(&value, *intptr); + break; + } + case UI_ICON: { + g_value_init(&value, G_TYPE_OBJECT); + UiIcon *icon = data; +#if GTK_MAJOR_VERSION >= 4 + g_value_set_object(&value, icon->info); // TODO: does this work? +#else + if(!icon->pixbuf && icon->info) { + GError *error = NULL; + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); + icon->pixbuf = pixbuf; + } + + if(icon->pixbuf) { + g_value_set_object(&value, icon->pixbuf); + } +#endif + break; + } + case UI_ICON_TEXT: + case UI_ICON_TEXT_FREE: { + UiIcon *icon = data; +#if GTK_MAJOR_VERSION >= 4 + if(icon) { + GValue iconvalue = G_VALUE_INIT; + g_value_init(&iconvalue, G_TYPE_OBJECT); + g_value_set_object(&iconvalue, ui_icon_pixbuf(icon)); + gtk_list_store_set_value(store, &iter, c, &iconvalue); + } +#else + GValue pixbufvalue = G_VALUE_INIT; + if(icon) { + if(!icon->pixbuf && icon->info) { + GError *error = NULL; + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); + icon->pixbuf = pixbuf; + } + g_value_init(&pixbufvalue, G_TYPE_OBJECT); + g_value_set_object(&pixbufvalue, icon->pixbuf); + gtk_list_store_set_value(store, &iter, c, &pixbufvalue); + } +#endif + c++; + + char *str = model->getvalue(elm, c); + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, str); + if(model->types[i] == UI_ICON_TEXT_FREE) { + free(str); + } + break; + } + } + + gtk_list_store_set_value(store, &iter, c, &value); + } + + // next row + elm = list->next(list); + } + } + + return store; +} + + +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // create treeview + GtkWidget *view = gtk_tree_view_new(); + ui_set_name_and_style(view, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, view, args.groups); + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); +#ifdef UI_GTK3 +#if GTK_MINOR_VERSION >= 8 + //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); +#else + // TODO: implement for older gtk3 +#endif +#else + // TODO: implement for gtk2 +#endif + + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue; + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); + g_object_unref(listmodel); + + UiListView *listview = malloc(sizeof(UiListView)); + listview->obj = obj; + listview->widget = view; + listview->var = var; + listview->model = model; + 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;itypes[i] == UI_ICON_TEXT) { + column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(column, model->titles[i]); + + GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); + GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new(); + + gtk_tree_view_column_pack_end(column, textrenderer, TRUE); + gtk_tree_view_column_pack_start(column, iconrenderer, FALSE); + + + gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i); + gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1); + + addi++; + } else if (model->types[i] == UI_ICON) { + GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); + column = gtk_tree_view_column_new_with_attributes( + model->titles[i], + iconrenderer, + "pixbuf", + i + addi, + NULL); + } else { + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + model->titles[i], + renderer, + "text", + i + addi, + NULL); + } + + int colsz = model->columnsize[i]; + if(colsz > 0) { + gtk_tree_view_column_set_fixed_width(column, colsz); + } else if(colsz < 0) { + gtk_tree_view_column_set_expand(column, TRUE); + } + + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + } + + //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); +#ifdef UI_GTK3 + //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); +#else + +#endif + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); + g_object_unref(listmodel); + + //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL); + //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL); + + // add TreeView as observer to the UiList to update the TreeView if the + // data changes + UiListView *tableview = malloc(sizeof(UiListView)); + tableview->obj = obj; + tableview->widget = view; + tableview->var = var; + tableview->model = model; + tableview->ondragstart = args.ondragstart; + tableview->ondragstartdata = args.ondragstartdata; + tableview->ondragcomplete = args.ondragcomplete; + tableview->ondragcompletedata = args.ondragcompletedata; + tableview->ondrop = args.ondrop; + tableview->ondropdata = args.ondropsdata; + 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;iobj; + 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]); + } +} diff --git a/ui/gtk/list.h b/ui/gtk/list.h new file mode 100644 index 0000000..3978fb3 --- /dev/null +++ b/ui/gtk/list.h @@ -0,0 +1,103 @@ +/* + * 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 */ + diff --git a/ui/gtk/menu.c b/ui/gtk/menu.c new file mode 100644 index 0000000..5e5dec0 --- /dev/null +++ b/ui/gtk/menu.c @@ -0,0 +1,638 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "menu.h" +#include "toolkit.h" +#include "../common/context.h" +#include "../common/menu.h" +#include "../common/types.h" +#include "../ui/properties.h" +#include "../ui/window.h" +#include "container.h" + +#include +#include + +#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;ioldcount;i++) { + g_menu_remove(list->menu, list->index); + } + UiList *ls = list->var->value; + + // add list items + ui_getvaluefunc getvalue = list->getvalue; + int i = 0; + void* elm = ui_list_first(ls); + while(elm) { + char *label = (char*) (getvalue ? getvalue(elm, 0) : elm); + + GMenuItem *item = g_menu_item_new(label, NULL); + GVariant *v = g_variant_new("i", i); + g_menu_item_set_action_and_target_value(item, list->action, v); + g_menu_insert_item(list->menu, list->index+i, item); + + elm = ui_list_next(ls); + i++; + } + + list->oldcount = i; +} + + +/* --------------------- context menu / menubuilder --------------------- */ + +static void remove_popover(GtkWidget *object, GtkPopoverMenu *menu) { + gtk_widget_unparent(GTK_WIDGET(menu)); +} + +UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, GtkWidget *widget) { + GMenu *menu = g_menu_new(); + ui_gmenu_add_menu_items(menu, 0, builder->menus_begin, obj); + GtkWidget *contextmenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu)); + gtk_popover_set_has_arrow(GTK_POPOVER(contextmenu), FALSE); + gtk_widget_set_halign(contextmenu, GTK_ALIGN_START); + gtk_widget_set_parent(GTK_WIDGET(contextmenu), widget); + g_signal_connect( + widget, + "destroy", + G_CALLBACK(remove_popover), + contextmenu); + return GTK_POPOVER_MENU(contextmenu); +} + +static void gesture_button_press(GtkGestureClick *gesture, gint n_press, gdouble x, gdouble y, gpointer user_data) { + gtk_popover_set_pointing_to(GTK_POPOVER(user_data), &(GdkRectangle){ x, y, 0, 0 }); + gtk_popover_popup(GTK_POPOVER(user_data)); +} + +void ui_widget_set_contextmenu(GtkWidget *widget, GtkPopoverMenu *menu) { + GtkGesture *gesture = gtk_gesture_click_new(); + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 3); + gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(gesture)); + g_signal_connect(gesture, "pressed", G_CALLBACK(gesture_button_press), menu); +} + +void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y) { + gtk_popover_set_pointing_to(GTK_POPOVER(menu), &(GdkRectangle){ x, y, 0, 0 }); + gtk_popover_popup(GTK_POPOVER(menu)); +} + +#endif diff --git a/ui/gtk/menu.h b/ui/gtk/menu.h new file mode 100644 index 0000000..1963bb1 --- /dev/null +++ b/ui/gtk/menu.h @@ -0,0 +1,121 @@ +/* + * 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 +#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 */ + diff --git a/ui/gtk/objs.mk b/ui/gtk/objs.mk new file mode 100644 index 0000000..865216e --- /dev/null +++ b/ui/gtk/objs.mk @@ -0,0 +1,51 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2017 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +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) diff --git a/ui/gtk/range.c b/ui/gtk/range.c new file mode 100644 index 0000000..c992159 --- /dev/null +++ b/ui/gtk/range.c @@ -0,0 +1,131 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "range.h" +#include "container.h" +#include "../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; +} diff --git a/ui/gtk/range.h b/ui/gtk/range.h new file mode 100644 index 0000000..0856e3b --- /dev/null +++ b/ui/gtk/range.h @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RANGE_H +#define RANGE_H + +#include "toolkit.h" +#include "../ui/range.h" + +#ifdef __cplusplus +extern "C" { +#endif + +gboolean ui_scrollbar_value_changed(GtkRange *range, UiEventData *event); + +double ui_scrollbar_get(UiRange *range); +void ui_scrollbar_set(UiRange *range, double value); +void ui_scrollbar_setrange(UiRange *range, double min, double max); +void ui_scrollbar_setextent(UiRange *range, double extent); + +#ifdef __cplusplus +} +#endif + +#endif /* RANGE_H */ + diff --git a/ui/gtk/text.c b/ui/gtk/text.c new file mode 100644 index 0000000..fe8aac3 --- /dev/null +++ b/ui/gtk/text.c @@ -0,0 +1,1123 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "text.h" +#include "container.h" + +#include + +#include + + +#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 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;ientry); + free(pathtf); +} + +void ui_path_button_clicked(GtkWidget *widget, UiEventDataExt *event) { + UiPathTextField *pathtf = event->customdata1; + for(int i=0;ivalue1;i++) { + if(i <= event->value0) { + WIDGET_REMOVE_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive"); + } else { + WIDGET_ADD_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive"); + } + } + + UiPathElm *elm = event->customdata0; + cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len)); + UiEvent evt; + evt.obj = event->obj; + evt.window = evt.obj->window; + evt.document = evt.obj->ctx->document; + evt.eventdata = elm->path; + evt.intval = event->value0; + event->callback(&evt, event->userdata); + free(path.ptr); +} + +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;icurrent_nelm;i++) { + UiPathElm *elm = &pathtf->current_pathelms[i]; + + GtkWidget *button = ui_path_elm_button(pathtf, elm, i); + gtk_widget_add_css_class(button, "flat"); + + gtk_box_append(GTK_BOX(pathtf->hbox), button); + + if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), CX_STR("/"))) { + GtkWidget *path_separator = gtk_label_new("/"); + gtk_widget_add_css_class(path_separator, "pathbar-button-inactive"); + gtk_box_append(GTK_BOX(pathtf->hbox), path_separator); + } + } + gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox); + + // create a widget for receiving button press events + GtkWidget *event_area = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + GtkGesture *handler = gtk_gesture_click_new(); + gtk_widget_add_controller(event_area, GTK_EVENT_CONTROLLER(handler)); + g_signal_connect( + handler, + "pressed", + G_CALLBACK(pathbar_pressed), + pathtf); + gtk_widget_set_hexpand(event_area, TRUE); + gtk_widget_set_vexpand(event_area, TRUE); + gtk_box_append(GTK_BOX(pathtf->hbox), event_area); + + return 0; +} + +#else + +static gboolean path_textfield_btn_pressed(GtkWidget *widget, GdkEventButton *event, UiPathTextField *pathtf) { + gtk_box_pack_start(GTK_BOX(pathtf->hbox), pathtf->entry, TRUE, TRUE, 0); + gtk_container_remove(GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox); + pathtf->buttonbox = NULL; + + gtk_widget_show(pathtf->entry); + gtk_widget_grab_focus(pathtf->entry); + + return TRUE; +} + +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;icurrent_nelm;i++) { + UiPathElm *elm = &pathtf->current_pathelms[i]; + GtkWidget *button = ui_path_elm_button(pathtf, elm, i); + gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 0); + } + + gtk_widget_show_all(buttonbox); + + return 0; +} + +#endif + +char* ui_path_textfield_get(UiString *str) { + if(str->value.ptr) { + str->value.free(str->value.ptr); + } + 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; + } +} diff --git a/ui/gtk/text.h b/ui/gtk/text.h new file mode 100644 index 0000000..6e8d198 --- /dev/null +++ b/ui/gtk/text.h @@ -0,0 +1,157 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEXT_H +#define TEXT_H + +#include "../ui/text.h" +#include "toolkit.h" +#include +#include "../common/context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define UI_TEXTBUF_INSERT 0 +#define UI_TEXTBUF_DELETE 1 + +typedef struct UiTextBufOp 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 */ + diff --git a/ui/gtk/toolbar.c b/ui/gtk/toolbar.c new file mode 100644 index 0000000..60c4bac --- /dev/null +++ b/ui/gtk/toolbar.c @@ -0,0 +1,428 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "toolbar.h" +#include "menu.h" +#include "button.h" +#include "icon.h" +#include "list.h" +#include +#include +#include +#include +#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 */ diff --git a/ui/gtk/toolbar.h b/ui/gtk/toolbar.h new file mode 100644 index 0000000..80bd113 --- /dev/null +++ b/ui/gtk/toolbar.h @@ -0,0 +1,151 @@ +/* + * 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 +#include + +#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 */ + diff --git a/ui/gtk/toolkit.c b/ui/gtk/toolkit.c new file mode 100644 index 0000000..91ce244 --- /dev/null +++ b/ui/gtk/toolkit.c @@ -0,0 +1,453 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "toolkit.h" +#include "toolbar.h" +#include "icon.h" +#include "../common/document.h" +#include "../common/properties.h" +#include "../common/menu.h" +#include "../common/toolbar.h" +#include "../common/threadpool.h" + +#include +#include +#include + +#include + +#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= 4 + gtk_widget_add_css_class(widget, m.ptr); +#elif GTK_MAJOR_VERSION >= 3 + GtkStyleContext *ctx = gtk_widget_get_style_context(widget); + gtk_style_context_add_class(ctx, m.ptr); +#endif + free(m.ptr); + } + free(cls); + + } +} + +void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups) { + if(!groups) { + return; + } + size_t ngroups = uic_group_array_size(groups); + ui_set_widget_ngroups(ctx, widget, groups, ngroups); +} + +void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups) { + if(ngroups > 0) { + uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups); + ui_set_enabled(widget, FALSE); + } +} diff --git a/ui/gtk/toolkit.h b/ui/gtk/toolkit.h new file mode 100644 index 0000000..cdaf42a --- /dev/null +++ b/ui/gtk/toolkit.h @@ -0,0 +1,185 @@ +/* + * 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 */ + diff --git a/ui/gtk/window.c b/ui/gtk/window.c new file mode 100644 index 0000000..96e1e68 --- /dev/null +++ b/ui/gtk/window.c @@ -0,0 +1,846 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "../ui/window.h" +#include "../ui/properties.h" +#include "../common/context.h" +#include "../common/menu.h" +#include "../common/toolbar.h" + +#include + +#include "menu.h" +#include "toolbar.h" +#include "container.h" +#include "headerbar.h" +#include "button.h" + +static int nwindows = 0; + +static int window_default_width = 650; +static int window_default_height = 550; + +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;ivalue; + int multi = mode & UI_FILEDIALOG_SELECT_MULTI; + if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) { + if(multi) { + selection = gtk_file_dialog_select_multiple_folders_finish(GTK_FILE_DIALOG(source), result, &error); + } else { + file = gtk_file_dialog_select_folder_finish(GTK_FILE_DIALOG(source), result, &error); + } + } else if((mode & UI_GTK_FILEDIALOG_OPEN) == UI_GTK_FILEDIALOG_OPEN) { + if(multi) { + selection = gtk_file_dialog_open_multiple_finish(GTK_FILE_DIALOG(source), result, &error); + } else { + file = gtk_file_dialog_open_finish(GTK_FILE_DIALOG(source), result, &error); + } + } else { + file = gtk_file_dialog_save_finish(GTK_FILE_DIALOG(source), result, &error); + } + + UiEvent evt; + evt.obj = event->obj; + evt.document = evt.obj->ctx->document; + evt.window = evt.obj->window; + evt.intval = 0; + + UiFileList flist; + flist.files = NULL; + flist.nfiles = 0; + evt.eventdata = &flist; + + if(selection) { + flist = listmodel2filelist(selection); + g_object_unref(selection); + } else if(file) { + char *path = g_file_get_path(file); + if(path) { + flist.nfiles = 1; + flist.files = calloc(flist.nfiles, sizeof(char*)); + flist.files[0] = strdup(path); + } + g_object_unref(file); + } + + if(event->callback) { + event->callback(&evt, event->userdata); + } + + for(int i=0;icallback = 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;iwidget), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + "Cancel", + GTK_RESPONSE_CANCEL, + "Select Folder", + GTK_RESPONSE_ACCEPT, + NULL); + } else if(action == GTK_FILE_CHOOSER_ACTION_OPEN) { + dialog = gtk_file_chooser_dialog_new ( + "Select Folder", + GTK_WINDOW(obj->widget), + action, + "Cancel", + GTK_RESPONSE_CANCEL, + "Open File", + GTK_RESPONSE_ACCEPT, + NULL); + } else { + dialog = gtk_file_chooser_dialog_new ( + "Save File", + GTK_WINDOW(obj->widget), + action, + "Cancel", + GTK_RESPONSE_CANCEL, + "Save File", + GTK_RESPONSE_ACCEPT, + NULL); + } + + if((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) { + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); + } + + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = cbdata; + event->callback = file_selected_callback; + event->value = 0; + event->customdata = NULL; + + g_signal_connect( + dialog, + "response", + G_CALLBACK(filechooser_response), + event); + g_signal_connect( + dialog, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + + + UiEvent evt; + evt.obj = obj; + evt.document = evt.obj->ctx->document; + evt.window = evt.obj->window; + evt.intval = 0; + + UiFileList flist; + flist.files = NULL; + flist.nfiles = 0; + evt.eventdata = &flist; + + gtk_widget_show(dialog); +} +#endif + +void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { + ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_OPEN, mode, NULL, file_selected_callback, cbdata); +} + +void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) { + ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_SAVE, 0, name, file_selected_callback, cbdata); +} + +#if GTK_CHECK_VERSION(4, 10, 0) +#define DIALOG_NEW() gtk_window_new() +#else +#define DIALOG_NEW() gtk_dialog_new() + +static void ui_dialogwindow_response(GtkDialog* self, gint response_id, gpointer user_data) { + UiEventData *event = user_data; + // TODO: do we need to check if response_id == GTK_RESPONSE_DELETE_EVENT? + if(event->callback) { + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = NULL; + e.intval = event->value; + event->callback(&e, event->userdata); + } +} + +#endif + +#if GTK_CHECK_VERSION(4, 0, 0) +#define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(headerbar), set) +#define DEFAULT_BUTTON(window, button) gtk_window_set_default_widget(GTK_WINDOW(window), button) +#else +#define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(headerbar), set) +#define DEFAULT_BUTTON(window, button) gtk_widget_set_can_default(button, TRUE); gtk_window_set_default(GTK_WINDOW(window), button) +#endif + + + +UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) { + GtkWidget *dialog = DIALOG_NEW(); + if(args.width > 0 || args.height > 0) { + gtk_window_set_default_size( + GTK_WINDOW(dialog), + args.width, + args.height); + } + + + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget)); + if(args.modal != UI_OFF) { + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + } + + CxMempool *mp = cxBasicMempoolCreate(256); + UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); + obj->ctx = uic_context(obj, mp); + obj->widget = dialog; + obj->ref = 0; + obj->destroy = ui_window_widget_destroy; + nwindows++; + + if(args.title != NULL) { + gtk_window_set_title(GTK_WINDOW(dialog), args.title); + } + +#if ! GTK_CHECK_VERSION(4, 10, 0) + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = args.onclickdata; + event->callback = args.onclick; + event->value = 0; + event->customdata = NULL; + + g_signal_connect(dialog, "response", G_CALLBACK(ui_dialogwindow_response), event); + g_signal_connect( + dialog, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); +#endif + + g_signal_connect( + dialog, + "destroy", + G_CALLBACK(ui_exit_event), + obj); +#if GTK_MAJOR_VERSION >= 4 + g_signal_connect( + obj->widget, + "close-request", + G_CALLBACK(close_request), + obj); +#else + g_signal_connect( + obj->widget, + "delete-event", + G_CALLBACK(close_request), + obj); +#endif + +#if GTK_MAJOR_VERSION < 4 + GtkWidget *c = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_container_remove(GTK_CONTAINER(dialog), c); +#endif + + GtkWidget *content_vbox = ui_gtk_vbox_new(0); + obj->container = ui_box_container(obj, content_vbox, UI_CONTAINER_VBOX); + if(args.lbutton1 || args.lbutton2 || args.rbutton3 || args.rbutton4) { +#if GTK_CHECK_VERSION(3, 10, 0) + if(args.titlebar_buttons != UI_OFF) { + GtkWidget *headerbar = gtk_header_bar_new(); + gtk_window_set_titlebar(GTK_WINDOW(dialog), headerbar); + if(args.show_closebutton == UI_OFF) { + HEADERBAR_SHOW_CLOSEBUTTON(headerbar, FALSE); + } + + if(args.lbutton1) { + GtkWidget *button = ui_create_button(obj, args.lbutton1, NULL, args.onclick, args.onclickdata, 1, args.default_button == 1); + gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button); + if(args.default_button == 1) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + if(args.lbutton2) { + GtkWidget *button = ui_create_button(obj, args.lbutton2, NULL, args.onclick, args.onclickdata, 2, args.default_button == 2); + gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button); + if(args.default_button == 2) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + + if(args.rbutton4) { + GtkWidget *button = ui_create_button(obj, args.rbutton4, NULL, args.onclick, args.onclickdata, 4, args.default_button == 4); + gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button); + if(args.default_button == 4) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + if(args.rbutton3) { + GtkWidget *button = ui_create_button(obj, args.rbutton3, NULL, args.onclick, args.onclickdata, 3, args.default_button == 3); + gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button); + if(args.default_button == 3) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + WINDOW_SET_CONTENT(obj->widget, content_vbox); + return obj; + } +#endif + GtkWidget *vbox = ui_gtk_vbox_new(0); + WINDOW_SET_CONTENT(obj->widget, vbox); + + GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + + GtkWidget *grid = ui_create_grid_widget(10, 10); + GtkWidget *widget = ui_box_set_margin(grid, 16); + gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE); + + if(args.lbutton1) { + GtkWidget *button = ui_create_button(obj, args.lbutton1, NULL, args.onclick, args.onclickdata, 1, args.default_button == 1); + gtk_grid_attach(GTK_GRID(grid), button, 0, 0, 1, 1); + if(args.default_button == 1) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + if(args.lbutton2) { + GtkWidget *button = ui_create_button(obj, args.lbutton2, NULL, args.onclick, args.onclickdata, 2, args.default_button == 2); + gtk_grid_attach(GTK_GRID(grid), button, 1, 0, 1, 1); + if(args.default_button == 2) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + GtkWidget *space = gtk_label_new(NULL); + gtk_widget_set_hexpand(space, TRUE); + gtk_grid_attach(GTK_GRID(grid), space, 2, 0, 1, 1); + if(args.rbutton3) { + GtkWidget *button = ui_create_button(obj, args.rbutton3, NULL, args.onclick, args.onclickdata, 3, args.default_button == 3); + gtk_grid_attach(GTK_GRID(grid), button, 3, 0, 1, 1); + if(args.default_button == 3) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + if(args.rbutton4) { + GtkWidget *button = ui_create_button(obj, args.rbutton4, NULL, args.onclick, args.onclickdata, 4, args.default_button == 4); + gtk_grid_attach(GTK_GRID(grid), button, 4, 0, 1, 1); + if(args.default_button == 4) { + WIDGET_ADD_CSS_CLASS(button, "suggested-action"); + DEFAULT_BUTTON(dialog, button); + } + } + + BOX_ADD_EXPAND(vbox, content_vbox); + BOX_ADD_NO_EXPAND(vbox, separator); + BOX_ADD_NO_EXPAND(vbox, widget); + } else { + WINDOW_SET_CONTENT(obj->widget, content_vbox); + } + + return obj; +} diff --git a/ui/motif/Makefile b/ui/motif/Makefile new file mode 100644 index 0000000..3f7c064 --- /dev/null +++ b/ui/motif/Makefile @@ -0,0 +1,33 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2012 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +$(MOTIF_OBJPRE)%.o: motif/%.c + $(CC) -o $@ -c -I../ucx $(CFLAGS) $(TK_CFLAGS) $< + +$(UI_LIB): $(OBJ) + $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ) diff --git a/ui/motif/button.c b/ui/motif/button.c new file mode 100644 index 0000000..8757fdf --- /dev/null +++ b/ui/motif/button.c @@ -0,0 +1,214 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "button.h" +#include "container.h" +#include "../common/context.h" +#include + +#include +#include +#include + + +UIWIDGET ui_button(UiObject *obj, char *label, ui_callback f, void *data) { + UiContainer *ct = uic_get_current_container(obj); + XmString str = XmStringCreateLocalized(label); + + int n = 0; + Arg args[16]; + + XtSetArg(args[n], XmNlabelString, str); + n++; + + Widget parent = ct->prepare(ct, args, &n, FALSE); + Widget button = XmCreatePushButton(parent, "button", args, n); + ct->add(ct, button); + + if(f) { + UiEventData *event = 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; + } +} diff --git a/ui/motif/button.h b/ui/motif/button.h new file mode 100644 index 0000000..9fd7dca --- /dev/null +++ b/ui/motif/button.h @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BUTTON_H +#define BUTTON_H + +#include "../ui/button.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + 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 */ + diff --git a/ui/motif/container.c b/ui/motif/container.c new file mode 100644 index 0000000..007c10f --- /dev/null +++ b/ui/motif/container.c @@ -0,0 +1,814 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "container.h" +#include "../common/context.h" +#include "../common/object.h" + +#include +#include +#include + +#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; +} diff --git a/ui/motif/container.h b/ui/motif/container.h new file mode 100644 index 0000000..a012f8b --- /dev/null +++ b/ui/motif/container.h @@ -0,0 +1,160 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CONTAINER_H +#define CONTAINER_H + +#include "../ui/toolkit.h" +#include "../ui/container.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout)) + +typedef struct MotifTabbedPane MotifTabbedPane; +typedef struct UiTab UiTab; +typedef struct UiBoxContainer UiBoxContainer; +typedef struct UiGridContainer UiGridContainer; +typedef struct UiTabViewContainer UiTabViewContainer; +typedef struct UiLayout UiLayout; + +typedef Widget (*ui_container_add_f)(UiContainer*, Arg*, int*, UiBool); + +typedef enum UiLayoutBool UiLayoutBool; +typedef enum UiBoxOrientation UiBoxOrientation; + + +enum UiLayoutBool { + UI_LAYOUT_UNDEFINED = 0, + UI_LAYOUT_TRUE, + UI_LAYOUT_FALSE, +}; + +enum UiBoxOrientation { + UI_BOX_VERTICAL = 0, + UI_BOX_HORIZONTAL +}; + +struct UiLayout { + UiLayoutBool fill; + UiBool newline; + char *label; + UiBool hexpand; + UiBool vexpand; + int gridwidth; +}; + +struct UiContainer { + Widget widget; + Widget (*prepare)(UiContainer*, Arg *, int*, UiBool); + void (*add)(UiContainer*, Widget); + UiLayout layout; + Widget current; + Widget menu; +}; + +struct UiBoxContainer { + UiContainer container; + Widget prev_widget; + UiBool has_fill; + UiBoxOrientation orientation; + int margin; + int spacing; +}; + +struct UiGridContainer { + UiContainer container; + 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 */ + diff --git a/ui/motif/dnd.c b/ui/motif/dnd.c new file mode 100644 index 0000000..2f2313e --- /dev/null +++ b/ui/motif/dnd.c @@ -0,0 +1,45 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "dnd.h" + +void ui_selection_settext(UiSelection *sel, char *str, int len) { + +} + +void ui_selection_seturis(UiSelection *sel, char **uris, int nelm) { + +} + +char* ui_selection_gettext(UiSelection *sel) { + return NULL; +} + +char** ui_selection_geturis(UiSelection *sel, size_t *nelm) { + return NULL; +} diff --git a/ui/motif/dnd.h b/ui/motif/dnd.h new file mode 100644 index 0000000..3653692 --- /dev/null +++ b/ui/motif/dnd.h @@ -0,0 +1,46 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DND_H +#define DND_H + +#include "../ui/dnd.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + + +#ifdef __cplusplus +} +#endif + +#endif /* DND_H */ + diff --git a/ui/motif/graphics.c b/ui/motif/graphics.c new file mode 100644 index 0000000..fefeca6 --- /dev/null +++ b/ui/motif/graphics.c @@ -0,0 +1,283 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "graphics.h" + +#include "container.h" + +static void ui_drawingarea_expose(Widget widget, XtPointer u, XtPointer c) { + UiDrawEvent *drawevent = u; + //XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)c; + //XEvent *event = cbs->event; + Display *dpy = XtDisplay(widget); + + UiEvent ev; + ev.obj = drawevent->obj; + ev.window = drawevent->obj->window; + ev.document = drawevent->obj->ctx->document; + ev.eventdata = NULL; + ev.intval = 0; + + XtVaGetValues( + widget, + XmNwidth, + &drawevent->gr.g.width, + XmNheight, + &drawevent->gr.g.height, + NULL); + + XGCValues gcvals; + gcvals.foreground = BlackPixelOfScreen(XtScreen(widget)); + drawevent->gr.gc = XCreateGC(dpy, XtWindow(widget), (GCForeground), &gcvals); + + drawevent->callback(&ev, &drawevent->gr.g, drawevent->userdata); +} + +UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata) { + UiContainer *ct = uic_get_current_container(obj); + + int n = 0; + Arg args[16]; + + Widget parent = ct->prepare(ct, args, &n, TRUE); + Widget drawingarea = XmCreateDrawingArea(parent, "drawingarea", args, n); + + if(f) { + UiDrawEvent *event = malloc(sizeof(UiDrawEvent)); + event->obj = obj; + event->callback = f; + event->userdata = userdata; + + event->gr.display = XtDisplay(drawingarea); + event->gr.widget = drawingarea; + + Colormap colormap; + XtVaGetValues(drawingarea, XmNcolormap, &colormap, NULL); + event->gr.colormap = colormap; + + XtAddCallback( + drawingarea, + XmNexposeCallback, + ui_drawingarea_expose, + event); + + XtVaSetValues(drawingarea, XmNuserData, event, NULL); + } + + XtManageChild(drawingarea); + return drawingarea; +} + +static void ui_drawingarea_input(Widget widget, XtPointer u, XtPointer c) { + XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c; + XEvent *xevent = cbs->event; + UiMouseEventData *event = u; + + if (cbs->reason == XmCR_INPUT) { + if (xevent->xany.type == ButtonPress) { + UiMouseEvent me; + me.x = xevent->xbutton.x; + me.y = xevent->xbutton.y; + // TODO: configurable double click time + me.type = xevent->xbutton.time - event->last_event > 300 ? UI_PRESS : UI_PRESS2; + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = &me; + e.intval = 0; + event->callback(&e, event->userdata); + + + event->last_event = me.type == UI_PRESS2 ? 0 : xevent->xbutton.time; + } + } + +} + +void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u) { + if(f) { + UiMouseEventData *event = malloc(sizeof(UiMouseEventData)); + event->obj = obj; + event->callback = f; + event->userdata = u; + event->last_event = 0; + + XtAddCallback(widget, XmNinputCallback, ui_drawingarea_input, event); + } +} + +void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) { + XtVaGetValues( + drawingarea, + XmNwidth, + width, + XmNheight, + height, + NULL); +} + +void ui_drawingarea_redraw(UIWIDGET drawingarea) { + //XClearArea(XtDisplay(drawingarea), drawingarea->core.window, 0, 0, drawingarea->core.width, drawingarea->core.height, True); + UiDrawEvent *event; + XtVaGetValues(drawingarea, XmNuserData, &event, NULL); + ui_drawingarea_expose(drawingarea, event, NULL); +} + + +/* -------------------- text layout functions -------------------- */ +UiTextLayout* ui_text(UiGraphics *g) { + UiTextLayout *text = malloc(sizeof(UiTextLayout)); + memset(text, 0, sizeof(UiTextLayout)); + text->text = NULL; + text->length = 0; + text->widget = ((UiXlibGraphics*)g)->widget; + text->fontset = NULL; + return text; +} + +static void create_default_fontset(UiTextLayout *layout) { + char **missing = NULL; + int num_missing = 0; + char *def = NULL; + Display *dpy = XtDisplay(layout->widget); + XFontSet fs = XCreateFontSet( + dpy, + "-dt-interface system-medium-r-normal-s*utf*:," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-1," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-10," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-15," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-2," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-3," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-4," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-5," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-iso8859-9," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-e," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-r," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-ru," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-u," + "-misc-liberation sans-medium-r-normal--0-0-0-0-p-0-koi8-uni," + "-misc-fixed-medium-r-normal--14-130-75-75-c-140-jisx0208", + &missing, &num_missing, &def); + layout->fontset = fs; +} + +void ui_text_free(UiTextLayout *text) { + // TODO +} + +void ui_text_setstring(UiTextLayout *layout, char *str) { + ui_text_setstringl(layout, str, strlen(str)); +} + +void ui_text_setstringl(UiTextLayout *layout, char *str, int len) { + layout->text = str; + layout->length = len; + layout->changed = 1; +} + +void ui_text_setfont(UiTextLayout *layout, char *font, int size) { + create_default_fontset(layout);//TODO + layout->changed = 1; +} + +void ui_text_getsize(UiTextLayout *layout, int *width, int *height) { + if(layout->changed) { + XRectangle ext, lext; + XmbTextExtents(layout->fontset, layout->text, layout->length, &ext, &lext); + layout->width = ext.width; + layout->height = ext.height; + layout->changed = 0; + } + *width = layout->width; + *height = layout->height; +} + +void ui_text_setwidth(UiTextLayout *layout, int width) { + layout->maxwidth = width; +} + + +/* -------------------- drawing functions -------------------- */ + +void ui_graphics_color(UiGraphics *g, int red, int green, int blue) { + UiXlibGraphics *gr = (UiXlibGraphics*)g; + XColor color; + color.flags= DoRed | DoGreen | DoBlue; + color.red = red * 257; + color.green = green * 257; + color.blue = blue * 257; + XAllocColor(gr->display, gr->colormap, &color); + XSetForeground(gr->display, gr->gc, color.pixel); +} + +void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2) { + UiXlibGraphics *gr = (UiXlibGraphics*)g; + XDrawLine(gr->display, XtWindow(gr->widget), gr->gc, x1, y1, x2, y2); +} + +void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill) { + UiXlibGraphics *gr = (UiXlibGraphics*)g; + if(fill) { + XFillRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h); + } else { + XDrawRectangle(gr->display, XtWindow(gr->widget), gr->gc, x, y, w, h); + } +} + +void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text) { + UiXlibGraphics *gr = (UiXlibGraphics*)g; + int width, height; + ui_text_getsize(text, &width, &height); + if(text->maxwidth > 0) { + XRectangle clip; + clip.x = x; + clip.y = y; + clip.width = text->maxwidth; + clip.height = height; + XSetClipRectangles(gr->display, gr->gc, 0, 0, &clip, 1, Unsorted); + } + + XmbDrawString( + gr->display, + XtWindow(gr->widget), + text->fontset, + gr->gc, + x, + y + height, + text->text, + text->length); + + XSetClipMask(gr->display, gr->gc, None); +} diff --git a/ui/motif/graphics.h b/ui/motif/graphics.h new file mode 100644 index 0000000..fa248b7 --- /dev/null +++ b/ui/motif/graphics.h @@ -0,0 +1,78 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GRAPHICS_H +#define GRAPHICS_H + +#include "../ui/graphics.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiXlibGraphics { + UiGraphics g; + Display *display; + Widget widget; + Colormap colormap; + GC gc; +} UiXlibGraphics; + +typedef struct UiDrawEvent { + ui_drawfunc callback; + UiObject *obj; + void *userdata; + UiXlibGraphics gr; +} UiDrawEvent; + +typedef struct UiMouseEventData { + UiObject *obj; + ui_callback callback; + void *userdata; + Time last_event; +} UiMouseEventData; + +struct UiTextLayout { + char *text; + size_t length; + Widget widget; + XFontSet fontset; + int maxwidth; + int width; + int height; + int changed; +}; + + +#ifdef __cplusplus +} +#endif + +#endif /* GRAPHICS_H */ + diff --git a/ui/motif/image.c b/ui/motif/image.c new file mode 100644 index 0000000..5142dbc --- /dev/null +++ b/ui/motif/image.c @@ -0,0 +1,40 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +#include "image.h" + +UiIcon* ui_icon(const char *name, int size) { + return NULL; +} + +UiIcon* ui_icon_unscaled(const char *name, int size) { + return NULL; +} + +void ui_free_icon(UiIcon *icon) { + +} + +UiImage* ui_icon_image(UiIcon *icon) { + return NULL; +} + +UiImage* ui_image(const char *filename) { + return NULL; +} + +UiImage* ui_named_image(const char *filename, const char *name) { + return NULL; +} + +UiImage* ui_load_image_from_path(const char *path, const char *name) { + return NULL; +} + +void ui_free_image(UiImage *img) { + +} + diff --git a/ui/motif/image.h b/ui/motif/image.h new file mode 100644 index 0000000..681d232 --- /dev/null +++ b/ui/motif/image.h @@ -0,0 +1,31 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +/* + * File: image.h + * Author: olaf + * + * Created on 1. Juli 2018, 19:01 + */ + +#ifndef IMAGE_H +#define IMAGE_H + +#include "../ui/image.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + + +#ifdef __cplusplus +} +#endif + +#endif /* IMAGE_H */ + diff --git a/ui/motif/label.c b/ui/motif/label.c new file mode 100644 index 0000000..8962197 --- /dev/null +++ b/ui/motif/label.c @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#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; +} diff --git a/ui/motif/label.h b/ui/motif/label.h new file mode 100644 index 0000000..adc644c --- /dev/null +++ b/ui/motif/label.h @@ -0,0 +1,44 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LABEL_H +#define LABEL_H + +#ifdef __cplusplus +extern "C" { +#endif + + + + +#ifdef __cplusplus +} +#endif + +#endif /* LABEL_H */ + diff --git a/ui/motif/list.c b/ui/motif/list.c new file mode 100644 index 0000000..0c5b193 --- /dev/null +++ b/ui/motif/list.c @@ -0,0 +1,208 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "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;ictx->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;inext(list); + } + + *count = num; + return items; +} + + +void ui_listview_update(UiEvent *event, UiListView *view) { + int count; + XmStringTable items = ui_create_stringlist( + view->list->value, + view->getvalue, + &count); + + XtVaSetValues( + view->widget, + XmNitems, count == 0 ? NULL : items, + XmNitemCount, + count, + NULL); + + for (int i=0;ievent.obj; + e.window = event->event.obj->window; + e.document = event->event.obj->ctx->document; + UiList *list = event->var->value; + e.eventdata = list->get(list, cbs->item_position - 1); + e.intval = cbs->item_position - 1; + event->event.callback(&e, event->event.userdata); +} + + +/* --------------------------- ComboBox --------------------------- */ + +UIWIDGET ui_combobox_str(UiObject *obj, UiList *list, ui_callback f, void *udata) { + return ui_combobox(obj, list, ui_strmodel_getvalue, f, udata); +} + +UIWIDGET ui_combobox(UiObject *obj, UiList *list, ui_getvaluefunc getvalue, ui_callback f, void *udata) { + UiVar *var = malloc(sizeof(UiVar)); + var->value = list; + var->type = UI_VAR_SPECIAL; + return ui_combobox_var(obj, var, getvalue, f, udata); +} + +UIWIDGET ui_combobox_nv(UiObject *obj, char *varname, ui_getvaluefunc getvalue, ui_callback f, void *udata) { + UiVar *var = uic_create_var(obj->ctx, varname, UI_VAR_LIST); + if(var) { + UiListVar *value = var->value; + return ui_combobox_var(obj, var, getvalue, f, udata); + } else { + // TODO: error + } + return NULL; +} + +UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata) { + UiListView *listview = 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; +} diff --git a/ui/motif/list.h b/ui/motif/list.h new file mode 100644 index 0000000..35f97ea --- /dev/null +++ b/ui/motif/list.h @@ -0,0 +1,64 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIST_H +#define LIST_H + +#include "toolkit.h" +#include "../ui/tree.h" +#include "../common/context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiListView { + Widget widget; + UiVar *list; + ui_getvaluefunc getvalue; +} UiListView; + +typedef struct UiListViewEventData { + UiEventData event; + UiVar *var; +} UiListViewEventData; + +void* ui_strmodel_getvalue(void *elm, int column); + +XmStringTable ui_create_stringlist(UiList *list, ui_getvaluefunc getvalue, int *count); +void ui_listview_update(UiEvent *event, UiListView *view); +void ui_list_selection_callback (Widget widget, UiListViewEventData *event, XtPointer data); + +UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); + +#ifdef __cplusplus +} +#endif + +#endif /* LIST_H */ + diff --git a/ui/motif/menu.c b/ui/motif/menu.c new file mode 100644 index 0000000..1b27a62 --- /dev/null +++ b/ui/motif/menu.c @@ -0,0 +1,484 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "menu.h" +#include "button.h" +#include "toolkit.h" +#include "stock.h" +#include "container.h" +#include "../common/context.h" +#include "../ui/window.h" + +#include +#include + +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;ioldcount;i++) { + XtDestroyWidget(children[list->index + i]); + } + } + + char *str = ui_list_first(list->list); + if(str) { + // add separator + XtSetArg(args[0], XmNpositionIndex, list->index); + Widget s = XmCreateSeparatorGadget (list->menu, "menu_separator", args, 1); + XtManageChild(s); + } + int i = 1; + while(str) { + XmString label = XmStringCreateLocalized(str); + XtSetArg(args[0], XmNlabelString, label); + XtSetArg(args[1], XmNpositionIndex, list->index + i); + + Widget mitem = XtCreateManagedWidget( + "menubutton", + xmPushButtonWidgetClass, + list->menu, + args, + 2); + XmStringFree(label); + + if(list->callback) { + // TODO: use mempool + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = list->object; + event->userdata = list->userdata; + event->callback = list->callback; + event->value = i - 1; + + XtAddCallback( + mitem, + XmNactivateCallback, + (XtCallbackProc)ui_push_button_callback, + event); + } + + str = ui_list_next(list->list); + i++; + } + + list->oldcount = i; +} + +void ui_menu_event_wrapper(Widget widget, XtPointer udata, XtPointer cdata) { + UiEventData *event = udata; + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.intval = 0; + event->callback(&e, event->userdata); +} + + +/* + * widget menu functions + */ + +static void ui_popup_handler(Widget widget, XtPointer data, XEvent *event, Boolean *c) { + Widget menu = data; + XmMenuPosition(menu, (XButtonPressedEvent *)event); + XtManageChild(menu); + + *c = FALSE; +} + +UIMENU ui_contextmenu(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + if(ct->current) { + return ui_contextmenu_w(obj, ct->current); + } else { + return NULL; // TODO: warn + } +} + +UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) { + UiContainer *ct = uic_get_current_container(obj); + + Widget menu = XmCreatePopupMenu(widget, "popup_menu", NULL, 0); + ct->menu = menu; + + XtAddEventHandler(widget, ButtonPressMask, FALSE, ui_popup_handler, menu); + + return menu; +} + +void ui_contextmenu_popup(UIMENU menu) { + +} + +void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) { + ui_widget_menuitem_gr(obj, label, f, userdata, -1); +} + +void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) { + UiContainer *ct = uic_get_current_container(obj); + if(!ct->menu) { + return; + } + + // add groups + 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); +} diff --git a/ui/motif/menu.h b/ui/motif/menu.h new file mode 100644 index 0000000..db75775 --- /dev/null +++ b/ui/motif/menu.h @@ -0,0 +1,73 @@ +/* + * 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 */ + diff --git a/ui/motif/objs.mk b/ui/motif/objs.mk new file mode 100644 index 0000000..179deac --- /dev/null +++ b/ui/motif/objs.mk @@ -0,0 +1,49 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2012 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +MOTIF_SRC_DIR = ui/motif/ +MOTIF_OBJPRE = $(OBJ_DIR)$(MOTIF_SRC_DIR) + +MOTIFOBJ = toolkit.o +MOTIFOBJ += stock.o +MOTIFOBJ += window.o +MOTIFOBJ += container.o +MOTIFOBJ += menu.o +MOTIFOBJ += toolbar.o +MOTIFOBJ += button.o +MOTIFOBJ += label.o +MOTIFOBJ += text.o +MOTIFOBJ += list.o +MOTIFOBJ += tree.o +MOTIFOBJ += graphics.o +MOTIFOBJ += range.o +MOTIFOBJ += dnd.o +MOTIFOBJ += image.o + +TOOLKITOBJS += $(MOTIFOBJ:%=$(MOTIF_OBJPRE)%) +TOOLKITSOURCE += $(MOTIFOBJ:%.o=motif/%.c) diff --git a/ui/motif/range.c b/ui/motif/range.c new file mode 100644 index 0000000..91cb045 --- /dev/null +++ b/ui/motif/range.c @@ -0,0 +1,137 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "range.h" +#include "container.h" +#include "../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; +} diff --git a/ui/motif/range.h b/ui/motif/range.h new file mode 100644 index 0000000..1c6da04 --- /dev/null +++ b/ui/motif/range.h @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RANGE_H +#define RANGE_H + +#include "toolkit.h" +#include "../ui/range.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void ui_scrollbar_callback(Widget scrollbar, UiEventData *event, XtPointer cdata); +double ui_scrollbar_get(UiRange *range); +void ui_scrollbar_set(UiRange *range, double value); +void ui_scrollbar_setrange(UiRange *range, double min, double max); +void ui_scrollbar_setextent(UiRange *range, double extent); + + +#ifdef __cplusplus +} +#endif + +#endif /* RANGE_H */ + diff --git a/ui/motif/stock.c b/ui/motif/stock.c new file mode 100644 index 0000000..4866d54 --- /dev/null +++ b/ui/motif/stock.c @@ -0,0 +1,76 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "stock.h" +#include "../ui/properties.h" +#include + +static CxMap *stock_items; + +void ui_stock_init() { + stock_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 64); + + ui_add_stock_item(UI_STOCK_NEW, "New", "CtrlN", "Ctrl+N", NULL); + ui_add_stock_item(UI_STOCK_OPEN, "Open", "CtrlO", "Ctrl+O", NULL); + ui_add_stock_item(UI_STOCK_SAVE, "Save", "CtrlS", "Ctrl+S", NULL); + ui_add_stock_item(UI_STOCK_SAVE_AS, "Save as ...", NULL, NULL, NULL); + ui_add_stock_item(UI_STOCK_REVERT_TO_SAVED, "Revert to saved", NULL, NULL, NULL); + ui_add_stock_item(UI_STOCK_CLOSE, "Close", "CtrlW", "Ctrl+W", NULL); + ui_add_stock_item(UI_STOCK_UNDO, "Undo", "CtrlZ", "Ctrl+Z", NULL); + ui_add_stock_item(UI_STOCK_REDO, "Redo", NULL, NULL, NULL); + ui_add_stock_item(UI_STOCK_GO_BACK, "Back", NULL, NULL, NULL); + ui_add_stock_item(UI_STOCK_GO_FORWARD, "Forward", NULL, NULL, NULL); + ui_add_stock_item(UI_STOCK_CUT, "Cut", "CtrlX", "Ctrl+X", NULL); + ui_add_stock_item(UI_STOCK_COPY, "Copy", "CtrlC", "Ctrl+C", NULL); + ui_add_stock_item(UI_STOCK_PASTE, "Paste", "CtrlV", "Ctrl+V", NULL); + ui_add_stock_item(UI_STOCK_DELETE, "Delete", NULL, NULL, NULL); +} + +void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon) { + UiStockItem *i = malloc(sizeof(UiStockItem)); + i->label = label; + i->accelerator = accelerator; + i->accelerator_label = accelerator_label; + // TODO: icon + + 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; +} diff --git a/ui/motif/stock.h b/ui/motif/stock.h new file mode 100644 index 0000000..03e643f --- /dev/null +++ b/ui/motif/stock.h @@ -0,0 +1,56 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef STOCK_H +#define STOCK_H + +#include "../ui/stock.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiStockItem { + char *label; + char *accelerator; + char *accelerator_label; + // TODO: icon +} UiStockItem; + +void ui_stock_init(); + +void ui_add_stock_item(char *id, char *label, char *accelerator, char *accelerator_label, void *icon); + +UiStockItem* ui_get_stock_item(char *id); + +#ifdef __cplusplus +} +#endif + +#endif /* STOCK_H */ + diff --git a/ui/motif/text.c b/ui/motif/text.c new file mode 100644 index 0000000..1e98b84 --- /dev/null +++ b/ui/motif/text.c @@ -0,0 +1,507 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "text.h" +#include "container.h" + + +UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) { + UiContainer *ct = uic_get_current_container(obj); + int n = 0; + Arg args[16]; + + //XtSetArg(args[n], XmNeditable, TRUE); + //n++; + XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); + n++; + + Widget parent = ct->prepare(ct, args, &n, TRUE); + Widget text_area = XmCreateScrolledText(parent, "text_area", args, n); + ct->add(ct, XtParent(text_area)); + XtManageChild(text_area); + + UiTextArea *uitext = 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 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; +} + diff --git a/ui/motif/text.h b/ui/motif/text.h new file mode 100644 index 0000000..d40ce5c --- /dev/null +++ b/ui/motif/text.h @@ -0,0 +1,93 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEXT_H +#define TEXT_H + +#include "../ui/text.h" +#include "toolkit.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define UI_TEXTBUF_INSERT 0 +#define UI_TEXTBUF_DELETE 1 +typedef struct UiTextBufOp 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 */ + diff --git a/ui/motif/toolbar.c b/ui/motif/toolbar.c new file mode 100644 index 0000000..35914f9 --- /dev/null +++ b/ui/motif/toolbar.c @@ -0,0 +1,381 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "toolbar.h" +#include "button.h" +#include "stock.h" +#include "list.h" + +#include +#include +#include + +#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) { + +} diff --git a/ui/motif/toolbar.h b/ui/motif/toolbar.h new file mode 100644 index 0000000..777adf3 --- /dev/null +++ b/ui/motif/toolbar.h @@ -0,0 +1,105 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TOOLBAR_H +#define TOOLBAR_H + +#include "../ui/toolbar.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiToolItemI UiToolItemI; +typedef struct UiToolItem UiToolItem; +typedef struct UiStToolItem UiStToolItem; + +typedef struct UiToolbarComboBox UiToolbarComboBox; +typedef struct UiToolbarComboBoxNV UiToolbarComboBoxNV; + +typedef void(*ui_toolbar_add_f)(Widget, UiToolItemI*, UiObject*); + +struct UiToolItemI { + ui_toolbar_add_f add_to; +}; + +struct UiToolItem { + UiToolItemI item; + char *label; + void *image; + ui_callback callback; + void *userdata; + 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 */ + diff --git a/ui/motif/toolkit.c b/ui/motif/toolkit.c new file mode 100644 index 0000000..f975b4f --- /dev/null +++ b/ui/motif/toolkit.c @@ -0,0 +1,302 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "toolkit.h" +#include "toolbar.h" +#include "stock.h" +#include "../common/document.h" +#include "../common/properties.h" +#include + +static XtAppContext app; +static Display *display; +static Widget active_window; +static char *application_name; + +static ui_callback startup_func; +static void *startup_data; +static ui_callback open_func; +void *open_data; +static ui_callback exit_func; +void *exit_data; + +static ui_callback appclose_fnc; +static void *appclose_udata; + +static int is_toplevel_realized = 0; + +int event_pipe[2]; + + +static String fallback[] = { + //"*fontList: -dt-interface system-medium-r-normal-s*utf*:", + "*text_area*renderTable: f1", + "*f1*fontType: FONT_IS_XFT", + "*f1*fontName: Monospace", + "*f1*fontSize: 11", + "*renderTable: rt", + "*rt*fontType: FONT_IS_XFT", + "*rt*fontName: Sans", + "*rt*fontSize: 11", + NULL +}; + +void input_proc(XtPointer data, int *source, XtInputId *iid) { + void *ptr; + read(event_pipe[0], &ptr, sizeof(void*)); +} + +void ui_init(char *appname, int argc, char **argv) { + application_name = appname; + + XtToolkitInitialize(); + XtSetLanguageProc(NULL, NULL, NULL); + app = XtCreateApplicationContext(); + XtAppSetFallbackResources(app, fallback); + + display = XtOpenDisplay(app, NULL, appname, appname, NULL, 0, &argc, argv); + char **missing = NULL; + int nm = 0; + char *def = NULL; + XCreateFontSet(display, "-dt-interface system-medium-r-normal-s*utf*", &missing, &nm, &def); + + uic_docmgr_init(); + ui_toolbar_init(); + ui_stock_init(); + + uic_load_app_properties(); + + if(pipe(event_pipe)) { + fprintf(stderr, "UiError: Cannot create event pipe\n"); + exit(-1); + } + XtAppAddInput( + app, + event_pipe[0], + (XtPointer)XtInputReadMask, + input_proc, + NULL); +} + +char* ui_appname() { + return application_name; +} + +Display* ui_get_display() { + return display; +} + +void ui_onstartup(ui_callback f, void *userdata) { + startup_func = f; + startup_data = userdata; +} + +void ui_onopen(ui_callback f, void *userdata) { + open_func = f; + open_data = userdata; +} + +void ui_onexit(ui_callback f, void *userdata) { + exit_func = f; + exit_data = userdata; +} + +void ui_main() { + if(startup_func) { + startup_func(NULL, startup_data); + } + XtAppMainLoop(app); + if(exit_func) { + exit_func(NULL, exit_data); + } + uic_store_app_properties(); +} + +void ui_exit_mainloop() { + XtAppSetExitFlag(app); +} + +void ui_secondary_event_loop(int *loop) { + while(*loop && !XtAppGetExitFlag(app)) { + XEvent event; + XtAppNextEvent(app, &event); + XtDispatchEvent(&event); + } +} + +void ui_show(UiObject *obj) { + uic_check_group_widgets(obj->ctx); + XtRealizeWidget(obj->widget); + ui_window_dark_theme(XtDisplay(obj->widget), XtWindow(obj->widget)); // TODO: if +} + +// implemented in window.c +//void ui_close(UiObject *obj) + +void ui_set_enabled(UIWIDGET widget, int enabled) { + XtSetSensitive(widget, enabled); +} + +void ui_set_show_all(UIWIDGET widget, int value) { + if(!value) { + XtUnmanageChild(widget); + } +} + +void ui_set_visible(UIWIDGET widget, int visible) { + if(visible) { + XtManageChild(widget); + } else { + XtUnmanageChild(widget); + } +} + +static Boolean ui_job_finished(void *data) { + printf("WorkProc\n"); + UiJob *job = data; + + UiEvent event; + event.obj = job->obj; + event.window = job->obj->window; + event.document = job->obj->ctx->document; + event.intval = 0; + event.eventdata = NULL; + + job->finish_callback(&event, job->finish_data); + free(job); + return TRUE; +} + +static void* ui_jobthread(void *data) { + UiJob *job = data; + int result = job->job_func(job->job_data); + if(!result) { + printf("XtAppAddWorkProc\n"); + write(event_pipe[1], &job, sizeof(void*)); // hack + XtAppAddWorkProc(app, ui_job_finished, job); + + } + 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); +} diff --git a/ui/motif/toolkit.h b/ui/motif/toolkit.h new file mode 100644 index 0000000..b14cbd9 --- /dev/null +++ b/ui/motif/toolkit.h @@ -0,0 +1,74 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TOOLKIT_H +#define TOOLKIT_H + +#include +#include "../ui/toolkit.h" +#include "../common/context.h" +#include "../common/object.h" + +#ifdef __cplusplus +extern "C" { +#endif + +Display* ui_get_display(); + +typedef struct UiEventData { + UiObject *obj; + ui_callback callback; + void *userdata; + int value; +} UiEventData; + +typedef struct UiJob { + UiObject *obj; + ui_threadfunc job_func; + void *job_data; + ui_callback finish_callback; + void *finish_data; +} UiJob; + +typedef enum UiOrientation UiOrientation; +enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL }; + +void ui_exit_mainloop(); + +void ui_set_active_window(Widget w); +Widget ui_get_active_window(); + +void ui_secondary_event_loop(int *loop); +void ui_window_dark_theme(Display *dp, Window window); + +#ifdef __cplusplus +} +#endif + +#endif /* TOOLKIT_H */ + diff --git a/ui/motif/tree.c b/ui/motif/tree.c new file mode 100644 index 0000000..1cac8a9 --- /dev/null +++ b/ui/motif/tree.c @@ -0,0 +1,328 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "tree.h" + +#include "container.h" +#include "../common/object.h" +#include "../common/context.h" +#include +#include +#include + +UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb) { + // TODO: check if modelinfo is complete + + Arg args[32]; + int n = 0; + + // create scrolled window + UiContainer *ct = uic_get_current_container(obj); + Widget parent = ct->prepare(ct, args, &n, TRUE); + + XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC); + n++; + XtSetArg(args[n], XmNshadowThickness, 0); + n++; + Widget scrollw = XmCreateScrolledWindow(parent, "scroll_win", args, n); + ct->add(ct, scrollw); + XtManageChild(scrollw); + + // create table headers + XmStringTable header = (XmStringTable)XtMalloc( + model->columns * sizeof(XmString)); + for(int i=0;icolumns;i++) { + header[i] = XmStringCreateLocalized(model->titles[i]); + } + n = 0; + XtSetArg(args[n], XmNdetailColumnHeading, header); + n++; + XtSetArg(args[n], XmNdetailColumnHeadingCount, model->columns); + n++; + + // set res + XtSetArg(args[n], XmNlayoutType, XmDETAIL); + n++; + XtSetArg(args[n], XmNentryViewType, XmSMALL_ICON); + n++; + XtSetArg(args[n], XmNselectionPolicy, XmSINGLE_SELECT); + n++; + XtSetArg(args[n], XmNwidth, 600); + n++; + + // create widget + //UiContainer *ct = uic_get_current_container(obj); + //Widget parent = ct->add(ct, args, &n); + + Widget container = XmCreateContainer(scrollw, "table", args, n); + XtManageChild(container); + + // add callbacks + UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); + event->obj = obj; + event->activate = cb.activate; + event->selection = cb.selection; + event->userdata = cb.userdata; + event->last_selection = NULL; + if(cb.selection) { + XtAddCallback( + container, + XmNselectionCallback, + (XtCallbackProc)ui_table_select_callback, + event); + } + if(cb.activate) { + XtAddCallback( + container, + XmNdefaultActionCallback, + (XtCallbackProc)ui_table_action_callback, + event); + } + + // add initial data + UiList *list = var->value; + void *data = list->first(list); + int width = 0; + while(data) { + int w = ui_add_icon_gadget(container, model, data); + if(w > width) { + width = w; + } + data = list->next(list); + } + + UiTableView *tableview = 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;icolumns;i++) { + XmStringFree(header[i]); + } + XtFree((char*)header); + + return scrollw; +} + +UIWIDGET ui_table(UiObject *obj, UiList *data, UiModel *model, UiListCallbacks cb) { + UiVar *var = malloc(sizeof(UiVar)); + var->value = data; + var->type = UI_VAR_SPECIAL; + return ui_table_var(obj, var, model, cb); +} + +void ui_table_update(UiEvent *event, UiTableView *view) { + // clear container + Widget *children; + int nc; + + XtVaGetValues( + view->widget, + XmNchildren, + &children, + XmNnumChildren, + &nc, + NULL); + + for(int i=0;ivar->value; + + void *data = list->first(list); + int width = 0; + while(data) { + int w = ui_add_icon_gadget(view->widget, view->model, data); + if(w > width) { + width = w; + } + data = list->next(list); + } + +} + +#define UI_COL_CHAR_WIDTH 12 + +int ui_add_icon_gadget(Widget container, UiModel *model, void *data) { + int width = 50; + + if(model->columns == 0) { + return width; + } + + XmString label = NULL; + Arg args[8]; + Boolean f; + // first column + if(model->types[0] != 12345678) { // TODO: icon/label type + char *str = ui_type_to_string( + model->types[0], + model->getvalue(data, 0), + &f); + + // column width + width += strlen(str) * UI_COL_CHAR_WIDTH; + + + XmString label = XmStringCreateLocalized(str); + XtSetArg(args[0], XmNlabelString, label); + if(f) { + free(str); + } + } else { + // TODO + } + + // remaining columns are the icon gadget details + XmStringTable details = (XmStringTable)XtMalloc( + (model->columns - 1) * sizeof(XmString)); + for(int i=1;icolumns;i++) { + char *str = ui_type_to_string( + model->types[i], + model->getvalue(data, i), + &f); + + // column width + width += strlen(str) * UI_COL_CHAR_WIDTH; + + details[i - 1] = XmStringCreateLocalized(str); + if(f) { + free(str); + } + } + XtSetArg(args[1], XmNdetail, details); + XtSetArg(args[2], XmNdetailCount, model->columns - 1); + XtSetArg(args[3], XmNshadowThickness, 0); + // create widget + Widget item = XmCreateIconGadget(container, "table_item", args, 4); + XtManageChild(item); + + // cleanup + XmStringFree(label); + for(int i=0;icolumns-1;i++) { + XmStringFree(details[i]); + } + XtFree((char*)details); + + return width; +} + +char* ui_type_to_string(UiModelType type, void *data, Boolean *free) { + switch(type) { + case UI_STRING: *free = FALSE; return data; + case UI_INTEGER: { + *free = TRUE; + int *val = data; + 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;icount;i++) { + int index; + XtVaGetValues(xs->selected_items[i], XmNpositionIndex, &index, NULL); + selection->rows[i] = index; + } + return selection; +} + +Boolean ui_compare_list_selection(UiListSelection *s1, UiListSelection *s2) { + if(!s1 || !s2) { + return FALSE; + } + if(s1->count != s2->count) { + return FALSE; + } + for(int i=0;icount;i++) { + if(s1->rows[i] != s2->rows[i]) { + return FALSE; + } + } + return TRUE; +} diff --git a/ui/motif/tree.h b/ui/motif/tree.h new file mode 100644 index 0000000..68e5b06 --- /dev/null +++ b/ui/motif/tree.h @@ -0,0 +1,76 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TREE_H +#define TREE_H + +#include "../ui/tree.h" +#include "../common/context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiTreeEventData { + UiObject *obj; + ui_callback activate; + ui_callback selection; + void *userdata; + UiListSelection *last_selection; +} UiTreeEventData; + +typedef struct UiTableView { + Widget widget; + UiVar *var; + UiModel *model; +} UiTableView; + +void ui_table_update(UiEvent *event, UiTableView *view); +int ui_add_icon_gadget(Widget container, UiModel *model, void *data); +char* ui_type_to_string(UiModelType type, void *data, Boolean *free); + +void ui_table_action_callback( + Widget widget, + UiTreeEventData *event, + XmContainerSelectCallbackStruct *sel); +void ui_table_select_callback( + Widget widget, + UiTreeEventData *event, + XmContainerSelectCallbackStruct *sel); + +UiListSelection* ui_list_selection(XmContainerSelectCallbackStruct *xs); + +Boolean ui_compare_list_selection(UiListSelection *s1, UiListSelection *s2); + + +#ifdef __cplusplus +} +#endif + +#endif /* TREE_H */ + diff --git a/ui/motif/window.c b/ui/motif/window.c new file mode 100644 index 0000000..b2f1504 --- /dev/null +++ b/ui/motif/window.c @@ -0,0 +1,211 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "toolkit.h" +#include "menu.h" +#include "toolbar.h" +#include "container.h" +#include "../ui/window.h" +#include "../common/context.h" + +#include + +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); +} diff --git a/ui/qt/Makefile b/ui/qt/Makefile new file mode 100644 index 0000000..a1e7f99 --- /dev/null +++ b/ui/qt/Makefile @@ -0,0 +1,41 @@ +# +# 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: + diff --git a/ui/qt/button.cpp b/ui/qt/button.cpp new file mode 100644 index 0000000..04b84a4 --- /dev/null +++ b/ui/qt/button.cpp @@ -0,0 +1,90 @@ +/* + * 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; + } +} diff --git a/ui/qt/button.h b/ui/qt/button.h new file mode 100644 index 0000000..1761333 --- /dev/null +++ b/ui/qt/button.h @@ -0,0 +1,47 @@ +/* + * 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 +#include +#include + +extern "C" { + +int ui_radiobutton_get(UiInteger *value); + +void ui_radiobutton_set(UiInteger *value, int i); + +} + +#endif /* BUTTON_H */ + diff --git a/ui/qt/container.cpp b/ui/qt/container.cpp new file mode 100644 index 0000000..9cb27d8 --- /dev/null +++ b/ui/qt/container.cpp @@ -0,0 +1,256 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "container.h" + +#include +#include + + +/* -------------------- 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; +} diff --git a/ui/qt/container.h b/ui/qt/container.h new file mode 100644 index 0000000..1a1c353 --- /dev/null +++ b/ui/qt/container.h @@ -0,0 +1,119 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#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 */ + diff --git a/ui/qt/graphics.cpp b/ui/qt/graphics.cpp new file mode 100644 index 0000000..d9a03c1 --- /dev/null +++ b/ui/qt/graphics.cpp @@ -0,0 +1,151 @@ +/* + * 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); +} + diff --git a/ui/qt/graphics.h b/ui/qt/graphics.h new file mode 100644 index 0000000..b4c5f0b --- /dev/null +++ b/ui/qt/graphics.h @@ -0,0 +1,67 @@ +/* + * 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 +#include +#include +#include + +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 */ + diff --git a/ui/qt/label.cpp b/ui/qt/label.cpp new file mode 100644 index 0000000..85bfc57 --- /dev/null +++ b/ui/qt/label.cpp @@ -0,0 +1,51 @@ +/* + * 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; +} diff --git a/ui/qt/label.h b/ui/qt/label.h new file mode 100644 index 0000000..8dd1b68 --- /dev/null +++ b/ui/qt/label.h @@ -0,0 +1,36 @@ +/* + * 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 + +#endif /* LABEL_H */ + diff --git a/ui/qt/menu.cpp b/ui/qt/menu.cpp new file mode 100644 index 0000000..6482e01 --- /dev/null +++ b/ui/qt/menu.cpp @@ -0,0 +1,425 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#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())); +} diff --git a/ui/qt/menu.h b/ui/qt/menu.h new file mode 100644 index 0000000..7bffb08 --- /dev/null +++ b/ui/qt/menu.h @@ -0,0 +1,134 @@ +/* + * 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 + +#include +#include +#include +#include + +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 */ + diff --git a/ui/qt/model.cpp b/ui/qt/model.cpp new file mode 100644 index 0000000..7cdf63e --- /dev/null +++ b/ui/qt/model.cpp @@ -0,0 +1,170 @@ +/* + * 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; +} diff --git a/ui/qt/model.h b/ui/qt/model.h new file mode 100644 index 0000000..b74b186 --- /dev/null +++ b/ui/qt/model.h @@ -0,0 +1,91 @@ +/* + * 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 +#include +#include +#include +#include +#include + +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 */ + diff --git a/ui/qt/objs.mk b/ui/qt/objs.mk new file mode 100644 index 0000000..50ac471 --- /dev/null +++ b/ui/qt/objs.mk @@ -0,0 +1,36 @@ +# +# 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) + diff --git a/ui/qt/qt4.pro b/ui/qt/qt4.pro new file mode 100644 index 0000000..efc299d --- /dev/null +++ b/ui/qt/qt4.pro @@ -0,0 +1,63 @@ +# +# 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 + diff --git a/ui/qt/stock.cpp b/ui/qt/stock.cpp new file mode 100644 index 0000000..43a3ea9 --- /dev/null +++ b/ui/qt/stock.cpp @@ -0,0 +1,77 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "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; +} + + diff --git a/ui/qt/stock.h b/ui/qt/stock.h new file mode 100644 index 0000000..f183f6e --- /dev/null +++ b/ui/qt/stock.h @@ -0,0 +1,49 @@ +/* + * 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 */ + diff --git a/ui/qt/text.cpp b/ui/qt/text.cpp new file mode 100644 index 0000000..3d7027e --- /dev/null +++ b/ui/qt/text.cpp @@ -0,0 +1,195 @@ +/* + * 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; +} diff --git a/ui/qt/text.h b/ui/qt/text.h new file mode 100644 index 0000000..3ca8fd2 --- /dev/null +++ b/ui/qt/text.h @@ -0,0 +1,56 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEXT_H +#define TEXT_H + +#include "toolkit.h" +#include "../ui/text.h" +#include +#include + +// 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 */ + diff --git a/ui/qt/toolbar.cpp b/ui/qt/toolbar.cpp new file mode 100644 index 0000000..c810e8c --- /dev/null +++ b/ui/qt/toolbar.cpp @@ -0,0 +1,165 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "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; +} diff --git a/ui/qt/toolbar.h b/ui/qt/toolbar.h new file mode 100644 index 0000000..77f059e --- /dev/null +++ b/ui/qt/toolbar.h @@ -0,0 +1,83 @@ +/* + * 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 +#include + +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 */ + diff --git a/ui/qt/toolkit.cpp b/ui/qt/toolkit.cpp new file mode 100644 index 0000000..13c8f29 --- /dev/null +++ b/ui/qt/toolkit.cpp @@ -0,0 +1,121 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "toolkit.h" +#include "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); +} diff --git a/ui/qt/toolkit.h b/ui/qt/toolkit.h new file mode 100644 index 0000000..f767ef6 --- /dev/null +++ b/ui/qt/toolkit.h @@ -0,0 +1,54 @@ +/* + * 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 + +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 */ + diff --git a/ui/qt/tree.cpp b/ui/qt/tree.cpp new file mode 100644 index 0000000..d62b20c --- /dev/null +++ b/ui/qt/tree.cpp @@ -0,0 +1,142 @@ +/* + * 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 +#include +#include + + +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; +} + diff --git a/ui/qt/tree.h b/ui/qt/tree.h new file mode 100644 index 0000000..54dd0cd --- /dev/null +++ b/ui/qt/tree.h @@ -0,0 +1,46 @@ +/* + * 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 + +class UiTableView { +public: + QTreeView *widget; + TableModel *model; +}; + +extern "C" void ui_table_update(UiEvent *event, UiTableView *view); + +#endif /* TREE_H */ + diff --git a/ui/qt/window.cpp b/ui/qt/window.cpp new file mode 100644 index 0000000..119ea3e --- /dev/null +++ b/ui/qt/window.cpp @@ -0,0 +1,95 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "../common/context.h" + +#include "window.h" +#include "menu.h" +#include "toolbar.h" +#include "container.h" + +#include +#include + +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; + } +} diff --git a/ui/qt/window.h b/ui/qt/window.h new file mode 100644 index 0000000..19b6c3f --- /dev/null +++ b/ui/qt/window.h @@ -0,0 +1,39 @@ +/* + * 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 + + + +#endif /* WINDOW_H */ + diff --git a/ui/ui/button.h b/ui/ui/button.h new file mode 100644 index 0000000..17407b7 --- /dev/null +++ b/ui/ui/button.h @@ -0,0 +1,102 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#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 */ + diff --git a/ui/ui/container.h b/ui/ui/container.h new file mode 100644 index 0000000..ec35c9c --- /dev/null +++ b/ui/ui/container.h @@ -0,0 +1,239 @@ +/* + * 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 */ + diff --git a/ui/ui/display.h b/ui/ui/display.h new file mode 100644 index 0000000..958f6f9 --- /dev/null +++ b/ui/ui/display.h @@ -0,0 +1,133 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * 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 */ + diff --git a/ui/ui/dnd.h b/ui/ui/dnd.h new file mode 100644 index 0000000..4c6913f --- /dev/null +++ b/ui/ui/dnd.h @@ -0,0 +1,57 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef 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 */ + diff --git a/ui/ui/entry.h b/ui/ui/entry.h new file mode 100644 index 0000000..d6fc039 --- /dev/null +++ b/ui/ui/entry.h @@ -0,0 +1,76 @@ +/* + * 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 */ + diff --git a/ui/ui/graphics.h b/ui/ui/graphics.h new file mode 100644 index 0000000..6eb0daf --- /dev/null +++ b/ui/ui/graphics.h @@ -0,0 +1,73 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_GRAPHICS_H +#define UI_GRAPHICS_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiGraphics UiGraphics; +typedef struct UiTextLayout UiTextLayout; + +typedef void(*ui_drawfunc)(UiEvent*, UiGraphics*, void*); + +struct UiGraphics { + int width; + int height; +}; + +UIWIDGET ui_drawingarea(UiObject *obj, ui_drawfunc f, void *userdata); +void ui_drawingarea_mousehandler(UiObject *obj, UIWIDGET widget, ui_callback f, void *u); +void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height); +void ui_drawingarea_redraw(UIWIDGET drawingarea); + +// text layout +UiTextLayout* ui_text(UiGraphics *g); +void ui_text_free(UiTextLayout *text); +void ui_text_setstring(UiTextLayout *layout, char *str); +void ui_text_setstringl(UiTextLayout *layout, char *str, int len); +void ui_text_setfont(UiTextLayout *layout, char *font, int size); +void ui_text_getsize(UiTextLayout *layout, int *width, int *height); +void ui_text_setwidth(UiTextLayout *layout, int width); + +// drawing functions +void ui_graphics_color(UiGraphics *g, int red, int green, int blue); +void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2); +void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, int fill); +void ui_draw_text(UiGraphics *g, int x, int y, UiTextLayout *text); + +#ifdef __cplusplus +} +#endif + +#endif /* UI_GRAPHICS_H */ + diff --git a/ui/ui/icons.h b/ui/ui/icons.h new file mode 100644 index 0000000..cd4ef6c --- /dev/null +++ b/ui/ui/icons.h @@ -0,0 +1,89 @@ +/* + * 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 */ + diff --git a/ui/ui/image.h b/ui/ui/image.h new file mode 100644 index 0000000..c26ec3c --- /dev/null +++ b/ui/ui/image.h @@ -0,0 +1,69 @@ +/* + * 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 */ + diff --git a/ui/ui/menu.h b/ui/ui/menu.h new file mode 100644 index 0000000..c01fa68 --- /dev/null +++ b/ui/ui/menu.h @@ -0,0 +1,111 @@ +/* + * 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 */ + diff --git a/ui/ui/properties.h b/ui/ui/properties.h new file mode 100644 index 0000000..5c985d9 --- /dev/null +++ b/ui/ui/properties.h @@ -0,0 +1,58 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef 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 */ + diff --git a/ui/ui/range.h b/ui/ui/range.h new file mode 100644 index 0000000..28ebb3f --- /dev/null +++ b/ui/ui/range.h @@ -0,0 +1,48 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_RANGE_H +#define UI_RANGE_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UIWIDGET ui_hscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata); +UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata); + + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_RANGE_H */ + diff --git a/ui/ui/stock.h b/ui/ui/stock.h new file mode 100644 index 0000000..ab2d13d --- /dev/null +++ b/ui/ui/stock.h @@ -0,0 +1,89 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_STOCK_H +#define UI_STOCK_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// motif stock ids +#if UI_MOTIF || UI_COCOA || UI_QT4 || UI_QT5 + +#define UI_STOCK_NEW "ui.stock.New" +#define UI_STOCK_OPEN "ui.stock.Open" +#define UI_STOCK_SAVE "ui.stock.Save" +#define UI_STOCK_SAVE_AS "ui.stock.SaveAs" +#define UI_STOCK_REVERT_TO_SAVED "ui.stock.RevertToSaved" +#define UI_STOCK_GO_BACK "ui.stock.GoBack" +#define UI_STOCK_GO_FORWARD "ui.stock.GoForward" +#define UI_STOCK_ADD "ui.stock.Add" +#define UI_STOCK_CLOSE "ui.stock.Close" + +#define UI_STOCK_UNDO "ui.stock.Undo" +#define UI_STOCK_REDO "ui.stock.Redo" +#define UI_STOCK_CUT "ui.stock.Cut" +#define UI_STOCK_COPY "ui.stock.Copy" +#define UI_STOCK_PASTE "ui.stock.Paste" +#define UI_STOCK_DELETE "ui.stock.Delete" + +#endif + +#if UI_GTK2 || UI_GTK3 + +#define UI_STOCK_NEW GTK_STOCK_NEW +#define UI_STOCK_OPEN GTK_STOCK_OPEN +#define UI_STOCK_SAVE GTK_STOCK_SAVE +#define UI_STOCK_SAVE_AS GTK_STOCK_SAVE_AS +#define UI_STOCK_REVERT_TO_SAVED GTK_STOCK_REVERT_TO_SAVED +#define UI_STOCK_UNDO GTK_STOCK_UNDO +#define UI_STOCK_REDO GTK_STOCK_REDO +#define UI_STOCK_GO_BACK GTK_STOCK_GO_BACK +#define UI_STOCK_GO_FORWARD GTK_STOCK_GO_FORWARD +#define UI_STOCK_ADD GTK_STOCK_ADD +#define UI_STOCK_CLOSE GTK_STOCK_CLOSE + +#define UI_STOCK_UNDO GTK_STOCK_UNDO +#define UI_STOCK_REDO GTK_STOCK_REDO +#define UI_STOCK_CUT GTK_STOCK_CUT +#define UI_STOCK_COPY GTK_STOCK_COPY +#define UI_STOCK_PASTE GTK_STOCK_PASTE +#define UI_STOCK_DELETE GTK_STOCK_DELETE + +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_STOCK_H */ + diff --git a/ui/ui/text.h b/ui/ui/text.h new file mode 100644 index 0000000..bbe300b --- /dev/null +++ b/ui/ui/text.h @@ -0,0 +1,142 @@ +/* + * 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 */ + diff --git a/ui/ui/toolbar.h b/ui/ui/toolbar.h new file mode 100644 index 0000000..0509321 --- /dev/null +++ b/ui/ui/toolbar.h @@ -0,0 +1,94 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_TOOLBAR_H +#define UI_TOOLBAR_H + +#include "toolkit.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +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 */ + diff --git a/ui/ui/toolkit.h b/ui/ui/toolkit.h new file mode 100644 index 0000000..9e3ab36 --- /dev/null +++ b/ui/ui/toolkit.h @@ -0,0 +1,579 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_TOOLKIT_H +#define UI_TOOLKIT_H + +#include + +#ifdef UI_COCOA + +#ifdef __OBJC__ +#import +#define UIWIDGET NSView* +#define UIMENU NSMenu* +#else +typedef void* UIWIDGET; +typedef void* UIMENU; +#endif + +#elif UI_GTK2 || UI_GTK3 || UI_GTK4 + +#include +#define UIWIDGET GtkWidget* + +#if UI_GTK2 || UI_GTK3 +#define UIMENU GtkMenu* +#endif +#ifdef UI_GTK4 +#define UIMENU GtkPopoverMenu* +#endif + +#define UI_GTK + +#ifdef UI_LIBADWAITA +#include +#endif + +#elif UI_MOTIF + +#include +#define UIWIDGET Widget +#define UIMENU Widget + +#elif defined(UI_QT4) || defined(UI_QT5) +#ifdef __cplusplus +#include +#include +#include +#define UIWIDGET QWidget* +#define UIMENU QMenu* +#else /* __cplusplus */ +#define UIWIDGET void* +#define UIMENU void* +#endif + +#elif UI_WINUI + +#define UIEXPORT __declspec(dllexport) + +#ifdef __cplusplus + +#include +#ifndef UI_WINUI_PCH +#include +#undef GetCurrentTime +#include +#include +#include +#endif + +class UiWindow { +public: + winrt::Microsoft::UI::Xaml::Window window { nullptr }; + + UiWindow(winrt::Microsoft::UI::Xaml::Window& win); +}; + +class UiWidget { +public: + winrt::Microsoft::UI::Xaml::UIElement uielement; + void* data1 = nullptr; + void* data2 = nullptr; + void* data3 = nullptr; + std::function Show; + UiWidget(winrt::Microsoft::UI::Xaml::UIElement& elm); + +}; + +#define UIWIDGET UiWidget* +#define UIWINDOW UiWindow* +#define UIMENU void* + +/* +// winrt::Microsoft::UI::Xaml::UIElement +#define UIWIDGET void* +// winrt::Microsoft::UI::Xaml::Window +#define UIWINDOW void* +#define UIMENU void* +*/ + +#else +#define UIWIDGET void* +#define UIWINDOW void* +#define UIMENU void* +#endif + + +#endif + +#ifndef TRUE +#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 */ + diff --git a/ui/ui/tree.h b/ui/ui/tree.h new file mode 100644 index 0000000..83ad1e8 --- /dev/null +++ b/ui/ui/tree.h @@ -0,0 +1,158 @@ +/* + * 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 */ + diff --git a/ui/ui/ui.h b/ui/ui/ui.h new file mode 100644 index 0000000..a0311e9 --- /dev/null +++ b/ui/ui/ui.h @@ -0,0 +1,51 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef 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 */ + diff --git a/ui/ui/window.h b/ui/ui/window.h new file mode 100644 index 0000000..ec568a9 --- /dev/null +++ b/ui/ui/window.h @@ -0,0 +1,98 @@ +/* + * 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 */ + diff --git a/ui/winui/App.idl b/ui/winui/App.idl new file mode 100644 index 0000000..73c7760 --- /dev/null +++ b/ui/winui/App.idl @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +namespace winui +{ +} diff --git a/ui/winui/App.xaml b/ui/winui/App.xaml new file mode 100644 index 0000000..c2677d7 --- /dev/null +++ b/ui/winui/App.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + Transparent + Transparent + + + diff --git a/ui/winui/App.xaml.cpp b/ui/winui/App.xaml.cpp new file mode 100644 index 0000000..b1a27ed --- /dev/null +++ b/ui/winui/App.xaml.cpp @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include "pch.h" + +#include "App.xaml.h" +#include "MainWindow.xaml.h" + +#include "toolkit.h" + +using namespace winrt; +using namespace Windows::Foundation; +using namespace Microsoft::UI::Xaml; +using namespace Microsoft::UI::Xaml::Controls; +using namespace Microsoft::UI::Xaml::Navigation; +using namespace winui; +using namespace winui::implementation; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +/// +/// Initializes the singleton application object. This is the first line of authored code +/// executed, and as such is the logical equivalent of main() or WinMain(). +/// +App::App() +{ + InitializeComponent(); + +#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION + UnhandledException([this](IInspectable const&, UnhandledExceptionEventArgs const& e) + { + if (IsDebuggerPresent()) + { + auto errorMessage = e.Message(); + __debugbreak(); + } + }); +#endif +} + +/// +/// Invoked when the application is launched. +/// +/// Details about the launch request and process. +void App::OnLaunched(LaunchActivatedEventArgs const&) +{ + ui_app_run_startup(); + //window = make(); + //window.Activate(); +} diff --git a/ui/winui/App.xaml.h b/ui/winui/App.xaml.h new file mode 100644 index 0000000..c2b407c --- /dev/null +++ b/ui/winui/App.xaml.h @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#pragma once + +#include "App.xaml.g.h" + +namespace winrt::winui::implementation +{ + struct App : AppT + { + App(); + + void OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&); + + private: + winrt::Microsoft::UI::Xaml::Window window{ nullptr }; + }; +} diff --git a/ui/winui/Assets/LockScreenLogo.scale-200.png b/ui/winui/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000..7440f0d Binary files /dev/null and b/ui/winui/Assets/LockScreenLogo.scale-200.png differ diff --git a/ui/winui/Assets/SplashScreen.scale-200.png b/ui/winui/Assets/SplashScreen.scale-200.png new file mode 100644 index 0000000..32f486a Binary files /dev/null and b/ui/winui/Assets/SplashScreen.scale-200.png differ diff --git a/ui/winui/Assets/Square150x150Logo.scale-200.png b/ui/winui/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000..53ee377 Binary files /dev/null and b/ui/winui/Assets/Square150x150Logo.scale-200.png differ diff --git a/ui/winui/Assets/Square44x44Logo.scale-200.png b/ui/winui/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000..f713bba Binary files /dev/null and b/ui/winui/Assets/Square44x44Logo.scale-200.png differ diff --git a/ui/winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/ui/winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000..dc9f5be Binary files /dev/null and b/ui/winui/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/ui/winui/Assets/StoreLogo.png b/ui/winui/Assets/StoreLogo.png new file mode 100644 index 0000000..a4586f2 Binary files /dev/null and b/ui/winui/Assets/StoreLogo.png differ diff --git a/ui/winui/Assets/Wide310x150Logo.scale-200.png b/ui/winui/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000..8b4a5d0 Binary files /dev/null and b/ui/winui/Assets/Wide310x150Logo.scale-200.png differ diff --git a/ui/winui/MainWindow.idl b/ui/winui/MainWindow.idl new file mode 100644 index 0000000..45d3303 --- /dev/null +++ b/ui/winui/MainWindow.idl @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +namespace winui +{ + [default_interface] + runtimeclass MainWindow : Microsoft.UI.Xaml.Window + { + MainWindow(); + Int32 MyProperty; + } +} diff --git a/ui/winui/MainWindow.xaml b/ui/winui/MainWindow.xaml new file mode 100644 index 0000000..064718f --- /dev/null +++ b/ui/winui/MainWindow.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/ui/winui/MainWindow.xaml.cpp b/ui/winui/MainWindow.xaml.cpp new file mode 100644 index 0000000..bb78427 --- /dev/null +++ b/ui/winui/MainWindow.xaml.cpp @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include "pch.h" +#include "MainWindow.xaml.h" +#if __has_include("MainWindow.g.cpp") +#include "MainWindow.g.cpp" +#endif + +using namespace winrt; +using namespace Microsoft::UI::Xaml; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace winrt::winui::implementation +{ + MainWindow::MainWindow() + { + InitializeComponent(); + //ExtendsContentIntoTitleBar(true); + //SetTitleBar(AppTitleBar()); + } + + int32_t MainWindow::MyProperty() + { + throw hresult_not_implemented(); + } + + void MainWindow::MyProperty(int32_t /* value */) + { + throw hresult_not_implemented(); + } + + void MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&) + { + + } +} diff --git a/ui/winui/MainWindow.xaml.h b/ui/winui/MainWindow.xaml.h new file mode 100644 index 0000000..2060aad --- /dev/null +++ b/ui/winui/MainWindow.xaml.h @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#pragma once + +#include "MainWindow.g.h" + +namespace winrt::winui::implementation +{ + struct MainWindow : MainWindowT + { + MainWindow(); + + int32_t MyProperty(); + void MyProperty(int32_t value); + + void myButton_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args); + }; +} + +namespace winrt::winui::factory_implementation +{ + struct MainWindow : MainWindowT + { + }; +} diff --git a/ui/winui/Package.appxmanifest b/ui/winui/Package.appxmanifest new file mode 100644 index 0000000..f273ce1 --- /dev/null +++ b/ui/winui/Package.appxmanifest @@ -0,0 +1,51 @@ + + + + + + + + + + winui + Olaf + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/winui/app.manifest b/ui/winui/app.manifest new file mode 100644 index 0000000..cddfee2 --- /dev/null +++ b/ui/winui/app.manifest @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + PerMonitorV2 + + + \ No newline at end of file diff --git a/ui/winui/appmenu.cpp b/ui/winui/appmenu.cpp new file mode 100644 index 0000000..5cb5274 --- /dev/null +++ b/ui/winui/appmenu.cpp @@ -0,0 +1,295 @@ +/* + * 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 "pch.h" + +#include "appmenu.h" + +#include +#include + +#include "../common/context.h" +#include "../common/object.h" + +#include "util.h" + + +using namespace winrt; +using namespace Microsoft::UI::Xaml; +using namespace Microsoft::UI::Xaml::Controls; +using namespace Microsoft::UI::Xaml::XamlTypeInfo; +using namespace Microsoft::UI::Xaml::Markup; +using namespace Windows::UI::Xaml::Interop; + +static void add_top_menu_widget(MenuBar &parent, int i, UiMenuItemI* item, UiObject* obj); + +static void add_menu_widget(winrt::Windows::Foundation::Collections::IVector parent, int i, UiMenuItemI* item, UiObject* obj); +static void add_menuitem_widget(winrt::Windows::Foundation::Collections::IVector parent, int i, UiMenuItemI* item, UiObject* obj); +static void add_menuseparator_widget(winrt::Windows::Foundation::Collections::IVector parent, int i, UiMenuItemI* item, UiObject* obj); +static void add_checkitem_widget(winrt::Windows::Foundation::Collections::IVector parent, int i, UiMenuItemI* item, UiObject* obj); +static void add_radioitem_widget(winrt::Windows::Foundation::Collections::IVector parent, int i, UiMenuItemI* item, UiObject* obj); +static void add_menuitem_list_widget(winrt::Windows::Foundation::Collections::IVector parent, int i, UiMenuItemI* item, UiObject* obj); + +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 +}; + +winrt::Microsoft::UI::Xaml::Controls::MenuBar ui_create_menubar(UiObject* obj) { + MenuBar mb = MenuBar(); + + UiMenu* menus_begin = uic_get_menu_list(); + + UiMenu* ls = menus_begin; + while (ls) { + UiMenu* menu = ls; + add_top_menu_widget(mb, 0, &menu->item, obj); + + ls = (UiMenu*)ls->item.next; + } + + return mb; +} + +static void add_top_menu_widget(MenuBar& parent, int i, UiMenuItemI* item, UiObject* obj) { + UiMenu* menu = (UiMenu*)item; + + MenuBarItem mi = MenuBarItem(); + wchar_t* wlabel = str2wstr(menu->label, NULL); + mi.Title(wlabel); + free(wlabel); + + UiMenuItemI* it = menu->items_begin; + int index = 0; + while (it) { + createMenuItem[it->type](mi.Items(), index, it, obj); + + it = it->next; + index++; + } + + parent.Items().Append(mi); +} + +static void add_menu_widget(winrt::Windows::Foundation::Collections::IVector parent, int i, UiMenuItemI* item, UiObject* obj) { + UiMenu* menu = (UiMenu*)item; + + MenuFlyoutSubItem mi = MenuFlyoutSubItem(); + wchar_t* wlabel = str2wstr(menu->label, NULL); + mi.Text(wlabel); + free(wlabel); + + parent.Append(mi); + + UiMenuItemI* it = menu->items_begin; + int index = 0; + while (it) { + createMenuItem[it->type](mi.Items(), index, it, obj); + + it = it->next; + index++; + } + + +} + +static void add_menuitem_widget(winrt::Windows::Foundation::Collections::IVector parent, int i, UiMenuItemI* item, UiObject* obj) { + UiMenuItem* it = (UiMenuItem*)item; + + MenuFlyoutItem mi = MenuFlyoutItem(); + wchar_t* wlabel = str2wstr(it->label, NULL); + mi.Text(wlabel); + free(wlabel); + + parent.Append(mi); +} + +static void add_menuseparator_widget( + winrt::Windows::Foundation::Collections::IVector parent, + int i, + UiMenuItemI* item, + UiObject* obj) +{ + +} + +static void add_checkitem_widget( + winrt::Windows::Foundation::Collections::IVector parent, + int i, + UiMenuItemI* item, + UiObject* obj) +{ + +} + +static void add_radioitem_widget( + winrt::Windows::Foundation::Collections::IVector parent, + int i, + UiMenuItemI* item, + UiObject* obj) +{ + +} + + +class UiMenuList { +public: + UiObject *obj = nullptr; + winrt::Windows::Foundation::Collections::IVector parent = { nullptr }; + UiMenuItemType type; + int prevSize = 0; + int insertPos = 0; + UiVar* var = nullptr; + ui_getvaluefunc getvalue = nullptr; + ui_callback callback = nullptr; + void* userdata = nullptr; + + UiMenuList() { + + } + + void updateItems() { + UiList* list = (UiList*)var->value; + + // delete previous items + for (int i = 0; i < prevSize; i++) { + parent.RemoveAt(insertPos); + } + + // insert new items + int count = 0; + void* elm = list->first(list); + while (elm) { + char *menuItemLabel = (char*) (getvalue ? getvalue(elm, 0) : elm); + + MenuFlyoutItem mi = MenuFlyoutItem(); + wchar_t* wlabel = str2wstr(menuItemLabel ? menuItemLabel : "", NULL); + mi.Text(wlabel); + free(wlabel); + + if (callback) { + mi.Click([this, elm, count](Windows::Foundation::IInspectable const& sender, RoutedEventArgs const& e) + { + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = elm; + evt.intval = count; + callback(&evt, userdata); + }); + } + + parent.InsertAt(insertPos + count, mi); + + elm = list->next(list); + count++; + } + + prevSize = count; + } +}; + +extern "C" void destroy_ui_menu_list(void* ptr) { + UiMenuList* ls = (UiMenuList*)ptr; + delete ls; +} + +static void ui_context_add_menu_list_destructor(UiContext* ctx, UiMenuList* list) { + cxMempoolRegister(ctx->mp, list, destroy_ui_menu_list); +} + +static void ui_menulist_update(UiEvent* event, void* userdata) { + UiMenuList* mlist = (UiMenuList*)userdata; + mlist->updateItems(); +} + +static void add_menuitem_list_widget( + winrt::Windows::Foundation::Collections::IVector parent, + int i, + UiMenuItemI* item, + UiObject* obj) +{ + UiMenuItemList* it = (UiMenuItemList*)item; + if (!it->varname) { + return; + } + + uint32_t size = parent.Size(); + + UiVar* var = uic_create_var(ui_global_context(), it->varname, UI_VAR_LIST); + + UiMenuList* mlist = new UiMenuList(); + mlist->obj = obj; + mlist->parent = parent; + mlist->getvalue = it->getvalue; + mlist->callback = it->callback; + mlist->userdata = it->userdata; + mlist->prevSize = 0; + mlist->insertPos = size; + mlist->type = item->type; + mlist->var = var; + ui_context_add_menu_list_destructor(obj->ctx, mlist); + + UiList* list = (UiList*)var->value; + list->observers = ui_add_observer(list->observers, ui_menulist_update, mlist); + + mlist->updateItems(); +} + + + + +winrt::Microsoft::UI::Xaml::Controls::MenuFlyout ui_create_menu_flyout(UiObject* obj, UiMenu* menudef) { + MenuFlyout flyout = MenuFlyout(); + + UiMenuItemI* it = menudef->items_begin; + int index = 0; + while (it) { + createMenuItem[it->type](flyout.Items(), index, it, obj); + + it = it->next; + index++; + } + + return flyout; +} + +UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget) { + return NULL; +} + +void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y) { + +} diff --git a/ui/winui/appmenu.h b/ui/winui/appmenu.h new file mode 100644 index 0000000..914ce62 --- /dev/null +++ b/ui/winui/appmenu.h @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#pragma once + +#include "toolkit.h" +#include "../common/menu.h" + +#include +#undef GetCurrentTime +#include +#include +#include +#include +#include +#include + + + +typedef void(*ui_menu_add_f)(winrt::Windows::Foundation::Collections::IVector parent, int, UiMenuItemI*, UiObject*); + +winrt::Microsoft::UI::Xaml::Controls::MenuBar ui_create_menubar(UiObject* obj); + +winrt::Microsoft::UI::Xaml::Controls::MenuFlyout ui_create_menu_flyout(UiObject* obj, UiMenu* menudef); + diff --git a/ui/winui/button.cpp b/ui/winui/button.cpp new file mode 100644 index 0000000..75e2578 --- /dev/null +++ b/ui/winui/button.cpp @@ -0,0 +1,408 @@ +/* + * 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 "pch.h" + +#include "button.h" + +#include "util.h" +#include "container.h" +#include "icons.h" + +#include "../common/object.h" +#include "../common/context.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; + + + +void ui_set_button_label(ButtonBase button, const char* label, const char* stockid, const char *icon, UiLabelType type) { + // TODO: stockid + + if (type == UI_LABEL_ICON) { + label = NULL; + } + else if (type == UI_LABEL_TEXT) { + icon = NULL; + } + + IconElement icon_elm = { nullptr }; + if (icon) { + icon_elm = ui_get_icon(icon); + } + + if (label && icon_elm) { + StackPanel panel = StackPanel(); + panel.Orientation(Orientation::Horizontal); + panel.Spacing(5); + + panel.Children().Append(icon_elm); + + wchar_t* wlabel = str2wstr(label, nullptr); + TextBlock label = TextBlock(); + label.Text(wlabel); + panel.Children().Append(label); + free(wlabel); + + button.Content(panel); + } + else if (label) { + wchar_t* wlabel = str2wstr(label, nullptr); + button.Content(box_value(wlabel)); + free(wlabel); + } + else if (icon_elm) { + button.Content(ui_get_icon(icon)); + } +} + + +UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) { + UiObject* current = uic_current_obj(obj); + + // create button with label + Button button = Button(); + ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype); + + // create toolkit wrapper object and register destructor + UIElement elm = button; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + // register callback + if (args.onclick) { + ui_callback cbfunc = args.onclick; + void* cbdata = args.onclickdata; + button.Click([cbfunc, cbdata, obj](IInspectable const& sender, RoutedEventArgs) { + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = nullptr; + evt.intval = 0; + cbfunc(&evt, cbdata); + }); + } + + // add button to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(button, false); + + return widget; +} + + +void togglebutton_register_checked_observers(ToggleButton button, UiObject* obj, UiVar* var) { + button.Checked([button, obj, var](IInspectable const& sender, RoutedEventArgs) { + UiInteger* i = (UiInteger*)var->value; + UiEvent evt = ui_create_int_event(obj, i->get(i)); + ui_notify_evt(i->observers, &evt); + }); +} + +void togglebutton_register_unchecked_observers(ToggleButton button, UiObject* obj, UiVar* var) { + button.Unchecked([button, obj, var](IInspectable const& sender, RoutedEventArgs) { + UiInteger* i = (UiInteger*)var->value; + UiEvent evt = ui_create_int_event(obj, i->get(i)); + ui_notify_evt(i->observers, &evt); + }); +} + +void togglebutton_register_callback(ToggleButton button, UiObject *obj, UiToggleArgs& args) { + ui_callback callback = args.onchange; + void* cbdata = args.onchangedata; + if (callback) { + button.Checked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) { + UiEvent evt = ui_create_int_event(obj, true); + callback(&evt, cbdata); + }); + button.Unchecked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) { + UiEvent evt = ui_create_int_event(obj, false); + callback(&evt, cbdata); + }); + } +} + +// for some stupid reason the ToggleSwitch is not a ToggleButton and we need to write the same +// code again, because although everything is basically the same, it is named differently +static void switch_register_observers(ToggleSwitch button, UiObject* obj, UiVar* var) { + button.Toggled([button, obj, var](IInspectable const& sender, RoutedEventArgs) { + UiInteger* i = (UiInteger*)var->value; + UiEvent evt = ui_create_int_event(obj, i->get(i)); + ui_notify_evt(i->observers, &evt); + }); +} + +static void switch_register_callback(ToggleSwitch button, UiObject* obj, UiToggleArgs& args) { + ui_callback callback = args.onchange; + void* cbdata = args.onchangedata; + if (callback) { + button.Toggled([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) { + UiEvent evt = ui_create_int_event(obj, button.IsOn()); + callback(&evt, cbdata); + }); + } +} + +static void togglebutton_changed(UiObject *obj, bool checked, ui_callback onchange, void *onchangedata, int enable_state) { + if (onchange) { + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = nullptr; + evt.intval = checked; + onchange(&evt, onchangedata); + } + if (enable_state > 0) { + if (checked) { + ui_set_group(obj->ctx, enable_state); + } else { + ui_unset_group(obj->ctx, enable_state); + } + } +} + +static UIWIDGET create_togglebutton(UiObject *obj, ToggleButton button, UiToggleArgs args) { + UiObject* current = uic_current_obj(obj); + + // set label + ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype); + togglebutton_register_callback(button, obj, args); + + // create toolkit wrapper object and register destructor + UIElement elm = button; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + // bind variable + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER); + if (var) { + UiInteger* value = (UiInteger*)var->value; + value->obj = widget; + value->get = ui_toggle_button_get; + value->set = ui_toggle_button_set; + + // listener for notifying observers + togglebutton_register_checked_observers(button, obj, var); + togglebutton_register_unchecked_observers(button, obj, var); + } + + if (args.enable_group > 0 || args.onchange) { + button.Checked([obj, args](IInspectable const& sender, RoutedEventArgs) { + togglebutton_changed(obj, true, args.onchange, args.onchangedata, args.enable_group); + }); + button.Unchecked([obj, args](IInspectable const& sender, RoutedEventArgs) { + togglebutton_changed(obj, false, args.onchange, args.onchangedata, args.enable_group); + }); + } + + // add button to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(button, false); + + return widget; +} + +UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) { + ToggleButton button = ToggleButton(); + return create_togglebutton(obj, button, args); +} + +UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) { + CheckBox button = CheckBox(); + return create_togglebutton(obj, button, args); +} + +UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) { + ToggleSwitch button = ToggleSwitch(); + if (args.label) { + wchar_t* wlabel = str2wstr(args.label, nullptr); + button.Header(box_value(wlabel)); + free(wlabel); + } + switch_register_callback(button, obj, args); + + UiObject* current = uic_current_obj(obj); + + // create toolkit wrapper object and register destructor + UIElement elm = button; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + // bind variable + UiVar* var = nullptr; + if (args.value) { + var = uic_create_value_var(current->ctx, args.value); + } + else if (args.varname) { + var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER); + } + if (var) { + UiInteger* value = (UiInteger*)var->value; + value->obj = widget; + value->get = ui_toggle_button_get; + value->set = ui_toggle_button_set; + + // listener for notifying observers + switch_register_observers(button, obj, var); + } + + // add button to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(button, false); + + return widget; +} + +UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args) { + RadioButton button = RadioButton(); + + UiObject* current = uic_current_obj(obj); + + // set label + ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype); + togglebutton_register_callback(button, obj, args); + + // create toolkit wrapper object and register destructor + UIElement elm = button; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + UiVar* var = nullptr; + if (args.value) { + var = uic_create_value_var(current->ctx, args.value); + } + else if (args.varname) { + var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER); + } + + // bind radio button to the value + if (var) { + UiInteger* value = (UiInteger*)var->value; + + // store a list of radio buttons in the value + CxList* radioButtons = (CxList*) (value->obj ? value->obj : cxLinkedListCreate(current->ctx->allocator, NULL, CX_STORE_POINTERS)); + // get or create the group name + static int groupCount = 0; + winrt::hstring groupName; + if (cxListSize(radioButtons) == 0) { + groupName = winrt::to_hstring(groupCount++); + } else { + UiWidget* firstButtonWidget = (UiWidget*)cxListAt(radioButtons, 0); + RadioButton firstRadioButton = firstButtonWidget->uielement.as(); + groupName = firstRadioButton.GroupName(); + } + + // set the group name for the new radiobutton + cxListAdd(radioButtons, widget); + button.GroupName(groupName); + + value->obj = radioButtons; + value->get = ui_radio_button_get; + value->set = ui_radio_button_set; + + // listener for notifying observers (only checked, not unchecked) + togglebutton_register_checked_observers(button, obj, var); + } + + // add button to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(button, false); + + return widget; +} + + +int64_t ui_toggle_button_get(UiInteger* integer) { + UiWidget* widget = (UiWidget*)integer->obj; + ToggleButton toggleButton = widget->uielement.as(); + int val = toggleButton.IsChecked().GetBoolean(); + integer->value = val; + return val; +} + +void ui_toggle_button_set(UiInteger* integer, int64_t value) { + UiWidget* widget = (UiWidget*)integer->obj; + ToggleButton toggleButton = widget->uielement.as(); + toggleButton.IsChecked((bool)value); + integer->value = value; +} + +int64_t ui_switch_get(UiInteger * integer) { + UiWidget* widget = (UiWidget*)integer->obj; + ToggleSwitch toggleButton = widget->uielement.as(); + int val = toggleButton.IsOn(); + integer->value = val; + return val; +} + +void ui_switch_set(UiInteger * integer, int64_t value) { + UiWidget* widget = (UiWidget*)integer->obj; + ToggleSwitch toggleButton = widget->uielement.as(); + toggleButton.IsOn((bool)value); + integer->value = value; +} + +int64_t ui_radio_button_get(UiInteger * integer) { + CxList* list = (CxList*)integer->obj; + CxIterator i = cxListIterator(list); + int selection = -1; + cx_foreach(UiWidget*, widget, i) { + ToggleButton button = widget->uielement.as(); + if (button.IsChecked().GetBoolean()) { + selection = i.index; + break; + } + } + integer->value = selection; + return selection; +} + +void ui_radio_button_set(UiInteger * integer, int64_t value) { + CxList* list = (CxList*)integer->obj; + UiWidget* widget = (UiWidget*)cxListAt(list, value); + if (widget) { + ToggleButton button = widget->uielement.as(); + button.IsChecked(true); + integer->value = value; + } +} diff --git a/ui/winui/button.h b/ui/winui/button.h new file mode 100644 index 0000000..b4779ff --- /dev/null +++ b/ui/winui/button.h @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#pragma once + +#include "toolkit.h" +#include "../ui/container.h" + +#include "../ui/button.h" + +#include "../common/context.h" + +void ui_set_button_label(winrt::Microsoft::UI::Xaml::Controls::Primitives::ButtonBase button, const char* label, const char* stockid, const char *icon, UiLabelType type); + +void togglebutton_register_checked_observers(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiVar* var); +void togglebutton_register_unchecked_observers(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiVar* var); +void togglebutton_register_callback(winrt::Microsoft::UI::Xaml::Controls::Primitives::ToggleButton button, UiObject* obj, UiToggleArgs& args); + +extern "C" int64_t ui_toggle_button_get(UiInteger * integer); +extern "C" void ui_toggle_button_set(UiInteger * integer, int64_t value); + +extern "C" int64_t ui_switch_get(UiInteger * integer); +extern "C" void ui_switch_set(UiInteger * integer, int64_t value); + +extern "C" int64_t ui_radio_button_get(UiInteger * integer); +extern "C" void ui_radio_button_set(UiInteger * integer, int64_t value); diff --git a/ui/winui/commandbar.cpp b/ui/winui/commandbar.cpp new file mode 100644 index 0000000..335bddb --- /dev/null +++ b/ui/winui/commandbar.cpp @@ -0,0 +1,218 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pch.h" + +#include "commandbar.h" + +#include "util.h" +#include "../common/object.h" +#include "../common/context.h" + +#include "button.h" +#include "appmenu.h" +#include "icons.h" + +using namespace winrt; +using namespace Microsoft::UI::Xaml; +using namespace Microsoft::UI::Xaml::Controls; +using namespace Microsoft::UI::Xaml::XamlTypeInfo; +using namespace Microsoft::UI::Xaml::Markup; +using namespace Windows::UI::Xaml::Interop; + +static void create_item(UiObject* obj, Windows::Foundation::Collections::IObservableVector cb, UiToolbarItemI* i); +static void create_cmditem(UiObject* obj, Windows::Foundation::Collections::IObservableVector cb, UiToolbarItem* item); +static void create_toggleitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector cb, UiToolbarToggleItem* item); +static void create_menuitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector cb, UiToolbarMenuItem* item); + +static void create_appmenu_items(UiObject* obj, Windows::Foundation::Collections::IObservableVector cb, UiToolbarMenuItem* i); + +CommandBar ui_create_toolbar(UiObject *obj, CxList* defaults, bool addappmenu) { + + CommandBar cb = CommandBar(); + cb.DefaultLabelPosition(CommandBarDefaultLabelPosition::Right); + + // add pre-configured items + CxIterator i = cxListIterator(defaults); + cx_foreach(char*, def, i) { + UiToolbarItemI* item = uic_toolbar_get_item(def); + if (!item) { + exit(-1); // TODO: maybe an error dialog? + } + create_item(obj, cb.PrimaryCommands(), item); + } + + // add appmenu + if (addappmenu) { + UiToolbarMenuItem* appmenu = uic_get_appmenu(); + if (appmenu) { + create_appmenu_items(obj, cb.SecondaryCommands(), appmenu); + } + } + + return cb; +} + +static void create_item(UiObject* obj, Windows::Foundation::Collections::IObservableVector cb, UiToolbarItemI* i) { + switch (i->type) { + case UI_TOOLBAR_ITEM: { + create_cmditem(obj, cb, (UiToolbarItem*)i); + break; + } + case UI_TOOLBAR_TOGGLEITEM: { + create_toggleitem(obj, cb, (UiToolbarToggleItem*)i); + break; + } + case UI_TOOLBAR_MENU: { + create_menuitem(obj, cb, (UiToolbarMenuItem*)i); + break; + } + } +} + +static void create_appmenu_items(UiObject* obj, Windows::Foundation::Collections::IObservableVector cb, UiToolbarMenuItem* i) { + for (UiMenuItemI* mi = i->menu.items_begin; mi; mi = mi->next) { + // convert UiMenuItemI to UiToolbarItemI + switch (mi->type) { + case UI_MENU: { + UiMenu* mitem = (UiMenu*)mi; + UiToolbarMenuItem tbitem; + memset(&tbitem, 0, sizeof(UiToolbarMenuItem)); + tbitem.item.type = UI_TOOLBAR_MENU; + tbitem.args.label = mitem->label; + tbitem.menu.items_begin = mitem->items_begin; + tbitem.menu.items_end = mitem->items_end; + create_menuitem(obj, cb, &tbitem); + break; + } + case UI_MENU_ITEM: { + UiMenuItem* mitem = (UiMenuItem*)mi; + UiToolbarItem tbitem; + memset(&tbitem, 0, sizeof(UiToolbarItem)); + tbitem.item.type = UI_TOOLBAR_ITEM; + tbitem.args.label = mitem->label; + tbitem.args.onclick = mitem->callback; + tbitem.args.onclickdata = mitem->userdata; + create_cmditem(obj, cb, &tbitem); + break; + } + } + } +} + +static void create_cmditem(UiObject* obj, Windows::Foundation::Collections::IObservableVector cb, UiToolbarItem* item) { + AppBarButton button = AppBarButton(); + if (item->args.label) { + wchar_t* wlabel = str2wstr(item->args.label, nullptr); + button.Label(wlabel); + free(wlabel); + } + if(item->args.icon) { + button.Icon(ui_get_icon(item->args.icon)); + } + + UIElement elm = button; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(obj->ctx, widget); + ui_set_widget_groups(obj->ctx, widget, item->args.groups); + + // register callback + if (item->args.onclick) { + ui_callback cbfunc = item->args.onclick; + void* cbdata = item->args.onclickdata; + button.Click([cbfunc, cbdata, obj](Windows::Foundation::IInspectable const& sender, RoutedEventArgs) { + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = nullptr; + evt.intval = 0; + cbfunc(&evt, cbdata); + }); + } + + cb.Append(button); +} + +static void create_toggleitem(UiObject *obj, Windows::Foundation::Collections::IObservableVector cb, UiToolbarToggleItem* item) { + AppBarToggleButton button = AppBarToggleButton(); + if (item->args.label) { + wchar_t* wlabel = str2wstr(item->args.label, nullptr); + button.Label(wlabel); + free(wlabel); + } + if (item->args.icon) { + button.Icon(ui_get_icon(item->args.icon)); + } + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, nullptr, item->args.varname, UI_VAR_INTEGER); + if (var) { + UIElement elm = button; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(obj->ctx, widget); + ui_set_widget_groups(obj->ctx, widget, item->args.groups); + + UiInteger* value = (UiInteger*)var->value; + int64_t i = value->value; + value->get = ui_toggle_button_get; + value->set = ui_toggle_button_set; + value->obj = widget; + ui_toggle_button_set(value, i); // init togglebutton state + + // listener for notifying observers + togglebutton_register_checked_observers(button, obj, var); + togglebutton_register_unchecked_observers(button, obj, var); + } + + UiToggleArgs args = {}; + args.onchange = item->args.onchange; + args.onchangedata = item->args.onchangedata; + togglebutton_register_callback(button, obj, args); + + + cb.Append(button); +} + +static void create_menuitem(UiObject* obj, Windows::Foundation::Collections::IObservableVector cb, UiToolbarMenuItem* item) { + AppBarButton button = AppBarButton(); + if (item->args.label) { + wchar_t* wlabel = str2wstr(item->args.label, nullptr); + button.Label(wlabel); + free(wlabel); + } + if (item->args.icon) { + button.Icon(ui_get_icon(item->args.icon)); + } + + MenuFlyoutItem mi = MenuFlyoutItem(); + + MenuFlyout flyout = ui_create_menu_flyout(obj, &item->menu); + button.Flyout(flyout); + + cb.Append(button); +} diff --git a/ui/winui/commandbar.h b/ui/winui/commandbar.h new file mode 100644 index 0000000..198e280 --- /dev/null +++ b/ui/winui/commandbar.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#pragma once + +#include "toolkit.h" +#include "../ui/toolbar.h" +#include "../common/toolbar.h" + +#include +#undef GetCurrentTime +#include +#include +#include +#include +#include +#include + +winrt::Microsoft::UI::Xaml::Controls::CommandBar ui_create_toolbar(UiObject *obj, CxList* defaults, bool addappmenu); + +extern "C" int64_t ui_appbar_togglebutton_get(UiInteger * integer); +extern "C" void ui_appbar_togglebutton_set(UiInteger * integer, int64_t value); \ No newline at end of file diff --git a/ui/winui/condvar.cpp b/ui/winui/condvar.cpp new file mode 100644 index 0000000..d7ffd5c --- /dev/null +++ b/ui/winui/condvar.cpp @@ -0,0 +1,65 @@ +/* + * 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 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 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; +} diff --git a/ui/winui/condvar.h b/ui/winui/condvar.h new file mode 100644 index 0000000..5561c32 --- /dev/null +++ b/ui/winui/condvar.h @@ -0,0 +1,55 @@ +/* + * 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 +#include +#include + +#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 */ + diff --git a/ui/winui/container.cpp b/ui/winui/container.cpp new file mode 100644 index 0000000..b3e6353 --- /dev/null +++ b/ui/winui/container.cpp @@ -0,0 +1,932 @@ +/* +* 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 "pch.h" + +#include "container.h" + +#include "../common/context.h" +#include "../common/object.h" + +#include "util.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; +} + + +// --------------------- UiBoxContainer --------------------- + +static UIWIDGET ui_box(UiObject* obj, UiContainerArgs args, UiBoxContainerType type) { + UiObject* current = uic_current_obj(obj); + UI_APPLY_LAYOUT1(current, args); + + Grid grid = Grid(); + current->container->Add(grid, true); + + UIElement elm = grid; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + + UiObject* newobj = uic_object_new(obj, widget); + newobj->container = new UiBoxContainer(grid, type, args.margin, args.spacing); + ui_context_add_container_destructor(current->ctx, newobj->container); + uic_obj_add(obj, newobj); + + return widget; +} + +UIWIDGET ui_vbox_create(UiObject* obj, UiContainerArgs args) { + return ui_box(obj, args, UI_BOX_CONTAINER_VBOX); +} + +UIWIDGET ui_hbox_create(UiObject* obj, UiContainerArgs args) { + return ui_box(obj, args, UI_BOX_CONTAINER_HBOX); +} + +UiBoxContainer::UiBoxContainer(Grid grid, enum UiBoxContainerType type, int margin, int spacing) { + this->grid = grid; + this->type = type; + + Thickness t = { (double)margin, (double)margin, (double)margin, (double)margin }; + grid.Margin(t); + grid.ColumnSpacing((double)spacing); + grid.RowSpacing((double)spacing); + + GridLength gl; + gl.Value = 1; + gl.GridUnitType = GridUnitType::Star; + + // hbox needs one row def, vbox needs one col def + // all other col/row defs are created when elements are added + if (type == UI_BOX_CONTAINER_HBOX) { + boxRowDef = RowDefinition(); + boxRowDef.Height(gl); + grid.RowDefinitions().Append(boxRowDef); + } else { + boxColDef = ColumnDefinition(); + boxColDef.Width(gl); + grid.ColumnDefinitions().Append(boxColDef); + } + + ui_reset_layout(layout); +} + +void UiBoxContainer::Add(FrameworkElement control, UiBool fill) { + if (this->layout.fill != UI_LAYOUT_UNDEFINED) { + fill = ui_lb2bool(this->layout.fill); + } + + GridLength gl; + if (fill) { + gl.Value = 1; + gl.GridUnitType = GridUnitType::Star; + } + else { + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + } + + control.HorizontalAlignment(HorizontalAlignment::Stretch); + control.VerticalAlignment(VerticalAlignment::Stretch); + + if (type == UI_CONTAINER_HBOX) { + ColumnDefinition coldef = ColumnDefinition(); + coldef.Width(gl); + grid.ColumnDefinitions().Append(coldef); + grid.SetColumn(control, grid.Children().Size()); + grid.SetRow(control, 0); + } else { + RowDefinition rowdef = RowDefinition(); + rowdef.Height(gl); + grid.RowDefinitions().Append(rowdef); + grid.SetRow(control, grid.Children().Size()); + grid.SetColumn(control, 0); + } + + grid.Children().Append(control); + + ui_reset_layout(layout); +} + + +// --------------------- UiGridContainer --------------------- + +UIWIDGET ui_grid_create(UiObject* obj, UiContainerArgs args) { + UiObject* current = uic_current_obj(obj); + UI_APPLY_LAYOUT1(current, args); + + Grid grid = Grid(); + current->container->Add(grid, true); + + UIElement elm = grid; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + + UiObject* newobj = uic_object_new(obj, widget); + newobj->container = new UiGridContainer(grid, args.margin, args.columnspacing, args.rowspacing); + ui_context_add_container_destructor(current->ctx, newobj->container); + uic_obj_add(obj, newobj); + + return widget; +} + +UiGridContainer::UiGridContainer(Grid grid, int margin, int columnspacing, int rowspacing) { + this->grid = grid; + Thickness t = { (double)margin, (double)margin, (double)margin, (double)margin }; + grid.Margin(t); + grid.ColumnSpacing((double)columnspacing); + grid.RowSpacing((double)rowspacing); + ui_reset_layout(layout); +} + +void UiGridContainer::Add(FrameworkElement control, UiBool fill) { + GridLength gl; + + bool hexpand = false; + bool vexpand = false; + bool hfill = false; + bool vfill = false; + if(layout.fill != UI_LAYOUT_UNDEFINED) { + fill = ui_lb2bool(layout.fill); + } + if (layout.hexpand != UI_LAYOUT_UNDEFINED) { + hexpand = layout.hexpand; + hfill = true; + } + if (layout.vexpand != UI_LAYOUT_UNDEFINED) { + vexpand = layout.vexpand; + vfill = true; + } + if (fill) { + hfill = true; + vfill = true; + } + + // create new RowDefinition for the new line + if (layout.newline || y == -1) { + x = 0; + y++; + RowDefinition rowdef = RowDefinition(); + if (vexpand) { + gl.GridUnitType = GridUnitType::Star; + gl.Value = 1; + } + else { + gl.GridUnitType = GridUnitType::Auto; + gl.Value = 0; + } + rowdef.Height(gl); + grid.RowDefinitions().Append(rowdef); + } else if (vexpand) { + // adjust row + gl.GridUnitType = GridUnitType::Star; + gl.Value = 1; + grid.RowDefinitions().GetAt(y).Height(gl); + } + + // create new columndefinition, if a new column is added + if (x == cols) { + if (hexpand) { + gl.GridUnitType = GridUnitType::Star; + gl.Value = 1; + } + else { + gl.GridUnitType = GridUnitType::Auto; + gl.Value = 0; + } + ColumnDefinition coldef = ColumnDefinition(); + coldef.Width(gl); + grid.ColumnDefinitions().Append(coldef); + cols++; + } else if(hexpand) { + // adjust column + if (layout.colspan == 0) { + gl.GridUnitType = GridUnitType::Star; + gl.Value = 1; + grid.ColumnDefinitions().GetAt(x).Width(gl); + } else { + int adjust_col = x; + bool adjust = true; + for (int i = 0; i < layout.colspan; i++) { + if (grid.ColumnDefinitions().Size() == x + i) { + break; + } + adjust_col = x + i; + GridLength w = grid.ColumnDefinitions().GetAt(adjust_col).Width(); + if (w.GridUnitType == GridUnitType::Star) { + adjust = false; + break; + } + } + + if (adjust) { + gl.GridUnitType = GridUnitType::Star; + gl.Value = 1; + grid.ColumnDefinitions().GetAt(adjust_col).Width(gl); + } + } + } + + // add control + if (hfill) { + control.HorizontalAlignment(HorizontalAlignment::Stretch); + } + if (vfill) { + control.VerticalAlignment(VerticalAlignment::Stretch); + } + + if (layout.colspan > 0) { + grid.SetColumnSpan(control, layout.colspan); + } + if (layout.rowspan > 0) { + grid.SetRowSpan(control, layout.rowspan); + } + + grid.SetRow(control, y); + grid.SetColumn(control, x); + grid.Children().Append(control); + + x++; + + ui_reset_layout(layout); +} + +// --------------------- UI Frame --------------------- + +UIWIDGET ui_frame_create(UiObject* obj, UiFrameArgs args) { + // create a grid for the frame, that contains the label and a sub-frame + Grid frame = Grid(); + + GridLength gl; + gl.GridUnitType = GridUnitType::Star; + gl.Value = 1; + + ColumnDefinition coldef = ColumnDefinition(); + coldef.Width(gl); + frame.ColumnDefinitions().Append(coldef); + + RowDefinition rowdefFrame = RowDefinition(); + rowdefFrame.Height(gl); + + // label + int row = 0; + if (args.label) { + RowDefinition rowdefLabel = RowDefinition(); + gl.GridUnitType = GridUnitType::Auto; + gl.Value = 0; + rowdefLabel.Height(gl); + frame.RowDefinitions().Append(rowdefLabel); + + TextBlock label = TextBlock(); + wchar_t* wlabel = str2wstr(args.label, nullptr); + winrt::hstring hstr(wlabel); + label.Text(hstr); + free(wlabel); + + frame.SetRow(label, row++); + frame.SetColumn(label, 0); + frame.Children().Append(label); + } + + // workarea frame + frame.RowDefinitions().Append(rowdefFrame); + + Grid workarea = Grid(); + frame.SetRow(workarea, row); + frame.SetColumn(workarea, 0); + frame.Children().Append(workarea); + + // some styling for the workarea + winrt::Microsoft::UI::Xaml::Media::SolidColorBrush brush{ winrt::Microsoft::UI::ColorHelper::FromArgb(150, 150, 150, 150) }; + workarea.BorderBrush(brush); + CornerRadius radius{ 8, 8, 8, 8 }; + Thickness t = { 1, 1, 1, 1 }; + workarea.CornerRadius(radius); + workarea.BorderThickness(t); + + Thickness padding = { 10, 10, 10, 10 }; + workarea.Padding(padding); + + // add frame to the parent container + UiObject* current = uic_current_obj(obj); + UI_APPLY_LAYOUT1(current, args); + current->container->Add(frame, true); + + UIElement elm = frame; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + + // sub container + UiContainer* ctn = nullptr; + switch (args.subcontainer) { + default: + case UI_CONTAINER_VBOX: { + ctn = new UiBoxContainer(workarea, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing); + break; + } + case UI_CONTAINER_HBOX: { + ctn = new UiBoxContainer(workarea, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing); + break; + } + case UI_CONTAINER_GRID: { + ctn = new UiGridContainer(workarea, args.margin, args.columnspacing, args.rowspacing); + break; + } + } + ui_context_add_container_destructor(current->ctx, ctn); + + UiObject* newobj = uic_object_new(obj, widget); + newobj->container = ctn; + uic_obj_add(obj, newobj); + + return widget; +} + +// --------------------- UI Expander --------------------- + +UIWIDGET ui_expander_create(UiObject* obj, UiFrameArgs args) { + Expander expander = Expander(); + if (args.label) { + wchar_t* wlabel = str2wstr(args.label, nullptr); + expander.Header(box_value(wlabel)); + free(wlabel); + } + expander.IsExpanded(args.isexpanded); + + // add frame to the parent container + UiObject* current = uic_current_obj(obj); + UI_APPLY_LAYOUT1(current, args); + current->container->Add(expander, true); + + UIElement elm = expander; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + + Grid content = Grid(); + expander.Content(content); + + UiContainer* ctn = nullptr; + switch (args.subcontainer) { + default: + case UI_CONTAINER_VBOX: { + ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing); + break; + } + case UI_CONTAINER_HBOX: { + ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing); + break; + } + case UI_CONTAINER_GRID: { + ctn = new UiGridContainer(content, args.margin, args.columnspacing, args.rowspacing); + break; + } + } + ui_context_add_container_destructor(current->ctx, ctn); + + UiObject* newobj = uic_object_new(obj, widget); + newobj->container = ctn; + uic_obj_add(obj, newobj); + + return widget; +} + +// --------------------- UI ScrolledWindow --------------------- + +UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args) { + ScrollViewer scrollW = ScrollViewer(); + + // add frame to the parent container + UiObject* current = uic_current_obj(obj); + UI_APPLY_LAYOUT1(current, args); + current->container->Add(scrollW, true); + + UIElement elm = scrollW; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + + // create child container + Grid content = Grid(); + scrollW.Content(content); + + UiContainer* ctn = nullptr; + switch (args.subcontainer) { + default: + case UI_CONTAINER_VBOX: { + ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing); + break; + } + case UI_CONTAINER_HBOX: { + ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing); + break; + } + case UI_CONTAINER_GRID: { + ctn = new UiGridContainer(content, args.margin, args.columnspacing, args.rowspacing); + break; + } + } + ui_context_add_container_destructor(current->ctx, ctn); + + UiObject* newobj = uic_object_new(obj, widget); + newobj->container = ctn; + uic_obj_add(obj, newobj); + + return widget; +} + +// --------------------- UI TabView --------------------- + +UiTabViewContainer::UiTabViewContainer(UiTabView* tabview) { + this->tabview = tabview; +} + +void UiTabViewContainer::Add(FrameworkElement control, UiBool fill) { + // noop +} + +static UiObject* create_subcontainer_obj(UiObject* current, Grid subcontainer, UiSubContainerType type, int margin, int spacing, int columnspacing, int rowspacing) { + UiContainer* ctn = nullptr; + switch (type) { + default: + case UI_CONTAINER_VBOX: { + ctn = new UiBoxContainer(subcontainer, UI_BOX_CONTAINER_VBOX, margin, spacing); + break; + } + case UI_CONTAINER_HBOX: { + ctn = new UiBoxContainer(subcontainer, UI_BOX_CONTAINER_HBOX, margin, spacing); + break; + } + case UI_CONTAINER_GRID: { + ctn = new UiGridContainer(subcontainer, margin, columnspacing, rowspacing); + break; + } + } + ui_context_add_container_destructor(current->ctx, ctn); + + UIElement elm = subcontainer; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + UiObject* newobj = uic_object_new(current, widget); + newobj->container = ctn; + return newobj; +} + +static UiTabView* tabview_pivot_create(UiObject* obj, UiTabViewArgs args) { + Pivot pivot = Pivot(); + UiPivotTabView* tabview = new UiPivotTabView(obj, pivot, args); + + return tabview; +} + +UiPivotTabView::UiPivotTabView(UiObject* obj, Pivot pivot, UiTabViewArgs args) { + this->current = obj; + this->pivot = pivot; + this->subcontainer = args.subcontainer; + this->margin = args.margin; + this->spacing = args.spacing; + this->columnspacing = args.columnspacing; + this->rowspacing = args.rowspacing; +} + +UiObject* UiPivotTabView::AddTab(const char* label, int index) { + TextBlock text = TextBlock(); + wchar_t* wlabel = str2wstr(label, nullptr); + winrt::hstring hstr(wlabel); + text.Text(hstr); + free(wlabel); + + PivotItem item = PivotItem(); + item.Header(text); + + // sub container + Grid subcontainer = Grid(); + item.Content(subcontainer); + pivot.Items().Append(item); + + return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing); +} + +void UiPivotTabView::Remove(int index) { + pivot.Items().RemoveAt(index); +} + +void UiPivotTabView::Select(int index) { + +} + +FrameworkElement UiPivotTabView::GetFrameworkElement() { + return pivot; +} + + +static UiTabView* tabview_invisible_create(UiObject *obj, UiTabViewArgs args) { + Grid container = Grid(); + container.HorizontalAlignment(HorizontalAlignment::Stretch); + container.VerticalAlignment(VerticalAlignment::Stretch); + UiInvisibleTabView *tabview = new UiInvisibleTabView(obj, container, args); + return tabview; +} + +UiInvisibleTabView::UiInvisibleTabView(UiObject* obj, Grid container, UiTabViewArgs args) { + this->current = obj; + this->container = container; + this->subcontainer = args.subcontainer; + this->margin = args.margin; + this->spacing = args.spacing; + this->columnspacing = args.columnspacing; + this->rowspacing = args.rowspacing; + this->currentIndex = -1; + + GridLength gl; + gl.GridUnitType = GridUnitType::Star; + gl.Value = 1; + + ColumnDefinition coldef = ColumnDefinition(); + coldef.Width(gl); + container.ColumnDefinitions().Append(coldef); + + RowDefinition rowdef = RowDefinition(); + rowdef.Height(gl); + container.RowDefinitions().Append(rowdef); +} + +UiObject* UiInvisibleTabView::AddTab(const char* label, int index) { + Grid subcontainer = Grid(); + subcontainer.HorizontalAlignment(HorizontalAlignment::Stretch); + subcontainer.VerticalAlignment(VerticalAlignment::Stretch); + + if (pages.size() == 0) { + container.Children().Append(subcontainer); + currentIndex = 0; + } + + if (index < 0) { + pages.push_back(subcontainer); + } else { + pages.insert(pages.begin() + index, subcontainer); + } + + // sub container + return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing); +} + +void UiInvisibleTabView::Remove(int index) { + +} + +void UiInvisibleTabView::Select(int index) { + if (index >= 0 && index < pages.size()) { + if (currentIndex != -1) { + container.Children().RemoveAt(0); + } + + container.Children().Append(pages.at(index)); + } +} + +FrameworkElement UiInvisibleTabView::GetFrameworkElement() { + return container; +} + + +static UiTabView* tabview_main_create(UiObject* obj, UiTabViewArgs args) { + TabView tabview = TabView(); + tabview.IsAddTabButtonVisible(false); + //tabview.CanDragTabs(false); + //tabview.CanReorderTabs(false); + UiMainTabView* uitabview = new UiMainTabView(obj, tabview, args); + + return uitabview; +} + +UiMainTabView::UiMainTabView(UiObject* obj, TabView tabview, UiTabViewArgs args) { + this->current = obj; + this->tabview = tabview; + this->subcontainer = args.subcontainer; + this->margin = args.margin; + this->spacing = args.spacing; + this->columnspacing = args.columnspacing; + this->rowspacing = args.rowspacing; +} + +UiObject* UiMainTabView::AddTab(const char* label, int index) { + TextBlock text = TextBlock(); + wchar_t* wlabel = str2wstr(label, nullptr); + winrt::hstring hstr(wlabel); + text.Text(hstr); + free(wlabel); + + TabViewItem item = TabViewItem(); + item.Header(text); + item.CanDrag(false); + item.IsClosable(false); + + // sub container + Grid subcontainer = Grid(); + item.Content(subcontainer); + tabview.TabItems().Append(item); + + return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing); +} + +void UiMainTabView::Remove(int index) { + this->tabview.TabItems().RemoveAt(index); +} + +void UiMainTabView::Select(int index) { + +} + +FrameworkElement UiMainTabView::GetFrameworkElement() { + return tabview; +} + + +static UiTabView* tabview_navigationview_create(UiObject* obj, UiTabViewArgs args, UiTabViewType type) { + NavigationView navigationview = NavigationView(); + UiNavigationTabView* tabview = new UiNavigationTabView(obj, navigationview, args, type); + navigationview.IsBackButtonVisible(NavigationViewBackButtonVisible::Collapsed); + navigationview.IsSettingsVisible(false); + + return tabview; +} + +UiNavigationTabView::UiNavigationTabView(UiObject* obj, NavigationView navigationview, UiTabViewArgs args, UiTabViewType type) { + this->current = obj; + this->navigationview = navigationview; + this->type = type; + this->margin = args.margin; + this->spacing = args.spacing; + this->columnspacing = args.columnspacing; + this->rowspacing = args.rowspacing; + + if (type == UI_TABVIEW_NAVIGATION_TOP) { + navigationview.PaneDisplayMode(NavigationViewPaneDisplayMode::Top); + } + + navigationview.SelectionChanged({ this, &UiNavigationTabView::SelectionChanged }); +} + +UiObject* UiNavigationTabView::AddTab(const char* label, int index1) { + TextBlock text = TextBlock(); + wchar_t* wlabel = str2wstr(label, nullptr); + winrt::hstring hstr(wlabel); + text.Text(hstr); + free(wlabel); + + NavigationViewItem item = NavigationViewItem(); + item.Content(text); + + // sub container + Grid subcontainer = Grid(); + if (pages.size() == 0) { + navigationview.Content(subcontainer); + navigationview.SelectedItem(item); + } + + navigationview.MenuItems().Append(item); + auto page = std::tuple{ item, subcontainer }; + pages.push_back(page); + + return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing); +} + +void UiNavigationTabView::Remove(int index) { + navigationview.MenuItems().RemoveAt(index); + pages.erase(pages.begin() + index); +} + +void UiNavigationTabView::Select(int index) { + +} + +FrameworkElement UiNavigationTabView::GetFrameworkElement() { + return navigationview; +} + +void UiNavigationTabView::SelectionChanged(NavigationView const& sender, NavigationViewSelectionChangedEventArgs const& args) { + for (auto page : pages) { + NavigationViewItem item = std::get<0>(page); + FrameworkElement elm = std::get<1>(page); + if (item == navigationview.SelectedItem()) { + navigationview.Content(elm); + break; + } + } +} + +static int64_t ui_tabview_get(UiInteger *i) { + return 0; +} + +static void ui_tabview_set(UiInteger *i, int64_t value) { + UiTabView *tabview = (UiTabView*)i->obj; + tabview->Select(value); +} + +UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs args) { + UiTabViewType type = args.tabview == UI_TABVIEW_DEFAULT ? UI_TABVIEW_NAVIGATION_TOP2 : args.tabview; + UiTabView* tabview = nullptr; + switch (type) { + default: { + tabview = tabview_pivot_create(obj, args); + break; + } + case UI_TABVIEW_DOC: { + tabview = tabview_main_create(obj, args); + break; + } + case UI_TABVIEW_NAVIGATION_SIDE: { + tabview = tabview_navigationview_create(obj, args, type); + break; + } + case UI_TABVIEW_NAVIGATION_TOP: { + tabview = tabview_navigationview_create(obj, args, type); + break; + } + case UI_TABVIEW_NAVIGATION_TOP2: { + tabview = tabview_pivot_create(obj, args); + break; + } + case UI_TABVIEW_INVISIBLE: { + tabview = tabview_invisible_create(obj, args); + break; + } + } + UiTabViewContainer* ctn = new UiTabViewContainer(tabview); + + // add frame to the parent container + UiObject* current = uic_current_obj(obj); + UI_APPLY_LAYOUT1(current, args); + current->container->Add(tabview->GetFrameworkElement(), true); + + UIElement elm = tabview->GetFrameworkElement(); + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + widget->data1 = tabview; + + // TODO: add tabview destructor + + // bind variable + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER); + if (var) { + UiInteger *i = (UiInteger*)var->value; + i->obj = tabview; + i->get = ui_tabview_get; + i->set = ui_tabview_set; + } + + UiObject* newobj = uic_object_new(obj, widget); + newobj->container = ctn; + uic_obj_add(obj, newobj); + + return widget; +} + +void ui_tab_create(UiObject* obj, const char* title) { + UiObject* current = uic_current_obj(obj); + UiTabView* tabview = (UiTabView*)current->widget->data1; + UiObject* newobj = tabview->AddTab(title); + uic_obj_add(current, newobj); +} + +UIEXPORT void ui_tabview_select(UIWIDGET tabview, int tab) { + UiTabView* t = (UiTabView*)tabview->data1; + t->Select(tab); +} + +UIEXPORT void ui_tabview_remove(UIWIDGET tabview, int tab) { + UiTabView* t = (UiTabView*)tabview->data1; + t->Remove(tab); +} + +UIEXPORT UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) { + UiTabView* t = (UiTabView*)tabview->data1; + UiObject* newobj = t->AddTab(name, tab_index); + return newobj; +} + + + +// --------------------- UI Headerbar --------------------- + +// TODO: replace placeholder implementation + +UIEXPORT UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) { + UiContainerArgs boxargs = { }; + boxargs.fill = UI_OFF; + return ui_hbox_create(obj, boxargs); +} + +UIEXPORT void ui_headerbar_start_create(UiObject *obj) { + UiContainerArgs boxargs = { }; + boxargs.fill = UI_OFF; + ui_hbox_create(obj, boxargs); +} + +UIEXPORT void ui_headerbar_center_create(UiObject *obj) { + UiContainerArgs boxargs = { }; + boxargs.fill = UI_OFF; + ui_hbox_create(obj, boxargs); +} + +UIEXPORT void ui_headerbar_end_create(UiObject *obj) { + UiContainerArgs boxargs = { }; + boxargs.fill = UI_OFF; + ui_hbox_create(obj, boxargs); +} + + +/* +* -------------------- 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_width(UiObject* obj, int width) { + UiContainer* ct = uic_get_current_container(obj); + ct->layout.width = width; +} + +void ui_layout_height(UiObject* obj, int height) { + UiContainer* ct = uic_get_current_container(obj); + ct->layout.height = height; +} + +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; +} + diff --git a/ui/winui/container.h b/ui/winui/container.h new file mode 100644 index 0000000..fc66d3d --- /dev/null +++ b/ui/winui/container.h @@ -0,0 +1,187 @@ +/* + * 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. + */ + +#pragma once + +#include "toolkit.h" +#include "../ui/container.h" + +#include +#undef GetCurrentTime +#include +#include +#include +#include +#include +#include + + +#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 struct UiLayout UiLayout; +typedef enum UiLayoutBool UiLayoutBool; + +using namespace winrt; +using namespace Microsoft::UI::Xaml; +using namespace Microsoft::UI::Xaml::Controls; +using namespace Microsoft::UI::Xaml::XamlTypeInfo; +using namespace Microsoft::UI::Xaml::Markup; +using namespace Windows::UI::Xaml::Interop; + +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 height; + int colspan; + int rowspan; +}; + +struct UiContainer { + UiLayout layout; + int close = 0; + + virtual void Add(FrameworkElement control, UiBool fill) = 0; +}; + +enum UiBoxContainerType { + UI_BOX_CONTAINER_VBOX = 0, + UI_BOX_CONTAINER_HBOX +}; + +enum UiNavigationViewType { + UI_NAVIGATIONVIEW_TOP = 0, + UI_NAVIGATIONVIEW_SIDE +}; + +struct UiBoxContainer : UiContainer { + Grid grid; + enum UiBoxContainerType type; + RowDefinition boxRowDef; + ColumnDefinition boxColDef; + + UiBoxContainer(Grid grid, enum UiBoxContainerType type, int margin, int spacing); + + void Add(FrameworkElement control, UiBool fill); +}; + +struct UiGridContainer : UiContainer { + Grid grid; + int x = 0; + int y = -1; + int cols = 0; + + UiGridContainer(Grid grid, int margin, int columnspacing, int rowspacing); + + void Add(FrameworkElement control, UiBool fill); +}; + +struct UiTabView { + UiObject* current; + UiSubContainerType subcontainer; + int margin; + int spacing; + int columnspacing; + int rowspacing; + + virtual UiObject* AddTab(const char* label, int index = -1) = 0; + virtual void Remove(int index) = 0; + virtual void Select(int index) = 0; + virtual FrameworkElement GetFrameworkElement() = 0; +}; + +struct UiTabViewContainer : UiContainer { + UiTabView* tabview; + + UiTabViewContainer(UiTabView* tabview); + + void Add(FrameworkElement control, UiBool fill); +}; + +struct UiPivotTabView : UiTabView { + Pivot pivot; + + UiPivotTabView(UiObject *obj, Pivot pivot, UiTabViewArgs args); + + UiObject* AddTab(const char* label, int index = -1); + void Remove(int index); + void Select(int index); + FrameworkElement GetFrameworkElement(); +}; + +struct UiMainTabView : UiTabView { + TabView tabview; + + UiMainTabView(UiObject* obj, TabView tabview, UiTabViewArgs args); + + UiObject* AddTab(const char* label, int index = -1); + void Remove(int index); + void Select(int index); + FrameworkElement GetFrameworkElement(); +}; + +struct UiNavigationTabView : UiTabView { + NavigationView navigationview; + UiTabViewType type; + std::vector > pages; + + UiNavigationTabView(UiObject* obj, NavigationView navigationview, UiTabViewArgs args, UiTabViewType type); + + UiObject* AddTab(const char* label, int index = -1); + void Remove(int index); + void Select(int index); + FrameworkElement GetFrameworkElement(); + + void SelectionChanged(NavigationView const& sender, NavigationViewSelectionChangedEventArgs const& args); +}; + +struct UiInvisibleTabView : UiTabView { + Grid container; + std::vector pages; + int currentIndex; + + UiInvisibleTabView(UiObject *obj, Grid container, UiTabViewArgs args); + + UiObject* AddTab(const char* label, int index = -1); + void Remove(int index); + void Select(int index); + FrameworkElement GetFrameworkElement(); +}; \ No newline at end of file diff --git a/ui/winui/dnd.cpp b/ui/winui/dnd.cpp new file mode 100644 index 0000000..68af932 --- /dev/null +++ b/ui/winui/dnd.cpp @@ -0,0 +1,91 @@ +/* + * 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 "pch.h" + +#include "dnd.h" +#include "util.h" + +#include + +using namespace winrt; +using namespace Windows::ApplicationModel::DataTransfer; +using namespace Windows::Storage; +using namespace Windows::Storage::Streams; + +UIEXPORT void ui_selection_settext(UiDnD* dnd, char* str, int len) { + if (dnd->data) { + if (len < 0) { + len = strlen(str); + } + wchar_t *wstr = str2wstr_len(str, len, nullptr); + + dnd->data.SetText(wstr); + + free(wstr); + + } +} + +UIEXPORT void ui_selection_seturis(UiDnD* dnd, char** uris, int nelm) { + +} + + +UIEXPORT char* ui_selection_gettext(UiDnD* dnd) { + return nullptr; +} + + +UIEXPORT UiFileList ui_selection_geturis(UiDnD *dnd) { + UiFileList flist; + flist.files = nullptr; + flist.nfiles = 0; + + if (dnd->dataview.Contains(StandardDataFormats::StorageItems())) { + UiFileList *flist_ptr = &flist; + + // we need to execute this in a different thread + // this could block the main gui thread, but shouldn't happen with a simple uri list + std::thread getDataThread([dnd, flist_ptr]() { + auto items = dnd->dataview.GetStorageItemsAsync().get(); + + char **uris = (char**)calloc(items.Size(), sizeof(char*)); + flist_ptr->files = uris; + flist_ptr->nfiles = items.Size(); + + int i = 0; + for (IStorageItem const& item : items) { + winrt::hstring path = item.Path(); + uris[i++] = wchar2utf8(path.c_str(), path.size()); + } + }); + getDataThread.join(); + } + return flist; +} diff --git a/ui/winui/dnd.h b/ui/winui/dnd.h new file mode 100644 index 0000000..463a9d2 --- /dev/null +++ b/ui/winui/dnd.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#pragma once + +#include "../ui/dnd.h" + +struct UiDnD { + int evttype = 0; + winrt::Microsoft::UI::Xaml::DragStartingEventArgs dndstartargs = { nullptr }; + winrt::Microsoft::UI::Xaml::DropCompletedEventArgs dndcompletedargs = { nullptr }; + winrt::Microsoft::UI::Xaml::DragEventArgs drageventargs = { nullptr }; + winrt::Windows::ApplicationModel::DataTransfer::DataPackage data = { nullptr }; + winrt::Windows::ApplicationModel::DataTransfer::DataPackageView dataview = { nullptr }; +}; diff --git a/ui/winui/icons.cpp b/ui/winui/icons.cpp new file mode 100644 index 0000000..5c9f402 --- /dev/null +++ b/ui/winui/icons.cpp @@ -0,0 +1,421 @@ +/* + * 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 +#include + +#include "util.h" + +#include +#include + + +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 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; +} diff --git a/ui/winui/icons.h b/ui/winui/icons.h new file mode 100644 index 0000000..073d0e1 --- /dev/null +++ b/ui/winui/icons.h @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#pragma once + +#include "../ui/toolkit.h" + + + +struct UiIcon { + //virtual ~UiIcon() = 0; + + virtual winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon() = 0; +}; + +struct UiSymbolIcon : UiIcon { + winrt::Microsoft::UI::Xaml::Controls::Symbol symbol; + + UiSymbolIcon(winrt::Microsoft::UI::Xaml::Controls::Symbol sym); + + ~UiSymbolIcon(); + + winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon(); +}; + +struct UiImageIcon : UiIcon { + winrt::Windows::Foundation::Uri uri{ nullptr }; + + UiImageIcon(const char* uristr); + + ~UiImageIcon(); + + winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon(); +}; + +struct UiBitmapIcon : UiIcon { + winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap{ nullptr }; + + UiBitmapIcon(winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapSource bitmap); + + ~UiBitmapIcon(); + + winrt::Microsoft::UI::Xaml::Controls::IconElement getIcon(); +}; + + +winrt::Microsoft::UI::Xaml::Controls::IconElement ui_get_icon(const char* name); + +winrt::Microsoft::UI::Xaml::Media::Imaging::WriteableBitmap ui_dllicon2bitmap(const char* dll, int iconindex, bool large); + +UiIcon* ui_dllicon(const char* dll, int iconindex, bool large); diff --git a/ui/winui/image.cpp b/ui/winui/image.cpp new file mode 100644 index 0000000..cb18ae3 --- /dev/null +++ b/ui/winui/image.cpp @@ -0,0 +1,124 @@ +/* +* 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 "image.h" + +#include "toolkit.h" +#include "container.h" +#include "../common/object.h" +#include "../common/context.h" +#include "util.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 winrt::Microsoft::UI::Xaml::Media; + +UiImageSource::UiImageSource(winrt::Microsoft::UI::Xaml::Media::ImageSource& src) : imgsrc(src) {} + +UIEXPORT UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) { + UiObject* current = uic_current_obj(obj); + + Image image = Image(); + FrameworkElement elm = image; + if (args.scrollarea) { + ScrollViewer scroll = ScrollViewer(); + scroll.Content(image); + elm = scroll; + } + + // create toolkit wrapper object and register destructor + UIElement uielm = image; + UiWidget* widget = new UiWidget(uielm); + ui_context_add_widget_destructor(current->ctx, widget); + + // bind variable + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_GENERIC); + if (var) { + UiGeneric *value = (UiGeneric*)var->value; + value->obj = widget; + value->get = ui_image_get; + value->set = ui_image_set; + } + + // add button to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(elm, true); + + return widget; +} + +extern "C" void* ui_image_get(UiGeneric *g) { + + + return NULL; +} + +extern "C" int ui_image_set(UiGeneric *g, void *data, const char *type) { + if(!type || strcmp(type, UI_IMAGE_OBJECT_TYPE)) { + return 1; + } + + UiImageSource *imgdata = (UiImageSource*)data; + if (g->value) { + UiImageSource *prevData = (UiImageSource*)g->value; + delete prevData; + } + g->value = imgdata; + + UiWidget* widget = (UiWidget*)g->obj; + Image image = widget->uielement.as(); + image.Source(imgdata->imgsrc); + + return 0; +} + +UIEXPORT int ui_image_load_file(UiGeneric *obj, const char *path) { + wchar_t* wpath = str2wstr(path, nullptr); + std::wstring wPath = wpath; + std::wstring uriPath = L"file:///" + wPath; + Uri uri{ uriPath }; + + BitmapImage bitmapImage = BitmapImage(); + bitmapImage.UriSource(uri); + ImageSource src = bitmapImage; + + UiImageSource *imgdata = new UiImageSource(src); + obj->set(obj, imgdata, UI_IMAGE_OBJECT_TYPE); + + free(wpath); + + return 0; +} diff --git a/ui/winui/image.h b/ui/winui/image.h new file mode 100644 index 0000000..637ea24 --- /dev/null +++ b/ui/winui/image.h @@ -0,0 +1,43 @@ +/* +* 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. +*/ + +#pragma once + +#include "../ui/toolkit.h" +#include "../ui/image.h" + +class UiImageSource { +public: + winrt::Microsoft::UI::Xaml::Media::ImageSource imgsrc { nullptr }; + + UiImageSource(winrt::Microsoft::UI::Xaml::Media::ImageSource& src); +}; + + +extern "C" void* ui_image_get(UiGeneric *g); +extern "C" int ui_image_set(UiGeneric *g, void *data, const char *type); diff --git a/ui/winui/label.cpp b/ui/winui/label.cpp new file mode 100644 index 0000000..7a2ce4a --- /dev/null +++ b/ui/winui/label.cpp @@ -0,0 +1,199 @@ +/* + * 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 "pch.h" + +#include "label.h" +#include "text.h" +#include "util.h" + +#include "toolkit.h" +#include "container.h" +#include "../common/object.h" +#include "../common/context.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; + +UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs args) { + UiObject* current = uic_current_obj(obj); + + // create textbox and toolkit wrapper + TextBlock label = TextBlock(); + if (args.label) { + wchar_t* wlabel = str2wstr(args.label, nullptr); + label.Text(wlabel); + free(wlabel); + } + + UIElement elm = label; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + + 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; + + // listener for notifying observers + // TODO: + } + + // add label to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(label, 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* str) { + UiWidget* widget = (UiWidget*)str->obj; + TextBlock box = widget->uielement.as(); + std::wstring wstr(box.Text()); + return ui_wstring_get(str, wstr); +} + +void ui_label_set(UiString* str, const char* newvalue) { + UiWidget* widget = (UiWidget*)str->obj; + TextBlock box = widget->uielement.as(); + box.Text(ui_wstring_set(str, newvalue)); +} + + +// -------------------- progressbar ------------------------- + +UIWIDGET ui_progressbar_create(UiObject* obj, UiProgressbarArgs args) { + UiObject* current = uic_current_obj(obj); + + // create textbox and toolkit wrapper + ProgressBar progressbar = ProgressBar(); + progressbar.Minimum(args.min); + progressbar.Maximum(args.max == 0 ? 100 : args.max); + if (args.width > 0) { + progressbar.Width(args.width); + } + + UIElement elm = progressbar; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE); + if (var) { + UiDouble* value = (UiDouble*)var->value; + value->obj = widget; + value->get = ui_progressbar_get; + value->set = ui_progressbar_set; + + // listener for notifying observers + // TODO: + } + + // add button to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(progressbar, false); + + return widget; +} + +double ui_progressbar_get(UiDouble * d) { + UiWidget* widget = (UiWidget*)d->obj; + ProgressBar progressbar = widget->uielement.as(); + d->value = progressbar.Value(); + return d->value; +} + +void ui_progressbar_set(UiDouble * d, double newvalue) { + UiWidget* widget = (UiWidget*)d->obj; + ProgressBar progressbar = widget->uielement.as(); + d->value = newvalue; + progressbar.Value(newvalue); +} + +UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs args) { + UiObject* current = uic_current_obj(obj); + + // create textbox and toolkit wrapper + ProgressRing spinner = ProgressRing(); + spinner.IsActive(false); + + UIElement elm = spinner; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_DOUBLE); + if (var) { + UiInteger* value = (UiInteger*)var->value; + value->obj = widget; + value->get = ui_progressspinner_get; + value->set = ui_progressspinner_set; + + // listener for notifying observers + // TODO: + } + + // add button to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(spinner, false); + + return widget; +} + +int64_t ui_progressspinner_get(UiInteger * i) { + UiWidget* widget = (UiWidget*)i->obj; + ProgressRing spinner = widget->uielement.as(); + i->value = spinner.IsActive(); + return i->value; +} + +void ui_progressspinner_set(UiInteger * i, int64_t newvalue) { + UiWidget* widget = (UiWidget*)i->obj; + ProgressRing spinner = widget->uielement.as(); + i->value = newvalue != 0 ? 1 : 0; + spinner.IsActive(i->value); +} diff --git a/ui/winui/label.h b/ui/winui/label.h new file mode 100644 index 0000000..3911578 --- /dev/null +++ b/ui/winui/label.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + + +#pragma once + +#include "../ui/toolkit.h" +#include "../ui/display.h" + +extern "C" char* ui_label_get(UiString * str); +extern "C" void ui_label_set(UiString * str, const char* newvalue); + +extern "C" double ui_progressbar_get(UiDouble *d); +extern "C" void ui_progressbar_set(UiDouble *d, double newvalue); + +extern "C" int64_t ui_progressspinner_get(UiInteger * i); +extern "C" void ui_progressspinner_set(UiInteger * i, int64_t newvalue); diff --git a/ui/winui/list.cpp b/ui/winui/list.cpp new file mode 100644 index 0000000..e9f87ca --- /dev/null +++ b/ui/winui/list.cpp @@ -0,0 +1,346 @@ +/* + * 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 "pch.h" + +#include "list.h" +#include "container.h" +#include "util.h" + +#include "../common/context.h" +#include "../common/object.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 Microsoft::UI::Xaml::Markup; +using namespace Microsoft::UI::Xaml::Media; +using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives; + + +UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // create listview and toolkit wrapper + ListView listview = ListView(); + if (args.multiselection) { + listview.SelectionMode(ListViewSelectionMode::Extended); + } + + bool clickEnabled = listview.IsItemClickEnabled(); + listview.IsItemClickEnabled(true); + + + UIElement elm = listview; + UiWidget* widget = new UiWidget(elm); + widget->data1 = args.model; + widget->data2 = args.getvalue; + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + // bind var + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + if (var) { + UiList* list = (UiList*)var->value; + list->update = ui_simple_list_update; + list->getselection = ui_listview_getselection; + list->setselection = ui_listview_setselection; + list->obj = widget; + + ui_simple_list_update(list, 0); + } + + if (args.onselection) { + ui_callback onselection = args.onselection; + void* cbdata = args.onselectiondata; + listview.SelectionChanged([onselection, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) { + std::vector selectedRows = ui_create_listview_selection(sender.as()); + + UiListSelection selection; + selection.rows = selectedRows.data(); + selection.count = selectedRows.size(); + + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = &selection; + evt.intval = 0; + onselection(&evt, cbdata); + }); + } + if (args.onactivate) { + ui_callback cb = args.onactivate; + void* cbdata = args.onactivatedata; + listview.ItemClick([cb, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) { + std::vector selectedRows = ui_create_listview_selection(sender.as()); + UiListSelection selection; + selection.rows = selectedRows.data(); + selection.count = selectedRows.size(); + + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = &selection; + evt.intval = 0; + cb(&evt, cbdata); + }); + } + + // add listview to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(listview, false); + + return widget; +} + + +UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // create listview and toolkit wrapper + ComboBox combobox = ComboBox(); + + UIElement elm = combobox; + UiWidget* widget = new UiWidget(elm); + widget->data1 = args.model; + widget->data2 = args.getvalue; + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + // bind var + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + if (var) { + UiList* list = (UiList*)var->value; + list->update = ui_simple_list_update; + list->getselection = ui_dropdown_getselection; + list->setselection = ui_dropdown_setselection; + list->obj = widget; + ui_simple_list_update(list, 0); + } + + if (args.onactivate) { + ui_callback cb = args.onactivate; + void* cbdata = args.onactivatedata; + combobox.SelectionChanged([cb, cbdata, obj](IInspectable const& sender, RoutedEventArgs evtargs) { + int selectedrow = sender.as().SelectedIndex(); + UiListSelection selection; + selection.count = 1; + selection.rows = &selectedrow; + + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = &selection; + evt.intval = selectedrow; + cb(&evt, cbdata); + }); + } + + // add listview to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(combobox, false); + + return widget; +} + +UiListSelection ui_listview_getselection(UiList *list) { + UiWidget *widget = (UiWidget*)list->obj; + ListView listview = widget->uielement.as(); + std::vector selectedRows = ui_create_listview_selection(listview); + + UiListSelection selection = { NULL, 0 }; + if (selectedRows.size() > 0) { + selection.count = selectedRows.size(); + int *data = selectedRows.data(); + selection.rows = (int*)calloc(selection.count, sizeof(int)); + memcpy(selection.rows, data, selection.count); + } + + return selection; +} + +void ui_listview_setselection(UiList *list, UiListSelection selection) { + UiWidget* widget = (UiWidget*)list->obj; + if (selection.count > 0) { + ListView listview = widget->uielement.as(); + listview.SelectedIndex(selection.rows[0]); + } +} + +UiListSelection ui_dropdown_getselection(UiList *list) { + UiWidget* widget = (UiWidget*)list->obj; + ComboBox cb = widget->uielement.as(); + int index = cb.SelectedIndex(); + UiListSelection selection = { NULL, 0 }; + if (index >= 0) { + selection.rows = (int*)calloc(1, sizeof(int)); + selection.count = 1; + selection.rows[0] = index; + } + return selection; +} + +void ui_dropdown_setselection(UiList *list, UiListSelection selection) { + UiWidget* widget = (UiWidget*)list->obj; + if (selection.count > 0) { + ComboBox cb = widget->uielement.as(); + cb.SelectedIndex(selection.rows[0]); + } +} + +UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // create listview and toolkit wrapper + BreadcrumbBar bcbar = BreadcrumbBar(); + + UIElement elm = bcbar; + UiWidget* widget = new UiWidget(elm); + widget->data1 = args.model; + widget->data2 = args.getvalue; + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + // bind var + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + if (var) { + UiList* list = (UiList*)var->value; + list->update = ui_breadcrumbbar_update; + list->obj = widget; + ui_breadcrumbbar_update(list, 0); + } + + if (args.onactivate) { + ui_callback cb = args.onactivate; + void* cbdata = args.onactivatedata; + bcbar.ItemClicked([cb, cbdata, obj](IInspectable const& sender, BreadcrumbBarItemClickedEventArgs evtargs) { + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = nullptr; + evt.intval = evtargs.Index(); + cb(&evt, cbdata); + }); + } + + // add listview to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(bcbar, false); + + return widget; +} + +static void* getstrvalue(void* elm, int ignore) { + return elm; +} + +void ui_simple_list_update(UiList* list, int i) { + UiWidget* widget = (UiWidget*)list->obj; + UiModel* model = (UiModel*)widget->data1; + ui_getvaluefunc getvalue = (ui_getvaluefunc)widget->data2; + ItemsControl listview = widget->uielement.as(); + auto items = listview.Items(); + + // priority: getvalue, model.getvalue, getstrvalue (fallback) + if (getvalue == nullptr) { + if (model && model->getvalue) { + getvalue = model->getvalue; + } else { + getvalue = getstrvalue; + } + } + + // add list elements to listview.Items + items.Clear(); + void* elm = list->first(list); + while (elm) { + char* value = (char*)getvalue(elm, 0); + wchar_t* wstr = str2wstr(value, nullptr); + items.Append(box_value(wstr)); + free(wstr); + + elm = list->next(list); + } +} + +extern "C" void ui_breadcrumbbar_update(UiList * list, int i) { + UiWidget* widget = (UiWidget*)list->obj; + UiModel* model = (UiModel*)widget->data1; + ui_getvaluefunc getvalue = (ui_getvaluefunc)widget->data2; + + // priority: getvalue, model.getvalue, getstrvalue (fallback) + if (getvalue == nullptr) { + if (model && model->getvalue) { + getvalue = model->getvalue; + } + else { + getvalue = getstrvalue; + } + } + + BreadcrumbBar bar = widget->uielement.as(); + + Windows::Foundation::Collections::IVector items { winrt::single_threaded_vector() }; + void* elm = list->first(list); + while (elm) { + char* value = (char*)getvalue(elm, 0); + wchar_t* wstr = str2wstr(value, nullptr); + items.Append(box_value(wstr)); + free(wstr); + + elm = list->next(list); + } + + bar.ItemsSource(items); +} + + +std::vector ui_create_listview_selection(ListView listview) { + std::vector selection; + int p = 0; + auto ranges = listview.SelectedRanges(); + for (auto range : ranges) { + int begin = range.FirstIndex(); + int end = range.LastIndex(); + for (int i = begin; i <= end; i++) { + selection.push_back(i); + } + } + return selection; +} + diff --git a/ui/winui/list.h b/ui/winui/list.h new file mode 100644 index 0000000..bbb63f6 --- /dev/null +++ b/ui/winui/list.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#pragma once + +#include "../ui/tree.h" +#include "toolkit.h" + +#include "../ui/container.h" + + +extern "C" void ui_simple_list_update(UiList * list, int i); + +extern "C" void ui_breadcrumbbar_update(UiList * list, int i); + +std::vector ui_create_listview_selection(winrt::Microsoft::UI::Xaml::Controls::ListView listview); + +extern "C" UiListSelection ui_listview_getselection(UiList *list); +extern "C" void ui_listview_setselection(UiList *list, UiListSelection selection); + +extern "C" UiListSelection ui_dropdown_getselection(UiList *list); +extern "C" void ui_dropdown_setselection(UiList *list, UiListSelection selection); diff --git a/ui/winui/packages.config b/ui/winui/packages.config new file mode 100644 index 0000000..fc8fa30 --- /dev/null +++ b/ui/winui/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ui/winui/pch.cpp b/ui/winui/pch.cpp new file mode 100644 index 0000000..83ab966 --- /dev/null +++ b/ui/winui/pch.cpp @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include "pch.h" diff --git a/ui/winui/pch.h b/ui/winui/pch.h new file mode 100644 index 0000000..5ed2dd1 --- /dev/null +++ b/ui/winui/pch.h @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#pragma once +#include +#include +#include +#include + +// Undefine GetCurrentTime macro to prevent +// conflict with Storyboard::GetCurrentTime +#undef GetCurrentTime + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include diff --git a/ui/winui/readme.txt b/ui/winui/readme.txt new file mode 100644 index 0000000..e0c958e --- /dev/null +++ b/ui/winui/readme.txt @@ -0,0 +1,27 @@ +======================================================================== + winui Project Overview +======================================================================== + +This project demonstrates how to get started writing WinUI3 apps directly +with standard C++, using the Windows App SDK and C++/WinRT packages and +XAML compiler support to generate implementation headers from interface +(IDL) files. These headers can then be used to implement the local +Windows Runtime classes referenced in the app's XAML pages. + +Steps: +1. Create an interface (IDL) file to define any local Windows Runtime + classes referenced in the app's XAML pages. +2. Build the project once to generate implementation templates under + the "Generated Files" folder, as well as skeleton class definitions + under "Generated Files\sources". +3. Use the skeleton class definitions for reference to implement your + Windows Runtime classes. + +======================================================================== +Learn more about Windows App SDK here: +https://docs.microsoft.com/windows/apps/windows-app-sdk/ +Learn more about WinUI3 here: +https://docs.microsoft.com/windows/apps/winui/winui3/ +Learn more about C++/WinRT here: +http://aka.ms/cppwinrt/ +======================================================================== diff --git a/ui/winui/stock.cpp b/ui/winui/stock.cpp new file mode 100644 index 0000000..740b12f --- /dev/null +++ b/ui/winui/stock.cpp @@ -0,0 +1,5 @@ + + +#include "pch.h" + +#include "stock.h" \ No newline at end of file diff --git a/ui/winui/stock.h b/ui/winui/stock.h new file mode 100644 index 0000000..79e8ef9 --- /dev/null +++ b/ui/winui/stock.h @@ -0,0 +1,32 @@ +/* + * 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. + */ + +#pragma once + +#include "../ui/stock.h" + diff --git a/ui/winui/table.cpp b/ui/winui/table.cpp new file mode 100644 index 0000000..3641f99 --- /dev/null +++ b/ui/winui/table.cpp @@ -0,0 +1,648 @@ +/* + * 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 "pch.h" + +#include "table.h" +#include "container.h" +#include "util.h" +#include "icons.h" + +#include "../common/context.h" +#include "../common/object.h" +#include "../common/types.h" + +#include +#include +#include +#include +#include +#include + +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; +using namespace winrt::Windows::UI::Xaml::Input; + +static UINT ui_double_click_time = GetDoubleClickTime(); + +extern "C" void reg_table_destructor(UiContext * ctx, UiTable * table) { + // TODO: +} + +static void textblock_set_str(TextBlock& t, const char* str) { + if (str) { + wchar_t* wstr = str2wstr(str, nullptr); + t.Text(winrt::hstring(wstr)); + free(wstr); + } +} + +static void textblock_set_int(TextBlock& t, int i) { + wchar_t buf[16]; + swprintf(buf, 16, L"%d", i); + t.Text(winrt::hstring(buf)); +} + +UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs args) { + if (!args.model) { + return nullptr; + } + + UiObject* current = uic_current_obj(obj); + + // create widgets and wrapper obj + ScrollViewer scrollW = ScrollViewer(); + Grid grid = Grid(); + scrollW.Content(grid); + UiTable* uitable = new UiTable(obj, scrollW, grid); + reg_table_destructor(current->ctx, uitable); + + uitable->getvalue = args.model->getvalue ? args.model->getvalue : args.getvalue; + uitable->onselection = args.onselection; + uitable->onselectiondata = args.onselectiondata; + uitable->onactivate = args.onactivate; + uitable->onactivatedata = args.onactivatedata; + uitable->ondragstart = args.ondragstart; + uitable->ondragstartdata = args.ondragstartdata; + uitable->ondragcomplete = args.ondragcomplete; + uitable->ondrop = args.ondrop; + uitable->ondropdata = args.ondropsdata; + + // grid styling + winrt::Windows::UI::Color bg = { 255, 255, 255, 255 }; // test color + SolidColorBrush brush = SolidColorBrush(bg); + grid.Background(brush); + + // add columns from args.model + uitable->add_header(args.model); + + // bind var + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + if (var) { + UiList* list = (UiList*)var->value; + list->update = ui_table_update; + list->getselection = ui_table_selection; + list->obj = uitable; + uitable->update(list, 0); + } + + // create toolkit wrapper object and register destructor + UIElement elm = scrollW; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + + // add scrollW to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(scrollW, false); + + return widget; +} + +extern "C" void ui_table_update(UiList * list, int i) { + UiTable* table = (UiTable*)list->obj; + table->clear(); + table->update(list, i); +} + +extern "C" UiListSelection ui_table_selection(UiList * list) { + UiTable* table = (UiTable*)list->obj; + return table->uiselection(); +} + +UiTable::UiTable(UiObject *obj, winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollW, winrt::Microsoft::UI::Xaml::Controls::Grid grid) { + this->obj = obj; + + this->scrollw = scrollw; + this->grid = grid; + + winrt::Windows::UI::Color highlightBg = { 255, 234, 234, 234 }; + highlightBrush = SolidColorBrush(highlightBg); + + winrt::Windows::UI::Color defaultBg = { 0, 0, 0, 0 }; // default + defaultBrush = SolidColorBrush(defaultBg); + + winrt::Windows::UI::Color selectedBg = { 255, 204, 232, 255 }; // test color + selectedBrush = SolidColorBrush(selectedBg); + + winrt::Windows::UI::Color selectedFg = { 255, 0, 90, 158 }; // test color + selectedBorderBrush = SolidColorBrush(selectedFg); + + grid.KeyDown( + winrt::Microsoft::UI::Xaml::Input::KeyEventHandler( + [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& args) { + // key event for hanling the table cursor or enter + }) + ); +} + +UiTable::~UiTable() { + ui_model_free(NULL, model); +} + +void UiTable::add_header(UiModel* model) { + this->model = ui_model_copy(NULL, model); + + GridLength gl; + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + + // add header row definition + auto headerRowDef = RowDefinition(); + headerRowDef.Height(gl); + grid.RowDefinitions().Append(headerRowDef); + + winrt::Windows::UI::Color borderColor = { 63, 0, 0, 0 }; + SolidColorBrush borderBrush = SolidColorBrush(borderColor); + + + for (int i = 0; i < model->columns;i++) { + char* title = model->titles[i]; + UiModelType type = model->types[i]; + + // add grid column definition + auto colDef = ColumnDefinition(); + colDef.Width(gl); + grid.ColumnDefinitions().Append(colDef); + + // header column border + Border headerBorder = Border(); + Thickness border = { 0,0,1,0 }; + headerBorder.BorderThickness(border); + headerBorder.BorderBrush(borderBrush); + + // add text + auto hLabel = TextBlock(); + textblock_set_str(hLabel, title); + Thickness cellpadding = { 10,4,4,4 }; + hLabel.Padding(cellpadding); + hLabel.VerticalAlignment(VerticalAlignment::Stretch); + + // event handler for highlighting and column resizing + headerBorder.PointerPressed( + winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( + [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { + // the last column doesn't need resize capabilities + if (i + 1 < model->columns) { + double width = headerBorder.ActualWidth(); + auto point = args.GetCurrentPoint(headerBorder); + auto position = point.Position(); + if (position.X + 4 >= width) { + this->resize = true; + this->resizedCol = headerBorder; + } + } + }) + ); + headerBorder.PointerReleased( + winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( + [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { + this->resize = false; + }) + ); + headerBorder.PointerMoved( + winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( + [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { + if (this->resize) { + auto point = args.GetCurrentPoint(this->resizedCol); + auto position = point.Position(); + if (position.X > 1) { + this->resizedCol.Width(position.X); + } + } + }) + ); + headerBorder.PointerEntered( + winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( + [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { + // TODO: background + }) + ); + headerBorder.PointerExited( + winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( + [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { + // TODO: background + }) + ); + + + + // add controls + headerBorder.Child(hLabel); + + grid.SetColumn(headerBorder, i); + grid.SetRow(headerBorder, 0); + grid.Children().Append(headerBorder); + + UiTableColumn h; + h.header = headerBorder; + header.push_back(h); + } + + maxrows = 1; +} + +static ULONG64 getsystime() { + SYSTEMTIME st; + GetSystemTime(&st); + return st.wYear * 10000000000000 + + st.wMonth * 100000000000 + + st.wDay * 1000000000 + + st.wHour * 10000000 + + st.wMinute * 100000 + + st.wSecond * 1000 + + st.wMilliseconds; +} + +void UiTable::update(UiList* list, int i) { + if (getvalue == nullptr) { + return; + } + + Thickness b1 = { 1, 1, 0, 1 }; // first col + Thickness b2 = { 0, 1, 0, 1 }; // middle + Thickness b3 = { 0, 1, 1, 1 }; // last col + + GridLength gl; + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + + // iterate model + int row = 1; + void* elm = list->first(list); + while (elm) { + if (row >= maxrows) { + auto rowdef = RowDefinition(); + rowdef.Height(gl); + grid.RowDefinitions().Append(rowdef); + maxrows = row; + } + + Thickness cellpadding = { 10,0,4,0 }; + + // model column, usually the same as col, however UI_ICON_TEXT uses two columns in the model + int model_col = 0; + for (int col = 0; col < header.size(); col++, model_col++) { + // create ui elements with the correct cell border + // dependeing on the column + Border cellBorder = Border(); + cellBorder.Background(defaultBrush); + cellBorder.BorderBrush(defaultBrush); + if (col == 0) { + cellBorder.BorderThickness(b1); + } + else if (col + 1 == header.size()) { + cellBorder.BorderThickness(b3); + } + else { + cellBorder.BorderThickness(b2); + } + + // dnd + if (ondragstart) { + cellBorder.CanDrag(true); + cellBorder.DragStarting([this](IInspectable const& sender, DragStartingEventArgs args) { + UiDnD dnd; + dnd.evttype = 0; + dnd.dndstartargs = args; + dnd.dndcompletedargs = { nullptr }; + dnd.drageventargs = { nullptr }; + dnd.data = args.Data(); + + UiEvent evt; + evt.obj = this->obj; + evt.window = evt.obj->window; + evt.document = obj->ctx->document; + evt.eventdata = &dnd; + evt.intval = 0; + + this->ondragstart(&evt, this->ondragstartdata); + }); + cellBorder.DropCompleted([this](IInspectable const& sender, DropCompletedEventArgs args) { + UiDnD dnd; + dnd.evttype = 1; + dnd.dndstartargs = { nullptr }; + dnd.dndcompletedargs = args; + dnd.drageventargs = { nullptr }; + dnd.data = { nullptr }; + + UiEvent evt; + evt.obj = this->obj; + evt.window = evt.obj->window; + evt.document = obj->ctx->document; + evt.eventdata = &dnd; + evt.intval = 0; + + if (this->ondragcomplete) { + this->ondragcomplete(&evt, this->ondragcompletedata); + } + }); + } + if (ondrop) { + cellBorder.AllowDrop(true); + cellBorder.Drop(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){ + UiDnD dnd; + dnd.evttype = 2; + dnd.dndstartargs = { nullptr }; + dnd.dndcompletedargs = { nullptr }; + dnd.drageventargs = args; + dnd.dataview = args.DataView(); + + UiEvent evt; + evt.obj = this->obj; + evt.window = evt.obj->window; + evt.document = obj->ctx->document; + evt.eventdata = &dnd; + evt.intval = 0; + + this->ondrop(&evt, this->ondropdata); + })); + cellBorder.DragOver(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){ + args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Copy); + })); + } + + // set the cell value + // depending on the type, we create different cell controls + UiModelType type = model->types[col]; + switch (type) { + case UI_STRING_FREE: + case UI_STRING: { + TextBlock cell = TextBlock(); + cell.Padding(cellpadding); + cell.VerticalAlignment(VerticalAlignment::Stretch); + char *val = (char*)getvalue(elm, model_col); + textblock_set_str(cell, val); + cellBorder.Child(cell); + if (type == UI_STRING_FREE && val) { + free(val); + } + + break; + } + case UI_INTEGER: { + TextBlock cell = TextBlock(); + cell.Padding(cellpadding); + cell.VerticalAlignment(VerticalAlignment::Stretch); + int *value = (int*)getvalue(elm, model_col); + if (value) { + textblock_set_int(cell, *value); + } + cellBorder.Child(cell); + break; + } + case UI_ICON: { + UiIcon* iconConstr = (UiIcon*)getvalue(elm, model_col); + if (iconConstr) { + IconElement icon = iconConstr->getIcon(); + cellBorder.Child(icon); + } + break; + } + case UI_ICON_TEXT_FREE: + case UI_ICON_TEXT: { + StackPanel cellPanel = StackPanel(); + cellPanel.Spacing(2); + cellPanel.Padding(cellpadding); + cellPanel.VerticalAlignment(VerticalAlignment::Stretch); + + cellPanel.Orientation(Orientation::Horizontal); + UiIcon* iconConstr = (UiIcon*)getvalue(elm, model_col++); + char* str = (char*)getvalue(elm, model_col); + if (iconConstr) { + IconElement icon = iconConstr->getIcon(); + cellPanel.Children().Append(icon); + } + TextBlock cell = TextBlock(); + textblock_set_str(cell, str); + cellPanel.Children().Append(cell); + cellBorder.Child(cellPanel); + if (type == UI_ICON_TEXT_FREE && str) { + free(str); + } + break; + } + } + + // event handler + cellBorder.PointerPressed( + winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( + [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { + winrt::Windows::System::VirtualKeyModifiers modifiers = args.KeyModifiers(); + bool update_selection = true; + + if (modifiers == winrt::Windows::System::VirtualKeyModifiers::Control) { + // add/remove current row + if (!is_row_selected(row)) { + row_background(row, selectedBrush, selectedBorderBrush); + selection.push_back(row); + } + else { + row_background(row, highlightBrush, highlightBrush); + remove_from_selection(row); + } + } + else if (modifiers == winrt::Windows::System::VirtualKeyModifiers::None || selection.size() == 0) { + // no modifier or shift is pressed but there is no selection + if (selection.size() > 0) { + change_rows_bg(selection, defaultBrush, defaultBrush); + } + + row_background(row, selectedBrush, selectedBorderBrush); + selection = { row }; + if (modifiers == winrt::Windows::System::VirtualKeyModifiers::None) { + SYSTEMTIME st; + GetSystemTime(&st); + + ULONG64 now = getsystime(); + ULONG64 tdiff = now - lastPointerPress; + if (tdiff < ui_double_click_time && onactivate != nullptr) { + // two pointer presse events in short time and we have an onactivate handler + update_selection = false; // we don't want an additional selection event + lastPointerPress = 0; // reset double-click + + int selectedrow = row - 1; // subtract header row + + UiListSelection selection; + selection.count = 1; + selection.rows = &selectedrow; + + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = &selection; + evt.intval = selectedrow; + onactivate(&evt, onactivatedata); + } + else { + lastPointerPress = now; + } + } + } + else if (modifiers == winrt::Windows::System::VirtualKeyModifiers::Shift) { + // select everything between the first selection and the current row + std::sort(selection.begin(), selection.end()); + int first = selection.front(); + int last = row; + if (first > row) { + last = first; + first = row; + } + + // clear previous selection + change_rows_bg(selection, defaultBrush, defaultBrush); + + // create new selection + std::vector newselection; + for (int s = first; s <= last; s++) { + newselection.push_back(s); + } + selection = newselection; + change_rows_bg(selection, selectedBrush, selectedBorderBrush); + } + + if (update_selection) { + call_handler(onselection, onselectiondata); + } + }) + ); + cellBorder.PointerReleased( + winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( + [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { + + }) + ); + cellBorder.PointerEntered( + winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( + [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { + if (!is_row_selected(row)) { + row_background(row, highlightBrush, highlightBrush); + } + }) + ); + cellBorder.PointerExited( + winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( + [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { + if (!is_row_selected(row)) { + row_background(row, defaultBrush, defaultBrush); + } + }) + ); + + grid.SetColumn(cellBorder, col); + grid.SetRow(cellBorder, row); + grid.Children().Append(cellBorder); + } + + row++; + elm = list->next(list); + } +} + +void UiTable::clear() { + for (int i = grid.Children().Size()-1; i >= 0; i--) { + FrameworkElement elm = grid.Children().GetAt(i).as(); + int child_row = grid.GetRow(elm); + if (child_row > 0) { + grid.Children().RemoveAt(i); + } + } + + // TODO: should we clean row definitions? +} + +void UiTable::row_background(int row, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush) { + Thickness b1 = { 1, 1, 0, 1 }; // first col + Thickness b2 = { 0, 1, 0, 1 }; // middle + Thickness b3 = { 0, 1, 1, 1 }; // last col + + for (auto child : grid.Children()) { + FrameworkElement elm = child.as(); + int child_row = grid.GetRow(elm); + if (child_row == row) { + Border b = elm.as(); + b.Background(brush); + b.BorderBrush(borderBrush); + } + } +} + +void UiTable::change_rows_bg(std::vector rows, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush) { + std::for_each(rows.cbegin(), rows.cend(), [&](const int& row) {row_background(row, brush, borderBrush); }); +} + +bool UiTable::is_row_selected(int row) { + return std::find(selection.begin(), selection.end(), row) != selection.end() ? true : false; +} + +void UiTable::remove_from_selection(int row) { + selection.erase(std::remove(selection.begin(), selection.end(), row), selection.end()); + selection.shrink_to_fit(); +} + +UiListSelection UiTable::uiselection() { + std::sort(selection.begin(), selection.end()); + + UiListSelection selobj; + selobj.count = selection.size(); + selobj.rows = nullptr; + if (selobj.count > 0) { + selobj.rows = (int*)calloc(selobj.count, sizeof(int)); + memcpy(selobj.rows, selection.data(), selobj.count * sizeof(int)); + for (int i = 0; i < selobj.count; i++) { + selobj.rows[i]--; + } + } + return selobj; +} + +void UiTable::call_handler(ui_callback cb, void* cbdata) { + if (!cb) { + return; + } + + UiListSelection selobj = uiselection(); + + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = &selobj; + evt.intval = 0; + cb(&evt, cbdata); + + if (selobj.rows) { + free(selobj.rows); + } +} diff --git a/ui/winui/table.h b/ui/winui/table.h new file mode 100644 index 0000000..51fa97d --- /dev/null +++ b/ui/winui/table.h @@ -0,0 +1,98 @@ +/* + * 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. + */ + +#pragma once + +#include "../ui/tree.h" +#include "toolkit.h" +#include "dnd.h" + +#include "../ui/container.h" + + +typedef struct UiTableColumn { + winrt::Microsoft::UI::Xaml::Controls::Border header; + +} UiTableColumn; + +typedef struct UiTable { + winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollw; + winrt::Microsoft::UI::Xaml::Controls::Grid grid; + winrt::Microsoft::UI::Xaml::Media::SolidColorBrush defaultBrush; + winrt::Microsoft::UI::Xaml::Media::SolidColorBrush highlightBrush; + winrt::Microsoft::UI::Xaml::Media::SolidColorBrush selectedBrush; + winrt::Microsoft::UI::Xaml::Media::SolidColorBrush selectedBorderBrush; + + winrt::Microsoft::UI::Xaml::Controls::Border resizedCol{ nullptr }; + bool resize = false; + + UiObject* obj; + ui_callback onactivate; + void* onactivatedata; + ui_callback onselection; + void* onselectiondata; + ui_callback ondragstart; + void* ondragstartdata; + ui_callback ondragcomplete; + void* ondragcompletedata; + ui_callback ondrop; + void* ondropdata; + UiModel* model = nullptr; + std::vector header; + ui_getvaluefunc getvalue = nullptr; + int maxrows = 0; + int lastSelection = 0; + ULONG64 lastPointerPress = 0; + std::vector selection; + + UiTable(UiObject *obj, winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollW, winrt::Microsoft::UI::Xaml::Controls::Grid grid); + + ~UiTable(); + + void add_header(UiModel* model); + + void update(UiList* list, int i); + + void clear(); + + void row_background(int row, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush); + + void change_rows_bg(std::vector rows, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush); + + bool is_row_selected(int row); + + void remove_from_selection(int row); + + UiListSelection uiselection(); + + void call_handler(ui_callback cb, void *cbdata); +} UiTable; + +extern "C" void ui_table_update(UiList * list, int i); + +extern "C" UiListSelection ui_table_selection(UiList * list); diff --git a/ui/winui/text.cpp b/ui/winui/text.cpp new file mode 100644 index 0000000..ce983ff --- /dev/null +++ b/ui/winui/text.cpp @@ -0,0 +1,609 @@ +/* + * 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 "pch.h" + +#include "text.h" + +#include "../common/context.h" +#include "../common/object.h" + +#include +#include + +#include "util.h" +#include "container.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 Microsoft::UI::Xaml::Markup; +using namespace Microsoft::UI::Xaml::Media; +using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives; +using namespace winrt::Windows::UI::Xaml::Input; + + +UIEXPORT UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) { + UiObject* current = uic_current_obj(obj); + + // create textarea and toolkit wrapper + TextBox textarea = TextBox(); + textarea.AcceptsReturn(true); + ScrollViewer::SetVerticalScrollBarVisibility(textarea, ScrollBarVisibility::Auto); + UIElement elm = textarea; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_TEXT); + if (var) { + UiText* value = (UiText*)var->value; + value->obj = widget; + value->undomgr = NULL; + 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->remove = ui_textarea_remove; + } + + // add textarea to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(textarea, true); + + return widget; +} + +UIEXPORT UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) { + return textarea; +} + +UIEXPORT void ui_text_undo(UiText *value) { + +} + +UIEXPORT void ui_text_redo(UiText *value) { + +} + +// -------------------------- getter/setter for textarea UiText -------------------------- + +char* ui_wtext_get(UiText *text, std::wstring &value) { + if (text->value.ptr) { + text->value.free(text->value.ptr); + } + + text->value.ptr = wchar2utf8(value.c_str(), value.length()); + text->value.free = free; + + return text->value.ptr; +} + +std::wstring ui_wtext_set(UiText *text, const char* value) { + if (text->value.ptr) { + text->value.free(text->value.ptr); + } + + text->value.ptr = _strdup(value); + text->value.free = free; + + int len; + wchar_t* wstr = str2wstr(value, &len); + std::wstring s(wstr); + free(wstr); + + return s; +} + +extern "C" char* ui_textarea_get(UiText *text) { + UiWidget* widget = (UiWidget*)text->obj; + TextBox box = widget->uielement.as(); + std::wstring wstr(box.Text()); + return ui_wtext_get(text, wstr); +} + +extern "C" void ui_textarea_set(UiText *text, const char *newvalue) { + UiWidget* widget = (UiWidget*)text->obj; + TextBox box = widget->uielement.as(); + box.Text(ui_wtext_set(text, newvalue)); +} + +extern "C" char* ui_textarea_getsubstr(UiText *text, int begin, int end) { + return NULL; +} + +extern "C" void ui_textarea_insert(UiText *text, int pos, char *str) { + +} + +extern "C" void ui_textarea_setposition(UiText *text, int pos) { + +} + +extern "C" int ui_textarea_position(UiText *text) { + return 0; +} + +extern "C" void ui_textarea_selection(UiText *text, int *begin, int *end) { + +} + +extern "C" int ui_textarea_length(UiText *text) { + return 0; +} + +extern "C" void ui_textarea_remove(UiText *text, int begin, int end) { + +} + + + + +UIWIDGET ui_textfield_create(UiObject* obj, UiTextFieldArgs args) { + UiObject* current = uic_current_obj(obj); + + // create textbox and toolkit wrapper + TextBox textfield = TextBox(); + UIElement elm = textfield; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + 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_textfield_get; + value->set = ui_textfield_set; + + // listener for notifying observers + // TODO: + } + + // add textfield to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(textfield, false); + + return widget; +} + +UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) { + return ui_textfield_create(obj, args); +} + +UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) { + UiObject* current = uic_current_obj(obj); + + // create textbox and toolkit wrapper + PasswordBox textfield = PasswordBox(); + UIElement elm = textfield; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + 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_passwordfield_get; + value->set = ui_passwordfield_set; + + // listener for notifying observers + // TODO: + } + + // add textfield to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(textfield, false); + + return widget; +} + + +// -------------------------- getter/setter for textfield UiString -------------------------- + +char* ui_wstring_get(UiString* str, std::wstring &value) { + if (str->value.ptr) { + str->value.free(str->value.ptr); + } + + str->value.ptr = wchar2utf8(value.c_str(), value.length()); + str->value.free = free; + + return str->value.ptr; +} + +std::wstring ui_wstring_set(UiString* str, const char* value) { + if (str->value.ptr) { + str->value.free(str->value.ptr); + } + + str->value.ptr = _strdup(value); + str->value.free = free; + + int len; + wchar_t* wstr = str2wstr(value, &len); + std::wstring s(wstr); + free(wstr); + + return s; +} + +char* ui_textfield_get(UiString * str) { + UiWidget* widget = (UiWidget*)str->obj; + TextBox box = widget->uielement.as(); + std::wstring wstr(box.Text()); + return ui_wstring_get(str, wstr); +} + +void ui_textfield_set(UiString * str, const char* newvalue) { + UiWidget* widget = (UiWidget*)str->obj; + TextBox box = widget->uielement.as(); + box.Text(ui_wstring_set(str, newvalue)); +} + + +char* ui_passwordfield_get(UiString * str) { + UiWidget* widget = (UiWidget*)str->obj; + PasswordBox box = widget->uielement.as(); + std::wstring wstr(box.Password()); + return ui_wstring_get(str, wstr); +} + +void ui_passwordfield_set(UiString * str, const char* newvalue) { + UiWidget* widget = (UiWidget*)str->obj; + PasswordBox box = widget->uielement.as(); + box.Password(ui_wstring_set(str, newvalue)); +} + + +// ------------------------ path textfield -------------------------------------- + +extern "C" static void destroy_ui_pathtextfield(void* ptr) { + UiPathTextField* pb = (UiPathTextField*)ptr; + delete pb; +} + +static void ui_context_add_pathtextfield_destructor(UiContext* ctx, UiPathTextField* pb) { + cxMempoolRegister(ctx->mp, pb, destroy_ui_pathtextfield); +} + +static void ui_pathtextfield_clear(StackPanel& buttons) { + for (int i = buttons.Children().Size() - 1; i >= 0; i--) { + buttons.Children().RemoveAt(i); + } +} + +static void ui_pathfield_free_pathelms(UiPathElm* elms, size_t nelm) { + if (!elms) { + return; + } + for (int i = 0; i < nelm; i++) { + UiPathElm e = elms[i]; + free(e.name); + free(e.path); + } + free(elms); +} + +UiPathTextField::~UiPathTextField() { + ui_pathfield_free_pathelms(this->current_path, this->current_path_nelms); +} + +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 nullptr; + } + + 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; +} + +int ui_pathtextfield_update(UiPathTextField* pb, const char *full_path) { + Grid grid = pb->grid; + + ui_pathelm_func getpathelm = pb->getpathelm; + void* getpathelmdata = pb->getpathelmdata; + + size_t full_path_len = full_path ? strlen(full_path) : 0; + + size_t nelm = 0; + UiPathElm* path_elm = getpathelm(full_path, full_path_len, &nelm, getpathelmdata); + if (!path_elm) { + return 1; + } + + // hide textbox, show button panel + pb->textbox.Visibility(Visibility::Collapsed); + pb->buttons.Visibility(Visibility::Visible); + + // clear old buttons + ui_pathtextfield_clear(pb->buttons); + + ui_pathfield_free_pathelms(pb->current_path, pb->current_path_nelms); + pb->current_path = path_elm; + pb->current_path_nelms = nelm; + + // add new buttons + int j = 0; + for (int i = 0; i < nelm;i++) { + UiPathElm elm = path_elm[i]; + wchar_t* wstr = str2wstr_len(elm.name, elm.name_len, nullptr); + Button button = Button(); + button.Content(box_value(wstr)); + free(wstr); + + if (pb->onactivate) { + button.Click([pb, j, elm](IInspectable const& sender, RoutedEventArgs) { + // copy elm.path because it could be a non-terminated string + cxmutstr elmpath = cx_strdup(cx_strn(elm.path, elm.path_len)); + + UiEvent evt; + evt.obj = pb->obj; + evt.window = evt.obj->window; + evt.document = evt.obj->ctx->document; + evt.eventdata = elmpath.ptr; + evt.intval = j; + pb->onactivate(&evt, pb->onactivatedata); + + free(elmpath.ptr); + }); + } + + Thickness t = { 0, 0, 1, 0 }; + CornerRadius c = { 0 ,0, 0, 0 }; + button.BorderThickness(t); + button.CornerRadius(c); + + pb->buttons.Children().Append(button); + + j++; + } + + return 0; +} + +char* ui_path_textfield_get(UiString * str) { + UiPathTextField* widget = (UiPathTextField*)str->obj; + TextBox box = widget->textbox; + std::wstring wstr(box.Text()); + return ui_wstring_get(str, wstr); +} + +void ui_path_textfield_set(UiString* str, const char* newvalue) { + UiPathTextField* widget = (UiPathTextField*)str->obj; + TextBox box = widget->textbox; + box.Text(ui_wstring_set(str, newvalue)); + ui_pathtextfield_update(widget, newvalue); +} + +UIEXPORT UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) { + UiObject* current = uic_current_obj(obj); + + // create view and toolkit wrapper + Border pathbar = Border(); + + IInspectable bgRes = Application::Current().Resources().Lookup(box_value(L"TextControlBackground")); + IInspectable borderThicknessRes = Application::Current().Resources().Lookup(box_value(L"TextControlBorderThemeThickness")); + IInspectable borderBrushRes = Application::Current().Resources().Lookup(box_value(L"TextControlBorderBrush")); + // IInspectable cornerRes = Application::Current().Resources().Lookup(box_value(L"TextControlCornerRadius")); + + Brush bgBrush = unbox_value(bgRes); + Thickness border = unbox_value(borderThicknessRes); + Brush borderBrush = unbox_value(borderBrushRes); + CornerRadius cornerRadius = { 4, 4, 4, 4 }; //unbox_value(cornerRes); + + pathbar.Background(bgBrush); + pathbar.BorderBrush(borderBrush); + pathbar.BorderThickness(border); + pathbar.CornerRadius(cornerRadius); + + Grid content = Grid(); + pathbar.Child(content); + + GridLength gl; + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + + ColumnDefinition coldef = ColumnDefinition(); + coldef.Width(gl); + content.ColumnDefinitions().Append(coldef); + + gl.Value = 1; + gl.GridUnitType = GridUnitType::Star; + + ColumnDefinition coldef2 = ColumnDefinition(); + coldef2.Width(gl); + content.ColumnDefinitions().Append(coldef2); + + TextBox pathTextBox = TextBox(); + Thickness t = { 0, 0, 0, 0 }; + CornerRadius c = { 0 ,0, 0, 0 }; + pathTextBox.BorderThickness(t); + //pathTextBox.CornerRadius(c); + + + pathTextBox.HorizontalAlignment(HorizontalAlignment::Stretch); + content.SetColumn(pathTextBox, 0); + content.SetColumnSpan(pathTextBox, 2); + + content.Children().Append(pathTextBox); + + // stackpanel for buttons + StackPanel buttons = StackPanel(); + buttons.Orientation(Orientation::Horizontal); + buttons.Visibility(Visibility::Collapsed); + content.SetColumn(buttons, 0); + content.Children().Append(buttons); + + TextBlock filler = TextBlock(); + filler.VerticalAlignment(VerticalAlignment::Stretch); + //filler.Text(winrt::hstring(L"hello filler")); + + filler.HorizontalAlignment(HorizontalAlignment::Stretch); + filler.VerticalAlignment(VerticalAlignment::Stretch); + content.SetColumn(filler, 1); + content.Children().Append(filler); + + filler.PointerPressed( + winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( + [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { + pathTextBox.Visibility(Visibility::Visible); + buttons.Visibility(Visibility::Collapsed); + filler.Visibility(Visibility::Collapsed); + pathTextBox.SelectionStart(pathTextBox.Text().size()); + pathTextBox.SelectionLength(0); + pathTextBox.Focus(FocusState::Keyboard); + }) + ); + + //pathTextBox.Visibility(Visibility::Collapsed); + + UiPathTextField* uipathbar = new UiPathTextField; + ui_context_add_pathtextfield_destructor(current->ctx, uipathbar); + uipathbar->grid = content; + uipathbar->buttons = buttons; + uipathbar->textbox = pathTextBox; + uipathbar->filler = filler; + uipathbar->obj = obj; + uipathbar->getpathelm = args.getpathelm ? args.getpathelm : default_pathelm_func; + uipathbar->getpathelmdata = args.getpathelmdata; + uipathbar->onactivate = args.onactivate; + uipathbar->onactivatedata = args.onactivatedata; + uipathbar->ondragstart = args.ondragstart; + uipathbar->ondragstartdata = args.ondragstartdata; + uipathbar->ondragcomplete = args.ondragcomplete; + uipathbar->ondragcompletedata = args.ondragcompletedata; + uipathbar->ondrop = args.ondrop; + uipathbar->ondropdata = args.ondropsdata; + + + pathTextBox.KeyDown( + winrt::Microsoft::UI::Xaml::Input::KeyEventHandler( + [=](winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) { + auto key = e.Key(); + bool showButtons = false; + bool update = false; + if (key == Windows::System::VirtualKey::Escape) { + showButtons = true; + } + else if (key == Windows::System::VirtualKey::Enter) { + showButtons = true; + update = true; + } + + if (showButtons) { + pathTextBox.Visibility(Visibility::Collapsed); + buttons.Visibility(Visibility::Visible); + filler.Visibility(Visibility::Visible); + if (update) { + std::wstring value(pathTextBox.Text()); + char* full_path = wchar2utf8(value.c_str(), value.length()); + + if (!ui_pathtextfield_update(uipathbar, full_path)) { + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = full_path; + evt.intval = -1; + args.onactivate(&evt, args.onactivatedata); + } + + free(full_path); + } + + //buttons.Focus(FocusState::Keyboard); + } + }) + ); + + + UIElement elm = pathbar; + UiWidget* widget = new UiWidget(elm); + widget->data1 = uipathbar; + ui_context_add_widget_destructor(current->ctx, widget); + + // bind var + 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 = uipathbar; + value->get = ui_path_textfield_get; + value->set = ui_path_textfield_set; + } + + // add listview to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(pathbar, false); + + return widget; +} diff --git a/ui/winui/text.h b/ui/winui/text.h new file mode 100644 index 0000000..66ec6e1 --- /dev/null +++ b/ui/winui/text.h @@ -0,0 +1,86 @@ +/* + * 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. + */ + +#pragma once + +#include "../ui/text.h" +#include "toolkit.h" + +#include "../ui/container.h" + +struct UiPathTextField { + winrt::Microsoft::UI::Xaml::Controls::Grid grid = { nullptr }; + winrt::Microsoft::UI::Xaml::Controls::StackPanel buttons = { nullptr }; + winrt::Microsoft::UI::Xaml::Controls::TextBox textbox = { nullptr }; + winrt::Microsoft::UI::Xaml::Controls::TextBlock filler = { nullptr }; + + ~UiPathTextField(); + + UiPathElm* current_path = nullptr; + size_t current_path_nelms = 0; + + UiObject* obj; + + 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; +}; + +char* ui_wtext_get(UiText *text, std::wstring &value); +std::wstring ui_wtext_set(UiText *text, const char* value); + +char* ui_wstring_get(UiString* str, std::wstring& value); +std::wstring ui_wstring_set(UiString* str, const char* value); + +extern "C" char* ui_textarea_get(UiText *text); +extern "C" void ui_textarea_set(UiText *text, const char *newvalue); +extern "C" char* ui_textarea_getsubstr(UiText*, int, int); +extern "C" void ui_textarea_insert(UiText*, int, char*); +extern "C" void ui_textarea_setposition(UiText*,int); +extern "C" int ui_textarea_position(UiText*); +extern "C" void ui_textarea_selection(UiText*, int*, int*); +extern "C" int ui_textarea_length(UiText*); +extern "C" void ui_textarea_remove(UiText*, int, int); + +extern "C" char* ui_textfield_get(UiString *str); +extern "C" void ui_textfield_set(UiString *str, const char *newvalue); + +extern "C" char* ui_passwordfield_get(UiString * str); +extern "C" void ui_passwordfield_set(UiString * str, const char* newvalue); + +extern "C" char* ui_path_textfield_get(UiString * str); +extern "C" void ui_path_textfield_set(UiString * str, const char* newvalue); diff --git a/ui/winui/toolkit.cpp b/ui/winui/toolkit.cpp new file mode 100644 index 0000000..3b684dd --- /dev/null +++ b/ui/winui/toolkit.cpp @@ -0,0 +1,382 @@ +/* + * 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 "pch.h" + +#include "toolkit.h" + +#include +#include + +#include "../common/context.h" +#include "../common/document.h" +#include "../common/toolbar.h" +#include "../common/properties.h" + +#include "icons.h" + +#include "MainWindow.xaml.h" + +#include "App.xaml.h" + +using namespace winrt; +using namespace Microsoft::UI::Xaml; +using namespace Microsoft::UI::Xaml::Controls; +using namespace Microsoft::UI::Xaml::XamlTypeInfo; +using namespace Microsoft::UI::Xaml::Markup; +using namespace Windows::UI::Xaml::Interop; +using namespace winrt::Windows::Foundation; +using namespace Windows::UI::Core; + +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 winrt::Microsoft::UI::Dispatching::DispatcherQueue uiDispatcherQueue = { nullptr }; + +void ui_app_run_startup() { + uiDispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread(); + + if (startup_func) { + startup_func(NULL, startup_data); + } +} + +class App : public ApplicationT { +public: + void OnLaunched(LaunchActivatedEventArgs const&) { + Resources().MergedDictionaries().Append(XamlControlsResources()); + if (startup_func) { + startup_func(NULL, startup_data); + } + + //auto window = make(); + //window.Activate(); + } + IXamlType GetXamlType(TypeName const& type) { + return provider.GetXamlType(type); + } + IXamlType GetXamlType(hstring const& fullname) { + return provider.GetXamlType(fullname); + } + com_array GetXmlnsDefinitions() { + return provider.GetXmlnsDefinitions(); + } +private: + XamlControlsXamlMetaDataProvider provider; +}; + +UiWidget::UiWidget(winrt::Microsoft::UI::Xaml::UIElement& elm) : uielement(elm) {} + +extern "C" void destroy_ui_window_wrapper(void* ptr) { + UiWindow* win = (UiWindow*)ptr; + delete win; +} + +extern "C" void destroy_ui_widget_wrapper(void* ptr) { + UiWidget* widget = (UiWidget*)ptr; + delete widget; +} + +extern "C" void destroy_ui_container_wrapper(void* ptr) { + UiContainer* ctn = (UiContainer*)ptr; + delete ctn; +} + +void ui_context_add_window_destructor(UiContext* ctx, UiWindow* win) { + cxMempoolRegister(ctx->mp, win, destroy_ui_window_wrapper); +} + +void ui_context_add_widget_destructor(UiContext* ctx, UiWidget* widget) { + cxMempoolRegister(ctx->mp, widget, destroy_ui_widget_wrapper); +} + +void ui_context_add_container_destructor(UiContext* ctx, UiContainer *container) { + cxMempoolRegister(ctx->mp, container, destroy_ui_container_wrapper); +} + + +UiEvent ui_create_int_event(UiObject* obj, int64_t i) { + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = nullptr; + evt.intval = i; + return evt; +} + + +#include + +void ui_appsdk_bootstrap(void) { + const UINT32 majorMinorVersion{ 0x00010002 }; + PCWSTR versionTag{ L"" }; + const PACKAGE_VERSION minVersion{}; + + const HRESULT hr = MddBootstrapInitialize(majorMinorVersion, versionTag, minVersion); + if (FAILED(hr)) { + exit(102); + } +} + +void ui_init(const char* appname, int argc, char** argv) { + application_name = appname; + + //ui_appsdk_bootstrap(); + + uic_init_global_context(); + uic_docmgr_init(); + uic_menu_init(); + uic_toolbar_init(); + + uic_load_app_properties(); +} + +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; +} + +void ui_main() { + /* + init_apartment(); + //Application::Start([](auto&&) {make(); }); + + ::winrt::Microsoft::UI::Xaml::Application::Start( + [](auto&&) + { + ::winrt::make<::winrt::winui::implementation::App>(); + }); + */ + { + void (WINAPI * pfnXamlCheckProcessRequirements)(); + auto module = ::LoadLibrary(L"Microsoft.ui.xaml.dll"); + if (module) + { + pfnXamlCheckProcessRequirements = reinterpret_cast(GetProcAddress(module, "XamlCheckProcessRequirements")); + if (pfnXamlCheckProcessRequirements) + { + (*pfnXamlCheckProcessRequirements)(); + } + + ::FreeLibrary(module); + } + } + + winrt::init_apartment(winrt::apartment_type::single_threaded); + ::winrt::Microsoft::UI::Xaml::Application::Start( + [](auto&&) + { + ::winrt::make<::winrt::winui::implementation::App>(); + }); +} + +class UiWin { +public: + Window window; +}; + +void ui_show(UiObject* obj) { + if (obj->wobj) { + obj->wobj->window.Activate(); + } else if(obj->widget && obj->widget->Show) { + obj->widget->Show(); + } +} + +void ui_close(UiObject* obj) { + if (obj->wobj) { + obj->wobj->window.Close(); + } +} + +static void ui_job_thread(UiJob* job) { + if (!job->job_func(job->job_data) && job->finish_callback) { + bool isQueued = uiDispatcherQueue.TryEnqueue([job]() + { + 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); + delete job; + }); + if (!isQueued) { + // TODO: error or try again? + exit(-1); + } + } + else { + delete job; + } +} + +UIEXPORT void ui_job(UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd) { + UiJob* job = new UiJob; + job->obj = obj; + job->job_func = tf; + job->job_data = td; + job->finish_callback = f; + job->finish_data = fd; + + std::thread jobThread(ui_job_thread, job); + jobThread.detach(); +} + +UIEXPORT void ui_call_mainthread(ui_threadfunc tf, void* td) { + bool isQueued = uiDispatcherQueue.TryEnqueue([tf, td]() + { + (void)tf(td); + }); + if (!isQueued) { + // TODO: error or try again? + exit(-1); + } +} + +static UiJob kill_job; // &kill_job indicates to stop the thread + +static void ui_threadpool_run(UiThreadpool* pool) { + for (;;) { + UiJob* job = pool->GetJob(); + if (job == &kill_job) { + return; + } + else if (job) { + ui_job_thread(job); + } + } +} + +UiThreadpool::UiThreadpool(int nthreads) { + for (int i = 0; i < nthreads; i++) { + std::thread thread(ui_threadpool_run, this); + thread.detach(); + } +} + +void UiThreadpool::EnqueueJob(UiJob* job) +{ + std::unique_lock lock(mutex); + queue.push(job); + lock.unlock(); + condition.notify_one(); +} + +UiJob* UiThreadpool::GetJob() { + std::unique_lock lock(mutex); + + UiJob* job = nullptr; + while (!job) { + if (queue.empty()) { + condition.wait(lock); + continue; + } + else + { + job = queue.front(); + queue.pop(); + } + } + + return job; +} + +UIEXPORT UiThreadpool* ui_threadpool_create(int nthreads) { + return new UiThreadpool(nthreads); +} + +UIEXPORT void ui_threadpool_destroy(UiThreadpool* pool) { + // TODO +} + +UIEXPORT void ui_threadpool_job(UiThreadpool* pool, UiObject* obj, ui_threadfunc tf, void* td, ui_callback f, void* fd) { + UiJob* job = new UiJob; + job->obj = obj; + job->job_func = tf; + job->job_data = td; + job->finish_callback = f; + job->finish_data = fd; + pool->EnqueueJob(job); +} + + + +void ui_set_widget_groups(UiContext *ctx, UIWIDGET 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, UIWIDGET 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); + } +} + + +UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled) { + Control ctrl = widget->uielement.as(); + if (ctrl) { + ctrl.IsEnabled(enabled); + } +} diff --git a/ui/winui/toolkit.h b/ui/winui/toolkit.h new file mode 100644 index 0000000..ab421ec --- /dev/null +++ b/ui/winui/toolkit.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#pragma once + +#include "../ui/toolkit.h" + +#include +#include +#include + +typedef struct UiJob { + UiObject* obj; + ui_threadfunc job_func; + void* job_data; + ui_callback finish_callback; + void* finish_data; +} UiJob; + +struct UiThreadpool +{ + std::queue queue; + std::mutex mutex; + std::condition_variable condition; + + UiThreadpool(int nthreads); + + void EnqueueJob(UiJob* job); + + UiJob* GetJob(); +}; + +typedef void(*ui_eventfunc)(void*, void*); + +void ui_app_run_startup(); + +extern "C" void destroy_ui_window_wrapper(void* ptr); +extern "C" void destroy_ui_widget_wrapper(void* ptr); + +void ui_context_add_window_destructor(UiContext* ctx, UiWindow* win); +void ui_context_add_widget_destructor(UiContext* ctx, UiWidget* widget); +void ui_context_add_container_destructor(UiContext* ctx, UiContainer *container); + +UiEvent ui_create_int_event(UiObject* obj, int64_t i); + +void ui_set_widget_groups(UiContext *ctx, UIWIDGET widget, const int *groups); +void ui_set_widget_ngroups(UiContext *ctx, UIWIDGET widget, const int *groups, size_t ngroups); diff --git a/ui/winui/util.cpp b/ui/winui/util.cpp new file mode 100644 index 0000000..f550244 --- /dev/null +++ b/ui/winui/util.cpp @@ -0,0 +1,46 @@ +#include "pch.h" + +#include "util.h" + +#include + + +wchar_t* str2wstr(const char* str, int* newlen) { + size_t len = strlen(str); + + return str2wstr_len(str, len, newlen); +} + +wchar_t* str2wstr_len(const char* str, size_t len, int* newlen) { + wchar_t* wstr = (wchar_t*)calloc(len + 1, sizeof(wchar_t)); + int wlen = MultiByteToWideChar( + CP_UTF8, + 0, + str, + len, + wstr, + len + 1 + ); + if (newlen) { + *newlen = wlen; + } + wstr[wlen] = 0; + + return wstr; +} + +char* wchar2utf8(const wchar_t* wstr, size_t wlen) { + size_t maxlen = wlen * 4; + char* ret = (char*)malloc(maxlen + 1); + int ret_len = WideCharToMultiByte( + CP_UTF8, + 0, + wstr, + wlen, + ret, + maxlen, + NULL, + NULL); + ret[ret_len] = 0; + return ret; +} diff --git a/ui/winui/util.h b/ui/winui/util.h new file mode 100644 index 0000000..592eeb4 --- /dev/null +++ b/ui/winui/util.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +wchar_t* str2wstr(const char* str, int* newlen); + +wchar_t* str2wstr_len(const char* str, size_t len, int* newlen); + +char* wchar2utf8(const wchar_t* wstr, size_t wlen); diff --git a/ui/winui/window.cpp b/ui/winui/window.cpp new file mode 100644 index 0000000..208c23c --- /dev/null +++ b/ui/winui/window.cpp @@ -0,0 +1,637 @@ +/* + * 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 "pch.h" + + +#include "window.h" + +#include "appmenu.h" +#include "commandbar.h" +#include "container.h" +#include "util.h" +#include "button.h" + +#include "../common/context.h" +#include "../common/object.h" + +#include + +#include + +#include "MainWindow.xaml.h" + + +#include +#include +#include + +using namespace winrt; +using namespace Microsoft::UI::Xaml; +using namespace Microsoft::UI::Xaml::Controls; +using namespace Microsoft::UI::Xaml::Controls::Primitives; +using namespace Microsoft::UI::Xaml::XamlTypeInfo; +using namespace Microsoft::UI::Xaml::Markup; +using namespace Windows::UI::Xaml::Interop; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Storage::Pickers; + +UiWindow::UiWindow(winrt::Microsoft::UI::Xaml::Window& win) : window(win) {} + +UiObject* ui_window(const char* title, void* window_data) { + UiObject* obj = ui_simple_window(title, window_data); + + /* + if (uic_get_menu_list()) { + // create/add menubar + MenuBar mb = ui_create_menubar(obj); + mb.VerticalAlignment(VerticalAlignment::Top); + obj->container->Add(mb, false); + } + */ + + if (uic_toolbar_isenabled()) { + // create a grid for the toolbar: ColumnDefinitions="Auto, *, Auto" + Grid toolbar_grid = Grid(); + GridLength gl; + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + + ColumnDefinition coldef0 = ColumnDefinition(); + coldef0.Width(gl); + toolbar_grid.ColumnDefinitions().Append(coldef0); + + gl.Value = 1; + gl.GridUnitType = GridUnitType::Star; + ColumnDefinition coldef1 = ColumnDefinition(); + coldef1.Width(gl); + toolbar_grid.ColumnDefinitions().Append(coldef1); + + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + ColumnDefinition coldef2 = ColumnDefinition(); + coldef2.Width(gl); + toolbar_grid.ColumnDefinitions().Append(coldef2); + + // rowdef + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + RowDefinition rowdef = RowDefinition(); + rowdef.Height(gl); + toolbar_grid.RowDefinitions().Append(rowdef); + + + // create commandbar + CxList* def_l = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT); + CxList* def_c = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER); + CxList* def_r = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT); + + bool addappmenu = true; + if (cxListSize(def_r) > 0) { + CommandBar toolbar_r = ui_create_toolbar(obj, def_r, addappmenu); + toolbar_grid.SetColumn(toolbar_r, 2); + toolbar_grid.SetRow(toolbar_r, 0); + toolbar_grid.Children().Append(toolbar_r); + addappmenu = false; + } + if (cxListSize(def_c) > 0) { + CommandBar toolbar_c = ui_create_toolbar(obj, def_c, addappmenu); + toolbar_c.HorizontalAlignment(HorizontalAlignment::Center); + toolbar_grid.SetColumn(toolbar_c, 1); + toolbar_grid.SetRow(toolbar_c, 0); + toolbar_grid.Children().Append(toolbar_c); + addappmenu = false; + } + if (cxListSize(def_l) > 0) { + CommandBar toolbar_l = ui_create_toolbar(obj, def_l, addappmenu); + toolbar_grid.SetColumn(toolbar_l, 0); + toolbar_grid.SetRow(toolbar_l, 0); + toolbar_grid.Children().Append(toolbar_l); + } + + toolbar_grid.VerticalAlignment(VerticalAlignment::Top); + obj->container->Add(toolbar_grid, false); + } + + return obj; +} + +UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data) { + CxMempool* mp = cxBasicMempoolCreate(256); + UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject)); + + obj->ctx = uic_context(obj, mp); + obj->window = window_data; + + Window window = Window(); + //Window window = make(); + + winrt::Windows::Foundation::Uri resourceLocator{ L"ms-appx:///MainWindow.xaml" }; + Application::LoadComponent(window, resourceLocator, ComponentResourceLocation::Nested); + + window.ExtendsContentIntoTitleBar(true); + + Grid grid = Grid(); + window.Content(grid); + + StackPanel titleBar = StackPanel(); + Thickness titleBarPadding = { 10, 5, 5, 10 }; + titleBar.Padding(titleBarPadding); + titleBar.Orientation(Orientation::Horizontal); + TextBlock titleLabel = TextBlock(); + titleBar.Children().Append(titleLabel); + + if (title) { + wchar_t* wtitle = str2wstr(title, nullptr); + window.Title(wtitle); + titleLabel.Text(hstring(wtitle)); + free(wtitle); + } + + window.SetTitleBar(titleBar); + + obj->wobj = new UiWindow(window); + ui_context_add_window_destructor(obj->ctx, obj->wobj); + + window.Closed([obj](IInspectable const& sender, WindowEventArgs) { + if (obj->ctx->close_callback) { + UiEvent evt; + evt.obj = obj; + evt.document = obj->ctx->document; + evt.window = obj->window; + evt.eventdata = NULL; + evt.intval = 0; + obj->ctx->close_callback(&evt, obj->ctx->close_data); + } else { + ui_context_destroy(obj->ctx); + } + }); + + obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0); + + titleBar.VerticalAlignment(VerticalAlignment::Top); + obj->container->Add(titleBar, false); + + obj->window = window_data; + + return obj; +} + +static void dialog_button_add_callback(ContentDialog dialog, Button button, int num, UiObject *obj, ui_callback onclick, void *onclickdata) { + button.Click([dialog, num, obj, onclick, onclickdata](IInspectable const& sender, RoutedEventArgs) { + if (onclick) { + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = nullptr; + evt.intval = num; + onclick(&evt, onclickdata); + } + dialog.Hide(); + }); +} + +UIEXPORT UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) { + UiWindow *window = parent->wobj; + if (!window) { + return NULL; + } + + CxMempool* mp = cxBasicMempoolCreate(256); + UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject)); + + obj->ctx = uic_context(obj, mp); + + ContentDialog dialog = ContentDialog(); + UIElement elm = dialog; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(obj->ctx, widget); + obj->widget = widget; + + if (args.title) { + wchar_t* wtitle = str2wstr(args.title, nullptr); + dialog.Title(box_value(wtitle)); + free(wtitle); + } + + + Grid dialogContent = Grid(); + GridLength gl; + + // content row + gl.Value = 1; + gl.GridUnitType = GridUnitType::Star; + RowDefinition rowdef0 = RowDefinition(); + rowdef0.Height(gl); + dialogContent.RowDefinitions().Append(rowdef0); + + // button row + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + RowDefinition rowdef1 = RowDefinition(); + rowdef1.Height(gl); + dialogContent.RowDefinitions().Append(rowdef1); + + // coldef + gl.Value = 1; + gl.GridUnitType = GridUnitType::Star; + ColumnDefinition coldef = ColumnDefinition(); + coldef.Width(gl); + dialogContent.ColumnDefinitions().Append(coldef); + + // content + Grid grid = Grid(); + grid.SetRow(grid, 0); + grid.SetColumn(grid, 0); + dialogContent.Children().Append(grid); + obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0); + + // buttons + Grid buttons = Grid(); + Thickness btnsMargin = { (double)0, (double)10, (double)0, (double)0 }; + buttons.Margin(btnsMargin); + + RowDefinition btnrowdef = RowDefinition(); + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + btnrowdef.Height(gl); + buttons.RowDefinitions().Append(btnrowdef); + + gl.Value = 1; + gl.GridUnitType = GridUnitType::Auto; + int c = 0; + if (args.lbutton1) { + ColumnDefinition bcoldef = ColumnDefinition(); + bcoldef.Width(gl); + buttons.ColumnDefinitions().Append(bcoldef); + + Button btn = Button(); + ui_set_button_label(btn, args.lbutton1, NULL, NULL, UI_LABEL_TEXT); + Thickness margin = { (double)5, (double)5, (double)5, (double)5 }; + btn.Margin(margin); + btn.HorizontalAlignment(HorizontalAlignment::Stretch); + dialog_button_add_callback(dialog, btn, 1, obj, args.onclick, args.onclickdata); + + buttons.SetRow(btn, 0); + buttons.SetColumn(btn, c++); + buttons.Children().Append(btn); + } + if (args.lbutton2) { + ColumnDefinition bcoldef = ColumnDefinition(); + bcoldef.Width(gl); + buttons.ColumnDefinitions().Append(bcoldef); + + Button btn = Button(); + ui_set_button_label(btn, args.lbutton2, NULL, NULL, UI_LABEL_TEXT); + Thickness margin = { (double)5, (double)5, (double)5, (double)5 }; + btn.Margin(margin); + btn.HorizontalAlignment(HorizontalAlignment::Stretch); + dialog_button_add_callback(dialog, btn, 2, obj, args.onclick, args.onclickdata); + + buttons.SetRow(btn, 0); + buttons.SetColumn(btn, c++); + buttons.Children().Append(btn); + } + if (args.rbutton3) { + ColumnDefinition bcoldef = ColumnDefinition(); + bcoldef.Width(gl); + buttons.ColumnDefinitions().Append(bcoldef); + + Button btn = Button(); + ui_set_button_label(btn, args.rbutton3, NULL, NULL, UI_LABEL_TEXT); + Thickness margin = { (double)5, (double)5, (double)5, (double)5 }; + btn.Margin(margin); + btn.HorizontalAlignment(HorizontalAlignment::Stretch); + dialog_button_add_callback(dialog, btn, 3, obj, args.onclick, args.onclickdata); + + buttons.SetRow(btn, 0); + buttons.SetColumn(btn, c++); + buttons.Children().Append(btn); + } + if (args.rbutton4) { + ColumnDefinition bcoldef = ColumnDefinition(); + bcoldef.Width(gl); + buttons.ColumnDefinitions().Append(bcoldef); + + Button btn = Button(); + ui_set_button_label(btn, args.rbutton4, NULL, NULL, UI_LABEL_TEXT); + Thickness margin = { (double)5, (double)5, (double)5, (double)5 }; + btn.Margin(margin); + btn.HorizontalAlignment(HorizontalAlignment::Stretch); + dialog_button_add_callback(dialog, btn, 4, obj, args.onclick, args.onclickdata); + + buttons.SetRow(btn, 0); + buttons.SetColumn(btn, c++); + buttons.Children().Append(btn); + } + + dialogContent.SetRow(buttons, 1); + dialogContent.SetColumn(buttons, 0); + dialogContent.Children().Append(buttons); + + + dialog.Content(dialogContent); + dialog.XamlRoot(window->window.Content().XamlRoot()); + + obj->widget->Show = [dialog]() { + dialog.ShowAsync(); + }; + + return obj; +} + +void ui_window_size(UiObject *obj, int width, int height) { + UIWINDOW win = obj->wobj; + if (win) { + winrt::Windows::Graphics::SizeInt32 wsize; + wsize.Width = width; + wsize.Height = height; + win->window.AppWindow().Resize(wsize); + } +} + + + + +static Windows::Foundation::IAsyncAction create_dialog_async(UiObject *obj, UiDialogArgs args) { + UiObject* current = uic_current_obj(obj); + Window parentWindow = current->wobj->window; + + ContentDialog dialog = ContentDialog(); + dialog.XamlRoot(parentWindow.Content().XamlRoot()); + + if (args.title) { + wchar_t *str = str2wstr(args.title, nullptr); + dialog.Title(winrt::box_value(str)); + free(str); + } + + TextBox textfield{ nullptr }; + PasswordBox password{ nullptr }; + if(args.input || args.password) { + StackPanel panel = StackPanel(); + panel.Orientation(Orientation::Vertical); + if (args.content) { + wchar_t *str = str2wstr(args.content, nullptr); + TextBlock label = TextBlock(); + label.Text(str); + panel.Children().Append(label); + free(str); + } + + Thickness margin = { 0, 5, 0, 0 }; + if (args.password) { + password = PasswordBox(); + password.Margin(margin); + panel.Children().Append(password); + } else { + textfield = TextBox(); + textfield.Margin(margin); + panel.Children().Append(textfield); + } + + panel.Margin(margin); + + dialog.Content(panel); + + } else { + if (args.content) { + wchar_t *str = str2wstr(args.content, nullptr); + dialog.Content(winrt::box_value(str)); + free(str); + } + } + + if (args.button1_label) { + wchar_t *str = str2wstr(args.button1_label, nullptr); + dialog.PrimaryButtonText(winrt::hstring(str)); + free(str); + dialog.DefaultButton(ContentDialogButton::Primary); + } + if (args.button2_label) { + wchar_t *str = str2wstr(args.button2_label, nullptr); + dialog.SecondaryButtonText(winrt::hstring(str)); + free(str); + } + if (args.closebutton_label) { + wchar_t *str = str2wstr(args.closebutton_label, nullptr); + dialog.CloseButtonText(winrt::hstring(str)); + free(str); + } + + ContentDialogResult result = co_await dialog.ShowAsync(); + + if (args.result) { + UiEvent evt; + evt.obj = current; + evt.document = current->ctx->document; + evt.window = current->window; + evt.eventdata = NULL; + evt.intval = 0; + if (result == ContentDialogResult::Primary) { + evt.intval = 1; + } else if (result == ContentDialogResult::Secondary) { + evt.intval = 2; + } + + if (args.password) { + std::wstring wstr(password.Password()); + char *text = wchar2utf8(wstr.c_str(), wstr.length()); + evt.eventdata = text; + } else if (args.input) { + std::wstring wstr(textfield.Text()); + char *text = wchar2utf8(wstr.c_str(), wstr.length()); + evt.eventdata = text; + } + + args.result(&evt, args.resultdata); + + if (evt.eventdata) { + free(evt.eventdata); + } + } +} + +UIEXPORT void ui_dialog_create(UiObject *obj, UiDialogArgs args) { + create_dialog_async(obj, args); +} + + + +// --------------------------------------- File Dialog --------------------------------------- + +static void filedialog_callback( + UiObject *obj, + ui_callback file_selected_callback, + void *cbdata, + winrt::Windows::Foundation::Collections::IVectorView result) +{ + UiFileList flist; + flist.nfiles = result.Size(); + flist.files = new char*[flist.nfiles]; + + int i = 0; + for (auto const& file : result) { + winrt::hstring path = file.Path(); + flist.files[i++] = wchar2utf8(path.c_str(), path.size()); + } + + UiEvent evt; + evt.obj = obj; + evt.document = obj->ctx->document; + evt.window = obj->window; + evt.eventdata = &flist; + evt.intval = 0; + file_selected_callback(&evt, cbdata); + + for (int i = 0; i < flist.nfiles;i++) { + free(flist.files[i]); + } + delete[] flist.files; +} + +static Windows::Foundation::IAsyncAction open_filedialog_async(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { + FileOpenPicker openFileDialog = FileOpenPicker(); + auto initializeWithWindow { openFileDialog.as<::IInitializeWithWindow>() + }; + + HWND hwnd{ nullptr }; + winrt::check_hresult(obj->wobj->window.as()->get_WindowHandle(&hwnd)); + + initializeWithWindow->Initialize(hwnd); + + openFileDialog.FileTypeFilter().Append(L"*"); + + if ((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) { + auto files = co_await openFileDialog.PickMultipleFilesAsync(); + filedialog_callback(obj, file_selected_callback, cbdata, files); + } else { + auto file = co_await openFileDialog.PickSingleFileAsync(); + auto files = single_threaded_vector(); + files.Append(file); + filedialog_callback(obj, file_selected_callback, cbdata, files.GetView()); + } +} + +static Windows::Foundation::IAsyncAction save_filedialog_async(UiObject *obj, char *name, ui_callback file_selected_callback, void *cbdata) { + IFileSaveDialog *saveFileDialog; + + HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL, IID_IFileSaveDialog, reinterpret_cast(&saveFileDialog)); + if (FAILED(hr)) + { + co_return; + } + + if (name) { + wchar_t *wname = str2wstr(name, NULL); + saveFileDialog->SetFileName(wname); + free(wname); + free(name); + } + + + hr = saveFileDialog->Show(NULL); + if (SUCCEEDED(hr)) { + IShellItem *item; + hr = saveFileDialog->GetResult(&item); + if (SUCCEEDED(hr)) { + PWSTR wpath; + hr = item->GetDisplayName(SIGDN_FILESYSPATH, &wpath); + + if (SUCCEEDED(hr)) { + char *path = wchar2utf8(wpath, lstrlen(wpath)); + CoTaskMemFree(wpath); + + UiFileList flist; + flist.nfiles = 1; + flist.files = new char*[1]; + flist.files[0] = path; + + UiEvent evt; + evt.obj = obj; + evt.document = obj->ctx->document; + evt.window = obj->window; + evt.eventdata = &flist; + evt.intval = 0; + file_selected_callback(&evt, cbdata); + + free(path); + delete[] flist.files; + } + item->Release(); + } + } + + // cleanup + saveFileDialog->Release(); +} + +static Windows::Foundation::IAsyncAction folderdialog_async(UiObject *obj, ui_callback file_selected_callback, void *cbdata) { + FolderPicker folderPicker = FolderPicker(); + auto initializeWithWindow { folderPicker.as<::IInitializeWithWindow>() + }; + + HWND hwnd{ nullptr }; + winrt::check_hresult(obj->wobj->window.as()->get_WindowHandle(&hwnd)); + + initializeWithWindow->Initialize(hwnd); + + folderPicker.FileTypeFilter().Append(L"*"); + + auto folder = co_await folderPicker.PickSingleFolderAsync(); + if (folder) { + winrt::hstring hpath = folder.Path(); + char *cpath = wchar2utf8(hpath.c_str(), hpath.size()); + + UiFileList flist; + flist.nfiles = 1; + flist.files = &cpath; + + UiEvent evt; + evt.obj = obj; + evt.document = obj->ctx->document; + evt.window = obj->window; + evt.eventdata = &flist; + evt.intval = 0; + file_selected_callback(&evt, cbdata); + } +} + +UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { + if ((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) { + folderdialog_async(obj, file_selected_callback, cbdata); + } else { + open_filedialog_async(obj, mode, file_selected_callback, cbdata); + } +} + +UIEXPORT void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) { + char *n = name ? _strdup(name) : NULL; + save_filedialog_async(obj, n, file_selected_callback, cbdata); +} diff --git a/ui/winui/window.h b/ui/winui/window.h new file mode 100644 index 0000000..308b5f6 --- /dev/null +++ b/ui/winui/window.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#pragma once + +#include "toolkit.h" + +#include "../ui/window.h" + +#include +#undef GetCurrentTime +#include +#include +#include +#include +#include +#include + + diff --git a/ui/winui/winui.vcxproj b/ui/winui/winui.vcxproj new file mode 100644 index 0000000..9f72b38 --- /dev/null +++ b/ui/winui/winui.vcxproj @@ -0,0 +1,311 @@ + + + + + + + true + true + true + {59f97886-bf49-4b3f-9ef6-fa7a84f3ab56} + winui + winui + + $(RootNamespace) + de-DE + 16.0 + false + false + Windows Store + 10.0 + 10.0 + 10.0.17763.0 + true + true + None + true + + + + + Debug + Win32 + + + Debug + x64 + + + Debug + ARM64 + + + Release + Win32 + + + Release + x64 + + + Release + ARM64 + + + + DynamicLibrary + v143 + Unicode + true + + + true + true + + + false + true + true + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + + + + + _DEBUG;DISABLE_XAML_GENERATED_MAIN__;UI_WINUI;UI_WINUI_PCH;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + $(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories) + stdc17 + MultiThreadedDebugDLL + + + shell32.lib;gdi32.lib;%(AdditionalDependencies) + + + + + DISABLE_XAML_GENERATED_MAIN__;UI_WINUI;UI_WINUI_PCH;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + $(SolutionDir)..\..\ucx;%(AdditionalIncludeDirectories) + false + stdc17 + + + true + true + shell32.lib;gdi32.lib;%(AdditionalDependencies) + + + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + App.xaml + + + MainWindow.xaml + + + + + + + + + + + + + + + NotUsing + NotUsing + + + NotUsing + NotUsing + + + NotUsing + NotUsing + + + NotUsing + NotUsing + + + NotUsing + NotUsing + + + NotUsing + NotUsing + + + NotUsing + NotUsing + + + NotUsing + NotUsing + + + + + + + + + + + + + Create + + + App.xaml + + + MainWindow.xaml + + + + + + + + + + + + Code + App.xaml + + + Code + MainWindow.xaml + + + + + false + + + + + + + + + + + + + + + + + + + + + {27da0164-3475-43e2-a1a4-a5d07d305749} + + + + + true + + + $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\ + ..\..\build\vs\winui\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\build\vs\$(Platform)\$(Configuration)\ + ..\..\build\vs\winui\$(Platform)\$(Configuration)\ + + + + + + + + + + + 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}". + + + + + + + + + + \ No newline at end of file diff --git a/ui/winui/winui.vcxproj.filters b/ui/winui/winui.vcxproj.filters new file mode 100644 index 0000000..7340b7f --- /dev/null +++ b/ui/winui/winui.vcxproj.filters @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + + + + + + + + + + + + + + public + + + public + + + public + + + public + + + public + + + public + + + public + + + public + + + public + + + public + + + public + + + public + + + public + + + public + + + public + + + public + + + public + + + + + + + + + public + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + + + {59f97886-bf49-4b3f-9ef6-fa7a84f3ab56} + + + {2b58fe46-d27b-4335-b63c-13ec2204fa24} + + + {35c88d5c-b36f-41b3-86c0-784227845ae9} + + + + + + + + + + + + + + + \ No newline at end of file