From 297cdbb2a5637a05fc3526499b54cfcb381ccf5a Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Tue, 28 Oct 2025 20:46:41 +0100 Subject: [PATCH] add toolkit --- Makefile | 2 +- application/Makefile | 4 +- configure | 750 ++++- make/Makefile.mk | 17 +- make/configure.vm | 31 +- make/mingw.mk | 46 + make/osx.mk | 43 + make/package_osx.sh | 12 + make/package_windows.sh | 2 + make/project.xml | 172 +- make/vs/testapp/app.manifest | 20 + make/vs/testapp/main.c | 805 +++++ make/vs/testapp/packages.config | 7 + make/vs/testapp/testapp.vcxproj | 177 ++ make/vs/testapp/testapp.vcxproj.filters | 31 + make/vs/toolkit.sln | 85 + make/vs/ucx/ucx.vcxproj | 180 ++ make/vs/ucx/ucx.vcxproj.filters | 138 + make/vs/uicommon/uicommon.vcxproj | 158 + make/vs/uicommon/uicommon.vcxproj.filters | 65 + make/vs/uicommon/uicommon.vcxproj.user | 4 + make/windows.mk | 42 + .../toolkit/toolkit.xcodeproj/project.pbxproj | 738 +++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/xcschemes/toolkit.xcscheme | 78 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 58 + .../toolkit/Assets.xcassets/Contents.json | 6 + .../toolkit/toolkit/Base.lproj/MainMenu.xib | 93 + make/xcode/toolkit/toolkit/main.m | 277 ++ .../toolkit/toolkit/toolkit.entitlements | 10 + ucx/Makefile | 21 +- ucx/allocator.c | 2 +- ucx/array_list.c | 218 +- ucx/compare.c | 14 + ucx/cx/allocator.h | 184 +- ucx/cx/array_list.h | 295 +- ucx/cx/buffer.h | 190 +- ucx/cx/collection.h | 8 +- ucx/cx/common.h | 50 +- ucx/cx/compare.h | 187 +- ucx/cx/hash_key.h | 175 +- ucx/cx/hash_map.h | 25 +- ucx/cx/iterator.h | 107 +- ucx/cx/json.h | 429 +-- ucx/cx/kv_list.h | 260 ++ ucx/cx/linked_list.h | 311 +- ucx/cx/list.h | 608 ++-- ucx/cx/map.h | 584 +--- ucx/cx/mempool.h | 63 +- ucx/cx/printf.h | 135 +- ucx/cx/properties.h | 182 +- ucx/cx/streams.h | 39 +- ucx/cx/string.h | 659 ++-- ucx/cx/test.h | 18 +- ucx/cx/tree.h | 463 +-- ucx/hash_key.c | 64 +- ucx/hash_map.c | 78 +- ucx/iterator.c | 35 +- ucx/json.c | 104 +- ucx/kv_list.c | 705 +++++ ucx/linked_list.c | 517 ++-- ucx/list.c | 385 ++- ucx/map.c | 47 +- ucx/mempool.c | 16 +- ucx/properties.c | 8 +- ucx/string.c | 49 +- ucx/tree.c | 107 +- ui/.qmake.stash | 22 + ui/Makefile | 48 + ui/cocoa/BoxContainer.h | 39 + ui/cocoa/BoxContainer.m | 30 + ui/cocoa/EventData.h | 51 + ui/cocoa/EventData.m | 71 + ui/cocoa/GridLayout.h | 73 + ui/cocoa/GridLayout.m | 361 +++ ui/cocoa/ListDataSource.h | 42 + ui/cocoa/ListDataSource.m | 116 + ui/cocoa/ListDelegate.h | 46 + ui/cocoa/ListDelegate.m | 99 + ui/cocoa/MainWindow.h | 73 + ui/cocoa/MainWindow.m | 415 +++ ui/cocoa/Makefile | 36 + ui/cocoa/TabView.h | 57 + ui/cocoa/TabView.m | 134 + ui/cocoa/Toolbar.h | 79 + ui/cocoa/Toolbar.m | 307 ++ ui/cocoa/UiThread.h | 45 + ui/cocoa/UiThread.m | 68 + ui/cocoa/WindowManager.h | 41 + ui/cocoa/WindowManager.m | 57 + ui/cocoa/appdelegate.h | 37 + ui/cocoa/appdelegate.m | 52 + ui/cocoa/button.h | 69 + ui/cocoa/button.m | 462 +++ ui/cocoa/container.h | 78 + ui/cocoa/container.m | 332 ++ ui/cocoa/entry.h | 61 + ui/cocoa/entry.m | 272 ++ ui/cocoa/image.h | 35 + ui/cocoa/image.m | 67 + ui/cocoa/label.h | 33 + ui/cocoa/label.m | 104 + ui/cocoa/list.h | 121 + ui/cocoa/list.m | 819 +++++ ui/cocoa/menu.h | 70 + ui/cocoa/menu.m | 228 ++ ui/cocoa/objs.mk | 56 + ui/cocoa/text.h | 49 + ui/cocoa/text.m | 250 ++ ui/cocoa/toolkit.h | 57 + ui/cocoa/toolkit.m | 206 ++ ui/cocoa/webview.h | 58 + ui/cocoa/webview.m | 273 ++ ui/cocoa/widget.h | 31 + ui/cocoa/widget.m | 64 + ui/cocoa/window.h | 40 + ui/cocoa/window.m | 435 +++ ui/common/args.c | 2430 +++++++++++++++ ui/common/args.h | 581 ++++ ui/common/condvar.c | 74 + ui/common/condvar.h | 53 + ui/common/container.c | 87 + ui/common/container.h | 59 + ui/common/context.c | 640 ++++ ui/common/context.h | 155 + ui/common/document.c | 77 + ui/common/document.h | 52 + ui/common/menu.c | 337 +++ ui/common/menu.h | 139 + ui/common/object.c | 147 + ui/common/object.h | 62 + ui/common/objs.mk | 48 + ui/common/properties.c | 339 +++ ui/common/properties.h | 57 + ui/common/threadpool.c | 196 ++ ui/common/threadpool.h | 89 + ui/common/toolbar.c | 156 + ui/common/toolbar.h | 101 + ui/common/types.c | 828 +++++ ui/common/types.h | 70 + ui/common/ucx_properties.c | 263 ++ ui/common/ucx_properties.h | 223 ++ ui/common/wrapper.c | 335 +++ ui/common/wrapper.h | 104 + ui/gtk/Makefile | 36 + ui/gtk/button.c | 1000 ++++++ ui/gtk/button.h | 115 + ui/gtk/container.c | 1341 +++++++++ ui/gtk/container.h | 206 ++ ui/gtk/display.c | 266 ++ ui/gtk/display.h | 53 + ui/gtk/dnd.c | 295 ++ ui/gtk/dnd.h | 72 + ui/gtk/draw_cairo.c | 151 + ui/gtk/draw_cairo.h | 51 + ui/gtk/draw_gdk.c | 93 + ui/gtk/draw_gdk.h | 51 + ui/gtk/entry.c | 220 ++ ui/gtk/entry.h | 44 + ui/gtk/graphics.c | 237 ++ ui/gtk/graphics.h | 64 + ui/gtk/headerbar.c | 227 ++ ui/gtk/headerbar.h | 93 + ui/gtk/icon.c | 208 ++ ui/gtk/icon.h | 67 + ui/gtk/image.c | 501 +++ ui/gtk/image.h | 137 + ui/gtk/list.c | 2675 +++++++++++++++++ ui/gtk/list.h | 203 ++ ui/gtk/menu.c | 851 ++++++ ui/gtk/menu.h | 131 + ui/gtk/objs.mk | 53 + ui/gtk/range.c | 135 + ui/gtk/range.h | 51 + ui/gtk/text.c | 1217 ++++++++ ui/gtk/text.h | 163 + ui/gtk/toolbar.c | 413 +++ ui/gtk/toolbar.h | 151 + ui/gtk/toolkit.c | 561 ++++ ui/gtk/toolkit.h | 203 ++ ui/gtk/webview.c | 185 ++ ui/gtk/webview.h | 75 + ui/gtk/widget.c | 59 + ui/gtk/widget.h | 46 + ui/gtk/window.c | 1053 +++++++ ui/motif/Grid.c | 651 ++++ ui/motif/Grid.h | 163 + ui/motif/Makefile | 36 + ui/motif/button.c | 443 +++ ui/motif/button.h | 63 + ui/motif/container.c | 621 ++++ ui/motif/container.h | 164 + ui/motif/dnd.c | 30 + ui/motif/dnd.h | 46 + ui/motif/graphics.c | 36 + ui/motif/graphics.h | 46 + ui/motif/image.c | 17 + ui/motif/image.h | 31 + ui/motif/label.c | 243 ++ ui/motif/label.h | 61 + ui/motif/list.c | 319 ++ ui/motif/list.h | 79 + ui/motif/menu.c | 349 +++ ui/motif/menu.h | 75 + ui/motif/objs.mk | 50 + ui/motif/range.c | 36 + ui/motif/range.h | 46 + ui/motif/stock.c | 36 + ui/motif/stock.h | 45 + ui/motif/text.c | 1045 +++++++ ui/motif/text.h | 101 + ui/motif/toolbar.c | 44 + ui/motif/toolbar.h | 45 + ui/motif/toolkit.c | 342 +++ ui/motif/toolkit.h | 105 + ui/motif/widget.c | 78 + ui/motif/window.c | 144 + ui/motif/window.h | 44 + ui/qt/Makefile | 44 + ui/qt/button.cpp | 239 ++ ui/qt/button.h | 54 + ui/qt/container.cpp | 277 ++ ui/qt/container.h | 99 + ui/qt/entry.cpp | 197 ++ ui/qt/entry.h | 55 + ui/qt/graphics.cpp | 33 + ui/qt/graphics.h | 42 + ui/qt/image.cpp | 39 + ui/qt/image.h | 36 + ui/qt/label.cpp | 63 + ui/qt/label.h | 36 + ui/qt/list.cpp | 143 + ui/qt/list.h | 40 + ui/qt/menu.cpp | 239 ++ ui/qt/menu.h | 63 + ui/qt/model.cpp | 266 ++ ui/qt/model.h | 121 + ui/qt/objs.mk | 36 + ui/qt/qt5.pro | 73 + ui/qt/stock.cpp | 35 + ui/qt/stock.h | 37 + ui/qt/text.cpp | 280 ++ ui/qt/text.h | 62 + ui/qt/toolbar.cpp | 133 + ui/qt/toolbar.h | 44 + ui/qt/toolkit.cpp | 202 ++ ui/qt/toolkit.h | 96 + ui/qt/widget.cpp | 68 + ui/qt/widget.h | 38 + ui/qt/window.cpp | 151 + ui/qt/window.h | 39 + ui/ui/button.h | 164 + ui/ui/container.h | 422 +++ ui/ui/display.h | 148 + ui/ui/dnd.h | 57 + ui/ui/entry.h | 85 + ui/ui/graphics.h | 101 + ui/ui/icons.h | 93 + ui/ui/image.h | 103 + ui/ui/menu.h | 111 + ui/ui/properties.h | 58 + ui/ui/range.h | 48 + ui/ui/stock.h | 89 + ui/ui/text.h | 163 + ui/ui/toolbar.h | 102 + ui/ui/toolkit.h | 698 +++++ ui/ui/tree.h | 339 +++ ui/ui/ui.h | 54 + ui/ui/webview.h | 107 + ui/ui/widget.h | 89 + ui/ui/win32.h | 71 + ui/ui/window.h | 107 + 273 files changed, 48254 insertions(+), 3504 deletions(-) create mode 100644 make/mingw.mk create mode 100644 make/osx.mk create mode 100755 make/package_osx.sh create mode 100644 make/package_windows.sh create mode 100644 make/vs/testapp/app.manifest create mode 100644 make/vs/testapp/main.c create mode 100644 make/vs/testapp/packages.config create mode 100644 make/vs/testapp/testapp.vcxproj create mode 100644 make/vs/testapp/testapp.vcxproj.filters create mode 100644 make/vs/toolkit.sln create mode 100644 make/vs/ucx/ucx.vcxproj create mode 100644 make/vs/ucx/ucx.vcxproj.filters create mode 100644 make/vs/uicommon/uicommon.vcxproj create mode 100644 make/vs/uicommon/uicommon.vcxproj.filters create mode 100644 make/vs/uicommon/uicommon.vcxproj.user create mode 100644 make/windows.mk create mode 100644 make/xcode/toolkit/toolkit.xcodeproj/project.pbxproj create mode 100644 make/xcode/toolkit/toolkit.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 make/xcode/toolkit/toolkit.xcodeproj/xcshareddata/xcschemes/toolkit.xcscheme create mode 100644 make/xcode/toolkit/toolkit/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 make/xcode/toolkit/toolkit/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 make/xcode/toolkit/toolkit/Assets.xcassets/Contents.json create mode 100644 make/xcode/toolkit/toolkit/Base.lproj/MainMenu.xib create mode 100644 make/xcode/toolkit/toolkit/main.m create mode 100644 make/xcode/toolkit/toolkit/toolkit.entitlements create mode 100644 ucx/cx/kv_list.h create mode 100644 ucx/kv_list.c create mode 100644 ui/.qmake.stash create mode 100644 ui/Makefile create mode 100644 ui/cocoa/BoxContainer.h create mode 100644 ui/cocoa/BoxContainer.m create mode 100644 ui/cocoa/EventData.h create mode 100644 ui/cocoa/EventData.m create mode 100644 ui/cocoa/GridLayout.h create mode 100644 ui/cocoa/GridLayout.m create mode 100644 ui/cocoa/ListDataSource.h create mode 100644 ui/cocoa/ListDataSource.m create mode 100644 ui/cocoa/ListDelegate.h create mode 100644 ui/cocoa/ListDelegate.m create mode 100644 ui/cocoa/MainWindow.h create mode 100644 ui/cocoa/MainWindow.m create mode 100644 ui/cocoa/Makefile create mode 100644 ui/cocoa/TabView.h create mode 100644 ui/cocoa/TabView.m create mode 100644 ui/cocoa/Toolbar.h create mode 100644 ui/cocoa/Toolbar.m create mode 100644 ui/cocoa/UiThread.h create mode 100644 ui/cocoa/UiThread.m create mode 100644 ui/cocoa/WindowManager.h create mode 100644 ui/cocoa/WindowManager.m create mode 100644 ui/cocoa/appdelegate.h create mode 100644 ui/cocoa/appdelegate.m create mode 100644 ui/cocoa/button.h create mode 100644 ui/cocoa/button.m create mode 100644 ui/cocoa/container.h create mode 100644 ui/cocoa/container.m create mode 100644 ui/cocoa/entry.h create mode 100644 ui/cocoa/entry.m create mode 100644 ui/cocoa/image.h create mode 100644 ui/cocoa/image.m create mode 100644 ui/cocoa/label.h create mode 100644 ui/cocoa/label.m create mode 100644 ui/cocoa/list.h create mode 100644 ui/cocoa/list.m create mode 100644 ui/cocoa/menu.h create mode 100644 ui/cocoa/menu.m create mode 100644 ui/cocoa/objs.mk create mode 100644 ui/cocoa/text.h create mode 100644 ui/cocoa/text.m create mode 100644 ui/cocoa/toolkit.h create mode 100644 ui/cocoa/toolkit.m create mode 100644 ui/cocoa/webview.h create mode 100644 ui/cocoa/webview.m create mode 100644 ui/cocoa/widget.h create mode 100644 ui/cocoa/widget.m create mode 100644 ui/cocoa/window.h create mode 100644 ui/cocoa/window.m create mode 100644 ui/common/args.c create mode 100644 ui/common/args.h create mode 100644 ui/common/condvar.c create mode 100644 ui/common/condvar.h create mode 100644 ui/common/container.c create mode 100644 ui/common/container.h create mode 100644 ui/common/context.c create mode 100644 ui/common/context.h create mode 100644 ui/common/document.c create mode 100644 ui/common/document.h create mode 100644 ui/common/menu.c create mode 100644 ui/common/menu.h create mode 100644 ui/common/object.c create mode 100644 ui/common/object.h create mode 100644 ui/common/objs.mk create mode 100644 ui/common/properties.c create mode 100644 ui/common/properties.h create mode 100644 ui/common/threadpool.c create mode 100644 ui/common/threadpool.h create mode 100644 ui/common/toolbar.c create mode 100644 ui/common/toolbar.h create mode 100644 ui/common/types.c create mode 100644 ui/common/types.h create mode 100644 ui/common/ucx_properties.c create mode 100644 ui/common/ucx_properties.h create mode 100644 ui/common/wrapper.c create mode 100644 ui/common/wrapper.h create mode 100644 ui/gtk/Makefile create mode 100644 ui/gtk/button.c create mode 100644 ui/gtk/button.h create mode 100644 ui/gtk/container.c create mode 100644 ui/gtk/container.h create mode 100644 ui/gtk/display.c create mode 100644 ui/gtk/display.h create mode 100644 ui/gtk/dnd.c create mode 100644 ui/gtk/dnd.h create mode 100644 ui/gtk/draw_cairo.c create mode 100644 ui/gtk/draw_cairo.h create mode 100644 ui/gtk/draw_gdk.c create mode 100644 ui/gtk/draw_gdk.h create mode 100644 ui/gtk/entry.c create mode 100644 ui/gtk/entry.h create mode 100644 ui/gtk/graphics.c create mode 100644 ui/gtk/graphics.h create mode 100644 ui/gtk/headerbar.c create mode 100644 ui/gtk/headerbar.h create mode 100644 ui/gtk/icon.c create mode 100644 ui/gtk/icon.h create mode 100644 ui/gtk/image.c create mode 100644 ui/gtk/image.h create mode 100644 ui/gtk/list.c create mode 100644 ui/gtk/list.h create mode 100644 ui/gtk/menu.c create mode 100644 ui/gtk/menu.h create mode 100644 ui/gtk/objs.mk create mode 100644 ui/gtk/range.c create mode 100644 ui/gtk/range.h create mode 100644 ui/gtk/text.c create mode 100644 ui/gtk/text.h create mode 100644 ui/gtk/toolbar.c create mode 100644 ui/gtk/toolbar.h create mode 100644 ui/gtk/toolkit.c create mode 100644 ui/gtk/toolkit.h create mode 100644 ui/gtk/webview.c create mode 100644 ui/gtk/webview.h create mode 100644 ui/gtk/widget.c create mode 100644 ui/gtk/widget.h create mode 100644 ui/gtk/window.c create mode 100644 ui/motif/Grid.c create mode 100644 ui/motif/Grid.h create mode 100644 ui/motif/Makefile create mode 100644 ui/motif/button.c create mode 100644 ui/motif/button.h create mode 100644 ui/motif/container.c create mode 100644 ui/motif/container.h create mode 100644 ui/motif/dnd.c create mode 100644 ui/motif/dnd.h create mode 100644 ui/motif/graphics.c create mode 100644 ui/motif/graphics.h create mode 100644 ui/motif/image.c create mode 100644 ui/motif/image.h create mode 100644 ui/motif/label.c create mode 100644 ui/motif/label.h create mode 100644 ui/motif/list.c create mode 100644 ui/motif/list.h create mode 100644 ui/motif/menu.c create mode 100644 ui/motif/menu.h create mode 100644 ui/motif/objs.mk create mode 100644 ui/motif/range.c create mode 100644 ui/motif/range.h create mode 100644 ui/motif/stock.c create mode 100644 ui/motif/stock.h create mode 100644 ui/motif/text.c create mode 100644 ui/motif/text.h create mode 100644 ui/motif/toolbar.c create mode 100644 ui/motif/toolbar.h create mode 100644 ui/motif/toolkit.c create mode 100644 ui/motif/toolkit.h create mode 100644 ui/motif/widget.c create mode 100644 ui/motif/window.c create mode 100644 ui/motif/window.h create mode 100644 ui/qt/Makefile create mode 100644 ui/qt/button.cpp create mode 100644 ui/qt/button.h create mode 100644 ui/qt/container.cpp create mode 100644 ui/qt/container.h create mode 100644 ui/qt/entry.cpp create mode 100644 ui/qt/entry.h create mode 100644 ui/qt/graphics.cpp create mode 100644 ui/qt/graphics.h create mode 100644 ui/qt/image.cpp create mode 100644 ui/qt/image.h create mode 100644 ui/qt/label.cpp create mode 100644 ui/qt/label.h create mode 100644 ui/qt/list.cpp create mode 100644 ui/qt/list.h create mode 100644 ui/qt/menu.cpp create mode 100644 ui/qt/menu.h create mode 100644 ui/qt/model.cpp create mode 100644 ui/qt/model.h create mode 100644 ui/qt/objs.mk create mode 100644 ui/qt/qt5.pro create mode 100644 ui/qt/stock.cpp create mode 100644 ui/qt/stock.h create mode 100644 ui/qt/text.cpp create mode 100644 ui/qt/text.h create mode 100644 ui/qt/toolbar.cpp create mode 100644 ui/qt/toolbar.h create mode 100644 ui/qt/toolkit.cpp create mode 100644 ui/qt/toolkit.h create mode 100644 ui/qt/widget.cpp create mode 100644 ui/qt/widget.h create mode 100644 ui/qt/window.cpp create mode 100644 ui/qt/window.h create mode 100644 ui/ui/button.h create mode 100644 ui/ui/container.h create mode 100644 ui/ui/display.h create mode 100644 ui/ui/dnd.h create mode 100644 ui/ui/entry.h create mode 100644 ui/ui/graphics.h create mode 100644 ui/ui/icons.h create mode 100644 ui/ui/image.h create mode 100644 ui/ui/menu.h create mode 100644 ui/ui/properties.h create mode 100644 ui/ui/range.h create mode 100644 ui/ui/stock.h create mode 100644 ui/ui/text.h create mode 100644 ui/ui/toolbar.h create mode 100644 ui/ui/toolkit.h create mode 100644 ui/ui/tree.h create mode 100644 ui/ui/ui.h create mode 100644 ui/ui/webview.h create mode 100644 ui/ui/widget.h create mode 100644 ui/ui/win32.h create mode 100644 ui/ui/window.h diff --git a/Makefile b/Makefile index 3cd3594..7162bba 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. # -# Copyright 2022 Olaf Wintermann. All rights reserved. +# 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: diff --git a/application/Makefile b/application/Makefile index 52e1f3c..f0ea68a 100644 --- a/application/Makefile +++ b/application/Makefile @@ -50,8 +50,8 @@ BINTARGET = uwplayer all: $(BUILD_ROOT)/build/bin/$(BINTARGET) $(BUILD_ROOT)/build/bin/$(BINTARGET): $(OBJ) - $(CC) -o $(BUILD_ROOT)/build/bin/$(BINTARGET) $(OBJ) -L$(BUILD_ROOT)/build/lib -lucx $(LDFLAGS) $(APP_LDFLAGS) + $(CC) -o $(BUILD_ROOT)/build/bin/$(BINTARGET) $(OBJ) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) $(LDFLAGS) $(TK_LDFLAGS) $(BUILD_ROOT)/build/application/%.$(OBJ_EXT): %.c - $(CC) $(CFLAGS) $(APP_CFLAGS) -o $@ -c $< + $(CC) $(CFLAGS) $(TK_CFLAGS) -o $@ -c $< diff --git a/configure b/configure index c839294..b8442b5 100755 --- a/configure +++ b/configure @@ -58,6 +58,7 @@ PKG_CONFIG=`command -v pkg-config` # $PLATFORM is used for platform dependent dependency selection OS=`uname -s` OS_VERSION=`uname -r` +ARCH=`uname -m` printf "detect platform... " if [ "$OS" = "SunOS" ]; then PLATFORM="solaris sunos unix svr4" @@ -110,6 +111,9 @@ Build Types: --debug add extra compile flags for debug builds --release add extra compile flags for release builds +Options: + --toolkit=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|cocoa|motif) + __EOF__ } @@ -174,6 +178,8 @@ do "--help"*) printhelp; abort_configure ;; "--debug") BUILD_TYPE="debug" ;; "--release") BUILD_TYPE="release" ;; + "--toolkit="*) OPT_TOOLKIT=${ARG#--toolkit=} ;; + "--toolkit") echo "option '$ARG' needs a value:"; echo " $ARG=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|cocoa|motif)"; abort_configure ;; "-"*) echo "unknown option: $ARG"; abort_configure ;; esac done @@ -229,6 +235,28 @@ elif [ -f "$prefix/etc/config.site" ]; then printf "loading site defaults... " . "$prefix/etc/config.site" echo ok +else + # try to detect the correct libdir on our own, except it was changed by the user + if test "$libdir" = '${exec_prefix}/lib'; then + if [ "$OS" = "SunOS" ]; then + test -d "${exec_prefix}/lib/amd64" && libdir='${exec_prefix}/lib/amd64' + else + # check if the standard libdir even exists + if test -d "${exec_prefix}/lib" ; then + : + else + # if it does not, maybe a lib32 exists + test -d "${exec_prefix}/lib32" && libdir='${exec_prefix}/lib32' + fi + # now check if there is a special 64bit libdir that we should use + for i in x86_64 ppc64 s390x aarch64 aarch64_be arm64 ; do + if [ $ARCH = $i ]; then + test -d "${exec_prefix}/lib64" && libdir='${exec_prefix}/lib64' + break + fi + done + fi + fi fi @@ -262,6 +290,9 @@ __EOF__ # check languages lang_c= lang_cpp= +if detect_cpp_compiler ; then + lang_cpp=1 +fi if detect_c_compiler ; then lang_c=1 fi @@ -290,12 +321,221 @@ print_check_msg() fi } -dependency_error_xft() +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_qt5() +{ + print_check_msg "$dep_checked_qt5" "checking for qt5... " + # dependency qt5 + while true + do + if [ -z "$lang_cpp" ] ; then + break + fi + if [ -z "$PKG_CONFIG" ]; then + break + fi + if which qmake-qt5 > /dev/null ; then + : + else + break + fi + if test_pkg_config "Qt5Widgets" "" "" "" ; then + TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags Qt5Widgets`" + TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs Qt5Widgets`" + else + break + fi + TEMP_CFLAGS="$TEMP_CFLAGS -DUI_QT5" + cat >> $TEMP_DIR/make.mk << __EOF__ +# Dependency: qt5 +QMAKE = qmake-qt5 +QT_PRO_FILE = qt5.pro +__EOF__ + print_check_msg "$dep_checked_qt5" "yes\n" + dep_checked_qt5=1 + return 1 + done + + print_check_msg "$dep_checked_qt5" "no\n" + dep_checked_qt5=1 + return 0 +} +dependency_error_gtk2() { - print_check_msg "$dep_checked_xft" "checking for xft... " - # dependency xft + 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_GTK4" + 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_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_webkitgtk6() +{ + print_check_msg "$dep_checked_webkitgtk6" "checking for webkitgtk6... " + # dependency webkitgtk6 + while true + do + if [ -z "$PKG_CONFIG" ]; then + break + fi + if test_pkg_config "webkitgtk-6.0" "" "" "" ; then + TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags webkitgtk-6.0`" + TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs webkitgtk-6.0`" + else + break + fi + TEMP_CFLAGS="$TEMP_CFLAGS -DUI_WEBVIEW" + print_check_msg "$dep_checked_webkitgtk6" "yes\n" + dep_checked_webkitgtk6=1 + return 1 + done + + # dependency webkitgtk6 + while true + do + print_check_msg "$dep_checked_webkitgtk6" "yes\n" + dep_checked_webkitgtk6=1 + return 1 + done + + print_check_msg "$dep_checked_webkitgtk6" "no\n" + dep_checked_webkitgtk6=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 if [ -z "$PKG_CONFIG" ]; then break fi @@ -311,23 +551,33 @@ dependency_error_xft() else break fi - print_check_msg "$dep_checked_xft" "yes\n" - dep_checked_xft=1 + 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 - print_check_msg "$dep_checked_xft" "no\n" - dep_checked_xft=1 - return 0 -} -dependency_error_motif() -{ - print_check_msg "$dep_checked_motif" "checking for motif... " # dependency motif while true do + if [ -z "$PKG_CONFIG" ]; then + break + fi + if test_pkg_config "xft" "" "" "" ; then + TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags xft`" + TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs xft`" + else + break + fi + if test_pkg_config "fontconfig" "" "" "" ; then + TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags fontconfig`" + TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs fontconfig`" + else + break + fi TEMP_CFLAGS="$TEMP_CFLAGS -DUI_MOTIF" - TEMP_LDFLAGS="$TEMP_LDFLAGS -lXm -lXt -lX11" + TEMP_LDFLAGS="$TEMP_LDFLAGS -lXm -lXt -lX11 -lpthread" print_check_msg "$dep_checked_motif" "yes\n" dep_checked_motif=1 return 1 @@ -337,6 +587,96 @@ dependency_error_motif() dep_checked_motif=1 return 0 } +dependency_error_webkit2gtk4() +{ + print_check_msg "$dep_checked_webkit2gtk4" "checking for webkit2gtk4... " + # dependency webkit2gtk4 + while true + do + if [ -z "$PKG_CONFIG" ]; then + break + fi + if test_pkg_config "webkit2gtk-4.1" "" "" "" ; then + TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags webkit2gtk-4.1`" + TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs webkit2gtk-4.1`" + else + break + fi + TEMP_CFLAGS="$TEMP_CFLAGS -DUI_WEBVIEW" + print_check_msg "$dep_checked_webkit2gtk4" "yes\n" + dep_checked_webkit2gtk4=1 + return 1 + done + + # dependency webkit2gtk4 + while true + do + if [ -z "$PKG_CONFIG" ]; then + break + fi + if test_pkg_config "webkit2gtk-4.0" "" "" "" ; then + TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags webkit2gtk-4.0`" + TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs webkit2gtk-4.0`" + else + break + fi + TEMP_CFLAGS="$TEMP_CFLAGS -DUI_WEBVIEW" + print_check_msg "$dep_checked_webkit2gtk4" "yes\n" + dep_checked_webkit2gtk4=1 + return 1 + done + + # dependency webkit2gtk4 + while true + do + print_check_msg "$dep_checked_webkit2gtk4" "yes\n" + dep_checked_webkit2gtk4=1 + return 1 + done + + print_check_msg "$dep_checked_webkit2gtk4" "no\n" + dep_checked_webkit2gtk4=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" @@ -356,7 +696,9 @@ do break fi - TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread" + cat >> "$TEMP_DIR/make.mk" << __EOF__ +LD = \$(CC) +__EOF__ break done break @@ -370,8 +712,62 @@ do do cat >> "$TEMP_DIR/make.mk" << __EOF__ -OBJ_EXT = o -LIB_EXT = a +SYS_MAKEFILE = Makefile.unix +__EOF__ + break + done + break +done +while true +do + if notisplatform "windows"; then + break + fi + while true + do + + cat >> "$TEMP_DIR/make.mk" << __EOF__ +SYS_MAKEFILE = Makefile.win32 +__EOF__ + 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 +SHLIB_EXT = .dylib +LIB_PREFIX = lib +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 +SHLIB_EXT = .so +LIB_PREFIX = lib PACKAGE_SCRIPT = package_unix.sh __EOF__ break @@ -379,6 +775,19 @@ __EOF__ break done while true +do + while true + do + + cat >> "$TEMP_DIR/make.mk" << __EOF__ +BUILD_BIN_DIR = bin +BUILD_LIB_DIR = lib +__EOF__ + break + done + break +done +while true do if notisplatform "bsd"; then break @@ -395,12 +804,12 @@ done # build type if [ "$BUILD_TYPE" = "debug" ]; then - TEMP_CFLAGS="\${DEBUG_CC_FLAGS} $TEMP_CFLAGS" - TEMP_CXXFLAGS="\${DEBUG_CXX_FLAGS} $TEMP_CXXFLAGS" + TEMP_CFLAGS="\${DEBUG_CFLAGS} $TEMP_CFLAGS" + TEMP_CXXFLAGS="\${DEBUG_CXXFLAGS} $TEMP_CXXFLAGS" fi if [ "$BUILD_TYPE" = "release" ]; then - TEMP_CFLAGS="\${RELEASE_CC_FLAGS} $TEMP_CFLAGS" - TEMP_CXXFLAGS="\${RELEASE_CXX_FLAGS} $TEMP_CXXFLAGS" + TEMP_CFLAGS="\${RELEASE_CFLAGS} $TEMP_CFLAGS" + TEMP_CXXFLAGS="\${RELEASE_CXXFLAGS} $TEMP_CXXFLAGS" fi # add general dependency flags to flags.mk @@ -418,38 +827,310 @@ fi # # OPTION VALUES # +checkopt_toolkit_libadwaita() +{ + VERR=0 + if dependency_error_libadwaita ; then + VERR=1 + fi + if dependency_error_webkitgtk6 ; 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 dependency_error_webkitgtk6 ; 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 dependency_error_webkit2gtk4 ; 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_cocoa() +{ + VERR=0 + if dependency_error_cocoa ; then + VERR=1 + fi + if [ $VERR -ne 0 ]; then + return 1 + fi + cat >> "$TEMP_DIR/make.mk" << __EOF__ +TOOLKIT = cocoa +__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: app" -echo "# flags for target app" >> "$TEMP_DIR/flags.mk" +echo "configuring target: tk" +echo "# flags for target tk" >> "$TEMP_DIR/flags.mk" TEMP_CFLAGS= TEMP_CXXFLAGS= TEMP_LDFLAGS= -if dependency_error_motif; then - DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED motif " - ERROR=1 -fi -if dependency_error_xft; then - DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED xft " - ERROR=1 -fi # 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_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" = "cocoa" ]; then + echo " toolkit: $OPT_TOOLKIT" >> $TEMP_DIR/options + if checkopt_toolkit_cocoa ; 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 + else + echo + echo "Invalid option value - usage:" + echo " --toolkit=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|cocoa|motif)" + abort_configure + fi +fi if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then - echo "APP_CFLAGS += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk" + echo "TK_CFLAGS += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk" fi if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then - echo "APP_CXXFLAGS += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk" + echo "TK_CXXFLAGS += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk" fi if [ -n "${TEMP_LDFLAGS}" ]; then - echo "APP_LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk" + echo "TK_LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk" fi @@ -524,6 +1205,9 @@ if [ "$orig_localedir" != "$localedir" ]; then echo " localedir: $localedir" fi echo +echo "Options:" +cat "$TEMP_DIR/options" +echo # generate the config.mk file pwd=`pwd` diff --git a/make/Makefile.mk b/make/Makefile.mk index 016534e..2c65696 100644 --- a/make/Makefile.mk +++ b/make/Makefile.mk @@ -1,7 +1,7 @@ # # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. # -# Copyright 2021 Olaf Wintermann. All rights reserved. +# Copyright 2023 Olaf Wintermann. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -32,19 +32,22 @@ BUILD_ROOT = ./ include config.mk BUILD_DIRS = build/bin build/lib -BUILD_DIRS += build/ucx -BUILD_DIRS += build/application +BUILD_DIRS += build/application build/ucx +BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT) -all: $(BUILD_DIRS) ucx application +all: $(BUILD_DIRS) ucx ui application make/$(PACKAGE_SCRIPT) $(BUILD_DIRS): mkdir -p $@ -ucx: $(BUILD_DIRS) FORCE - cd ucx; $(MAKE) +ui: ucx FORCE + cd ui; $(MAKE) all -application: $(BUILD_DIRS) ucx FORCE +ucx: FORCE + cd ucx; $(MAKE) all + +application: ui FORCE cd application; $(MAKE) FORCE: diff --git a/make/configure.vm b/make/configure.vm index ffb00a6..7be6ed7 100644 --- a/make/configure.vm +++ b/make/configure.vm @@ -59,6 +59,7 @@ PKG_CONFIG=`command -v pkg-config` # $PLATFORM is used for platform dependent dependency selection OS=`uname -s` OS_VERSION=`uname -r` +ARCH=`uname -m` printf "detect platform... " if [ "$OS" = "SunOS" ]; then PLATFORM="solaris sunos unix svr4" @@ -276,6 +277,28 @@ elif [ -f "$prefix/etc/config.site" ]; then printf "loading site defaults... " . "$prefix/etc/config.site" echo ok +else + # try to detect the correct libdir on our own, except it was changed by the user + if test "$libdir" = '${exec_prefix}/lib'; then + if [ "$OS" = "SunOS" ]; then + test -d "${exec_prefix}/lib/amd64" && libdir='${exec_prefix}/lib/amd64' + else + # check if the standard libdir even exists + if test -d "${exec_prefix}/lib" ; then + : + else + # if it does not, maybe a lib32 exists + test -d "${exec_prefix}/lib32" && libdir='${exec_prefix}/lib32' + fi + # now check if there is a special 64bit libdir that we should use + for i in x86_64 ppc64 s390x aarch64 aarch64_be arm64 ; do + if [ $ARCH = $i ]; then + test -d "${exec_prefix}/lib64" && libdir='${exec_prefix}/lib64' + break + fi + done + fi + fi fi ]]# ## End of unparsed content ** @@ -503,12 +526,12 @@ done # build type if [ "$BUILD_TYPE" = "debug" ]; then - TEMP_CFLAGS="\${DEBUG_CC_FLAGS} $TEMP_CFLAGS" - TEMP_CXXFLAGS="\${DEBUG_CXX_FLAGS} $TEMP_CXXFLAGS" + TEMP_CFLAGS="\${DEBUG_CFLAGS} $TEMP_CFLAGS" + TEMP_CXXFLAGS="\${DEBUG_CXXFLAGS} $TEMP_CXXFLAGS" fi if [ "$BUILD_TYPE" = "release" ]; then - TEMP_CFLAGS="\${RELEASE_CC_FLAGS} $TEMP_CFLAGS" - TEMP_CXXFLAGS="\${RELEASE_CXX_FLAGS} $TEMP_CXXFLAGS" + TEMP_CFLAGS="\${RELEASE_CFLAGS} $TEMP_CFLAGS" + TEMP_CXXFLAGS="\${RELEASE_CXXFLAGS} $TEMP_CXXFLAGS" fi # add general dependency flags to flags.mk 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 100755 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_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 index dda4003..13abef6 100644 --- a/make/project.xml +++ b/make/project.xml @@ -2,32 +2,182 @@ c + LD = \$(CC) + + + + SYS_MAKEFILE = Makefile.unix + + + SYS_MAKEFILE = Makefile.win32 + + + + libadwaita-1 + -DUI_GTK4 -DUI_LIBADWAITA -lpthread - - - -DUI_MOTIF - -lXm -lXt -lX11 + + gtk4 + -DUI_GTK4 + -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 + + + + which qmake-qt5 + cpp + -DUI_QT5 + Qt5Widgets + QMAKE = qmake-qt5 + QT_PRO_FILE = qt5.pro - - OBJ_EXT = o - LIB_EXT = a - PACKAGE_SCRIPT = package_unix.sh + + -DUI_COCOA + -lobjc -framework Cocoa - + + -DUI_MOTIF -I/usr/local/include/X11 + -lXm -lXt -lX11 -lpthread xft fontconfig + + -DUI_MOTIF + -lXm -lXt -lX11 -lpthread + xft + fontconfig + + + + webkitgtk-6.0 + -DUI_WEBVIEW + + + + + + + + webkit2gtk-4.1 + -DUI_WEBVIEW + + + + webkit2gtk-4.0 + -DUI_WEBVIEW + + + + + + + + OBJ_EXT = .o + LIB_EXT = .a + SHLIB_EXT = .dylib + LIB_PREFIX = lib + PACKAGE_SCRIPT = package_osx.sh + + + OBJ_EXT = .o + LIB_EXT = .a + SHLIB_EXT = .so + LIB_PREFIX = lib + PACKAGE_SCRIPT = package_unix.sh + + + BUILD_BIN_DIR = bin + BUILD_LIB_DIR = lib + + -I/usr/local/include -L/usr/local/lib - - motif,xft + + 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..fe5943e --- /dev/null +++ b/make/vs/testapp/main.c @@ -0,0 +1,805 @@ +/* + * 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 + +#include +#include + +#include + + +typedef struct { + UiString *str1; + UiString *str2; + UiString *path; + UiText *text; + UiDouble *progress; + UiList *list; + UiList *menulist; + UiInteger *radio; + UiInteger *tabview; + UiGeneric *image; +} MyDocument; + +MyDocument *doc1; +MyDocument *doc2; + +UIWIDGET tabview; + +static UiCondVar *cond; +static int thr_end = 0; +static int thr_started = 0; + +int threadfunc(void *data) { + printf("thr wait for data...\n"); + ui_condvar_wait(cond); + printf("thr data received: {%s} [%d]\n", cond->data, cond->intdata); + ui_condvar_destroy(cond); + cond = NULL; + + return 0; +} + +void action_start_thread(UiEvent *event, void *data) { + if(!thr_started) { + cond = ui_condvar_create(); + ui_job(event->obj, threadfunc, NULL, NULL, NULL); + thr_started = 1; + } +} + +void action_notify_thread(UiEvent *event, void *data) { + if(!thr_end) { + ui_condvar_signal(cond, "hello thread", 123); + thr_end = 1; + } +} + +void action_menu(UiEvent *event, void *userdata) { + +} + +void action_file_selected(UiEvent *event, void *userdata) { + UiFileList *files = event->eventdata; + MyDocument *doc = event->document; + printf("files: %d\n", (int)files->nfiles); + if(files->nfiles > 0) { + printf("selected file: %s\n", files->files[0]); + ui_image_load_file(doc->image, files->files[0]); + } +} + +void action_button(UiEvent *event, void *userdata) { + ui_openfiledialog(event->obj, UI_FILEDIALOG_SELECT_SINGLE, action_file_selected, NULL); +} + +void action_switch(UiEvent *event, void *userdata) { + +} + +void action_toolbar_button(UiEvent *event, void *userdata) { + printf("toolbar button\n"); + + ui_dialog(event->obj, .title = "Dialog Title", .content = "Content Label", .button1_label = "btn1", .button2_label = "btn2", .input = TRUE, .closebutton_label = "Cancel"); +} + +void action_dialog_button(UiEvent *event, void *userdata) { + ui_close(event->obj); +} + +void action_toolbar_dialog(UiEvent *event, void *userdata) { + + UiObject *dialog = ui_dialog_window(event->obj, .title = "Dialog Window", .lbutton1 = "Cancel 1", .lbutton2 = "Btn 2", .rbutton3 = "Btn3", .rbutton4 = "Login 4", .onclick = action_dialog_button, .default_button = 4, .show_closebutton = UI_OFF); + + ui_vbox(dialog, .margin = 10, .spacing = 10) { + ui_label(dialog, .label = "Enter password:"); + ui_passwordfield(dialog, .varname = "password"); + } + + ui_show(dialog); +} + +UiObject *new_window; + +static void action_unref_newwindow(UiEvent *event, void *userdata) { + ui_object_unref(event->obj); + new_window = NULL; +} + +void action_toolbar_newwindow(UiEvent *event, void *userdata) { + if (new_window) { + ui_show(new_window); + return; + } + + UiObject *obj = ui_simple_window("New Window", NULL); + new_window = obj; + ui_object_ref(obj); + + ui_headerbar0(obj) { + ui_headerbar_start(obj) { + ui_button(obj, .label = "Open"); + } + ui_headerbar_end(obj) { + ui_button(obj, .label = "Unref", .onclick = action_unref_newwindow); + } + } + + ui_textarea(obj, .varname="text"); + + ui_show(obj); +} + +MyDocument* create_doc(void) { + MyDocument *doc = ui_document_new(sizeof(MyDocument)); + UiContext *docctx = ui_document_context(doc); + doc->str1 = ui_string_new(docctx, "str1"); + doc->str1 = ui_string_new(docctx, "str2"); + doc->path = ui_string_new(docctx, "path"); + doc->progress = ui_double_new(docctx, "progress"); + doc->list = ui_list_new(docctx, "list"); + ui_list_append(doc->list, "test1"); + ui_list_append(doc->list, "test2"); + ui_list_append(doc->list, "test3"); + doc->radio = ui_int_new(docctx, "radio"); + doc->tabview = ui_int_new(docctx, "tabview"); + doc->image = ui_generic_new(docctx, "image"); + //doc->text = ui_text_new(docctx, "text"); + return doc; +} + +UiIcon *icon = NULL; + +static void* list_getvalue(void *elm, int col) { + /* + if(col == 0) { + if(!icon) { + icon = ui_icon("folder", 24); + } + return icon; + } + */ + + char *str = elm; + return col == 0 ? str : "x"; +} + +static UiList *menu_list; +int new_item_count = 0; + +void action_add_menu_item(UiEvent *event, void *userdata) { + char str[64]; + snprintf(str, 64, "new item %d", new_item_count++); + + ui_list_append(menu_list, strdup(str)); + ui_list_notify(menu_list); +} + +void action_menu_list(UiEvent *event, void *userdata) { + printf("menu list item: %d\n", event->intval); +} + +static int tab_x = 0; +void action_tab2_button(UiEvent *event, void *userdata) { + MyDocument *doc = event->document; + printf("current page: %d\n", (int)ui_get(doc->tabview)); + ui_set(doc->tabview, 0); +} + + +void action_group1(UiEvent *event, void *userdata) { + UiContext *ctx = event->obj->ctx; + if(userdata) { + ui_unset_group(ctx, 1); + } else { + ui_set_group(ctx, 1); + } +} + +void action_group2(UiEvent *event, void *userdata) { + UiContext *ctx = event->obj->ctx; + if(userdata) { + ui_unset_group(ctx, 2); + } else { + ui_set_group(ctx, 2); + } +} + +void application_startup(UiEvent *event, void *data) { + // global list + UiContext *global = ui_global_context(); + menu_list = ui_list_new(global, "menulist"); + ui_list_append(menu_list, "menu list item 1"); + ui_list_append(menu_list, "menu list item 2"); + ui_list_append(menu_list, "menu list item 3"); + + + + UiObject *obj = ui_window("Test", NULL); + + MyDocument *doc = create_doc(); + ui_attach_document(obj->ctx, doc); + + ui_tabview(obj, .spacing=10, .margin=10, .tabview = UI_TABVIEW_NAVIGATION_SIDE, .varname="tabview") { + ui_tab(obj, "Tab 1") { + ui_vbox(obj, .fill = UI_OFF, .margin = 15, .spacing = 15) { + ui_button(obj, .label = "Test Button", .icon = "application-x-generic", .onclick = action_button); + ui_togglebutton(obj, .label = "Toggle"); + ui_checkbox(obj, .label = "My Checkbox"); + } + ui_grid(obj, .fill = UI_OFF, .columnspacing = 15, .rowspacing = 15, .margin = 15) { + ui_button(obj, .label = "Activate Group 1", .hexpand = TRUE, .onclick = action_group1); + ui_button(obj, .label = "Disable Group 1", .onclick = action_group1, .onclickdata = "disable"); + ui_newline(obj); + ui_button(obj, .label = "Activate Group 2", .hexpand = TRUE, .onclick = action_group2); + ui_button(obj, .label = "Disable Group 2", .onclick = action_group2, .onclickdata = "disable"); + ui_newline(obj); + + ui_button(obj, .label = "Groups 1,2", .colspan = 2, .groups = UI_GROUPS(1, 2)); + ui_newline(obj); + + ui_label(obj, .label = "Label Col 1", .align = UI_ALIGN_LEFT); + ui_label(obj, .label = "Label Col 2", .style = UI_LABEL_STYLE_TITLE, .align = UI_ALIGN_RIGHT); + ui_newline(obj); + + //ui_spinner(obj, .step = 5); + //ui_newline(obj); + + ui_progressbar(obj, .colspan = 2, .varname = "progress"); + ui_set(doc->progress, 0.75); + ui_newline(obj); + + ui_textfield(obj, .value = doc->str1); + ui_newline(obj); + + //ui_button(obj, .label="Test"); + ui_path_textfield(obj, .varname = "path"); + ui_set(doc->path, "/test/path/longdirectoryname/123"); + ui_newline(obj); + + //UiModel *model = ui_model(obj->ctx, UI_ICON_TEXT, "Col 1", UI_STRING, "Col 2", -1); + //model->getvalue = list_getvalue; + ui_combobox(obj, .hexpand = true, .vexpand = false, .colspan = 2, .varname = "list", .getvalue = list_getvalue); + ui_newline(obj); + + ui_hbox0(obj) { + ui_radiobutton(obj, .label = "Radio 1", .varname = "radio"); + ui_radiobutton(obj, .label = "Radio 2", .varname = "radio"); + ui_radiobutton(obj, .label = "Radio 3", .varname = "radio"); + } + } + } + ui_tab(obj, "Tab 2") { + ui_button(obj, .label = "Button 1 Start Thread", .onclick=action_start_thread); + ui_button(obj, .label = "Button 2 Notify Thread", .onclick=action_notify_thread); + ui_button(obj, .label = "Button 3", .onclick=action_tab2_button); + ui_button(obj, .label = "Button 4", .onclick=action_tab2_button); + ui_button(obj, .label = "Button 5", .onclick=action_tab2_button); + ui_button(obj, .label = "Button 6", .onclick=action_tab2_button); + } + ui_tab(obj, "Tab 3") { + UiTabViewArgs args = {0}; + UI_CTN(obj, tabview=ui_tabview_create(obj, &args)) { + UiObject *tab1 = ui_tabview_add(tabview, "Sub 1", -1); + ui_button(tab1, .label = "Button 1"); + + + UiObject *tab2 = ui_tabview_add(tabview, "Sub 2", -1); + ui_button(tab2, .label = "Button 2"); + } + } + ui_tab(obj, "Tab 4") { + ui_grid0(obj) { + ui_button(obj, .label = "test1"); + ui_newline(obj); + ui_textarea(obj, .varname = "text", .vexpand = TRUE, .hexpand = TRUE); + } + } + ui_tab(obj, "Tab 5") { + ui_button(obj, .label = "Test Button", .icon = "application-x-generic", .onclick = action_button); + ui_imageviewer(obj, .varname = "image", .style_class = "imageviewer"); + } + + ui_tab(obj, "Tab 6") { + ui_scrolledwindow(obj, .fill = UI_ON) { + ui_expander(obj, .label = "Expander", .margin = 10, .spacing = 10) { + ui_label(obj, .label = "Test"); + ui_button(obj, .label = "Button"); + } + + ui_frame(obj, .label = "Frame", .margin = 10, .spacing = 10) { + ui_label(obj, .label = "Title", .style = UI_LABEL_STYLE_TITLE); + ui_label(obj, .label = "Sub-Title", .style = UI_LABEL_STYLE_SUBTITLE); + ui_label(obj, .label = "Dim Label", .style = UI_LABEL_STYLE_DIM); + ui_label(obj, .label = "No Style"); + } + + for(int i=0;i<100;i++) { + char labelstr[32]; + snprintf(labelstr, 32, "button %d", i); + ui_button(obj, .label = labelstr); + } + } + } + } + + /* + + */ + + ui_show(obj); +} + +/* +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; + +UiList* menuList; + +void event_mt(UiEvent* event, void* data) { + char* mt_str = data; + + printf("%s\n", mt_str); +} + +int test_threadfunc(void *data) { + char* str = data; + + return 0; +} + +void action_thread_test(UiEvent* event, void* data) { + ui_job(event->obj, test_threadfunc, "testdata", event_mt, "testdata2"); +} + +void action1(UiEvent* event, void* data) { + char* action = data; + + WindowData* wdata = event->window; + int64_t is_checked = ui_get(wdata->check); + int64_t radio = ui_get(wdata->radio); + + printf("data: %s %d\n", data, is_checked); + + double d = ui_get(wdata->progress); + ui_set(wdata->progress, d + 1); + + int spinner_active = ui_get(wdata->spinner); + ui_set(wdata->spinner, !spinner_active); + + ui_list_append(menuList, "List Item X"); + ui_list_append(menuList, "List Item X"); + ui_notify(menuList->observers, NULL); +} + +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 dialog_result(UiEvent *evt, void *data) { + char *str = evt->eventdata; + printf("dialog: %d\n", (int)evt->intval); +} + +void btn_dialog(UiEvent *evt, void *data) { + ui_dialog(evt->obj, .title = "Title", .input = TRUE, .content = "Hello World", .button1_label = "Yes", .button2_label = "No", .closebutton_label = "Close", .result = dialog_result); +} + + + + +void application_startup(UiEvent* event, void* data) { + UiContext* gctx = ui_global_context(); + menuList = ui_list_new(gctx, "menulist"); + ui_list_append(menuList, "List Item 1"); + ui_list_append(menuList, "List Item 2"); + ui_list_append(menuList, "List Item 3"); + ui_list_append(menuList, "List Item 4"); + ui_list_append(menuList, "List Item 5"); + ui_list_append(menuList, "List Item 6"); + + 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 = "Thread Test", .onclick = action_thread_test, .onclickdata = "action1"); + ui_button(obj, .label = "Button2", .icon = "Back", .onclick = action1, .onclickdata = "action2"); + ui_button(obj, .icon = "Forward", .onclick = action1, .onclickdata = "action3", .hexpand = true); + ui_newline(obj); + + ui_button(obj, .label = "Dialog Test", .onclick = btn_dialog, .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", NULL, 0); + ui_onstartup(application_startup, NULL); + + // menu + ui_menu("File") { + ui_menuitem(.label = "Test"); + } + + ui_toolbar_item("Test", .label = "Test", .onclick = action_toolbar_button); + ui_toolbar_item("Test2", .label = "New Window", .onclick = action_toolbar_newwindow); + ui_toolbar_item("Test3", .label = "Dialog", .onclick = action_toolbar_dialog); + ui_toolbar_item("Test4", .label = "Test 4", .onclick = action_toolbar_button); + ui_toolbar_item("Test5", .label = "Test 5", .onclick = action_toolbar_button); + ui_toolbar_item("Test6", .label = "Test 6", .onclick = action_toolbar_button); + ui_toolbar_toggleitem("Toggle", .label = "Toggle", .onchange = action_toolbar_button); + ui_toolbar_menu("Menu", .label = "Menu") { + ui_menuitem("Secondary Test", .onclick = action_toolbar_button, NULL); + ui_menu("Secondary Sub") { + ui_menuitem("Secondary subitem", NULL, NULL); + } + ui_menuseparator(); + ui_menu_itemlist(.varname = "menulist", .onselect=action_menu_list); + ui_menuseparator(); + ui_menuitem("last", .onclick = action_add_menu_item); + } + + ui_toolbar_appmenu() { + ui_menuitem("New"); + ui_menuitem("Open"); + ui_menuitem("Save"); + + ui_menuseparator(); + + ui_menuitem("Close"); + } + + ui_toolbar_add_default("Test", UI_TOOLBAR_LEFT); + ui_toolbar_add_default("Test6", UI_TOOLBAR_LEFT); + ui_toolbar_add_default("Toggle", UI_TOOLBAR_LEFT); + ui_toolbar_add_default("Menu", UI_TOOLBAR_LEFT); + + ui_toolbar_add_default("Test2", UI_TOOLBAR_CENTER); + ui_toolbar_add_default("Test3", UI_TOOLBAR_CENTER); + + ui_toolbar_add_default("Test4", UI_TOOLBAR_RIGHT); + ui_toolbar_add_default("Test5", UI_TOOLBAR_RIGHT); + + ui_main(); + + return (EXIT_SUCCESS); + + /* + 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_menu_itemlist(.varname = "menulist"); + 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..4deb2ac --- /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..4a81051 --- /dev/null +++ b/make/vs/testapp/testapp.vcxproj @@ -0,0 +1,177 @@ + + + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {3541f08b-e6cc-4c23-a0d3-51983aab33c6} + testapp + 10.0 + true + None + false + + + + 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 + false + _DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;UI_WINUI;%(PreprocessorDefinitions) + true + C:\Users\Olaf\Projekte\toolkit\ui;%(AdditionalIncludeDirectories) + stdc17 + + + 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/toolkit.sln b/make/vs/toolkit.sln new file mode 100644 index 0000000..6f79011 --- /dev/null +++ b/make/vs/toolkit.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}") = "testapp", "testapp\testapp.vcxproj", "{3541F08B-E6CC-4C23-A0D3-51983AAB33C6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ucx", "ucx\ucx.vcxproj", "{27DA0164-3475-43E2-A1A4-A5D07D305749}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winui", "..\..\ui\winui\winui.vcxproj", "{59F97886-BF49-4B3F-9EF6-FA7A84F3AB56}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "toolkit", "toolkit\toolkit.vcxproj", "{DF3F075F-1D6A-4B76-8479-94558ACEEA4C}" +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 + {3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Debug|ARM64.ActiveCfg = Debug|x64 + {3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Debug|ARM64.Build.0 = Debug|x64 + {3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Debug|x64.ActiveCfg = Debug|x64 + {3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Debug|x64.Build.0 = Debug|x64 + {3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Debug|x86.ActiveCfg = Debug|Win32 + {3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Debug|x86.Build.0 = Debug|Win32 + {3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Release|ARM64.ActiveCfg = Release|x64 + {3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Release|ARM64.Build.0 = Release|x64 + {3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Release|x64.ActiveCfg = Release|x64 + {3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Release|x64.Build.0 = Release|x64 + {3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Release|x86.ActiveCfg = Release|Win32 + {3541F08B-E6CC-4C23-A0D3-51983AAB33C6}.Release|x86.Build.0 = Release|Win32 + {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 + {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 + {DF3F075F-1D6A-4B76-8479-94558ACEEA4C}.Debug|ARM64.ActiveCfg = Debug|x64 + {DF3F075F-1D6A-4B76-8479-94558ACEEA4C}.Debug|ARM64.Build.0 = Debug|x64 + {DF3F075F-1D6A-4B76-8479-94558ACEEA4C}.Debug|x64.ActiveCfg = Debug|x64 + {DF3F075F-1D6A-4B76-8479-94558ACEEA4C}.Debug|x64.Build.0 = Debug|x64 + {DF3F075F-1D6A-4B76-8479-94558ACEEA4C}.Debug|x86.ActiveCfg = Debug|Win32 + {DF3F075F-1D6A-4B76-8479-94558ACEEA4C}.Debug|x86.Build.0 = Debug|Win32 + {DF3F075F-1D6A-4B76-8479-94558ACEEA4C}.Release|ARM64.ActiveCfg = Release|x64 + {DF3F075F-1D6A-4B76-8479-94558ACEEA4C}.Release|ARM64.Build.0 = Release|x64 + {DF3F075F-1D6A-4B76-8479-94558ACEEA4C}.Release|x64.ActiveCfg = Release|x64 + {DF3F075F-1D6A-4B76-8479-94558ACEEA4C}.Release|x64.Build.0 = Release|x64 + {DF3F075F-1D6A-4B76-8479-94558ACEEA4C}.Release|x86.ActiveCfg = Release|Win32 + {DF3F075F-1D6A-4B76-8479-94558ACEEA4C}.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/ucx/ucx.vcxproj b/make/vs/ucx/ucx.vcxproj new file mode 100644 index 0000000..4d0f5e9 --- /dev/null +++ b/make/vs/ucx/ucx.vcxproj @@ -0,0 +1,180 @@ + + + + + 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 + + + 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 + false + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdc17 + + + 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/ucx/ucx.vcxproj.filters b/make/vs/ucx/ucx.vcxproj.filters new file mode 100644 index 0000000..ae704d9 --- /dev/null +++ b/make/vs/ucx/ucx.vcxproj.filters @@ -0,0 +1,138 @@ + + + + + {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 + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + 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..e1fc29f --- /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\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 + 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/uicommon/uicommon.vcxproj.user b/make/vs/uicommon/uicommon.vcxproj.user new file mode 100644 index 0000000..0f14913 --- /dev/null +++ b/make/vs/uicommon/uicommon.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/make/windows.mk b/make/windows.mk new file mode 100644 index 0000000..2f3bc72 --- /dev/null +++ b/make/windows.mk @@ -0,0 +1,42 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +CC = gcc +LD = gcc +AR = ar +RM = rm + +CFLAGS = -std=gnu99 +LDFLAGS = +ARFLAGS = -r +RMFLAGS = -f + +OBJ_EXT = obj +LIB_EXT = lib +APP_EXT = .exe + diff --git a/make/xcode/toolkit/toolkit.xcodeproj/project.pbxproj b/make/xcode/toolkit/toolkit.xcodeproj/project.pbxproj new file mode 100644 index 0000000..87fa53d --- /dev/null +++ b/make/xcode/toolkit/toolkit.xcodeproj/project.pbxproj @@ -0,0 +1,738 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + ED18C9232E76CA5500B64EA5 /* entry.m in Sources */ = {isa = PBXBuildFile; fileRef = ED18C9222E76CA5500B64EA5 /* entry.m */; }; + ED2F55AE2E34FAD800A84793 /* Toolbar.m in Sources */ = {isa = PBXBuildFile; fileRef = ED2F55AD2E34FAD800A84793 /* Toolbar.m */; }; + ED52BFB02D86FC5D00FD8BE5 /* text.m in Sources */ = {isa = PBXBuildFile; fileRef = ED52BFAF2D86FC5D00FD8BE5 /* text.m */; }; + ED6580EE2CFF19F900F5402F /* context.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580DD2CFF19F900F5402F /* context.c */; }; + ED6580EF2CFF19F900F5402F /* menu.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580E12CFF19F900F5402F /* menu.c */; }; + ED6580F02CFF19F900F5402F /* threadpool.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580E72CFF19F900F5402F /* threadpool.c */; }; + ED6580F12CFF19F900F5402F /* condvar.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580DB2CFF19F900F5402F /* condvar.c */; }; + ED6580F22CFF19F900F5402F /* document.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580DF2CFF19F900F5402F /* document.c */; }; + ED6580F32CFF19F900F5402F /* object.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580E32CFF19F900F5402F /* object.c */; }; + ED6580F42CFF19F900F5402F /* toolbar.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580E92CFF19F900F5402F /* toolbar.c */; }; + ED6580F52CFF19F900F5402F /* types.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580EB2CFF19F900F5402F /* types.c */; }; + ED6580F62CFF19F900F5402F /* properties.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580E52CFF19F900F5402F /* properties.c */; }; + ED6580F72CFF19F900F5402F /* ucx_properties.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580ED2CFF19F900F5402F /* ucx_properties.c */; }; + ED65811D2CFF1A3000F5402F /* linked_list.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581142CFF1A3000F5402F /* linked_list.c */; }; + ED65811E2CFF1A3000F5402F /* tree.c in Sources */ = {isa = PBXBuildFile; fileRef = ED65811B2CFF1A3000F5402F /* tree.c */; }; + ED6581202CFF1A3000F5402F /* mempool.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581172CFF1A3000F5402F /* mempool.c */; }; + ED6581212CFF1A3000F5402F /* map.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581162CFF1A3000F5402F /* map.c */; }; + ED6581222CFF1A3000F5402F /* hash_map.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581122CFF1A3000F5402F /* hash_map.c */; }; + ED6581232CFF1A3000F5402F /* array_list.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580FA2CFF1A3000F5402F /* array_list.c */; }; + ED6581242CFF1A3000F5402F /* list.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581152CFF1A3000F5402F /* list.c */; }; + ED6581252CFF1A3000F5402F /* compare.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580FC2CFF1A3000F5402F /* compare.c */; }; + ED6581262CFF1A3000F5402F /* hash_key.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581112CFF1A3000F5402F /* hash_key.c */; }; + ED6581272CFF1A3000F5402F /* iterator.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581132CFF1A3000F5402F /* iterator.c */; }; + ED6581282CFF1A3000F5402F /* string.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581192CFF1A3000F5402F /* string.c */; }; + ED6581292CFF1A3000F5402F /* allocator.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580F92CFF1A3000F5402F /* allocator.c */; }; + ED65812B2CFF1A3000F5402F /* buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6580FB2CFF1A3000F5402F /* buffer.c */; }; + ED65812C2CFF1A3000F5402F /* printf.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6581182CFF1A3000F5402F /* printf.c */; }; + ED6581312CFF1A8800F5402F /* toolkit.m in Sources */ = {isa = PBXBuildFile; fileRef = ED6581302CFF1A8800F5402F /* toolkit.m */; }; + ED6581342CFF1F1900F5402F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = ED6581332CFF1F1900F5402F /* AppDelegate.m */; }; + ED6581392CFF287300F5402F /* EventData.m in Sources */ = {isa = PBXBuildFile; fileRef = ED6581362CFF287300F5402F /* EventData.m */; }; + ED6581432CFF3BCE00F5402F /* window.m in Sources */ = {isa = PBXBuildFile; fileRef = ED6581422CFF3BCE00F5402F /* window.m */; }; + ED6581442CFF3BCE00F5402F /* button.m in Sources */ = {isa = PBXBuildFile; fileRef = ED65813C2CFF3BCE00F5402F /* button.m */; }; + ED6581452CFF3BCE00F5402F /* Container.m in Sources */ = {isa = PBXBuildFile; fileRef = ED65813E2CFF3BCE00F5402F /* Container.m */; }; + ED6581462CFF3BCE00F5402F /* GridLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = ED6581402CFF3BCE00F5402F /* GridLayout.m */; }; + ED65815C2CFF3EE900F5402F /* MainWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = ED65815B2CFF3EE900F5402F /* MainWindow.m */; }; + ED65815F2CFF4BF200F5402F /* WindowManager.m in Sources */ = {isa = PBXBuildFile; fileRef = ED65815E2CFF4BF200F5402F /* WindowManager.m */; }; + ED679B0A2E5B266C001D4F71 /* label.m in Sources */ = {isa = PBXBuildFile; fileRef = ED679B092E5B266C001D4F71 /* label.m */; }; + ED679B0D2E5B2FB0001D4F71 /* UiThread.m in Sources */ = {isa = PBXBuildFile; fileRef = ED679B0C2E5B2FB0001D4F71 /* UiThread.m */; }; + ED6FB03D2E95466F006C6E8E /* args.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6FB0382E95466F006C6E8E /* args.c */; }; + ED6FB03E2E95466F006C6E8E /* container.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6FB03A2E95466F006C6E8E /* container.c */; }; + ED6FB03F2E95466F006C6E8E /* wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6FB03C2E95466F006C6E8E /* wrapper.c */; }; + ED83C2BF2E8EA49200054B22 /* BoxContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = ED83C2BE2E8EA49200054B22 /* BoxContainer.m */; }; + ED8687E52D999CF3002F3EC2 /* menu.m in Sources */ = {isa = PBXBuildFile; fileRef = ED8687E42D999CF3002F3EC2 /* menu.m */; }; + ED895DAA2EA0CACC00040078 /* TabView.m in Sources */ = {isa = PBXBuildFile; fileRef = ED895DA92EA0CACC00040078 /* TabView.m */; }; + ED99F04A2E5CBD2E00A4CC97 /* widget.m in Sources */ = {isa = PBXBuildFile; fileRef = ED99F0492E5CBD2E00A4CC97 /* widget.m */; }; + EDB452C32E302C65006FB12D /* image.m in Sources */ = {isa = PBXBuildFile; fileRef = EDB452C22E302C65006FB12D /* image.m */; }; + EDBC4D272EAD4BB0005CDF38 /* webview.m in Sources */ = {isa = PBXBuildFile; fileRef = EDBC4D262EAD4BB0005CDF38 /* webview.m */; }; + EDC315A92E9A739300403776 /* json.c in Sources */ = {isa = PBXBuildFile; fileRef = EDC315A62E9A739300403776 /* json.c */; }; + EDC315AA2E9A739300403776 /* properties.c in Sources */ = {isa = PBXBuildFile; fileRef = EDC315A72E9A739300403776 /* properties.c */; }; + EDC315AB2E9A739300403776 /* streams.c in Sources */ = {isa = PBXBuildFile; fileRef = EDC315A82E9A739300403776 /* streams.c */; }; + EDCD22272E59EEF5000612AF /* list.m in Sources */ = {isa = PBXBuildFile; fileRef = EDCD22262E59EEF5000612AF /* list.m */; }; + EDCD22352E59F3B1000612AF /* ListDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = EDCD22342E59F3B1000612AF /* ListDataSource.m */; }; + EDCD22382E5A160A000612AF /* ListDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EDCD22372E5A160A000612AF /* ListDelegate.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + ED18C9212E76CA5500B64EA5 /* entry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = entry.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/entry.h; sourceTree = ""; }; + ED18C9222E76CA5500B64EA5 /* entry.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = entry.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/entry.m; sourceTree = ""; }; + ED2F55AC2E34FAD800A84793 /* Toolbar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Toolbar.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/Toolbar.h; sourceTree = ""; }; + ED2F55AD2E34FAD800A84793 /* Toolbar.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Toolbar.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/Toolbar.m; sourceTree = ""; }; + ED52BFAE2D86FC5D00FD8BE5 /* text.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = text.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/text.h; sourceTree = ""; }; + ED52BFAF2D86FC5D00FD8BE5 /* text.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = text.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/text.m; sourceTree = ""; }; + ED6580AC2CFF122700F5402F /* toolkit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = toolkit.app; sourceTree = BUILT_PRODUCTS_DIR; }; + ED6580DA2CFF19F900F5402F /* condvar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = condvar.h; path = /Users/olaf/Projekte/toolkit/ui/common/condvar.h; sourceTree = ""; }; + ED6580DB2CFF19F900F5402F /* condvar.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = condvar.c; path = /Users/olaf/Projekte/toolkit/ui/common/condvar.c; sourceTree = ""; }; + ED6580DC2CFF19F900F5402F /* context.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = context.h; path = /Users/olaf/Projekte/toolkit/ui/common/context.h; sourceTree = ""; }; + ED6580DD2CFF19F900F5402F /* context.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = context.c; path = /Users/olaf/Projekte/toolkit/ui/common/context.c; sourceTree = ""; }; + ED6580DE2CFF19F900F5402F /* document.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = document.h; path = /Users/olaf/Projekte/toolkit/ui/common/document.h; sourceTree = ""; }; + ED6580DF2CFF19F900F5402F /* document.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = document.c; path = /Users/olaf/Projekte/toolkit/ui/common/document.c; sourceTree = ""; }; + ED6580E02CFF19F900F5402F /* menu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = menu.h; path = /Users/olaf/Projekte/toolkit/ui/common/menu.h; sourceTree = ""; }; + ED6580E12CFF19F900F5402F /* menu.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = menu.c; path = /Users/olaf/Projekte/toolkit/ui/common/menu.c; sourceTree = ""; }; + ED6580E22CFF19F900F5402F /* object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = object.h; path = /Users/olaf/Projekte/toolkit/ui/common/object.h; sourceTree = ""; }; + ED6580E32CFF19F900F5402F /* object.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = object.c; path = /Users/olaf/Projekte/toolkit/ui/common/object.c; sourceTree = ""; }; + ED6580E42CFF19F900F5402F /* properties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = properties.h; path = /Users/olaf/Projekte/toolkit/ui/common/properties.h; sourceTree = ""; }; + ED6580E52CFF19F900F5402F /* properties.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = properties.c; path = /Users/olaf/Projekte/toolkit/ui/common/properties.c; sourceTree = ""; }; + ED6580E62CFF19F900F5402F /* threadpool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = threadpool.h; path = /Users/olaf/Projekte/toolkit/ui/common/threadpool.h; sourceTree = ""; }; + ED6580E72CFF19F900F5402F /* threadpool.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = threadpool.c; path = /Users/olaf/Projekte/toolkit/ui/common/threadpool.c; sourceTree = ""; }; + ED6580E82CFF19F900F5402F /* toolbar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = toolbar.h; path = /Users/olaf/Projekte/toolkit/ui/common/toolbar.h; sourceTree = ""; }; + ED6580E92CFF19F900F5402F /* toolbar.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = toolbar.c; path = /Users/olaf/Projekte/toolkit/ui/common/toolbar.c; sourceTree = ""; }; + ED6580EA2CFF19F900F5402F /* types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = types.h; path = /Users/olaf/Projekte/toolkit/ui/common/types.h; sourceTree = ""; }; + ED6580EB2CFF19F900F5402F /* types.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = types.c; path = /Users/olaf/Projekte/toolkit/ui/common/types.c; sourceTree = ""; }; + ED6580EC2CFF19F900F5402F /* ucx_properties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ucx_properties.h; path = /Users/olaf/Projekte/toolkit/ui/common/ucx_properties.h; sourceTree = ""; }; + ED6580ED2CFF19F900F5402F /* ucx_properties.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = ucx_properties.c; path = /Users/olaf/Projekte/toolkit/ui/common/ucx_properties.c; sourceTree = ""; }; + ED6580F92CFF1A3000F5402F /* allocator.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = allocator.c; path = /Users/olaf/Projekte/toolkit/ucx/allocator.c; sourceTree = ""; }; + ED6580FA2CFF1A3000F5402F /* array_list.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = array_list.c; path = /Users/olaf/Projekte/toolkit/ucx/array_list.c; sourceTree = ""; }; + ED6580FB2CFF1A3000F5402F /* buffer.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = buffer.c; path = /Users/olaf/Projekte/toolkit/ucx/buffer.c; sourceTree = ""; }; + ED6580FC2CFF1A3000F5402F /* compare.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = compare.c; path = /Users/olaf/Projekte/toolkit/ucx/compare.c; sourceTree = ""; }; + ED6580FD2CFF1A3000F5402F /* allocator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = allocator.h; sourceTree = ""; }; + ED6580FE2CFF1A3000F5402F /* array_list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = array_list.h; sourceTree = ""; }; + ED6580FF2CFF1A3000F5402F /* buffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = buffer.h; sourceTree = ""; }; + ED6581002CFF1A3000F5402F /* collection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = collection.h; sourceTree = ""; }; + ED6581012CFF1A3000F5402F /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = ""; }; + ED6581032CFF1A3000F5402F /* compare.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = compare.h; sourceTree = ""; }; + ED6581042CFF1A3000F5402F /* hash_key.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hash_key.h; sourceTree = ""; }; + ED6581052CFF1A3000F5402F /* hash_map.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hash_map.h; sourceTree = ""; }; + ED6581062CFF1A3000F5402F /* iterator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iterator.h; sourceTree = ""; }; + ED6581072CFF1A3000F5402F /* linked_list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = linked_list.h; sourceTree = ""; }; + ED6581082CFF1A3000F5402F /* list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = list.h; sourceTree = ""; }; + ED6581092CFF1A3000F5402F /* map.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = map.h; sourceTree = ""; }; + ED65810A2CFF1A3000F5402F /* mempool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mempool.h; sourceTree = ""; }; + ED65810B2CFF1A3000F5402F /* printf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = printf.h; sourceTree = ""; }; + ED65810C2CFF1A3000F5402F /* string.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = string.h; sourceTree = ""; }; + ED65810D2CFF1A3000F5402F /* test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = test.h; sourceTree = ""; }; + ED65810E2CFF1A3000F5402F /* tree.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tree.h; sourceTree = ""; }; + ED6581112CFF1A3000F5402F /* hash_key.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = hash_key.c; path = /Users/olaf/Projekte/toolkit/ucx/hash_key.c; sourceTree = ""; }; + ED6581122CFF1A3000F5402F /* hash_map.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = hash_map.c; path = /Users/olaf/Projekte/toolkit/ucx/hash_map.c; sourceTree = ""; }; + ED6581132CFF1A3000F5402F /* iterator.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = iterator.c; path = /Users/olaf/Projekte/toolkit/ucx/iterator.c; sourceTree = ""; }; + ED6581142CFF1A3000F5402F /* linked_list.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = linked_list.c; path = /Users/olaf/Projekte/toolkit/ucx/linked_list.c; sourceTree = ""; }; + ED6581152CFF1A3000F5402F /* list.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = list.c; path = /Users/olaf/Projekte/toolkit/ucx/list.c; sourceTree = ""; }; + ED6581162CFF1A3000F5402F /* map.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = map.c; path = /Users/olaf/Projekte/toolkit/ucx/map.c; sourceTree = ""; }; + ED6581172CFF1A3000F5402F /* mempool.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = mempool.c; path = /Users/olaf/Projekte/toolkit/ucx/mempool.c; sourceTree = ""; }; + ED6581182CFF1A3000F5402F /* printf.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = printf.c; path = /Users/olaf/Projekte/toolkit/ucx/printf.c; sourceTree = ""; }; + ED6581192CFF1A3000F5402F /* string.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = string.c; path = /Users/olaf/Projekte/toolkit/ucx/string.c; sourceTree = ""; }; + ED65811B2CFF1A3000F5402F /* tree.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = tree.c; path = /Users/olaf/Projekte/toolkit/ucx/tree.c; sourceTree = ""; }; + ED65812F2CFF1A8800F5402F /* toolkit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = toolkit.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/toolkit.h; sourceTree = ""; }; + ED6581302CFF1A8800F5402F /* toolkit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = toolkit.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/toolkit.m; sourceTree = ""; }; + ED6581322CFF1F1900F5402F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/AppDelegate.h; sourceTree = ""; }; + ED6581332CFF1F1900F5402F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/AppDelegate.m; sourceTree = ""; }; + ED6581352CFF287300F5402F /* EventData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EventData.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/EventData.h; sourceTree = ""; }; + ED6581362CFF287300F5402F /* EventData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = EventData.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/EventData.m; sourceTree = ""; }; + ED65813B2CFF3BCE00F5402F /* button.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = button.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/button.h; sourceTree = ""; }; + ED65813C2CFF3BCE00F5402F /* button.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = button.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/button.m; sourceTree = ""; }; + ED65813D2CFF3BCE00F5402F /* Container.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Container.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/Container.h; sourceTree = ""; }; + ED65813E2CFF3BCE00F5402F /* Container.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Container.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/Container.m; sourceTree = ""; }; + ED65813F2CFF3BCE00F5402F /* GridLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = GridLayout.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/GridLayout.h; sourceTree = ""; }; + ED6581402CFF3BCE00F5402F /* GridLayout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = GridLayout.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/GridLayout.m; sourceTree = ""; }; + ED6581412CFF3BCE00F5402F /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = window.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/window.h; sourceTree = ""; }; + ED6581422CFF3BCE00F5402F /* window.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = window.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/window.m; sourceTree = ""; }; + ED6581482CFF3CA000F5402F /* button.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = button.h; path = /Users/olaf/Projekte/toolkit/ui/ui/button.h; sourceTree = ""; }; + ED6581492CFF3CA000F5402F /* container.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = container.h; path = /Users/olaf/Projekte/toolkit/ui/ui/container.h; sourceTree = ""; }; + ED65814A2CFF3CA000F5402F /* display.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = display.h; path = /Users/olaf/Projekte/toolkit/ui/ui/display.h; sourceTree = ""; }; + ED65814B2CFF3CA000F5402F /* dnd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = dnd.h; path = /Users/olaf/Projekte/toolkit/ui/ui/dnd.h; sourceTree = ""; }; + ED65814C2CFF3CA000F5402F /* entry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = entry.h; path = /Users/olaf/Projekte/toolkit/ui/ui/entry.h; sourceTree = ""; }; + ED65814D2CFF3CA000F5402F /* graphics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = graphics.h; path = /Users/olaf/Projekte/toolkit/ui/ui/graphics.h; sourceTree = ""; }; + ED65814E2CFF3CA000F5402F /* icons.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = icons.h; path = /Users/olaf/Projekte/toolkit/ui/ui/icons.h; sourceTree = ""; }; + ED65814F2CFF3CA000F5402F /* image.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = image.h; path = /Users/olaf/Projekte/toolkit/ui/ui/image.h; sourceTree = ""; }; + ED6581502CFF3CA000F5402F /* menu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = menu.h; path = /Users/olaf/Projekte/toolkit/ui/ui/menu.h; sourceTree = ""; }; + ED6581512CFF3CA000F5402F /* properties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = properties.h; path = /Users/olaf/Projekte/toolkit/ui/ui/properties.h; sourceTree = ""; }; + ED6581522CFF3CA000F5402F /* range.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = range.h; path = /Users/olaf/Projekte/toolkit/ui/ui/range.h; sourceTree = ""; }; + ED6581532CFF3CA000F5402F /* stock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = stock.h; path = /Users/olaf/Projekte/toolkit/ui/ui/stock.h; sourceTree = ""; }; + ED6581542CFF3CA000F5402F /* text.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = text.h; path = /Users/olaf/Projekte/toolkit/ui/ui/text.h; sourceTree = ""; }; + ED6581552CFF3CA000F5402F /* toolbar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = toolbar.h; path = /Users/olaf/Projekte/toolkit/ui/ui/toolbar.h; sourceTree = ""; }; + ED6581562CFF3CA000F5402F /* toolkit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = toolkit.h; path = /Users/olaf/Projekte/toolkit/ui/ui/toolkit.h; sourceTree = ""; }; + ED6581572CFF3CA000F5402F /* tree.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tree.h; path = /Users/olaf/Projekte/toolkit/ui/ui/tree.h; sourceTree = ""; }; + ED6581582CFF3CA000F5402F /* ui.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ui.h; path = /Users/olaf/Projekte/toolkit/ui/ui/ui.h; sourceTree = ""; }; + ED6581592CFF3CA000F5402F /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = window.h; path = /Users/olaf/Projekte/toolkit/ui/ui/window.h; sourceTree = ""; }; + ED65815A2CFF3EE900F5402F /* MainWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MainWindow.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/MainWindow.h; sourceTree = ""; }; + ED65815B2CFF3EE900F5402F /* MainWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MainWindow.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/MainWindow.m; sourceTree = ""; }; + ED65815D2CFF4BF200F5402F /* WindowManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WindowManager.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/WindowManager.h; sourceTree = ""; }; + ED65815E2CFF4BF200F5402F /* WindowManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = WindowManager.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/WindowManager.m; sourceTree = ""; }; + ED679B082E5B266C001D4F71 /* label.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = label.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/label.h; sourceTree = ""; }; + ED679B092E5B266C001D4F71 /* label.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = label.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/label.m; sourceTree = ""; }; + ED679B0B2E5B2FB0001D4F71 /* UiThread.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = UiThread.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/UiThread.h; sourceTree = ""; }; + ED679B0C2E5B2FB0001D4F71 /* UiThread.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = UiThread.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/UiThread.m; sourceTree = ""; }; + ED6FB0372E95466F006C6E8E /* args.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = args.h; path = /Users/olaf/Projekte/toolkit/ui/common/args.h; sourceTree = ""; }; + ED6FB0382E95466F006C6E8E /* args.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = args.c; path = /Users/olaf/Projekte/toolkit/ui/common/args.c; sourceTree = ""; }; + ED6FB0392E95466F006C6E8E /* container.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = container.h; path = /Users/olaf/Projekte/toolkit/ui/common/container.h; sourceTree = ""; }; + ED6FB03A2E95466F006C6E8E /* container.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = container.c; path = /Users/olaf/Projekte/toolkit/ui/common/container.c; sourceTree = ""; }; + ED6FB03B2E95466F006C6E8E /* wrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wrapper.h; path = /Users/olaf/Projekte/toolkit/ui/common/wrapper.h; sourceTree = ""; }; + ED6FB03C2E95466F006C6E8E /* wrapper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wrapper.c; path = /Users/olaf/Projekte/toolkit/ui/common/wrapper.c; sourceTree = ""; }; + ED83C2BD2E8EA49200054B22 /* BoxContainer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BoxContainer.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/BoxContainer.h; sourceTree = ""; }; + ED83C2BE2E8EA49200054B22 /* BoxContainer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BoxContainer.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/BoxContainer.m; sourceTree = ""; }; + ED8687E32D999CF3002F3EC2 /* menu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = menu.h; path = ../../../ui/cocoa/menu.h; sourceTree = SOURCE_ROOT; }; + ED8687E42D999CF3002F3EC2 /* menu.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = menu.m; path = ../../../ui/cocoa/menu.m; sourceTree = SOURCE_ROOT; }; + ED895DA82EA0CACC00040078 /* TabView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TabView.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/TabView.h; sourceTree = ""; }; + ED895DA92EA0CACC00040078 /* TabView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = TabView.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/TabView.m; sourceTree = ""; }; + ED99F0482E5CBD2E00A4CC97 /* widget.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = widget.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/widget.h; sourceTree = ""; }; + ED99F0492E5CBD2E00A4CC97 /* widget.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = widget.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/widget.m; sourceTree = ""; }; + ED99F04B2E5CBE5000A4CC97 /* webview.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = webview.h; path = /Users/olaf/Projekte/toolkit/ui/ui/webview.h; sourceTree = ""; }; + ED99F04C2E5CBE5000A4CC97 /* widget.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = widget.h; path = /Users/olaf/Projekte/toolkit/ui/ui/widget.h; sourceTree = ""; }; + EDB452C12E302C65006FB12D /* image.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = image.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/image.h; sourceTree = ""; }; + EDB452C22E302C65006FB12D /* image.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = image.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/image.m; sourceTree = ""; }; + EDBC4D252EAD4BB0005CDF38 /* webview.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = webview.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/webview.h; sourceTree = ""; }; + EDBC4D262EAD4BB0005CDF38 /* webview.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = webview.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/webview.m; sourceTree = ""; }; + EDC315A32E9A736900403776 /* json.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = json.h; path = /Users/olaf/Projekte/toolkit/ucx/cx/json.h; sourceTree = ""; }; + EDC315A42E9A736900403776 /* properties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = properties.h; path = /Users/olaf/Projekte/toolkit/ucx/cx/properties.h; sourceTree = ""; }; + EDC315A52E9A736900403776 /* streams.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = streams.h; path = /Users/olaf/Projekte/toolkit/ucx/cx/streams.h; sourceTree = ""; }; + EDC315A62E9A739300403776 /* json.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = json.c; path = /Users/olaf/Projekte/toolkit/ucx/json.c; sourceTree = ""; }; + EDC315A72E9A739300403776 /* properties.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = properties.c; path = /Users/olaf/Projekte/toolkit/ucx/properties.c; sourceTree = ""; }; + EDC315A82E9A739300403776 /* streams.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = streams.c; path = /Users/olaf/Projekte/toolkit/ucx/streams.c; sourceTree = ""; }; + EDCD22252E59EEF5000612AF /* list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = list.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/list.h; sourceTree = ""; }; + EDCD22262E59EEF5000612AF /* list.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = list.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/list.m; sourceTree = ""; }; + EDCD22332E59F3B1000612AF /* ListDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ListDataSource.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/ListDataSource.h; sourceTree = ""; }; + EDCD22342E59F3B1000612AF /* ListDataSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ListDataSource.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/ListDataSource.m; sourceTree = ""; }; + EDCD22362E5A160A000612AF /* ListDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ListDelegate.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/ListDelegate.h; sourceTree = ""; }; + EDCD22372E5A160A000612AF /* ListDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ListDelegate.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/ListDelegate.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + ED6580AE2CFF122700F5402F /* toolkit */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = toolkit; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + ED6580A92CFF122700F5402F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + ED6580A32CFF122700F5402F = { + isa = PBXGroup; + children = ( + ED6581472CFF3C8300F5402F /* public */, + ED65812E2CFF1A7200F5402F /* cocoa */, + ED6580F82CFF1A1200F5402F /* ucx */, + ED6580D92CFF19DB00F5402F /* common */, + ED6580AE2CFF122700F5402F /* toolkit */, + ED6580AD2CFF122700F5402F /* Products */, + ); + sourceTree = ""; + }; + ED6580AD2CFF122700F5402F /* Products */ = { + isa = PBXGroup; + children = ( + ED6580AC2CFF122700F5402F /* toolkit.app */, + ); + name = Products; + sourceTree = ""; + }; + ED6580D92CFF19DB00F5402F /* common */ = { + isa = PBXGroup; + children = ( + ED6FB0372E95466F006C6E8E /* args.h */, + ED6FB0382E95466F006C6E8E /* args.c */, + ED6FB0392E95466F006C6E8E /* container.h */, + ED6FB03A2E95466F006C6E8E /* container.c */, + ED6FB03B2E95466F006C6E8E /* wrapper.h */, + ED6FB03C2E95466F006C6E8E /* wrapper.c */, + ED6580DA2CFF19F900F5402F /* condvar.h */, + ED6580DB2CFF19F900F5402F /* condvar.c */, + ED6580DC2CFF19F900F5402F /* context.h */, + ED6580DD2CFF19F900F5402F /* context.c */, + ED6580DE2CFF19F900F5402F /* document.h */, + ED6580DF2CFF19F900F5402F /* document.c */, + ED6580E02CFF19F900F5402F /* menu.h */, + ED6580E12CFF19F900F5402F /* menu.c */, + ED6580E22CFF19F900F5402F /* object.h */, + ED6580E32CFF19F900F5402F /* object.c */, + ED6580E42CFF19F900F5402F /* properties.h */, + ED6580E52CFF19F900F5402F /* properties.c */, + ED6580E62CFF19F900F5402F /* threadpool.h */, + ED6580E72CFF19F900F5402F /* threadpool.c */, + ED6580E82CFF19F900F5402F /* toolbar.h */, + ED6580E92CFF19F900F5402F /* toolbar.c */, + ED6580EA2CFF19F900F5402F /* types.h */, + ED6580EB2CFF19F900F5402F /* types.c */, + ED6580EC2CFF19F900F5402F /* ucx_properties.h */, + ED6580ED2CFF19F900F5402F /* ucx_properties.c */, + ); + path = common; + sourceTree = ""; + }; + ED6580F82CFF1A1200F5402F /* ucx */ = { + isa = PBXGroup; + children = ( + EDC315A62E9A739300403776 /* json.c */, + EDC315A72E9A739300403776 /* properties.c */, + EDC315A82E9A739300403776 /* streams.c */, + ED6580F92CFF1A3000F5402F /* allocator.c */, + ED6580FA2CFF1A3000F5402F /* array_list.c */, + ED6580FB2CFF1A3000F5402F /* buffer.c */, + ED6580FC2CFF1A3000F5402F /* compare.c */, + ED6581102CFF1A3000F5402F /* cx */, + ED6581112CFF1A3000F5402F /* hash_key.c */, + ED6581122CFF1A3000F5402F /* hash_map.c */, + ED6581132CFF1A3000F5402F /* iterator.c */, + ED6581142CFF1A3000F5402F /* linked_list.c */, + ED6581152CFF1A3000F5402F /* list.c */, + ED6581162CFF1A3000F5402F /* map.c */, + ED6581172CFF1A3000F5402F /* mempool.c */, + ED6581182CFF1A3000F5402F /* printf.c */, + ED6581192CFF1A3000F5402F /* string.c */, + ED65811B2CFF1A3000F5402F /* tree.c */, + ); + path = ucx; + sourceTree = ""; + }; + ED6581102CFF1A3000F5402F /* cx */ = { + isa = PBXGroup; + children = ( + ED6580FD2CFF1A3000F5402F /* allocator.h */, + ED6580FE2CFF1A3000F5402F /* array_list.h */, + ED6580FF2CFF1A3000F5402F /* buffer.h */, + ED6581002CFF1A3000F5402F /* collection.h */, + ED6581012CFF1A3000F5402F /* common.h */, + ED6581032CFF1A3000F5402F /* compare.h */, + ED6581042CFF1A3000F5402F /* hash_key.h */, + ED6581052CFF1A3000F5402F /* hash_map.h */, + ED6581062CFF1A3000F5402F /* iterator.h */, + ED6581072CFF1A3000F5402F /* linked_list.h */, + ED6581082CFF1A3000F5402F /* list.h */, + ED6581092CFF1A3000F5402F /* map.h */, + ED65810A2CFF1A3000F5402F /* mempool.h */, + ED65810B2CFF1A3000F5402F /* printf.h */, + ED65810C2CFF1A3000F5402F /* string.h */, + ED65810D2CFF1A3000F5402F /* test.h */, + ED65810E2CFF1A3000F5402F /* tree.h */, + EDC315A32E9A736900403776 /* json.h */, + EDC315A42E9A736900403776 /* properties.h */, + EDC315A52E9A736900403776 /* streams.h */, + ); + name = cx; + path = /Users/olaf/Projekte/toolkit/ucx/cx; + sourceTree = ""; + }; + ED65812E2CFF1A7200F5402F /* cocoa */ = { + isa = PBXGroup; + children = ( + EDBC4D252EAD4BB0005CDF38 /* webview.h */, + EDBC4D262EAD4BB0005CDF38 /* webview.m */, + ED895DA82EA0CACC00040078 /* TabView.h */, + ED895DA92EA0CACC00040078 /* TabView.m */, + ED83C2BD2E8EA49200054B22 /* BoxContainer.h */, + ED83C2BE2E8EA49200054B22 /* BoxContainer.m */, + ED18C9212E76CA5500B64EA5 /* entry.h */, + ED18C9222E76CA5500B64EA5 /* entry.m */, + ED99F0482E5CBD2E00A4CC97 /* widget.h */, + ED99F0492E5CBD2E00A4CC97 /* widget.m */, + ED679B0B2E5B2FB0001D4F71 /* UiThread.h */, + ED679B0C2E5B2FB0001D4F71 /* UiThread.m */, + ED679B082E5B266C001D4F71 /* label.h */, + ED679B092E5B266C001D4F71 /* label.m */, + EDCD22362E5A160A000612AF /* ListDelegate.h */, + EDCD22372E5A160A000612AF /* ListDelegate.m */, + EDCD22332E59F3B1000612AF /* ListDataSource.h */, + EDCD22342E59F3B1000612AF /* ListDataSource.m */, + EDCD22252E59EEF5000612AF /* list.h */, + EDCD22262E59EEF5000612AF /* list.m */, + ED2F55AC2E34FAD800A84793 /* Toolbar.h */, + ED2F55AD2E34FAD800A84793 /* Toolbar.m */, + EDB452C12E302C65006FB12D /* image.h */, + EDB452C22E302C65006FB12D /* image.m */, + ED8687E32D999CF3002F3EC2 /* menu.h */, + ED8687E42D999CF3002F3EC2 /* menu.m */, + ED52BFAE2D86FC5D00FD8BE5 /* text.h */, + ED52BFAF2D86FC5D00FD8BE5 /* text.m */, + ED65815D2CFF4BF200F5402F /* WindowManager.h */, + ED65815E2CFF4BF200F5402F /* WindowManager.m */, + ED65815A2CFF3EE900F5402F /* MainWindow.h */, + ED65815B2CFF3EE900F5402F /* MainWindow.m */, + ED65813B2CFF3BCE00F5402F /* button.h */, + ED65813C2CFF3BCE00F5402F /* button.m */, + ED65813D2CFF3BCE00F5402F /* Container.h */, + ED65813E2CFF3BCE00F5402F /* Container.m */, + ED65813F2CFF3BCE00F5402F /* GridLayout.h */, + ED6581402CFF3BCE00F5402F /* GridLayout.m */, + ED6581412CFF3BCE00F5402F /* window.h */, + ED6581422CFF3BCE00F5402F /* window.m */, + ED6581352CFF287300F5402F /* EventData.h */, + ED6581362CFF287300F5402F /* EventData.m */, + ED6581322CFF1F1900F5402F /* AppDelegate.h */, + ED6581332CFF1F1900F5402F /* AppDelegate.m */, + ED65812F2CFF1A8800F5402F /* toolkit.h */, + ED6581302CFF1A8800F5402F /* toolkit.m */, + ); + path = cocoa; + sourceTree = ""; + }; + ED6581472CFF3C8300F5402F /* public */ = { + isa = PBXGroup; + children = ( + ED99F04B2E5CBE5000A4CC97 /* webview.h */, + ED99F04C2E5CBE5000A4CC97 /* widget.h */, + ED6581482CFF3CA000F5402F /* button.h */, + ED6581492CFF3CA000F5402F /* container.h */, + ED65814A2CFF3CA000F5402F /* display.h */, + ED65814B2CFF3CA000F5402F /* dnd.h */, + ED65814C2CFF3CA000F5402F /* entry.h */, + ED65814D2CFF3CA000F5402F /* graphics.h */, + ED65814E2CFF3CA000F5402F /* icons.h */, + ED65814F2CFF3CA000F5402F /* image.h */, + ED6581502CFF3CA000F5402F /* menu.h */, + ED6581512CFF3CA000F5402F /* properties.h */, + ED6581522CFF3CA000F5402F /* range.h */, + ED6581532CFF3CA000F5402F /* stock.h */, + ED6581542CFF3CA000F5402F /* text.h */, + ED6581552CFF3CA000F5402F /* toolbar.h */, + ED6581562CFF3CA000F5402F /* toolkit.h */, + ED6581572CFF3CA000F5402F /* tree.h */, + ED6581582CFF3CA000F5402F /* ui.h */, + ED6581592CFF3CA000F5402F /* window.h */, + ); + path = public; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + ED6580AB2CFF122700F5402F /* toolkit */ = { + isa = PBXNativeTarget; + buildConfigurationList = ED6580BC2CFF122800F5402F /* Build configuration list for PBXNativeTarget "toolkit" */; + buildPhases = ( + ED6580A82CFF122700F5402F /* Sources */, + ED6580A92CFF122700F5402F /* Frameworks */, + ED6580AA2CFF122700F5402F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + ED6580AE2CFF122700F5402F /* toolkit */, + ); + name = toolkit; + packageProductDependencies = ( + ); + productName = toolkit; + productReference = ED6580AC2CFF122700F5402F /* toolkit.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + ED6580A42CFF122700F5402F /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 1610; + TargetAttributes = { + ED6580AB2CFF122700F5402F = { + CreatedOnToolsVersion = 16.1; + }; + }; + }; + buildConfigurationList = ED6580A72CFF122700F5402F /* Build configuration list for PBXProject "toolkit" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = ED6580A32CFF122700F5402F; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = ED6580AD2CFF122700F5402F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + ED6580AB2CFF122700F5402F /* toolkit */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + ED6580AA2CFF122700F5402F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + ED6580A82CFF122700F5402F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ED679B0A2E5B266C001D4F71 /* label.m in Sources */, + ED6FB03D2E95466F006C6E8E /* args.c in Sources */, + ED6FB03E2E95466F006C6E8E /* container.c in Sources */, + ED6FB03F2E95466F006C6E8E /* wrapper.c in Sources */, + ED6580EE2CFF19F900F5402F /* context.c in Sources */, + ED6580EF2CFF19F900F5402F /* menu.c in Sources */, + EDCD22352E59F3B1000612AF /* ListDataSource.m in Sources */, + ED6580F02CFF19F900F5402F /* threadpool.c in Sources */, + EDB452C32E302C65006FB12D /* image.m in Sources */, + ED6580F12CFF19F900F5402F /* condvar.c in Sources */, + EDCD22272E59EEF5000612AF /* list.m in Sources */, + ED65815F2CFF4BF200F5402F /* WindowManager.m in Sources */, + ED6580F22CFF19F900F5402F /* document.c in Sources */, + ED65811D2CFF1A3000F5402F /* linked_list.c in Sources */, + ED2F55AE2E34FAD800A84793 /* Toolbar.m in Sources */, + ED65811E2CFF1A3000F5402F /* tree.c in Sources */, + ED6581202CFF1A3000F5402F /* mempool.c in Sources */, + EDC315A92E9A739300403776 /* json.c in Sources */, + EDC315AA2E9A739300403776 /* properties.c in Sources */, + EDC315AB2E9A739300403776 /* streams.c in Sources */, + ED6581212CFF1A3000F5402F /* map.c in Sources */, + ED6581222CFF1A3000F5402F /* hash_map.c in Sources */, + ED6581232CFF1A3000F5402F /* array_list.c in Sources */, + ED6581242CFF1A3000F5402F /* list.c in Sources */, + ED18C9232E76CA5500B64EA5 /* entry.m in Sources */, + ED65815C2CFF3EE900F5402F /* MainWindow.m in Sources */, + ED99F04A2E5CBD2E00A4CC97 /* widget.m in Sources */, + ED6581252CFF1A3000F5402F /* compare.c in Sources */, + ED6581262CFF1A3000F5402F /* hash_key.c in Sources */, + EDBC4D272EAD4BB0005CDF38 /* webview.m in Sources */, + ED6581272CFF1A3000F5402F /* iterator.c in Sources */, + ED6581282CFF1A3000F5402F /* string.c in Sources */, + ED6581312CFF1A8800F5402F /* toolkit.m in Sources */, + ED6581342CFF1F1900F5402F /* AppDelegate.m in Sources */, + ED679B0D2E5B2FB0001D4F71 /* UiThread.m in Sources */, + ED6581292CFF1A3000F5402F /* allocator.c in Sources */, + ED52BFB02D86FC5D00FD8BE5 /* text.m in Sources */, + ED6581432CFF3BCE00F5402F /* window.m in Sources */, + ED6581442CFF3BCE00F5402F /* button.m in Sources */, + ED6581452CFF3BCE00F5402F /* Container.m in Sources */, + ED6581462CFF3BCE00F5402F /* GridLayout.m in Sources */, + ED6581392CFF287300F5402F /* EventData.m in Sources */, + ED65812B2CFF1A3000F5402F /* buffer.c in Sources */, + ED895DAA2EA0CACC00040078 /* TabView.m in Sources */, + ED65812C2CFF1A3000F5402F /* printf.c in Sources */, + ED6580F32CFF19F900F5402F /* object.c in Sources */, + ED6580F42CFF19F900F5402F /* toolbar.c in Sources */, + ED6580F52CFF19F900F5402F /* types.c in Sources */, + ED8687E52D999CF3002F3EC2 /* menu.m in Sources */, + ED6580F62CFF19F900F5402F /* properties.c in Sources */, + ED83C2BF2E8EA49200054B22 /* BoxContainer.m in Sources */, + EDCD22382E5A160A000612AF /* ListDelegate.m in Sources */, + ED6580F72CFF19F900F5402F /* ucx_properties.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + ED6580BA2CFF122800F5402F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ../../../ucx; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + ED6580BB2CFF122800F5402F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ../../../ucx; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + }; + name = Release; + }; + ED6580BD2CFF122800F5402F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = toolkit/toolkit.entitlements; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + HEADER_SEARCH_PATHS = ( + ../../../ucx, + ../../../ui, + ); + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSMainNibFile = MainMenu; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_CFLAGS = "-DUI_COCOA"; + PRODUCT_BUNDLE_IDENTIFIER = de.unixwork.toolkit; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + }; + name = Debug; + }; + ED6580BE2CFF122800F5402F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = toolkit/toolkit.entitlements; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + HEADER_SEARCH_PATHS = ( + ../../../ucx, + ../../../ui, + ); + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSMainNibFile = MainMenu; + INFOPLIST_KEY_NSPrincipalClass = NSApplication; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_CFLAGS = "-DUI_COCOA"; + PRODUCT_BUNDLE_IDENTIFIER = de.unixwork.toolkit; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + ED6580A72CFF122700F5402F /* Build configuration list for PBXProject "toolkit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ED6580BA2CFF122800F5402F /* Debug */, + ED6580BB2CFF122800F5402F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + ED6580BC2CFF122800F5402F /* Build configuration list for PBXNativeTarget "toolkit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ED6580BD2CFF122800F5402F /* Debug */, + ED6580BE2CFF122800F5402F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = ED6580A42CFF122700F5402F /* Project object */; +} diff --git a/make/xcode/toolkit/toolkit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/make/xcode/toolkit/toolkit.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/make/xcode/toolkit/toolkit.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/make/xcode/toolkit/toolkit.xcodeproj/xcshareddata/xcschemes/toolkit.xcscheme b/make/xcode/toolkit/toolkit.xcodeproj/xcshareddata/xcschemes/toolkit.xcscheme new file mode 100644 index 0000000..765f961 --- /dev/null +++ b/make/xcode/toolkit/toolkit.xcodeproj/xcshareddata/xcschemes/toolkit.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/make/xcode/toolkit/toolkit/Assets.xcassets/AccentColor.colorset/Contents.json b/make/xcode/toolkit/toolkit/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/make/xcode/toolkit/toolkit/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/make/xcode/toolkit/toolkit/Assets.xcassets/AppIcon.appiconset/Contents.json b/make/xcode/toolkit/toolkit/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..3f00db4 --- /dev/null +++ b/make/xcode/toolkit/toolkit/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/make/xcode/toolkit/toolkit/Assets.xcassets/Contents.json b/make/xcode/toolkit/toolkit/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/make/xcode/toolkit/toolkit/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/make/xcode/toolkit/toolkit/Base.lproj/MainMenu.xib b/make/xcode/toolkit/toolkit/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..1212dc7 --- /dev/null +++ b/make/xcode/toolkit/toolkit/Base.lproj/MainMenu.xib @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/make/xcode/toolkit/toolkit/main.m b/make/xcode/toolkit/toolkit/main.m new file mode 100644 index 0000000..eae3197 --- /dev/null +++ b/make/xcode/toolkit/toolkit/main.m @@ -0,0 +1,277 @@ +/* + * 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. + */ + +#import + +#include + +#include + +typedef struct WindowData { + UiInteger *tbtoggle; +} WindowData; + +typedef struct MyDocument { + UiInteger *tbtoggle; + UiDouble *number; + UiList *list1; + UiList *list2; + UiList *sidebar_list; + UiList *sidebar_list2; + UiString *link; + UiGeneric *webview; +} MyDocument; + +MyDocument* create_doc(void) { + MyDocument *doc = ui_document_new(sizeof(MyDocument)); + UiContext *ctx = ui_document_context(doc); + doc->tbtoggle = ui_int_new(ctx, "tbtoggle"); + doc->number = ui_double_new(ctx, "number"); + doc->list1 = ui_list_new(ctx, "list1"); + ui_list_append(doc->list1, "Item 1"); + ui_list_append(doc->list1, "Item 2"); + ui_list_append(doc->list1, "Item 3"); + ui_list_append(doc->list1, "Item 4"); + + doc->list2 = ui_list_new(ctx, "list2"); + ui_list_append(doc->list2, "Option 1"); + ui_list_append(doc->list2, "Option 2"); + ui_list_append(doc->list2, "Option 3"); + ui_list_append(doc->list2, "Option 4"); + ui_list_append(doc->list2, "Option 5"); + ui_list_append(doc->list2, "Option 6"); + + doc->sidebar_list = ui_list_new(ctx, "sidebar_list"); + ui_list_append(doc->sidebar_list, "Item 1"); + ui_list_append(doc->sidebar_list, "Item 2"); + ui_list_append(doc->sidebar_list, "Item 3"); + ui_list_append(doc->sidebar_list, "Item 4"); + ui_list_append(doc->sidebar_list, "Item 5"); + ui_list_append(doc->sidebar_list, "Item 6"); + + doc->sidebar_list2 = ui_list_new(ctx, "sidebar_list2"); + ui_list_append(doc->sidebar_list2, "Item 1"); + ui_list_append(doc->sidebar_list2, "Item 2"); + ui_list_append(doc->sidebar_list2, "Item 3"); + ui_list_append(doc->sidebar_list2, "Item 4"); + + doc->link = ui_string_new(ctx, "link"); + doc->webview = ui_generic_new(ctx, "webview"); + + return doc; +} + +static int clickcount = 0; +static void action_button(UiEvent *event, void *userdata) { + printf("button click %d\n", clickcount++); +} + +static void toolbar_action(UiEvent *event, void *userdata) { + printf("toolbar item\n"); + ui_dialog(event->obj, .title = "Error", .content = "Error Message", .input = TRUE, .button1_label = "Add", .button2_label = "Remove", .closebutton_label = "Close"); +} + +static void toolbar_action2(UiEvent *event, void *userdata) { + printf("toolbar item 2\n"); + UiObject *obj = ui_dialog_window(event->obj, .title = "Dialog Window", .lbutton1 = "Cancel", .rbutton4 = "OK", .default_button = 4); + + ui_grid(obj, .margin = 10, .columnspacing = 10, .rowspacing = 10, .fill = TRUE) { + ui_rlabel(obj, .label = "Label 1"); + ui_textfield(obj, .value = NULL); + ui_newline(obj); + + ui_rlabel(obj, .label = "Label 2"); + ui_textfield(obj, .value = NULL); + ui_newline(obj); + + ui_textarea(obj, .colspan = 2, .hexpand = TRUE, .hfill = TRUE, .vfill = TRUE, .vexpand = TRUE, .width = 600, .height = 300); + } + + ui_show(obj); +} + +static void toolbar_toggle(UiEvent *event, void *userdata) { + MyDocument *doc = event->document; + int i = (int)(doc ? ui_get(doc->tbtoggle) : -1); + printf("toolbar toggle button %d %d\n", event->intval, i); +} + +static void action_menuitem(UiEvent *event, void *userdata) { + printf("menuitem\n"); +} + +static void action_list_selection(UiEvent *event, void *userdata) { + printf("selection\n"); +} + +static void action_list_activate(UiEvent *event, void *userdata) { + printf("activate\n"); +} + +static void* table_getvalue(void *elm, int col) { + return elm; +} + + + +void application_startup(UiEvent *event, void *data) { + UiObject *obj = ui_splitview_window("My Window", TRUE); + //WindowData *wdata = ui_malloc(obj->ctx, sizeof(WindowData)); + //wdata->tbtoggle = ui_int_new(obj->ctx, "tbtoggle"); + //obj->window = wdata; + MyDocument *doc = create_doc(); + ui_attach_document(obj->ctx, doc); + + UiSubList sublist[2]; + sublist[0].header = "iCloud"; + sublist[0].value = doc->sidebar_list; + sublist[0].separator = FALSE; + sublist[0].varname = NULL; + sublist[0].userdata = NULL; + sublist[1].header = "Tags"; + sublist[1].value = doc->sidebar_list2; + sublist[1].separator = FALSE; + sublist[1].varname = NULL; + sublist[1].userdata = NULL; + ui_sidebar(obj) { + ui_sourcelist(obj, .fill = TRUE, .sublists = sublist, .numsublists = 2); + } + + + ui_left_panel0(obj) { + ui_grid(obj, .margin_left = 10, .margin_right = 10, .columnspacing = 10, .rowspacing = 10, .fill = TRUE) { + ui_button(obj, .label = "left", .hexpand = TRUE, .hfill = TRUE); + ui_newline(obj); + ui_linkbutton(obj, .varname = "link", .hexpand = TRUE, .hfill = TRUE); + ui_newline(obj); + + ui_frame(obj, .label = "Test", .margin_bottom = 10, .hfill = TRUE, .vfill = TRUE, .hexpand = TRUE, .vexpand = TRUE, .subcontainer = UI_CONTAINER_VBOX, .spacing = 10) { + ui_button(obj, .label = "Test Button"); + ui_button(obj, .label = "Test Button"); + ui_button(obj, .label = "Test Button"); + ui_button(obj, .label = "Test Button"); + ui_button(obj, .label = "Test Button"); + } + } + } + + ui_right_panel0(obj) { + ui_tabview(obj, .padding = 20, .spacing = 10, .margin_left = 10, .margin_right = 10, .margin_bottom = 10, .fill = TRUE) { + ui_tab(obj, "Tab 1") { + ui_button(obj, .label = "Test"); + ui_textarea(obj, .fill = TRUE); + } + ui_tab(obj, "Tab 2") { + //ui_button(obj, .label = "Tab 2 Content"); + ui_webview(obj, .fill = TRUE, .value = doc->webview); + ui_webview_load_url(doc->webview, "https://unixwork.de/"); + } + } + + /* + ui_scrolledwindow(obj, .margin_left = 10, .margin_right = 10, .fill = TRUE, .subcontainer = UI_CONTAINER_NO_SUB) { + ui_vbox(obj, .margin = 0) { + for(int i=0;i<50;i++) { + char label[32]; + snprintf(label, 32, "Button %d", i); + ui_button(obj, .label = label); + } + } + } + */ + } + + ui_linkbutton_value_set(doc->link, "unixwork", "https://unixwork.de/"); + + + UiModel *model = ui_model(obj->ctx, UI_STRING, "Column 0", UI_STRING, "Column 1", UI_STRING, "Column 2", -1); + model->columnsize[1] = -1; + //ui_grid(obj, .columnspacing = 10, .rowspacing = 10) { + //ui_table(obj, .fill = UI_ON, .varname = "list1", .model = model, .getvalue = table_getvalue, .onactivate = action_list_activate, .onselection = action_list_selection, .multiselection = TRUE); + + //} + + /* + UiModel *model = ui_model(obj->ctx, UI_STRING, "Column 0", UI_STRING, "Column 1", UI_STRING, "Column 2", -1); + model->columnsize[1] = -1; + ui_grid(obj, .columnspacing = 10, .rowspacing = 10) { + ui_table(obj, .fill = UI_ON, .varname = "list1", .model = model, .getvalue = table_getvalue, .onactivate = action_list_activate, .onselection = action_list_selection, .multiselection = TRUE); + } + */ + + ui_show(obj); +} + +int main(int argc, char * argv[]) { + ui_init("app1", argc, argv); + ui_onstartup(application_startup, NULL); + + // menu + ui_menu("File") { + ui_menuitem(.label = "Test", .onclick = action_menuitem); + } + + ui_menu("Edit") { + ui_menuitem(.label = "Undo"); + ui_menu_toggleitem(.label = "Checkbox"); + ui_menuseparator(); + ui_menu_radioitem(.label = "Option 1", .varname = "menuoption"); + ui_menu_radioitem(.label = "Option 2", .varname = "menuoption"); + ui_menu_radioitem(.label = "Option 3", .varname = "menuoption"); + ui_menu_radioitem(.label = "Option 4", .varname = "menuoption"); + ui_menuseparator(); + ui_menu("Submenu") { + ui_menuitem(.label = "Subitem"); + } + } + + ui_toolbar_item("itemz", .label = "Test Z", .icon = "NSImageNameActionTemplate"); + ui_toolbar_item("item0", .label = "Test 0", .icon = "NSImageNameActionTemplate"); + ui_toolbar_item("item1", .label = "Test 1", .icon = "NSImageNameGoBackTemplate", .onclick = toolbar_action); + ui_toolbar_item("item2", .label = "Test 2", .icon = "NSImageNameGoForwardTemplate", .onclick = toolbar_action); + ui_toolbar_toggleitem("item3", .label = "Toggle", .icon = "NSImageNameActionTemplate", .varname = "tbtoggle", .onchange = toolbar_toggle); + ui_toolbar_menu("item4", .label = "Add", .icon = "NSImageNameAddTemplate") { + ui_menuitem(.label = "Test1", .onclick = action_menuitem); + ui_menu_toggleitem(.label = "Check"); + } + ui_toolbar_item("item5", .label = "Dialog Window", .icon = "NSImageNameAddTemplate", .onclick = toolbar_action2); + ui_toolbar_item("item6", .label = "Test", .icon = "NSImageNameGoBackTemplate"); + + ui_toolbar_add_default("itemz", UI_TOOLBAR_SIDEBAR_LEFT); + ui_toolbar_add_default("item0", UI_TOOLBAR_SIDEBAR_RIGHT); + ui_toolbar_add_default("item1", UI_TOOLBAR_LEFT); + ui_toolbar_add_default("item2", UI_TOOLBAR_LEFT); + ui_toolbar_add_default("item3", UI_TOOLBAR_CENTER); + ui_toolbar_add_default("item4", UI_TOOLBAR_RIGHT); + ui_toolbar_add_default("item5", UI_TOOLBAR_RIGHTPANEL_LEFT); + ui_toolbar_add_default("item6", UI_TOOLBAR_RIGHTPANEL_RIGHT); + + + ui_main(); + return 0; +} diff --git a/make/xcode/toolkit/toolkit/toolkit.entitlements b/make/xcode/toolkit/toolkit/toolkit.entitlements new file mode 100644 index 0000000..311b32b --- /dev/null +++ b/make/xcode/toolkit/toolkit/toolkit.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/ucx/Makefile b/ucx/Makefile index bcc694b..4b61a56 100644 --- a/ucx/Makefile +++ b/ucx/Makefile @@ -26,7 +26,6 @@ # POSSIBILITY OF SUCH DAMAGE. # -BUILD_ROOT = ../ include ../config.mk # list of source files @@ -37,28 +36,34 @@ SRC += buffer.c SRC += compare.c SRC += hash_key.c SRC += hash_map.c +SRC += iterator.c SRC += linked_list.c +SRC += kv_list.c SRC += list.c SRC += map.c SRC += printf.c SRC += string.c -SRC += streams.c SRC += tree.c +SRC += streams.c SRC += properties.c SRC += json.c -OBJ = $(SRC:%.c=../build/ucx/%.$(OBJ_EXT)) +OBJ = $(SRC:%.c=../build/ucx/%$(OBJ_EXT)) -UCX_LIB = ../build/lib/libucx.$(LIB_EXT) +UCX_LIB = ../build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) +UCX_SHLIB = ../build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(SHLIB_EXT) -all: ../build/ucx $(UCX_LIB) +all: $(UCX_LIB) $(UCX_SHLIB) $(UCX_LIB): $(OBJ) - $(AR) $(ARFLAGS) $(UCX_LIB) $(OBJ) + $(AR) $(ARFLAGS) $@ $(OBJ) + +$(UCX_SHLIB): $(OBJ) + $(CC) -o $@ $(LDFLAGS) $(SHLIB_LDFLAGS) $(OBJ) ../build/ucx: mkdir -p ../build/ucx -../build/ucx/%.$(OBJ_EXT): %.c - $(CC) $(CFLAGS) -o $@ -c $< +../build/ucx/%$(OBJ_EXT): %.c + $(CC) $(CFLAGS) $(SHLIB_CFLAGS) -o $@ -c $< diff --git a/ucx/allocator.c b/ucx/allocator.c index b10096c..323b14b 100644 --- a/ucx/allocator.c +++ b/ucx/allocator.c @@ -73,7 +73,7 @@ struct cx_allocator_s cx_stdlib_allocator = { NULL }; const CxAllocator * const cxStdlibAllocator = &cx_stdlib_allocator; -const CxAllocator * cxDefaultAllocator = cxStdlibAllocator; +const CxAllocator * cxDefaultAllocator = &cx_stdlib_allocator; int cx_reallocate_( void **mem, diff --git a/ucx/array_list.c b/ucx/array_list.c index a66db51..03c0bfe 100644 --- a/ucx/array_list.c +++ b/ucx/array_list.c @@ -50,7 +50,7 @@ static void *cx_array_default_realloc( } CxArrayReallocator cx_array_default_reallocator_impl = { - cx_array_default_realloc, NULL, NULL, 0, 0 + cx_array_default_realloc, NULL, NULL }; CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl; @@ -72,11 +72,11 @@ static void *cx_array_advanced_realloc( } // retrieve the pointer to the actual allocator - const CxAllocator *al = alloc->ptr1; + const CxAllocator *al = alloc->allocator; // check if the array is still located on the stack void *newmem; - if (array == alloc->ptr2) { + if (array == alloc->stack_ptr) { newmem = cxMalloc(al, n); if (newmem != NULL && array != NULL) { memcpy(newmem, array, old_capacity*elem_size); @@ -89,15 +89,14 @@ static void *cx_array_advanced_realloc( struct cx_array_reallocator_s cx_array_reallocator( const struct cx_allocator_s *allocator, - const void *stackmem + const void *stack_ptr ) { if (allocator == NULL) { allocator = cxDefaultAllocator; } return (struct cx_array_reallocator_s) { cx_array_advanced_realloc, - (void*) allocator, (void*) stackmem, - 0, 0 + allocator, stack_ptr, }; } @@ -291,7 +290,7 @@ int cx_array_copy( *target, oldcap, newcap, elem_size, reallocator ); if (newmem == NULL) { - return 1; + return 1; // LCOV_EXCL_LINE } // repair src pointer, if necessary @@ -335,7 +334,7 @@ int cx_array_copy( return 0; } -int cx_array_insert_sorted( +static int cx_array_insert_sorted_impl( void **target, size_t *size, size_t *capacity, @@ -343,7 +342,8 @@ int cx_array_insert_sorted( const void *sorted_data, size_t elem_size, size_t elem_count, - CxArrayReallocator *reallocator + CxArrayReallocator *reallocator, + bool allow_duplicates ) { // assert pointers assert(target != NULL); @@ -369,6 +369,7 @@ int cx_array_insert_sorted( // store some counts size_t old_size = *size; size_t old_capacity = *capacity; + // the necessary capacity is the worst case assumption, including duplicates size_t needed_capacity = old_size + elem_count; // if we need more than we have, try a reallocation @@ -418,13 +419,60 @@ int cx_array_insert_sorted( bptr, cmp_func ); + // binary search gives us the smallest index; + // we also want to include equal elements here + while (si + copy_len < elem_count + && cmp_func(bptr, src+copy_len*elem_size) == 0) { + copy_len++; + } // copy the source elements - bytes_copied = copy_len * elem_size; - memcpy(dest, src, bytes_copied); - dest += bytes_copied; - src += bytes_copied; - si += copy_len; + if (copy_len > 0) { + if (allow_duplicates) { + // we can copy the entire chunk + bytes_copied = copy_len * elem_size; + memcpy(dest, src, bytes_copied); + dest += bytes_copied; + src += bytes_copied; + si += copy_len; + di += copy_len; + } else { + // first, check the end of the source chunk + // for being a duplicate of the bptr + const char *end_of_src = src + (copy_len - 1) * elem_size; + size_t skip_len = 0; + while (copy_len > 0 && cmp_func(bptr, end_of_src) == 0) { + end_of_src -= elem_size; + skip_len++; + copy_len--; + } + char *last = dest == *target ? NULL : dest - elem_size; + // then iterate through the source chunk + // and skip all duplicates with the last element in the array + size_t more_skipped = 0; + for (unsigned j = 0; j < copy_len; j++) { + if (last != NULL && cmp_func(last, src) == 0) { + // duplicate - skip + src += elem_size; + si++; + more_skipped++; + } else { + memcpy(dest, src, elem_size); + src += elem_size; + last = dest; + dest += elem_size; + si++; + di++; + } + } + // skip the previously identified elements as well + src += skip_len * elem_size; + si += skip_len; + skip_len += more_skipped; + // reduce the actual size by the number of skipped elements + *size -= skip_len; + } + } // when all source elements are in place, we are done if (si >= elem_count) break; @@ -443,20 +491,103 @@ int cx_array_insert_sorted( memmove(dest, bptr, bytes_copied); dest += bytes_copied; bptr += bytes_copied; + di += copy_len; bi += copy_len; } - // still source elements left? simply append them + // still source elements left? if (si < elem_count) { - memcpy(dest, src, elem_size * (elem_count - si)); + if (allow_duplicates) { + // duplicates allowed or nothing inserted yet: simply copy everything + memcpy(dest, src, elem_size * (elem_count - si)); + } else { + if (dest != *target) { + // skip all source elements that equal the last element + char *last = dest - elem_size; + while (si < elem_count) { + if (last != NULL && cmp_func(last, src) == 0) { + src += elem_size; + si++; + (*size)--; + } else { + break; + } + } + } + // we must check the elements in the chunk one by one + while (si < elem_count) { + // find a chain of elements that can be copied + size_t copy_len = 1, skip_len = 0; + { + const char *left_src = src; + while (si + copy_len < elem_count) { + const char *right_src = left_src + elem_size; + int d = cmp_func(left_src, right_src); + if (d < 0) { + if (skip_len > 0) { + // new larger element found; + // handle it in the next cycle + break; + } + left_src += elem_size; + copy_len++; + } else if (d == 0) { + left_src += elem_size; + skip_len++; + } else { + break; + } + } + } + size_t bytes_copied = copy_len * elem_size; + memcpy(dest, src, bytes_copied); + dest += bytes_copied; + src += bytes_copied + skip_len * elem_size; + si += copy_len + skip_len; + di += copy_len; + *size -= skip_len; + } + } } - // still buffer elements left? - // don't worry, we already moved them to the correct place + // buffered elements need to be moved when we skipped duplicates + size_t total_skipped = new_size - *size; + if (bi < new_size && total_skipped > 0) { + // move the remaining buffer to the end of the array + memmove(dest, bptr, elem_size * (new_size - bi)); + } return 0; } +int cx_array_insert_sorted( + void **target, + size_t *size, + size_t *capacity, + cx_compare_func cmp_func, + const void *sorted_data, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +) { + return cx_array_insert_sorted_impl(target, size, capacity, + cmp_func, sorted_data, elem_size, elem_count, reallocator, true); +} + +int cx_array_insert_unique( + void **target, + size_t *size, + size_t *capacity, + cx_compare_func cmp_func, + const void *sorted_data, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +) { + return cx_array_insert_sorted_impl(target, size, capacity, + cmp_func, sorted_data, elem_size, elem_count, reallocator, false); +} + size_t cx_array_binary_search_inf( const void *arr, size_t size, @@ -502,6 +633,13 @@ size_t cx_array_binary_search_inf( result = cmp_func(elem, arr_elem); if (result == 0) { // found it! + // check previous elements; + // when they are equal, report the smallest index + arr_elem -= elem_size; + while (pivot_index > 0 && cmp_func(elem, arr_elem) == 0) { + pivot_index--; + arr_elem -= elem_size; + } return pivot_index; } else if (result < 0) { // element is smaller than pivot, continue search left @@ -700,6 +838,31 @@ static size_t cx_arl_insert_sorted( } } +static size_t cx_arl_insert_unique( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + // get a correctly typed pointer to the list + cx_array_list *arl = (cx_array_list *) list; + + if (cx_array_insert_unique( + &arl->data, + &list->collection.size, + &arl->capacity, + list->collection.cmpfunc, + sorted_data, + list->collection.elem_size, + n, + &arl->reallocator + )) { + // array list implementation is "all or nothing" + return 0; + } else { + return n; + } +} + static void *cx_arl_insert_element( struct cx_list_s *list, size_t index, @@ -717,7 +880,7 @@ static int cx_arl_insert_iter( const void *elem, int prepend ) { - struct cx_list_s *list = iter->src_handle.m; + struct cx_list_s *list = iter->src_handle; if (iter->index < list->collection.size) { if (cx_arl_insert_element(list, iter->index + 1 - prepend, elem) == NULL) { @@ -928,7 +1091,7 @@ static void cx_arl_reverse(struct cx_list_s *list) { 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; + const struct cx_list_s *list = iter->src_handle; return iter->index < list->collection.size; } @@ -941,23 +1104,25 @@ static void cx_arl_iter_next(void *it) { struct cx_iterator_s *iter = it; if (iter->base.remove) { iter->base.remove = false; - cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); + cx_arl_remove(iter->src_handle, iter->index, 1, NULL); + iter->elem_count--; } else { iter->index++; iter->elem_handle = ((char *) iter->elem_handle) - + ((const struct cx_list_s *) iter->src_handle.c)->collection.elem_size; + + ((const struct cx_list_s *) iter->src_handle)->collection.elem_size; } } static void cx_arl_iter_prev(void *it) { struct cx_iterator_s *iter = it; - const cx_array_list *list = iter->src_handle.c; if (iter->base.remove) { iter->base.remove = false; - cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); + cx_arl_remove(iter->src_handle, iter->index, 1, NULL); + iter->elem_count--; } iter->index--; + cx_array_list *list = iter->src_handle; if (iter->index < list->base.collection.size) { iter->elem_handle = ((char *) list->data) + iter->index * list->base.collection.elem_size; @@ -973,7 +1138,7 @@ static struct cx_iterator_s cx_arl_iterator( struct cx_iterator_s iter; iter.index = index; - iter.src_handle.c = list; + iter.src_handle = (void*)list; iter.elem_handle = cx_arl_at(list, index); iter.elem_size = list->collection.elem_size; iter.elem_count = list->collection.size; @@ -981,7 +1146,7 @@ static struct cx_iterator_s cx_arl_iterator( 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; + iter.base.allow_remove = true; return iter; } @@ -991,6 +1156,7 @@ static cx_list_class cx_array_list_class = { cx_arl_insert_element, cx_arl_insert_array, cx_arl_insert_sorted, + cx_arl_insert_unique, cx_arl_insert_iter, cx_arl_remove, cx_arl_clear, diff --git a/ucx/compare.c b/ucx/compare.c index b260aff..1fc036d 100644 --- a/ucx/compare.c +++ b/ucx/compare.c @@ -198,6 +198,20 @@ int cx_cmp_uint64(const void *i1, const void *i2) { return cx_vcmp_uint64(a, b); } +int cx_vcmp_size(size_t a, size_t b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_size(const void *i1, const void *i2) { + size_t a = *((const size_t *) i1); + size_t b = *((const size_t *) i2); + return cx_vcmp_size(a, b); +} + int cx_vcmp_float(float a, float b) { if (fabsf(a - b) < 1e-6f) { return 0; diff --git a/ucx/cx/allocator.h b/ucx/cx/allocator.h index c169a92..a69ef07 100644 --- a/ucx/cx/allocator.h +++ b/ucx/cx/allocator.h @@ -46,36 +46,22 @@ typedef struct { /** * The allocator's malloc() implementation. */ - void *(*malloc)( - void *data, - size_t n - ); + void *(*malloc)(void *data, size_t n); /** * The allocator's realloc() implementation. */ - void *(*realloc)( - void *data, - void *mem, - size_t n - ); + void *(*realloc)(void *data, void *mem, size_t n); /** * The allocator's calloc() implementation. */ - void *(*calloc)( - void *data, - size_t nmemb, - size_t size - ); + void *(*calloc)(void *data, size_t nmemb, size_t size); /** * The allocator's free() implementation. */ - void (*free)( - void *data, - void *mem - ); + void (*free)(void *data, void *mem); } cx_allocator_class; /** @@ -100,15 +86,13 @@ typedef struct cx_allocator_s CxAllocator; /** * A pre-defined allocator using standard library malloc() etc. */ -cx_attr_export -extern const CxAllocator * const cxStdlibAllocator; +CX_EXPORT extern const CxAllocator * const cxStdlibAllocator; /** * The default allocator that is used by UCX. * Initialized with cxStdlibAllocator, but you may change it. */ -cx_attr_export -extern const CxAllocator * cxDefaultAllocator; +CX_EXPORT extern const CxAllocator * cxDefaultAllocator; /** * Function pointer type for destructor functions. @@ -133,10 +117,33 @@ typedef void (*cx_destructor_func)(void *memory); * @param data an optional pointer to custom data * @param memory a pointer to the object to destruct */ -typedef void (*cx_destructor_func2)( - void *data, - void *memory -); +typedef void (*cx_destructor_func2)(void *data, void *memory); + + +/** + * Function pointer type for clone functions. + * + * A clone function is supposed to create a deep copy of the memory pointed to + * by the @p source pointer. + * If the @p target pointer is non-null, the clone function is supposed to store + * the copy into that memory region. + * Otherwise, the clone function shall use the specified @p allocator to create + * a new object. + * + * The return value of a clone function is always a pointer to the target + * memory region, or @c NULL if any allocation failed. + * A clone function SHOULD NOT fail for any other reason than an allocation + * failure. + * + * @param target the target memory or @c NULL, if memory shall be allocated + * @param source the source memory + * @param allocator the allocator that shall be used + * @param data optional additional data + * @return either the specified @p target, a pointer to the allocated memory, + * or @c NULL, if any error occurred + */ +typedef void*(cx_clone_func)(void *target, const void *source, + const CxAllocator *allocator, void *data); /** * Reallocate a previously allocated block and changes the pointer in-place, @@ -153,13 +160,8 @@ typedef void (*cx_destructor_func2)( * @retval non-zero failure * @see cx_reallocatearray() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_reallocate_( - void **mem, - size_t n -); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_reallocate_(void **mem, size_t n); /** * Reallocate a previously allocated block and changes the pointer in-place, @@ -180,14 +182,8 @@ int cx_reallocate_( * @retval non-zero failure * @see cx_reallocate() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_reallocatearray_( - void **mem, - size_t nmemb, - size_t size -); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_reallocatearray_(void **mem, size_t nmemb, size_t size); /** * Reallocate a previously allocated block and changes the pointer in-place, @@ -244,11 +240,7 @@ int cx_reallocatearray_( * @param mem a pointer to the block to free */ cx_attr_nonnull_arg(1) -cx_attr_export -void cxFree( - const CxAllocator *allocator, - void *mem -); +CX_EXPORT void cxFree(const CxAllocator *allocator, void *mem); /** * Allocate @p n bytes of memory. @@ -257,21 +249,14 @@ void cxFree( * @param n the number of bytes * @return a pointer to the allocated memory */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_malloc -cx_attr_dealloc_ucx -cx_attr_allocsize(2) -cx_attr_export -void *cxMalloc( - const CxAllocator *allocator, - size_t n -); +cx_attr_nodiscard cx_attr_nonnull +cx_attr_malloc cx_attr_dealloc_ucx cx_attr_allocsize(2) +CX_EXPORT void *cxMalloc(const CxAllocator *allocator, size_t n); /** * Reallocate 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 + * This function may return the same pointer passed to it if moving * the memory was not necessary. * * @note Re-allocating a block allocated by a different allocator is undefined. @@ -281,25 +266,18 @@ void *cxMalloc( * @param n the new size in bytes * @return a pointer to the reallocated memory */ -cx_attr_nodiscard -cx_attr_nonnull_arg(1) -cx_attr_dealloc_ucx -cx_attr_allocsize(3) -cx_attr_export -void *cxRealloc( - const CxAllocator *allocator, - void *mem, - size_t n -); +cx_attr_nodiscard cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx cx_attr_allocsize(3) +CX_EXPORT void *cxRealloc(const CxAllocator *allocator, void *mem, size_t n); /** * Reallocate 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 + * This function may return the same pointer passed to it if moving * the memory was not necessary. * * The size is calculated by multiplying @p nemb and @p size. - * If that multiplication overflows, this function returns @c NULL and @c errno + * If that multiplication overflows, this function returns @c NULL, and @c errno * will be set. * * @note Re-allocating a block allocated by a different allocator is undefined. @@ -310,17 +288,10 @@ void *cxRealloc( * @param size the size of each element * @return a pointer to the reallocated memory */ -cx_attr_nodiscard -cx_attr_nonnull_arg(1) -cx_attr_dealloc_ucx -cx_attr_allocsize(3, 4) -cx_attr_export -void *cxReallocArray( - const CxAllocator *allocator, - void *mem, - size_t nmemb, - size_t size -); +cx_attr_nodiscard cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx cx_attr_allocsize(3, 4) +CX_EXPORT void *cxReallocArray(const CxAllocator *allocator, + void *mem, size_t nmemb, size_t size); /** * Reallocate a previously allocated block and changes the pointer in-place, @@ -330,7 +301,7 @@ void *cxReallocArray( * @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. + * @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 @@ -338,14 +309,8 @@ void *cxReallocArray( * @retval zero success * @retval non-zero failure */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -int cxReallocate_( - const CxAllocator *allocator, - void **mem, - size_t n -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT int cxReallocate_(const CxAllocator *allocator, void **mem, size_t n); /** * Reallocate a previously allocated block and changes the pointer in-place, @@ -355,7 +320,7 @@ int cxReallocate_( * @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. + * @c errno will be set if the underlying realloc function does so. * * @param allocator (@c CxAllocator*) the allocator * @param mem (@c void**) pointer to the pointer to allocated block @@ -385,15 +350,9 @@ int cxReallocate_( * @retval zero success * @retval non-zero on failure */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -int cxReallocateArray_( - const CxAllocator *allocator, - void **mem, - size_t nmemb, - size_t size -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT int cxReallocateArray_(const CxAllocator *allocator, + void **mem, size_t nmemb, size_t size); /** * Reallocate a previously allocated block and changes the pointer in-place, @@ -425,17 +384,9 @@ int cxReallocateArray_( * @param size the size of each element in bytes * @return a pointer to the allocated memory */ -cx_attr_nonnull_arg(1) -cx_attr_nodiscard -cx_attr_malloc -cx_attr_dealloc_ucx -cx_attr_allocsize(2, 3) -cx_attr_export -void *cxCalloc( - const CxAllocator *allocator, - size_t nmemb, - size_t size -); +cx_attr_nonnull_arg(1) cx_attr_nodiscard +cx_attr_malloc cx_attr_dealloc_ucx cx_attr_allocsize(2, 3) +CX_EXPORT void *cxCalloc(const CxAllocator *allocator, size_t nmemb, size_t size); /** * Allocate @p n bytes of memory and sets every byte to zero. @@ -444,16 +395,9 @@ void *cxCalloc( * @param n the number of bytes * @return a pointer to the allocated memory */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_malloc -cx_attr_dealloc_ucx -cx_attr_allocsize(2) -cx_attr_export -void *cxZalloc( - const CxAllocator *allocator, - size_t n -); +cx_attr_nodiscard cx_attr_nonnull +cx_attr_malloc cx_attr_dealloc_ucx cx_attr_allocsize(2) +CX_EXPORT void *cxZalloc(const CxAllocator *allocator, size_t n); /** * Convenience macro that invokes cxMalloc() with the cxDefaultAllocator. diff --git a/ucx/cx/array_list.h b/ucx/cx/array_list.h index b38710d..97855dd 100644 --- a/ucx/cx/array_list.h +++ b/ucx/cx/array_list.h @@ -44,11 +44,10 @@ extern "C" { #endif /** - * The maximum item size in an array list that fits into stack buffer - * when swapped. + * The maximum item size in an array list that fits into + * a stack buffer when swapped. */ -cx_attr_export -extern const unsigned cx_array_swap_sbo_size; +CX_EXPORT extern const unsigned cx_array_swap_sbo_size; /** * Declares variables for an array that can be used with the convenience macros. @@ -84,7 +83,7 @@ extern const unsigned cx_array_swap_sbo_size; /** * Declares variables for an array that can be used with the convenience macros. * - * The size and capacity variables will have @c size_t type. + * The size and capacity variables will have type @c size_t. * Use #CX_ARRAY_DECLARE_SIZED() to specify a different type. * * @par Examples @@ -147,7 +146,7 @@ extern const unsigned cx_array_swap_sbo_size; * const CxAllocator *al = // ... * cx_array_initialize_a(al, myarray, 128); * // ... - * cxFree(al, myarray); // don't forget to free with same allocator + * cxFree(al, myarray); // remember to free with the same allocator * @endcode * * @param allocator (@c CxAllocator*) the allocator @@ -172,10 +171,9 @@ 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. + * This allows reallocation of static or stack memory by allocating heap memory + * and copying the array contents; namely when @c stack_ptr in this struct + * is not @c NULL and @p array equals @c stack_ptr. * * @param array the array to reallocate * @param old_capacity the old number of elements @@ -184,33 +182,18 @@ struct cx_array_reallocator_s { * @param alloc a reference to this allocator * @return a pointer to the reallocated memory or @c NULL on failure */ - cx_attr_nodiscard - cx_attr_nonnull_arg(5) - cx_attr_allocsize(3, 4) - void *(*realloc)( - void *array, - size_t old_capacity, - size_t new_capacity, - size_t elem_size, - struct cx_array_reallocator_s *alloc - ); + void *(*realloc)( void *array, size_t old_capacity, size_t new_capacity, + size_t elem_size, struct cx_array_reallocator_s *alloc); /** - * Custom data pointer. + * The allocator that shall be used for the reallocations. */ - void *ptr1; + const CxAllocator *allocator; /** - * Custom data pointer. + * Optional pointer to stack memory + * if the array is originally located on the stack. */ - void *ptr2; - /** - * Custom data integer. - */ - size_t int1; - /** - * Custom data integer. - */ - size_t int2; + const void *stack_ptr; }; /** @@ -221,32 +204,28 @@ typedef struct cx_array_reallocator_s CxArrayReallocator; /** * A default array reallocator that is based on the cxDefaultAllocator. */ -cx_attr_export -extern CxArrayReallocator *cx_array_default_reallocator; +CX_EXPORT extern CxArrayReallocator *cx_array_default_reallocator; /** * Creates a new array reallocator. * * When @p allocator is @c NULL, the cxDefaultAllocator will be used. * - * When @p stackmem is not @c NULL, the reallocator is supposed to be used - * @em only for the specific array that is initially located at @p stackmem. - * When reallocation is needed, the reallocator checks, if the array is - * still located at @p stackmem and copies the contents to the heap. + * When @p stack_ptr is not @c NULL, the reallocator is supposed to be used + * @em only for the specific array initially located at @p stack_ptr. + * When reallocation is needed, the reallocator checks if the array is + * still located at @p stack_ptr and copies the contents to the heap. * - * @note Invoking this function with both arguments @c NULL will return a + * @note Invoking this function with both arguments being @c NULL will return a * reallocator that behaves like #cx_array_default_reallocator. * * @param allocator the allocator this reallocator shall be based on - * @param stackmem the address of the array when the array is initially located - * on the stack or shall not reallocated in place + * @param stack_ptr the address of the array when the array is initially located + * on the stack or shall not reallocate in place * @return an array reallocator */ -cx_attr_export -CxArrayReallocator cx_array_reallocator( - const struct cx_allocator_s *allocator, - const void *stackmem -); +CX_EXPORT CxArrayReallocator cx_array_reallocator( + const struct cx_allocator_s *allocator, const void *stack_ptr); /** * Reserves memory for additional elements. @@ -263,7 +242,7 @@ CxArrayReallocator cx_array_reallocator( * * The @p width in bytes refers to the size and capacity. * Both must have the same width. - * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit + * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit * architecture. If set to zero, the native word width is used. * * @param array a pointer to the target array @@ -279,16 +258,9 @@ CxArrayReallocator cx_array_reallocator( * @see cx_array_reallocator() */ cx_attr_nonnull_arg(1, 2, 3) -cx_attr_export -int cx_array_reserve( - void **array, - void *size, - void *capacity, - unsigned width, - size_t elem_size, - size_t elem_count, - CxArrayReallocator *reallocator -); +CX_EXPORT int cx_array_reserve(void **array, void *size, void *capacity, + unsigned width, size_t elem_size, size_t elem_count, + CxArrayReallocator *reallocator); /** * Copies elements from one array to another. @@ -296,7 +268,7 @@ int cx_array_reserve( * The elements are copied to the @p target array at the specified @p index, * overwriting possible elements. The @p index does not need to be in range of * the current array @p size. If the new index plus the number of elements added - * would extend the array's size, the remaining @p capacity is used. + * extends the array's size, the remaining @p capacity is used. * * If the @p capacity is also insufficient to hold the new data, a reallocation * attempt is made with the specified @p reallocator. @@ -305,7 +277,7 @@ int cx_array_reserve( * * The @p width in bytes refers to the size and capacity. * Both must have the same width. - * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit + * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit * architecture. If set to zero, the native word width is used. * * @param target a pointer to the target array @@ -323,18 +295,9 @@ int cx_array_reserve( * @see cx_array_reallocator() */ cx_attr_nonnull_arg(1, 2, 3, 6) -cx_attr_export -int cx_array_copy( - void **target, - void *size, - void *capacity, - unsigned width, - size_t index, - const void *src, - size_t elem_size, - size_t elem_count, - CxArrayReallocator *reallocator -); +CX_EXPORT int cx_array_copy(void **target, void *size, void *capacity, unsigned width, + size_t index, const void *src, size_t elem_size, size_t elem_count, + CxArrayReallocator *reallocator); /** * Convenience macro that uses cx_array_copy() with a default layout and @@ -482,17 +445,9 @@ int cx_array_copy( * @retval non-zero failure */ cx_attr_nonnull_arg(1, 2, 3, 5) -cx_attr_export -int cx_array_insert_sorted( - void **target, - size_t *size, - size_t *capacity, - cx_compare_func cmp_func, - const void *src, - size_t elem_size, - size_t elem_count, - CxArrayReallocator *reallocator -); +CX_EXPORT int cx_array_insert_sorted(void **target, size_t *size, size_t *capacity, + cx_compare_func cmp_func, const void *src, size_t elem_size, size_t elem_count, + CxArrayReallocator *reallocator); /** * Inserts an element into a sorted array. @@ -500,7 +455,7 @@ int cx_array_insert_sorted( * 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 + * If the capacity is not enough to hold the new data, a reallocation * attempt is made. * * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. @@ -586,6 +541,127 @@ int cx_array_insert_sorted( #define cx_array_simple_insert_sorted(array, src, n, cmp_func) \ cx_array_simple_insert_sorted_a(NULL, array, src, n, cmp_func) + +/** + * Inserts a sorted array into another sorted array, avoiding duplicates. + * + * If either the target or the source array is not already sorted with respect + * to the specified @p cmp_func, the behavior is undefined. + * + * If the capacity is insufficient to hold the new data, a reallocation + * attempt is made. + * You can create your own reallocator by hand, use #cx_array_default_reallocator, + * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * + * @param target a pointer to the target array + * @param size a pointer to the size of the target array + * @param capacity a pointer to the capacity of the target array + * @param cmp_func the compare function for the elements + * @param src the source array + * @param elem_size the size of one element + * @param elem_count the number of elements to insert + * @param reallocator the array reallocator to use + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nonnull_arg(1, 2, 3, 5) +CX_EXPORT int cx_array_insert_unique(void **target, size_t *size, size_t *capacity, + cx_compare_func cmp_func, const void *src, size_t elem_size, size_t elem_count, + CxArrayReallocator *reallocator); + +/** + * Inserts an element into a sorted array if it does not exist. + * + * If the target array is not already sorted with respect + * to the specified @p cmp_func, the behavior is undefined. + * + * If the capacity is insufficient to hold the new data, a reallocation + * attempt is made. + * + * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. + * It is important, however, that @p size and @p capacity are pointers to + * variables of the same type. + * + * @param target (@c void**) a pointer to the target array + * @param size (@c SIZE_TYPE*) a pointer to the size of the target array + * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array + * @param elem_size (@c size_t) the size of one element + * @param elem (@c void*) a pointer to the element to add + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @retval zero success (also when the element was already present) + * @retval non-zero failure + */ +#define cx_array_add_unique(target, size, capacity, elem_size, elem, cmp_func, reallocator) \ + cx_array_insert_unique((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator) + +/** + * Convenience macro for cx_array_add_unique() with a default + * layout and the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_unique() + */ +#define cx_array_simple_add_unique_a(reallocator, array, elem, cmp_func) \ + cx_array_add_unique(&array, &(array##_size), &(array##_capacity), \ + sizeof((array)[0]), &(elem), cmp_func, reallocator) + +/** + * Convenience macro for cx_array_add_unique() with a default + * layout and the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_unique_a() + */ +#define cx_array_simple_add_unique(array, elem, cmp_func) \ + cx_array_simple_add_unique_a(NULL, array, elem, cmp_func) + +/** + * Convenience macro for cx_array_insert_unique() with a default + * layout and the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param src (@c void*) pointer to the source array + * @param n (@c size_t) number of elements in the source array + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_insert_unique() + */ +#define cx_array_simple_insert_unique_a(reallocator, array, src, n, cmp_func) \ + cx_array_insert_unique((void**)(&array), &(array##_size), &(array##_capacity), \ + cmp_func, src, sizeof((array)[0]), n, reallocator) + +/** + * Convenience macro for cx_array_insert_unique() with a default + * layout and the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param src (@c void*) pointer to the source array + * @param n (@c size_t) number of elements in the source array + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_insert_unique_a() + */ +#define cx_array_simple_insert_unique(array, src, n, cmp_func) \ + cx_array_simple_insert_unique_a(NULL, array, src, n, cmp_func) + /** * Searches the largest lower bound in a sorted array. * @@ -609,14 +685,8 @@ int cx_array_insert_sorted( * @see cx_array_binary_search() */ cx_attr_nonnull -cx_attr_export -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 -); +CX_EXPORT 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. @@ -635,14 +705,8 @@ size_t cx_array_binary_search_inf( * @see cx_array_binary_search_sup() */ cx_attr_nonnull -cx_attr_export -size_t cx_array_binary_search( - const void *arr, - size_t size, - size_t elem_size, - const void *elem, - cx_compare_func cmp_func -); +CX_EXPORT size_t cx_array_binary_search(const void *arr, size_t size, + size_t elem_size, const void *elem, cx_compare_func cmp_func); /** * Searches the smallest upper bound in a sorted array. @@ -667,37 +731,25 @@ size_t cx_array_binary_search( * @see cx_array_binary_search() */ cx_attr_nonnull -cx_attr_export -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 -); +CX_EXPORT size_t cx_array_binary_search_sup(const void *arr, size_t size, + size_t elem_size, const void *elem, cx_compare_func cmp_func); /** * Swaps two array elements. * * @param arr the array * @param elem_size the element size - * @param idx1 index of first element - * @param idx2 index of second element + * @param idx1 index of the first element + * @param idx2 index of the second element */ cx_attr_nonnull -cx_attr_export -void cx_array_swap( - void *arr, - size_t elem_size, - size_t idx1, - size_t idx2 -); +CX_EXPORT 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 stores pointers instead of - * copies of the added elements and the compare function will be automatically set + * copies of the added elements, 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 @@ -712,13 +764,8 @@ void cx_array_swap( cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1) -cx_attr_export -CxList *cxArrayListCreate( - const CxAllocator *allocator, - cx_compare_func comparator, - size_t elem_size, - size_t initial_capacity -); +CX_EXPORT 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. diff --git a/ucx/cx/buffer.h b/ucx/cx/buffer.h index 867fb30..6324a0b 100644 --- a/ucx/cx/buffer.h +++ b/ucx/cx/buffer.h @@ -62,7 +62,7 @@ extern "C" { * If this flag is enabled, the buffer will automatically free its contents when destroyed. * * Do NOT set this flag together with #CX_BUFFER_COPY_ON_WRITE. It will be automatically - * set when the copy-on-write operations is performed. + * set when the copy-on-write operation is performed. */ #define CX_BUFFER_FREE_CONTENTS 0x01 @@ -74,7 +74,7 @@ extern "C" { /** * If this flag is enabled, the buffer will allocate new memory when written to. * - * The current contents of the buffer will be copied to the new memory and the flag + * The current contents of the buffer will be copied to the new memory, and the flag * will be cleared while the #CX_BUFFER_FREE_CONTENTS flag will be set automatically. */ #define CX_BUFFER_COPY_ON_WRITE 0x04 @@ -127,7 +127,7 @@ struct cx_buffer_flush_config_s { size_t blkmax; /** - * The target for write function. + * The target for the write function. */ void *target; @@ -202,7 +202,7 @@ typedef struct cx_buffer_s CxBuffer; * you will need to cast the pointer, and you should set the * #CX_BUFFER_COPY_ON_WRITE flag. * - * You need to set the size manually after initialization, if + * You need to set the size manually after initialization if * you provide @p space which already contains data. * * When you specify stack memory as @p space and decide to use @@ -210,7 +210,7 @@ typedef struct cx_buffer_s CxBuffer; * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the * #CX_BUFFER_AUTO_EXTEND flag. * - * @note You may provide @c NULL as argument for @p space. + * @note You may provide @c NULL as the argument for @p space. * Then this function will allocate the space and enforce * the #CX_BUFFER_FREE_CONTENTS flag. In that case, specifying * copy-on-write should be avoided, because the allocated @@ -227,14 +227,8 @@ typedef struct cx_buffer_s CxBuffer; * @return zero on success, non-zero if a required allocation failed */ cx_attr_nonnull_arg(1) -cx_attr_export -int cxBufferInit( - CxBuffer *buffer, - void *space, - size_t capacity, - const CxAllocator *allocator, - int flags -); +CX_EXPORT int cxBufferInit(CxBuffer *buffer, void *space, size_t capacity, + const CxAllocator *allocator, int flags); /** * Configures the buffer for flushing. @@ -251,11 +245,7 @@ int cxBufferInit( * @see cxBufferWrite() */ cx_attr_nonnull -cx_attr_export -int cxBufferEnableFlushing( - CxBuffer *buffer, - CxBufferFlushConfig config -); +CX_EXPORT int cxBufferEnableFlushing(CxBuffer *buffer, CxBufferFlushConfig config); /** * Destroys the buffer contents. @@ -267,8 +257,7 @@ int cxBufferEnableFlushing( * @see cxBufferInit() */ cx_attr_nonnull -cx_attr_export -void cxBufferDestroy(CxBuffer *buffer); +CX_EXPORT void cxBufferDestroy(CxBuffer *buffer); /** * Deallocates the buffer. @@ -276,14 +265,10 @@ void cxBufferDestroy(CxBuffer *buffer); * If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys * the contents. If you @em only want to destroy the contents, use cxBufferDestroy(). * - * @remark As with all free() functions, this accepts @c NULL arguments in which - * case it does nothing. - * * @param buffer the buffer to deallocate * @see cxBufferCreate() */ -cx_attr_export -void cxBufferFree(CxBuffer *buffer); +CX_EXPORT void cxBufferFree(CxBuffer *buffer); /** * Allocates and initializes a fresh buffer. @@ -296,7 +281,7 @@ void cxBufferFree(CxBuffer *buffer); * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the * #CX_BUFFER_AUTO_EXTEND flag. * - * @note You may provide @c NULL as argument for @p space. + * @note You may provide @c NULL as the argument for @p space. * Then this function will allocate the space and enforce * the #CX_BUFFER_FREE_CONTENTS flag. * @@ -309,16 +294,9 @@ void cxBufferFree(CxBuffer *buffer); * @param flags buffer features (see cx_buffer_s.flags) * @return a pointer to the buffer on success, @c NULL if a required allocation failed */ -cx_attr_malloc -cx_attr_dealloc(cxBufferFree, 1) -cx_attr_nodiscard -cx_attr_export -CxBuffer *cxBufferCreate( - void *space, - size_t capacity, - const CxAllocator *allocator, - int flags -); +cx_attr_malloc cx_attr_dealloc(cxBufferFree, 1) cx_attr_nodiscard +CX_EXPORT CxBuffer *cxBufferCreate(void *space, size_t capacity, + const CxAllocator *allocator, int flags); /** * Shifts the contents of the buffer by the given offset. @@ -327,8 +305,8 @@ CxBuffer *cxBufferCreate( * 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. + * When the 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. @@ -336,15 +314,15 @@ CxBuffer *cxBufferCreate( * 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 + * The buffer position gets shifted alongside 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. + * cxBufferShiftRight() functions using a @c size_t as the 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 + * But you can do that manually 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. @@ -357,11 +335,7 @@ CxBuffer *cxBufferCreate( * @see cxBufferShiftRight() */ cx_attr_nonnull -cx_attr_export -int cxBufferShift( - CxBuffer *buffer, - off_t shift -); +CX_EXPORT int cxBufferShift(CxBuffer *buffer, off_t shift); /** * Shifts the buffer to the right. @@ -374,11 +348,7 @@ int cxBufferShift( * @see cxBufferShift() */ cx_attr_nonnull -cx_attr_export -int cxBufferShiftRight( - CxBuffer *buffer, - size_t shift -); +CX_EXPORT int cxBufferShiftRight(CxBuffer *buffer, size_t shift); /** * Shifts the buffer to the left. @@ -391,11 +361,7 @@ int cxBufferShiftRight( * @see cxBufferShift() */ cx_attr_nonnull -cx_attr_export -int cxBufferShiftLeft( - CxBuffer *buffer, - size_t shift -); +CX_EXPORT int cxBufferShiftLeft(CxBuffer *buffer, size_t shift); /** @@ -419,12 +385,7 @@ int cxBufferShiftLeft( * */ cx_attr_nonnull -cx_attr_export -int cxBufferSeek( - CxBuffer *buffer, - off_t offset, - int whence -); +CX_EXPORT int cxBufferSeek(CxBuffer *buffer, off_t offset, int whence); /** * Clears the buffer by resetting the position and deleting the data. @@ -439,8 +400,7 @@ int cxBufferSeek( * @see cxBufferReset() */ cx_attr_nonnull -cx_attr_export -void cxBufferClear(CxBuffer *buffer); +CX_EXPORT void cxBufferClear(CxBuffer *buffer); /** * Resets the buffer by resetting the position and size to zero. @@ -452,8 +412,7 @@ void cxBufferClear(CxBuffer *buffer); * @see cxBufferClear() */ cx_attr_nonnull -cx_attr_export -void cxBufferReset(CxBuffer *buffer); +CX_EXPORT void cxBufferReset(CxBuffer *buffer); /** * Tests, if the buffer position has exceeded the buffer size. @@ -463,10 +422,8 @@ void cxBufferReset(CxBuffer *buffer); * byte of the buffer's contents * @retval false otherwise */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -bool cxBufferEof(const CxBuffer *buffer); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT bool cxBufferEof(const CxBuffer *buffer); /** @@ -484,11 +441,7 @@ bool cxBufferEof(const CxBuffer *buffer); * @see cxBufferShrink() */ cx_attr_nonnull -cx_attr_export -int cxBufferMinimumCapacity( - CxBuffer *buffer, - size_t capacity -); +CX_EXPORT int cxBufferMinimumCapacity(CxBuffer *buffer, size_t capacity); /** * Shrinks the capacity of the buffer to fit its current size. @@ -507,17 +460,13 @@ int cxBufferMinimumCapacity( * @see cxBufferMinimumCapacity() */ cx_attr_nonnull -cx_attr_export -void cxBufferShrink( - CxBuffer *buffer, - size_t reserve -); +CX_EXPORT void cxBufferShrink(CxBuffer *buffer, size_t reserve); /** * Writes data to a CxBuffer. * * If automatic flushing is not enabled, the data is simply written into the - * buffer at the current position and the position of the buffer is increased + * buffer at the current position, and the position of the buffer is increased * by the number of bytes written. * * If flushing is enabled and the buffer needs to flush, the data is flushed to @@ -526,7 +475,7 @@ void cxBufferShrink( * 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 + * This function only stops writing more elements when the flush target and this * buffer are both incapable of taking more data or all data has been written. * * If, after flushing, the number of items that shall be written still exceeds @@ -534,14 +483,14 @@ void cxBufferShrink( * to the flush target, if possible. * * The number returned by this function is the number of elements from - * @c ptr that could be written to either the flush target or the buffer - * (so it does not include the number of items that had been already in the buffer - * in were flushed during the process). + * @c ptr that could be written to either the flush target or the buffer. + * That means it does @em not include the number of items that were already in + * the buffer and were also flushed during the process. * * @attention * When @p size is larger than one and the contents of the buffer are not aligned * with @p size, flushing stops after all complete items have been flushed, leaving - * the mis-aligned part in the buffer. + * the misaligned part in the buffer. * Afterward, this function only writes as many items as possible to the buffer. * * @note The signature is compatible with the fwrite() family of functions. @@ -555,13 +504,8 @@ void cxBufferShrink( * @see cxBufferRead() */ cx_attr_nonnull -cx_attr_export -size_t cxBufferWrite( - const void *ptr, - size_t size, - size_t nitems, - CxBuffer *buffer -); +CX_EXPORT size_t cxBufferWrite(const void *ptr, size_t size, + size_t nitems, CxBuffer *buffer); /** * Appends data to a CxBuffer. @@ -583,13 +527,8 @@ size_t cxBufferWrite( * @see cxBufferRead() */ cx_attr_nonnull -cx_attr_export -size_t cxBufferAppend( - const void *ptr, - size_t size, - size_t nitems, - CxBuffer *buffer -); +CX_EXPORT size_t cxBufferAppend(const void *ptr, size_t size, + size_t nitems, CxBuffer *buffer); /** * Performs a single flush-run on the specified buffer. @@ -614,19 +553,19 @@ size_t cxBufferAppend( * at position 200. The flush configuration is * @c blkmax=4 and @c blksize=64 . * Assume that the entire flush operation is successful. - * All 200 bytes on the left hand-side from the current + * All 200 bytes on the left-hand-side from the current * position are written. - * That means, the size of the buffer is now 140 and the + * That means the size of the buffer is now 140 and the * position is zero. * * @par Example 2 * Same as Example 1, but now the @c blkmax is 1. - * The size of the buffer is now 276 and the position is 136. + * The size of the buffer is now 276, and the position is 136. * * @par Example 3 * Same as Example 1, but now assume the flush target * only accepts 100 bytes before returning zero. - * That means, the flush operations manages to flush + * That means the flush operation manages to flush * one complete block and one partial block, ending * up with a buffer with size 240 and position 100. * @@ -636,8 +575,8 @@ size_t cxBufferAppend( * @remark When the buffer uses copy-on-write, the memory * is copied first, before attempting any flush. * This is, however, considered an erroneous use of the - * buffer, because it does not make much sense to put - * readonly data into an UCX buffer for flushing, instead + * buffer because it makes little sense to put + * readonly data into an UCX buffer for flushing instead * of writing it directly to the target. * * @param buffer the buffer @@ -645,8 +584,7 @@ size_t cxBufferAppend( * @see cxBufferEnableFlushing() */ cx_attr_nonnull -cx_attr_export -size_t cxBufferFlush(CxBuffer *buffer); +CX_EXPORT size_t cxBufferFlush(CxBuffer *buffer); /** * Reads data from a CxBuffer. @@ -664,13 +602,8 @@ size_t cxBufferFlush(CxBuffer *buffer); * @see cxBufferAppend() */ cx_attr_nonnull -cx_attr_export -size_t cxBufferRead( - void *ptr, - size_t size, - size_t nitems, - CxBuffer *buffer -); +CX_EXPORT size_t cxBufferRead(void *ptr, size_t size, + size_t nitems, CxBuffer *buffer); /** * Writes a character to a buffer. @@ -678,9 +611,9 @@ size_t cxBufferRead( * 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. + * is disabled or the buffer extension fails, @c EOF is returned. * - * On successful write, the position of the buffer is increased. + * On successful writing, the position of the buffer is increased. * * If you just want to write a null-terminator at the current position, you * should use cxBufferTerminate() instead. @@ -688,15 +621,11 @@ size_t cxBufferRead( * @param buffer the buffer to write to * @param c the character to write * @return the byte that has been written or @c EOF when the end of the stream is - * reached and automatic extension is not enabled or not possible + * reached, and automatic extension is not enabled or not possible * @see cxBufferTerminate() */ cx_attr_nonnull -cx_attr_export -int cxBufferPut( - CxBuffer *buffer, - int c -); +CX_EXPORT int cxBufferPut(CxBuffer *buffer, int c); /** * Writes a terminating zero to a buffer at the current position. @@ -710,8 +639,7 @@ int cxBufferPut( * @return zero, if the terminator could be written, non-zero otherwise */ cx_attr_nonnull -cx_attr_export -int cxBufferTerminate(CxBuffer *buffer); +CX_EXPORT int cxBufferTerminate(CxBuffer *buffer); /** * Writes a string to a buffer. @@ -722,13 +650,8 @@ int cxBufferTerminate(CxBuffer *buffer); * @param str the zero-terminated string * @return the number of bytes written */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -cx_attr_export -size_t cxBufferPutString( - CxBuffer *buffer, - const char *str -); +cx_attr_nonnull cx_attr_cstr_arg(2) +CX_EXPORT size_t cxBufferPutString(CxBuffer *buffer, const char *str); /** * Gets a character from a buffer. @@ -739,8 +662,7 @@ size_t cxBufferPutString( * @return the character or @c EOF, if the end of the buffer is reached */ cx_attr_nonnull -cx_attr_export -int cxBufferGet(CxBuffer *buffer); +CX_EXPORT int cxBufferGet(CxBuffer *buffer); #ifdef __cplusplus } diff --git a/ucx/cx/collection.h b/ucx/cx/collection.h index ba329dc..8e900d3 100644 --- a/ucx/cx/collection.h +++ b/ucx/cx/collection.h @@ -142,14 +142,16 @@ struct cx_collection_s { /** * Indicates whether the collection can guarantee that the stored elements are currently sorted. * - * This may return false even when the elements are sorted. - * It is totally up to the implementation of the collection whether it keeps track of the order of its elements. + * This may return @c false even when the elements are sorted. + * It is totally up to the implementation of the collection when to check if the elements are sorted. + * It is usually a good practice to establish this property as an invariant that does not need + * to be re-checked on certain operations. * * @param c a pointer to a struct that contains #CX_COLLECTION_BASE * @retval true if the elements are currently sorted wrt. the collection's compare function * @retval false if the order of elements is unknown */ -#define cxCollectionSorted(c) ((c)->collection.sorted) +#define cxCollectionSorted(c) ((c)->collection.sorted || (c)->collection.size == 0) /** * Sets a simple destructor function for this collection. diff --git a/ucx/cx/common.h b/ucx/cx/common.h index 0fc9e47..45a61d5 100644 --- a/ucx/cx/common.h +++ b/ucx/cx/common.h @@ -49,7 +49,7 @@ * https://uap-core.de/hg/ucx *

* - *

LICENCE

+ *

LICENSE

* * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. * @@ -130,6 +130,11 @@ #define __attribute__(x) #endif +/** + * Inform the compiler that falling through a switch case is intentional. + */ +#define cx_attr_fallthrough __attribute__((__fallthrough__)) + /** * All pointer arguments must be non-NULL. */ @@ -184,7 +189,7 @@ */ #define cx_attr_cstr_arg(idx) /** - * No support for access attribute in clang. + * No support for the access attribute in clang. */ #define cx_attr_access(mode, ...) #else @@ -260,15 +265,32 @@ #define _Thread_local __declspec(thread) #endif // _MSC_VER +// --------------------------------------------------------------------------- +// Exported and inlined functions +// --------------------------------------------------------------------------- + #if defined(CX_WINDLL_EXPORT) -#define cx_attr_export __declspec(dllexport) +#define CX_EXPORT __declspec(dllexport) #elif defined(CX_WINDLL) -#define cx_attr_export __declspec(dllimport) +#define CX_EXPORT __declspec(dllimport) #else /** Only used for building Windows DLLs. */ -#define cx_attr_export +#define CX_EXPORT #endif // CX_WINDLL / CX_WINDLL_EXPORT +#ifdef __GNUC__ +/** + * Declares a function to be inlined. + */ +#define CX_INLINE __attribute__((always_inline)) static inline +#else +#define CX_INLINE static inline +#endif +/** + * Declares a compatibility function for C++ builds. + */ +#define CX_CPPDECL static inline + // --------------------------------------------------------------------------- // Useful function pointers // --------------------------------------------------------------------------- @@ -276,22 +298,12 @@ /** * Function pointer compatible with fwrite-like functions. */ -typedef size_t (*cx_write_func)( - const void *, - size_t, - size_t, - void * -); +typedef size_t (*cx_write_func)(const void*, size_t, size_t, void*); /** * Function pointer compatible with fread-like functions. */ -typedef size_t (*cx_read_func)( - void *, - size_t, - size_t, - void * -); +typedef size_t (*cx_read_func)(void*, size_t, size_t, void*); // --------------------------------------------------------------------------- // Utility macros @@ -343,9 +355,7 @@ typedef size_t (*cx_read_func)( #if __cplusplus extern "C" #endif -cx_attr_export int cx_szmul_impl(size_t a, size_t b, size_t *result); +CX_EXPORT int cx_szmul_impl(size_t a, size_t b, size_t *result); #endif // cx_szmul - - #endif // UCX_COMMON_H diff --git a/ucx/cx/compare.h b/ucx/cx/compare.h index cb4ddf1..1b66e62 100644 --- a/ucx/cx/compare.h +++ b/ucx/cx/compare.h @@ -47,20 +47,14 @@ extern "C" { * * All functions from compare.h with the cx_cmp prefix are * compatible with this signature and can be used as - * compare function for collections, or other implementations + * compare function for collections or other implementations * that need to be type-agnostic. * * For simple comparisons the cx_vcmp family of functions * can be used, but they are NOT compatible with this function * pointer. */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -typedef int (*cx_compare_func)( - const void *left, - const void *right -); +typedef int (*cx_compare_func)(const void *left, const void *right); /** * Compares two integers of type int. @@ -74,10 +68,8 @@ typedef int (*cx_compare_func)( * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_int(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_int(const void *i1, const void *i2); /** * Compares two integers of type int. @@ -89,8 +81,7 @@ int cx_cmp_int(const void *i1, const void *i2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_int(int i1, int i2); +CX_EXPORT int cx_vcmp_int(int i1, int i2); /** * Compares two integers of type long int. @@ -104,10 +95,8 @@ int cx_vcmp_int(int i1, int i2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_longint(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_longint(const void *i1, const void *i2); /** * Compares two integers of type long int. @@ -119,8 +108,7 @@ int cx_cmp_longint(const void *i1, const void *i2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_longint(long int i1, long int i2); +CX_EXPORT int cx_vcmp_longint(long int i1, long int i2); /** * Compares two integers of type long long. @@ -134,10 +122,8 @@ int cx_vcmp_longint(long int i1, long int i2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_longlong(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_longlong(const void *i1, const void *i2); /** * Compares two integers of type long long. @@ -149,8 +135,7 @@ int cx_cmp_longlong(const void *i1, const void *i2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_longlong(long long int i1, long long int i2); +CX_EXPORT int cx_vcmp_longlong(long long int i1, long long int i2); /** * Compares two integers of type int16_t. @@ -164,10 +149,8 @@ int cx_vcmp_longlong(long long int i1, long long int i2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_int16(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_int16(const void *i1, const void *i2); /** * Compares two integers of type int16_t. @@ -179,8 +162,7 @@ int cx_cmp_int16(const void *i1, const void *i2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_int16(int16_t i1, int16_t i2); +CX_EXPORT int cx_vcmp_int16(int16_t i1, int16_t i2); /** * Compares two integers of type int32_t. @@ -194,10 +176,8 @@ int cx_vcmp_int16(int16_t i1, int16_t i2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_int32(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_int32(const void *i1, const void *i2); /** * Compares two integers of type int32_t. @@ -209,8 +189,7 @@ int cx_cmp_int32(const void *i1, const void *i2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_int32(int32_t i1, int32_t i2); +CX_EXPORT int cx_vcmp_int32(int32_t i1, int32_t i2); /** * Compares two integers of type int64_t. @@ -224,10 +203,8 @@ int cx_vcmp_int32(int32_t i1, int32_t i2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_int64(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_int64(const void *i1, const void *i2); /** * Compares two integers of type int64_t. @@ -239,8 +216,7 @@ int cx_cmp_int64(const void *i1, const void *i2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_int64(int64_t i1, int64_t i2); +CX_EXPORT int cx_vcmp_int64(int64_t i1, int64_t i2); /** * Compares two integers of type unsigned int. @@ -254,10 +230,8 @@ int cx_vcmp_int64(int64_t i1, int64_t i2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_uint(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_uint(const void *i1, const void *i2); /** * Compares two integers of type unsigned int. @@ -269,8 +243,7 @@ int cx_cmp_uint(const void *i1, const void *i2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_uint(unsigned int i1, unsigned int i2); +CX_EXPORT int cx_vcmp_uint(unsigned int i1, unsigned int i2); /** * Compares two integers of type unsigned long int. @@ -284,10 +257,8 @@ int cx_vcmp_uint(unsigned int i1, unsigned int i2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_ulongint(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_ulongint(const void *i1, const void *i2); /** * Compares two integers of type unsigned long int. @@ -299,8 +270,7 @@ int cx_cmp_ulongint(const void *i1, const void *i2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2); +CX_EXPORT int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2); /** * Compares two integers of type unsigned long long. @@ -314,10 +284,8 @@ int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_ulonglong(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_ulonglong(const void *i1, const void *i2); /** * Compares two integers of type unsigned long long. @@ -329,8 +297,7 @@ int cx_cmp_ulonglong(const void *i1, const void *i2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2); +CX_EXPORT int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2); /** * Compares two integers of type uint16_t. @@ -344,10 +311,8 @@ int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_uint16(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_uint16(const void *i1, const void *i2); /** * Compares two integers of type uint16_t. @@ -359,8 +324,7 @@ int cx_cmp_uint16(const void *i1, const void *i2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_uint16(uint16_t i1, uint16_t i2); +CX_EXPORT int cx_vcmp_uint16(uint16_t i1, uint16_t i2); /** * Compares two integers of type uint32_t. @@ -374,10 +338,8 @@ int cx_vcmp_uint16(uint16_t i1, uint16_t i2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_uint32(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_uint32(const void *i1, const void *i2); /** * Compares two integers of type uint32_t. @@ -389,8 +351,7 @@ int cx_cmp_uint32(const void *i1, const void *i2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_uint32(uint32_t i1, uint32_t i2); +CX_EXPORT int cx_vcmp_uint32(uint32_t i1, uint32_t i2); /** * Compares two integers of type uint64_t. @@ -404,10 +365,8 @@ int cx_vcmp_uint32(uint32_t i1, uint32_t i2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_uint64(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_uint64(const void *i1, const void *i2); /** * Compares two integers of type uint64_t. @@ -419,8 +378,34 @@ int cx_cmp_uint64(const void *i1, const void *i2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_uint64(uint64_t i1, uint64_t i2); +CX_EXPORT int cx_vcmp_uint64(uint64_t i1, uint64_t i2); + +/** + * Compares two integers of type size_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to size_t one + * @param i2 pointer to size_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_size(const void *i1, const void *i2); + +/** + * Compares two integers of type size_t. + * + * @param i1 size_t one + * @param i2 size_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +CX_EXPORT int cx_vcmp_size(size_t i1, size_t i2); /** * Compares two real numbers of type float with precision 1e-6f. @@ -434,10 +419,8 @@ int cx_vcmp_uint64(uint64_t i1, uint64_t i2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_float(const void *f1, const void *f2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_float(const void *f1, const void *f2); /** * Compares two real numbers of type float with precision 1e-6f. @@ -449,8 +432,7 @@ int cx_cmp_float(const void *f1, const void *f2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_float(float f1, float f2); +CX_EXPORT int cx_vcmp_float(float f1, float f2); /** * Compares two real numbers of type double with precision 1e-14. @@ -464,10 +446,8 @@ int cx_vcmp_float(float f1, float f2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_double(const void *d1, const void *d2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_double(const void *d1, const void *d2); /** * Compares two real numbers of type double with precision 1e-14. @@ -479,8 +459,7 @@ int cx_cmp_double(const void *d1, const void *d2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_double(double d1, double d2); +CX_EXPORT int cx_vcmp_double(double d1, double d2); /** * Compares the integer representation of two pointers. @@ -494,10 +473,8 @@ int cx_vcmp_double(double d1, double d2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_intptr(const void *ptr1, const void *ptr2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_intptr(const void *ptr1, const void *ptr2); /** * Compares the integer representation of two pointers. @@ -509,8 +486,7 @@ int cx_cmp_intptr(const void *ptr1, const void *ptr2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2); +CX_EXPORT int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2); /** * Compares the unsigned integer representation of two pointers. @@ -524,10 +500,8 @@ int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_uintptr(const void *ptr1, const void *ptr2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_uintptr(const void *ptr1, const void *ptr2); /** * Compares the unsigned integer representation of two pointers. @@ -539,8 +513,7 @@ int cx_cmp_uintptr(const void *ptr1, const void *ptr2); * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2); +CX_EXPORT int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2); /** * Compares the pointers specified in the arguments without dereferencing. @@ -551,10 +524,8 @@ int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2); * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_ptr(const void *ptr1, const void *ptr2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_ptr(const void *ptr1, const void *ptr2); #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/hash_key.h b/ucx/cx/hash_key.h index cdf8864..c501e9c 100644 --- a/ucx/cx/hash_key.h +++ b/ucx/cx/hash_key.h @@ -46,14 +46,17 @@ extern "C" { /** Internal structure for a key within a hash map. */ struct cx_hash_key_s { - /** The key data. */ + /** + * The key data. + * May be NULL when the hash is collision-free. + */ const void *data; /** * The key data length. */ size_t len; /** The hash value of the key data. */ - unsigned hash; + uint64_t hash; }; /** @@ -76,8 +79,45 @@ typedef struct cx_hash_key_s CxHashKey; * @see cx_hash_key() */ cx_attr_nonnull -cx_attr_export -void cx_hash_murmur(CxHashKey *key); +CX_EXPORT void cx_hash_murmur(CxHashKey *key); + +/** + * Mixes up a 32-bit integer to be used as a hash. + * + * This function produces no collisions and has a good statistical distribution. + * + * @param x the integer + * @return the hash + */ +CX_EXPORT uint32_t cx_hash_u32(uint32_t x); + +/** + * Mixes up a 64-bit integer to be used as a hash. + * + * This function produces no collisions and has a good statistical distribution. + * + * @param x the integer + * @return the hash + */ +CX_EXPORT uint64_t cx_hash_u64(uint64_t x); + +/** + * Computes a hash key from a 32-bit integer. + * + * @param x the integer + * @return the hash key + */ +cx_attr_nodiscard +CX_EXPORT CxHashKey cx_hash_key_u32(uint32_t x); + +/** + * Computes a hash key from a 64-bit integer. + * + * @param x the integer + * @return the hash key + */ +cx_attr_nodiscard +CX_EXPORT CxHashKey cx_hash_key_u64(uint64_t x); /** * Computes a hash key from a string. @@ -87,10 +127,22 @@ void cx_hash_murmur(CxHashKey *key); * @param str the string * @return the hash key */ -cx_attr_nodiscard -cx_attr_cstr_arg(1) -cx_attr_export -CxHashKey cx_hash_key_str(const char *str); +cx_attr_nodiscard cx_attr_cstr_arg(1) +CX_EXPORT CxHashKey cx_hash_key_str(const char *str); + +/** + * Computes a hash key from a string. + * + * Use this function when the string is represented + * as an unsigned char array. + * + * The string needs to be zero-terminated. + * + * @param str the string + * @return the hash key + */ +cx_attr_nodiscard cx_attr_cstr_arg(1) +CX_EXPORT CxHashKey cx_hash_key_ustr(const unsigned char *str); /** * Computes a hash key from a byte array. @@ -99,13 +151,8 @@ CxHashKey cx_hash_key_str(const char *str); * @param len the length * @return the hash key */ -cx_attr_nodiscard -cx_attr_access_r(1, 2) -cx_attr_export -CxHashKey cx_hash_key_bytes( - const unsigned char *bytes, - size_t len -); +cx_attr_nodiscard cx_attr_access_r(1, 2) +CX_EXPORT CxHashKey cx_hash_key_bytes(const unsigned char *bytes, size_t len); /** * Computes a hash key for an arbitrary object. @@ -115,16 +162,12 @@ CxHashKey cx_hash_key_bytes( * used for data exchange with different machines. * * @param obj a pointer to an arbitrary object - * @param len the length of object in memory + * @param len the length of the object in memory * @return the hash key */ cx_attr_nodiscard cx_attr_access_r(1, 2) -cx_attr_export -CxHashKey cx_hash_key( - const void *obj, - size_t len -); +CX_EXPORT CxHashKey cx_hash_key(const void *obj, size_t len); /** * Computes a hash key from a UCX string. @@ -133,20 +176,98 @@ CxHashKey cx_hash_key( * @return the hash key */ cx_attr_nodiscard -static inline CxHashKey cx_hash_key_cxstr(cxstring str) { - return cx_hash_key(str.ptr, str.length); -} +CX_EXPORT CxHashKey cx_hash_key_cxstr(cxstring str); /** * Computes a hash key from a UCX string. * - * @param str (@c cxstring or @c cxmutstr) the string - * @return (@c CxHashKey) the hash key + * @param str the string + * @return the hash key + */ +cx_attr_nodiscard +CX_EXPORT CxHashKey cx_hash_key_mutstr(cxmutstr str); + +/** + * The identity function for the CX_HASH_KEY() macro. + * You should never need to use this manually. + * + * @param key the key + * @return a copy of the key + */ +cx_attr_nodiscard +CX_INLINE CxHashKey cx_hash_key_identity(CxHashKey key) { + return key; +} + +#ifndef __cplusplus +/** + * Creates a hash key from any of the supported types with implicit length. + * + * Does nothing when passing a CxHashkey. + * + * Supported types are UCX strings, zero-terminated C strings, + * and 32-bit or 64-bit unsigned integers. + * + * @param key the key data + * @returns the @c CxHashKey + */ +#define CX_HASH_KEY(key) _Generic((key), \ + CxHashKey: cx_hash_key_identity, \ + cxstring: cx_hash_key_cxstr, \ + cxmutstr: cx_hash_key_mutstr, \ + char*: cx_hash_key_str, \ + const char*: cx_hash_key_str, \ + unsigned char*: cx_hash_key_ustr, \ + const unsigned char*: cx_hash_key_ustr, \ + uint32_t: cx_hash_key_u32, \ + uint64_t: cx_hash_key_u64) \ + (key) +#endif // __cplusplus + +/** + * Compare function for hash keys. + * + * @param left the first key + * @param right the second key + * @return zero when the keys equal, non-zero when they differ */ -#define cx_hash_key_cxstr(str) cx_hash_key_cxstr(cx_strcast(str)) +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT int cx_hash_key_cmp(const CxHashKey *left, const CxHashKey *right); #ifdef __cplusplus } // extern "C" + +// ---------------------------------------------------------- +// Overloads of CX_HASH_KEY (the C++ version of a _Generic) +// ---------------------------------------------------------- + +CX_CPPDECL CxHashKey CX_HASH_KEY(CxHashKey key) { + return key; +} + +CX_CPPDECL CxHashKey CX_HASH_KEY(cxstring str) { + return cx_hash_key_cxstr(str); +} + +CX_CPPDECL CxHashKey CX_HASH_KEY(cxmutstr str) { + return cx_hash_key_mutstr(str); +} + +CX_CPPDECL CxHashKey CX_HASH_KEY(const char *str) { + return cx_hash_key_str(str); +} + +CX_CPPDECL CxHashKey CX_HASH_KEY(const unsigned char *str) { + return cx_hash_key_ustr(str); +} + +CX_CPPDECL CxHashKey CX_HASH_KEY(uint32_t key) { + return cx_hash_key_u32(key); +} + +CX_CPPDECL CxHashKey CX_HASH_KEY(uint64_t key) { + return cx_hash_key_u64(key); +} #endif #endif // UCX_HASH_KEY_H diff --git a/ucx/cx/hash_map.h b/ucx/cx/hash_map.h index 6b11e2c..3c9a303 100644 --- a/ucx/cx/hash_map.h +++ b/ucx/cx/hash_map.h @@ -73,7 +73,8 @@ struct cx_hash_map_s { * copies of the added elements. * * @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. + * The index value of an iterator is incremented when the iterator advanced without + * removing an entry. * In other words, when the iterator is finished, @c index==size . * * @param allocator the allocator to use @@ -82,15 +83,9 @@ struct cx_hash_map_s { * @param buckets the initial number of buckets in this hash map * @return a pointer to the new hash map */ -cx_attr_nodiscard -cx_attr_malloc -cx_attr_dealloc(cxMapFree, 1) -cx_attr_export -CxMap *cxHashMapCreate( - const CxAllocator *allocator, - size_t itemsize, - size_t buckets -); +cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMapFree, 1) +CX_EXPORT CxMap *cxHashMapCreate(const CxAllocator *allocator, + size_t itemsize, size_t buckets); /** * Creates a new hash map with a default number of buckets. @@ -99,7 +94,8 @@ CxMap *cxHashMapCreate( * copies of the added elements. * * @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. + * The index value of an iterator is incremented when the iterator advanced without + * removing an entry. * In other words, when the iterator is finished, @c index==size . * * @param itemsize (@c size_t) the size of one element @@ -111,10 +107,10 @@ CxMap *cxHashMapCreate( * 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 + * 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 + * 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. * @@ -127,8 +123,7 @@ CxMap *cxHashMapCreate( * @retval non-zero if a memory allocation error occurred */ cx_attr_nonnull -cx_attr_export -int cxMapRehash(CxMap *map); +CX_EXPORT int cxMapRehash(CxMap *map); #ifdef __cplusplus diff --git a/ucx/cx/iterator.h b/ucx/cx/iterator.h index 204c4df..4fe304a 100644 --- a/ucx/cx/iterator.h +++ b/ucx/cx/iterator.h @@ -50,7 +50,10 @@ struct cx_iterator_base_s { * True if the iterator points to valid data. */ bool (*valid)(const void *); - + /** + * Original implementation in case the function needs to be wrapped. + */ + bool (*valid_impl)(const void *); /** * Returns a pointer to the current element. * @@ -62,17 +65,20 @@ struct cx_iterator_base_s { * Original implementation in case the function needs to be wrapped. */ void *(*current_impl)(const void *); - /** * Advances the iterator. * * When valid returns false, the behavior of this function is undefined. */ void (*next)(void *); + /** + * Original implementation in case the function needs to be wrapped. + */ + void (*next_impl)(void *); /** * Indicates whether this iterator may remove elements. */ - bool mutating; + bool allow_remove; /** * Internal flag for removing the current element when advancing. */ @@ -108,16 +114,7 @@ struct cx_iterator_s { /** * Handle for the source collection, if any. */ - union { - /** - * Access for mutating iterators. - */ - void *m; - /** - * Access for normal iterators. - */ - const void *c; - } src_handle; + void *src_handle; /** * If the iterator is position-aware, contains the index of the element in the underlying collection. @@ -141,11 +138,11 @@ struct cx_iterator_s { * 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 + * 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, + * any concurrent mutation of the collection other than by this iterator makes this iterator obsolete, * and it must not be used anymore. */ typedef struct cx_iterator_s CxIterator; @@ -178,13 +175,12 @@ typedef struct cx_iterator_s CxIterator; #define cxIteratorNext(iter) (iter).base.next(&iter) /** - * Flags the current element for removal, if this iterator is mutating. - * - * Does nothing for non-mutating iterators. + * Flags the current element for removal if the iterator allows it. * * @param iter the iterator + * @return @c true if removal is allowed, @c false otherwise */ -#define cxIteratorFlagRemoval(iter) (iter).base.remove |= (iter).base.mutating +#define cxIteratorFlagRemoval(iter) ((iter).base.remove = (iter).base.allow_remove) /** * Obtains a reference to an arbitrary iterator. @@ -210,7 +206,7 @@ for (type elem; cxIteratorValid(iter) && (elem = (type)cxIteratorCurrent(iter)) /** * Creates an iterator for the specified plain array. * - * The @p array can be @c NULL in which case the iterator will be immediately + * The @p array can be @c NULL, in which case the iterator will be immediately * initialized such that #cxIteratorValid() returns @c false. * * This iterator yields the addresses of the array elements. @@ -218,23 +214,6 @@ for (type elem; cxIteratorValid(iter) && (elem = (type)cxIteratorCurrent(iter)) * use cxIteratorPtr() to create an iterator which directly * yields the stored pointers. * - * @param array a pointer to the array (can be @c NULL) - * @param elem_size the size of one array element - * @param elem_count the number of elements in the array - * @return an iterator for the specified array - * @see cxIteratorPtr() - */ -cx_attr_nodiscard -cx_attr_export -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. @@ -244,67 +223,45 @@ CxIterator cxIterator( * 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 + * @see cxIteratorPtr() */ cx_attr_nodiscard -cx_attr_export -CxIterator cxMutIterator( - void *array, - size_t elem_size, - size_t elem_count, - bool remove_keeps_order -); +CX_EXPORT CxIterator cxIterator(const void *array, + size_t elem_size, size_t elem_count, bool remove_keeps_order); /** * Creates an iterator for the specified plain pointer array. * * This iterator assumes that every element in the array is a pointer - * and yields exactly those pointers during iteration (while in contrast - * an iterator created with cxIterator() would return the addresses - * of those pointers within the array). + * and yields exactly those pointers during iteration (on the other + * hand, an iterator created with cxIterator() would return the + * addresses of those pointers within the array). * - * @param array a pointer to the array (can be @c NULL) - * @param elem_count the number of elements in the array - * @return an iterator for the specified array - * @see cxIterator() - */ -cx_attr_nodiscard -cx_attr_export -CxIterator cxIteratorPtr( - const void *array, - size_t elem_count -); - -/** - * Creates a mutating iterator for the specified plain pointer 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. * - * This is the mutating variant of cxIteratorPtr(). See also - * cxMutIterator(). + * 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. * * @param array a pointer to the array (can be @c NULL) * @param elem_count the number of elements in the array * @param remove_keeps_order @c true if the order of elements must be preserved * when removing an element * @return an iterator for the specified array - * @see cxMutIterator() - * @see cxIteratorPtr() + * @see cxIterator() */ cx_attr_nodiscard -cx_attr_export -CxIterator cxMutIteratorPtr( - void *array, - size_t elem_count, - bool remove_keeps_order -); +CX_EXPORT CxIterator cxIteratorPtr(const void *array, size_t elem_count, + bool remove_keeps_order); #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/json.h b/ucx/cx/json.h index ee09ec1..8856ad4 100644 --- a/ucx/cx/json.h +++ b/ucx/cx/json.h @@ -90,11 +90,11 @@ enum cx_json_token_type { */ CX_JSON_TOKEN_STRING, /** - * A number token that can be represented as integer. + * A number token that can be represented as an integer. */ CX_JSON_TOKEN_INTEGER, /** - * A number token that cannot be represented as integer. + * A number token that cannot be represented as an integer. */ CX_JSON_TOKEN_NUMBER, /** @@ -196,11 +196,11 @@ typedef struct cx_json_object_s CxJsonObject; */ typedef struct cx_mutstr_s CxJsonString; /** - * Type alias for a number that can be represented as 64-bit signed integer. + * Type alias for a number that can be represented as a 64-bit signed integer. */ typedef int64_t CxJsonInteger; /** - * Type alias for number that is not an integer. + * Type alias for a number that is not an integer. */ typedef double CxJsonNumber; /** @@ -272,27 +272,27 @@ struct cx_json_value_s { */ union { /** - * The array data if type is #CX_JSON_ARRAY. + * The array data if the type is #CX_JSON_ARRAY. */ CxJsonArray array; /** - * The object data if type is #CX_JSON_OBJECT. + * The object data if the type is #CX_JSON_OBJECT. */ CxJsonObject object; /** - * The string data if type is #CX_JSON_STRING. + * The string data if the type is #CX_JSON_STRING. */ CxJsonString string; /** - * The integer if type is #CX_JSON_INTEGER. + * The integer if the type is #CX_JSON_INTEGER. */ CxJsonInteger integer; /** - * The number if type is #CX_JSON_NUMBER. + * The number if the type is #CX_JSON_NUMBER. */ CxJsonNumber number; /** - * The literal type if type is #CX_JSON_LITERAL. + * The literal type if the type is #CX_JSON_LITERAL. */ CxJsonLiteral literal; } value; @@ -377,7 +377,7 @@ struct cx_json_s { }; /** - * Status codes for the json interface. + * Status codes for the JSON interface. */ enum cx_json_status { /** @@ -391,7 +391,7 @@ enum cx_json_status { /** * The input ends unexpectedly. * - * Refill the buffer with cxJsonFill() to complete the json data. + * Refill the buffer with cxJsonFill() to complete the JSON data. */ CX_JSON_INCOMPLETE_DATA, /** @@ -400,7 +400,7 @@ enum cx_json_status { * You can use this enumerator to check for all "good" status results * by checking if the status is less than @c CX_JSON_OK. * - * A "good" status means, that you can refill data and continue parsing. + * A "good" status means that you can refill data and continue parsing. */ CX_JSON_OK, /** @@ -412,7 +412,7 @@ enum cx_json_status { */ CX_JSON_BUFFER_ALLOC_FAILED, /** - * Allocating memory for a json value failed. + * Allocating memory for a JSON value failed. */ CX_JSON_VALUE_ALLOC_FAILED, /** @@ -426,7 +426,7 @@ enum cx_json_status { }; /** - * Typedef for the json status enum. + * Typedef for the JSON status enum. */ typedef enum cx_json_status CxJsonStatus; @@ -445,7 +445,7 @@ struct cx_json_writer_s { /** * The maximum number of fractional digits in a number value. * The default value is 6 and values larger than 15 are reduced to 15. - * Note, that the actual number of digits may be lower, depending on the concrete number. + * Note that the actual number of digits may be lower, depending on the concrete number. */ uint8_t frac_max_digits; /** @@ -465,7 +465,7 @@ struct cx_json_writer_s { }; /** - * Typedef for the json writer. + * Typedef for the JSON writer. */ typedef struct cx_json_writer_s CxJsonWriter; @@ -475,8 +475,7 @@ typedef struct cx_json_writer_s CxJsonWriter; * @return new JSON writer settings */ cx_attr_nodiscard -cx_attr_export -CxJsonWriter cxJsonWriterCompact(void); +CX_EXPORT CxJsonWriter cxJsonWriterCompact(void); /** * Creates a default writer configuration for pretty output. @@ -485,8 +484,7 @@ CxJsonWriter cxJsonWriterCompact(void); * @return new JSON writer settings */ cx_attr_nodiscard -cx_attr_export -CxJsonWriter cxJsonWriterPretty(bool use_spaces); +CX_EXPORT CxJsonWriter cxJsonWriterPretty(bool use_spaces); /** * Writes a JSON value to a buffer or stream. @@ -496,9 +494,8 @@ CxJsonWriter cxJsonWriterPretty(bool use_spaces); * that the data is only partially written when an error occurs with no * way to indicate how much data was written. * To avoid this problem, you can use a CxBuffer as @p target which is - * unlikely to fail a write operation and either use the buffer's flush - * feature to relay the data or use the data in the buffer manually to - * write it to the actual target. + * unlikely to fail a write operation. You can, for example, use the buffer's flush + * feature to relay the data. * * @param target the buffer or stream where to write to * @param value the value that shall be written @@ -508,49 +505,38 @@ CxJsonWriter cxJsonWriterPretty(bool use_spaces); * @retval non-zero when no or not all data could be written */ cx_attr_nonnull_arg(1, 2, 3) -cx_attr_export -int cxJsonWrite( - void* target, - const CxJsonValue* value, - cx_write_func wfunc, - const CxJsonWriter* settings -); +CX_EXPORT int cxJsonWrite(void* target, const CxJsonValue* value, + cx_write_func wfunc, const CxJsonWriter* settings); /** - * Initializes the json interface. + * Initializes the JSON interface. * - * @param json the json interface + * @param json the JSON interface * @param allocator the allocator that shall be used for the produced values * @see cxJsonDestroy() */ cx_attr_nonnull_arg(1) -cx_attr_export -void cxJsonInit(CxJson *json, const CxAllocator *allocator); +CX_EXPORT void cxJsonInit(CxJson *json, const CxAllocator *allocator); /** - * Destroys the json interface. + * Destroys the JSON interface. * - * @param json the json interface + * @param json the JSON interface * @see cxJsonInit() */ cx_attr_nonnull -cx_attr_export -void cxJsonDestroy(CxJson *json); +CX_EXPORT void cxJsonDestroy(CxJson *json); /** - * Destroys and re-initializes the json interface. + * Destroys and re-initializes the JSON interface. * - * You might want to use this, to reset the parser after + * You might want to use this to reset the parser after * encountering a syntax error. * - * @param json the json interface + * @param json the JSON interface */ cx_attr_nonnull -static inline void cxJsonReset(CxJson *json) { - const CxAllocator *allocator = json->allocator; - cxJsonDestroy(json); - cxJsonInit(json, allocator); -} +CX_EXPORT void cxJsonReset(CxJson *json); /** * Fills the input buffer. @@ -563,48 +549,30 @@ static inline void cxJsonReset(CxJson *json) { * the additional data is appended - inevitably leading to * an allocation of a new buffer and copying the previous contents. * - * @param json the json interface + * @param json the JSON interface * @param buf the source buffer * @param len the length of the source buffer * @retval zero success * @retval non-zero internal allocation error * @see cxJsonFill() */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonFilln(CxJson *json, const char *buf, size_t len); - -#ifdef __cplusplus -} // extern "C" +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonFilln(CxJson *json, const char *buf, size_t len); -cx_attr_nonnull -static inline int cxJsonFill( - CxJson *json, - cxstring str -) { - return cxJsonFilln(json, str.ptr, str.length); -} +/** + * Internal function, do not use. + * + * @param json the JSON interface + * @param str the string + * @retval zero success + * @retval non-zero internal allocation error + */ cx_attr_nonnull -static inline int cxJsonFill( - CxJson *json, - cxmutstr str -) { +CX_INLINE int cx_json_fill(CxJson *json, cxstring str) { return cxJsonFilln(json, str.ptr, str.length); } -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cxJsonFill( - CxJson *json, - const char *str -) { - return cxJsonFilln(json, str, strlen(str)); -} - -extern "C" { -#else // __cplusplus /** * Fills the input buffer. * @@ -616,53 +584,13 @@ extern "C" { * the additional data is appended - inevitably leading to * an allocation of a new buffer and copying the previous contents. * - * @param json the json interface + * @param json the JSON interface * @param str the source string * @retval zero success * @retval non-zero internal allocation error * @see cxJsonFilln() */ -#define cxJsonFill(json, str) _Generic((str), \ - cxstring: cx_json_fill_cxstr, \ - cxmutstr: cx_json_fill_mutstr, \ - char*: cx_json_fill_str, \ - const char*: cx_json_fill_str) \ - (json, str) - -/** - * @copydoc cxJsonFill() - */ -cx_attr_nonnull -static inline int cx_json_fill_cxstr( - CxJson *json, - cxstring str -) { - return cxJsonFilln(json, str.ptr, str.length); -} - -/** - * @copydoc cxJsonFill() - */ -cx_attr_nonnull -static inline int cx_json_fill_mutstr( - CxJson *json, - cxmutstr str -) { - return cxJsonFilln(json, str.ptr, str.length); -} - -/** - * @copydoc cxJsonFill() - */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cx_json_fill_str( - CxJson *json, - const char *str -) { - return cxJsonFilln(json, str, strlen(str)); -} -#endif +#define cxJsonFill(json, str) cx_json_fill(json, cx_strcast(str)) /** * Creates a new (empty) JSON object. @@ -673,8 +601,7 @@ static inline int cx_json_fill_str( * @see cxJsonArrAddValues() */ cx_attr_nodiscard -cx_attr_export -CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator); +CX_EXPORT CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator); /** * Creates a new (empty) JSON array. @@ -685,8 +612,7 @@ CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator); * @see cxJsonArrAddValues() */ cx_attr_nodiscard -cx_attr_export -CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator); +CX_EXPORT CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator); /** * Creates a new JSON number value. @@ -698,8 +624,7 @@ CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator); * @see cxJsonArrAddNumbers() */ cx_attr_nodiscard -cx_attr_export -CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num); +CX_EXPORT CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num); /** * Creates a new JSON number value based on an integer. @@ -711,8 +636,7 @@ CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num); * @see cxJsonArrAddIntegers() */ cx_attr_nodiscard -cx_attr_export -CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num); +CX_EXPORT CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num); /** * Creates a new JSON string. @@ -724,11 +648,8 @@ CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num); * @see cxJsonObjPutString() * @see cxJsonArrAddStrings() */ -cx_attr_nodiscard -cx_attr_nonnull_arg(2) -cx_attr_cstr_arg(2) -cx_attr_export -CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str); +cx_attr_nodiscard cx_attr_nonnull_arg(2) cx_attr_cstr_arg(2) +CX_EXPORT CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str); /** * Creates a new JSON string. @@ -741,8 +662,7 @@ CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str); * @see cxJsonArrAddCxStrings() */ cx_attr_nodiscard -cx_attr_export -CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str); +CX_EXPORT CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str); /** * Creates a new JSON literal. @@ -754,8 +674,7 @@ CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str); * @see cxJsonArrAddLiterals() */ cx_attr_nodiscard -cx_attr_export -CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit); +CX_EXPORT CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit); /** * Adds number values to a JSON array. @@ -766,10 +685,8 @@ CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit * @retval zero success * @retval non-zero allocation failure */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count); /** * Adds number values, of which all are integers, to a JSON array. @@ -780,10 +697,8 @@ int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count); * @retval zero success * @retval non-zero allocation failure */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count); /** * Adds strings to a JSON array. @@ -797,10 +712,8 @@ int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count); * @retval non-zero allocation failure * @see cxJsonArrAddCxStrings() */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count); /** * Adds strings to a JSON array. @@ -814,10 +727,8 @@ int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count); * @retval non-zero allocation failure * @see cxJsonArrAddStrings() */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count); /** * Adds literals to a JSON array. @@ -828,10 +739,8 @@ int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count); * @retval zero success * @retval non-zero allocation failure */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count); /** * Add arbitrary values to a JSON array. @@ -845,10 +754,8 @@ int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t coun * @retval zero success * @retval non-zero allocation failure */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count); /** * Adds or replaces a value within a JSON object. @@ -865,8 +772,7 @@ int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count); * @retval non-zero allocation failure */ cx_attr_nonnull -cx_attr_export -int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); +CX_EXPORT int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); /** * Creates a new JSON object and adds it to an existing object. @@ -878,8 +784,7 @@ int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); * @see cxJsonCreateObj() */ cx_attr_nonnull -cx_attr_export -CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); +CX_EXPORT CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); /** * Creates a new JSON array and adds it to an object. @@ -891,8 +796,7 @@ CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); * @see cxJsonCreateArr() */ cx_attr_nonnull -cx_attr_export -CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); +CX_EXPORT CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); /** * Creates a new JSON number and adds it to an object. @@ -905,8 +809,7 @@ CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); * @see cxJsonCreateNumber() */ cx_attr_nonnull -cx_attr_export -CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num); +CX_EXPORT CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num); /** * Creates a new JSON number, based on an integer, and adds it to an object. @@ -919,8 +822,7 @@ CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num); * @see cxJsonCreateInteger() */ cx_attr_nonnull -cx_attr_export -CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num); +CX_EXPORT CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num); /** * Creates a new JSON string and adds it to an object. @@ -934,10 +836,8 @@ CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num); * @see cxJsonObjPut() * @see cxJsonCreateString() */ -cx_attr_nonnull -cx_attr_cstr_arg(3) -cx_attr_export -CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str); +cx_attr_nonnull cx_attr_cstr_arg(3) +CX_EXPORT CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str); /** * Creates a new JSON string and adds it to an object. @@ -952,8 +852,7 @@ CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str * @see cxJsonCreateCxString() */ cx_attr_nonnull -cx_attr_export -CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str); +CX_EXPORT CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str); /** * Creates a new JSON literal and adds it to an object. @@ -966,22 +865,20 @@ CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str) * @see cxJsonCreateLiteral() */ cx_attr_nonnull -cx_attr_export -CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); +CX_EXPORT CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); /** * Recursively deallocates the memory of a JSON value. * * @remark The type of each deallocated value will be changed - * to #CX_JSON_NOTHING and values of such type will be skipped - * by the de-allocation. That means, this function protects + * to #CX_JSON_NOTHING, and values of such a type will be skipped + * by the deallocation. That means this function protects * you from double-frees when you are accidentally freeing * a nested value and then the parent value (or vice versa). * * @param value the value */ -cx_attr_export -void cxJsonValueFree(CxJsonValue *value); +CX_EXPORT void cxJsonValueFree(CxJsonValue *value); /** * Tries to obtain the next JSON value. @@ -993,7 +890,7 @@ void cxJsonValueFree(CxJsonValue *value); * add the missing data with another invocation of cxJsonFill() * and then repeat the call to cxJsonNext(). * - * @param json the json interface + * @param json the JSON interface * @param value a pointer where the next value shall be stored * @retval CX_JSON_NO_ERROR successfully retrieve the @p value * @retval CX_JSON_NO_DATA there is no (more) data in the buffer to read from @@ -1005,10 +902,8 @@ void cxJsonValueFree(CxJsonValue *value); * @retval CX_JSON_FORMAT_ERROR_NUMBER the JSON text contains an illegally formatted number * @retval CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN JSON syntax error */ -cx_attr_nonnull -cx_attr_access_w(2) -cx_attr_export -CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value); +cx_attr_nonnull cx_attr_access_w(2) +CX_EXPORT CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value); /** * Checks if the specified value is a JSON object. @@ -1018,7 +913,7 @@ CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value); * @retval false otherwise */ cx_attr_nonnull -static inline bool cxJsonIsObject(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsObject(const CxJsonValue *value) { return value->type == CX_JSON_OBJECT; } @@ -1030,7 +925,7 @@ static inline bool cxJsonIsObject(const CxJsonValue *value) { * @retval false otherwise */ cx_attr_nonnull -static inline bool cxJsonIsArray(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsArray(const CxJsonValue *value) { return value->type == CX_JSON_ARRAY; } @@ -1042,14 +937,14 @@ static inline bool cxJsonIsArray(const CxJsonValue *value) { * @retval false otherwise */ cx_attr_nonnull -static inline bool cxJsonIsString(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsString(const CxJsonValue *value) { return value->type == CX_JSON_STRING; } /** * Checks if the specified value is a JSON number. * - * This function will return true for both floating point and + * This function will return true for both floating-point and * integer numbers. * * @param value a pointer to the value @@ -1058,7 +953,7 @@ static inline bool cxJsonIsString(const CxJsonValue *value) { * @see cxJsonIsInteger() */ cx_attr_nonnull -static inline bool cxJsonIsNumber(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsNumber(const CxJsonValue *value) { return value->type == CX_JSON_NUMBER || value->type == CX_JSON_INTEGER; } @@ -1071,7 +966,7 @@ static inline bool cxJsonIsNumber(const CxJsonValue *value) { * @see cxJsonIsNumber() */ cx_attr_nonnull -static inline bool cxJsonIsInteger(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsInteger(const CxJsonValue *value) { return value->type == CX_JSON_INTEGER; } @@ -1088,7 +983,7 @@ static inline bool cxJsonIsInteger(const CxJsonValue *value) { * @see cxJsonIsNull() */ cx_attr_nonnull -static inline bool cxJsonIsLiteral(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsLiteral(const CxJsonValue *value) { return value->type == CX_JSON_LITERAL; } @@ -1102,14 +997,14 @@ static inline bool cxJsonIsLiteral(const CxJsonValue *value) { * @see cxJsonIsFalse() */ cx_attr_nonnull -static inline bool cxJsonIsBool(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsBool(const CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL; } /** * Checks if the specified value is @c true. * - * @remark Be advised, that this is not the same as + * @remark Be advised that this is different from * testing @c !cxJsonIsFalse(v). * * @param value a pointer to the value @@ -1119,14 +1014,14 @@ static inline bool cxJsonIsBool(const CxJsonValue *value) { * @see cxJsonIsFalse() */ cx_attr_nonnull -static inline bool cxJsonIsTrue(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsTrue(const CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE; } /** * Checks if the specified value is @c false. * - * @remark Be advised, that this is not the same as + * @remark Be advised that this is different from * testing @c !cxJsonIsTrue(v). * * @param value a pointer to the value @@ -1136,7 +1031,7 @@ static inline bool cxJsonIsTrue(const CxJsonValue *value) { * @see cxJsonIsTrue() */ cx_attr_nonnull -static inline bool cxJsonIsFalse(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsFalse(const CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE; } @@ -1149,7 +1044,7 @@ static inline bool cxJsonIsFalse(const CxJsonValue *value) { * @see cxJsonIsLiteral() */ cx_attr_nonnull -static inline bool cxJsonIsNull(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsNull(const CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL; } @@ -1162,11 +1057,8 @@ static inline bool cxJsonIsNull(const CxJsonValue *value) { * @return the value represented as C string * @see cxJsonIsString() */ -cx_attr_nonnull -cx_attr_returns_nonnull -static inline char *cxJsonAsString(const CxJsonValue *value) { - return value->value.string.ptr; -} +cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT char *cxJsonAsString(const CxJsonValue *value); /** * Obtains a UCX string from the given JSON value. @@ -1178,9 +1070,7 @@ static inline char *cxJsonAsString(const CxJsonValue *value) { * @see cxJsonIsString() */ cx_attr_nonnull -static inline cxstring cxJsonAsCxString(const CxJsonValue *value) { - return cx_strcast(value->value.string); -} +CX_EXPORT cxstring cxJsonAsCxString(const CxJsonValue *value); /** * Obtains a mutable UCX string from the given JSON value. @@ -1192,12 +1082,10 @@ static inline cxstring cxJsonAsCxString(const CxJsonValue *value) { * @see cxJsonIsString() */ cx_attr_nonnull -static inline cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { - return value->value.string; -} +CX_EXPORT cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value); /** - * Obtains a double-precision floating point value from the given JSON value. + * Obtains a double-precision floating-point value from the given JSON value. * * If the @p value is not a JSON number, the behavior is undefined. * @@ -1206,13 +1094,7 @@ static inline cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { * @see cxJsonIsNumber() */ cx_attr_nonnull -static inline double cxJsonAsDouble(const CxJsonValue *value) { - if (value->type == CX_JSON_INTEGER) { - return (double) value->value.integer; - } else { - return value->value.number; - } -} +CX_EXPORT double cxJsonAsDouble(const CxJsonValue *value); /** * Obtains a 64-bit signed integer from the given JSON value. @@ -1227,13 +1109,7 @@ static inline double cxJsonAsDouble(const CxJsonValue *value) { * @see cxJsonIsInteger() */ cx_attr_nonnull -static inline int64_t cxJsonAsInteger(const CxJsonValue *value) { - if (value->type == CX_JSON_INTEGER) { - return value->value.integer; - } else { - return (int64_t) value->value.number; - } -} +CX_EXPORT int64_t cxJsonAsInteger(const CxJsonValue *value); /** * Obtains a Boolean value from the given JSON value. @@ -1246,7 +1122,7 @@ static inline int64_t cxJsonAsInteger(const CxJsonValue *value) { * @see cxJsonIsLiteral() */ cx_attr_nonnull -static inline bool cxJsonAsBool(const CxJsonValue *value) { +CX_INLINE bool cxJsonAsBool(const CxJsonValue *value) { return value->value.literal == CX_JSON_TRUE; } @@ -1260,7 +1136,7 @@ static inline bool cxJsonAsBool(const CxJsonValue *value) { * @see cxJsonIsArray() */ cx_attr_nonnull -static inline size_t cxJsonArrSize(const CxJsonValue *value) { +CX_INLINE size_t cxJsonArrSize(const CxJsonValue *value) { return value->value.array.array_size; } @@ -1278,10 +1154,24 @@ static inline size_t cxJsonArrSize(const CxJsonValue *value) { * @return the value at the specified index * @see cxJsonIsArray() */ +cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); + +/** + * Removes an element from a JSON array. + * + * If the @p value is not a JSON array, the behavior is undefined. + * + * This function, in contrast to cxJsonArrayGet(), returns @c NULL + * when the index is out of bounds. + * + * @param value the JSON value + * @param index the index in the array + * @return the removed value from the specified index or @c NULL when the index was out of bounds + * @see cxJsonIsArray() + */ cx_attr_nonnull -cx_attr_returns_nonnull -cx_attr_export -CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); +CX_EXPORT CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index); /** * Returns an iterator over the JSON array elements. @@ -1294,10 +1184,8 @@ CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); * @return an iterator over the array elements * @see cxJsonIsArray() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -CxIterator cxJsonArrIter(const CxJsonValue *value); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxIterator cxJsonArrIter(const CxJsonValue *value); /** * Returns an iterator over the JSON object members. @@ -1311,36 +1199,18 @@ CxIterator cxJsonArrIter(const CxJsonValue *value); * @return an iterator over the object members * @see cxJsonIsObject() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -CxIterator cxJsonObjIter(const CxJsonValue *value); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxIterator cxJsonObjIter(const CxJsonValue *value); /** - * @copydoc cxJsonObjGet() + * Internal function, do not use. + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key */ -cx_attr_nonnull -cx_attr_returns_nonnull -cx_attr_export -CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name); +cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name); -#ifdef __cplusplus -} // extern "C" - -static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) { - return cx_json_obj_get_cxstr(value, name); -} - -static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) { - return cx_json_obj_get_cxstr(value, cx_strcast(name)); -} - -static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, const char *name) { - return cx_json_obj_get_cxstr(value, cx_str(name)); -} - -extern "C" { -#else /** * Returns a value corresponding to a key in a JSON object. * @@ -1355,32 +1225,31 @@ extern "C" { * @return the value corresponding to the key * @see cxJsonIsObject() */ -#define cxJsonObjGet(value, name) _Generic((name), \ - cxstring: cx_json_obj_get_cxstr, \ - cxmutstr: cx_json_obj_get_mutstr, \ - char*: cx_json_obj_get_str, \ - const char*: cx_json_obj_get_str) \ - (value, name) +#define cxJsonObjGet(value, name) cx_json_obj_get(value, cx_strcast(name)) /** - * @copydoc cxJsonObjGet() + * Internal function, do not use. + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key or @c NULL when the key is not part of the object */ cx_attr_nonnull -cx_attr_returns_nonnull -static inline CxJsonValue *cx_json_obj_get_mutstr(const CxJsonValue *value, cxmutstr name) { - return cx_json_obj_get_cxstr(value, cx_strcast(name)); -} +CX_EXPORT CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name); /** - * @copydoc cxJsonObjGet() + * Removes and returns a value corresponding to a key in a JSON object. + * + * If the @p value is not a JSON object, the behavior is undefined. + * + * This function, in contrast to cxJsonObjGet() returns @c NULL when the + * object does not contain @p name. + * + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key or @c NULL when the key is not part of the object + * @see cxJsonIsObject() */ -cx_attr_nonnull -cx_attr_returns_nonnull -cx_attr_cstr_arg(2) -static inline CxJsonValue *cx_json_obj_get_str(const CxJsonValue *value, const char *name) { - return cx_json_obj_get_cxstr(value, cx_str(name)); -} -#endif +#define cxJsonObjRemove(value, name) cx_json_obj_remove(value, cx_strcast(name)) #ifdef __cplusplus } diff --git a/ucx/cx/kv_list.h b/ucx/cx/kv_list.h new file mode 100644 index 0000000..c66e46c --- /dev/null +++ b/ucx/cx/kv_list.h @@ -0,0 +1,260 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 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 kv_list.h + * @brief Linked list implementation with key/value-lookup. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_KV_LIST_H +#define UCX_KV_LIST_H + +#include "common.h" +#include "list.h" +#include "map.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. + * + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements, and the compare function will be automatically set + * to cx_cmp_ptr() if none is given. + * + * After creating the list, it can also be used as a map after converting the pointer + * to a CxMap pointer with cxKvListAsMap(). + * When you want to use the list interface again, you can also convert the map pointer back + * with cxKvListAsList(). + * + * @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 + * @see cxKvListAsMap() + * @see cxKvListAsList() + */ +cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1) +CX_EXPORT CxList *cxKvListCreate(const CxAllocator *allocator, + cx_compare_func comparator, size_t elem_size); + +/** + * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. + * + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements, and the compare function will be automatically set + * to cx_cmp_ptr() if none is given. + * + * This function creates the list with cxKvListCreate() and immediately applies + * cxKvListAsMap(). If you want to use the returned object as a list, you can call + * cxKvListAsList() later. + * + * @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 wrapped into the CxMap interface + * @see cxKvListAsMap() + * @see cxKvListAsList() + */ +cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMapFree, 1) +CX_EXPORT CxMap *cxKvListCreateAsMap(const CxAllocator *allocator, + cx_compare_func comparator, size_t elem_size); + +/** + * Allocates a linked list with a lookup-map 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 cxKvListCreate(). + * + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements, and the compare function will be automatically set + * to cx_cmp_ptr(). + * + * After creating the list, it can also be used as a map after converting the pointer + * to a CxMap pointer with cxKvListAsMap(). + * When you want to use the list interface again, you can also convert the map pointer back + * with cxKvListAsList(). + * + * @param elem_size (@c size_t) the size of each element in bytes + * @return (@c CxList*) the created list + * @see cxKvListAsMap() + * @see cxKvListAsList() + */ +#define cxKvListCreateSimple(elem_size) cxKvListCreate(NULL, NULL, elem_size) + +/** + * Allocates a linked list with a lookup-map 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 cxKvListCreate(). + * + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements, and the compare function will be automatically set + * to cx_cmp_ptr(). + * + * This macro behaves as if the list was created with cxKvListCreateSimple() and + * immediately followed up by cxKvListAsMap(). + * If you want to use the returned object as a list, you can call cxKvListAsList() later. + * + * @param elem_size (@c size_t) the size of each element in bytes + * @return (@c CxMap*) the created list wrapped into the CxMap interface + * @see cxKvListAsMap() + * @see cxKvListAsList() + */ +#define cxKvListCreateAsMapSimple(elem_size) cxKvListCreateAsMap(NULL, NULL, elem_size) + +/** + * Converts a map pointer belonging to a key-value-List back to the original list pointer. + * + * @param map a map pointer that was returned by a call to cxKvListAsMap() + * @return the original list pointer + */ +cx_attr_nodiscard cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT CxList *cxKvListAsList(CxMap *map); + +/** + * Converts a map pointer belonging to a key-value-List back to the original list pointer. + * + * @param list a list created by cxKvListCreate() or cxKvListCreateSimple() + * @return a map pointer that lets you use the list as if it was a map + */ +cx_attr_nodiscard cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT CxMap *cxKvListAsMap(CxList *list); + +/** + * Sets or updates the key of a list item. + * + * This is, for example, useful when you have inserted an element using the CxList interface, + * and now you want to associate this element with a key. + * + * @param list the list + * @param index the index of the element in the list + * @param key the key + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see cxKvListSetKey() + */ +cx_attr_nonnull +CX_EXPORT int cx_kv_list_set_key(CxList *list, size_t index, CxHashKey key); + +/** + * Inserts an item into the list at the specified index and associates it with the specified key. + * + * @param list the list + * @param index the index the inserted element shall have + * @param key the key + * @param value the value + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see cxKvListInsert() + */ +cx_attr_nonnull +CX_EXPORT int cx_kv_list_insert(CxList *list, size_t index, CxHashKey key, void *value); + +/** + * Sets or updates the key of a list item. + * + * This is, for example, useful when you have inserted an element using the CxList interface, + * and now you want to associate this element with a key. + * + * @param list (@c CxList*) the list + * @param index (@c size_t) the index of the element in the list + * @param key (any supported key type) the key + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see CX_HASH_KEY() + */ +#define cxKvListSetKey(list, index, key) cx_kv_list_set_key(list, index, CX_HASH_KEY(key)) + +/** + * Inserts an item into the list at the specified index and associates it with the specified key. + * + * @param list (@c CxList*) the list + * @param index (@c size_t) the index the inserted element shall have + * @param key (any supported key type) the key + * @param value (@c void*) the value + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see CX_HASH_KEY() + */ +#define cxKvListInsert(list, index, key, value) cx_kv_list_insert(list, index, CX_HASH_KEY(key), value) + + +/** + * Removes the key of a list item. + * + * This can be useful if you want to explicitly remove an item from the lookup map. + * + * If no key is associated with the item, nothing happens, and this function returns zero. + * + * @param list the list + * @param index the index of the element in the list + * @retval zero success + * @retval non-zero the index is out of bounds + */ +cx_attr_nonnull +CX_EXPORT int cxKvListRemoveKey(CxList *list, size_t index); + +/** + * Returns the key of a list item. + * + * @param list the list + * @param index the index of the element in the list + * @return a pointer to the key or @c NULL when the index is out of bounds or the item does not have a key + */ +cx_attr_nonnull +CX_EXPORT const CxHashKey *cxKvListGetKey(CxList *list, size_t index); + +/** + * Adds an item into the list and associates it with the specified key. + * + * @param list (@c CxList*) the list + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param value (@c void*) the value + * @retval zero success + * @retval non-zero memory allocation failure + */ +#define cxKvListAdd(list, key, value) cxKvListInsert(list, (list)->collection.size, key, value) + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_KV_LIST_H diff --git a/ucx/cx/linked_list.h b/ucx/cx/linked_list.h index 3da2cae..996dc58 100644 --- a/ucx/cx/linked_list.h +++ b/ucx/cx/linked_list.h @@ -43,12 +43,44 @@ extern "C" { #endif +/** + * Metadata for a linked list. + */ +typedef struct cx_linked_list_s { + /** Base members. */ + struct cx_list_s base; + /** + * Location of the prev pointer (mandatory). + */ + off_t loc_prev; + /** + * Location of the next pointer (mandatory). + */ + off_t loc_next; + /** + * Location of the payload (mandatory). + */ + off_t loc_data; + /** + * Additional bytes to allocate @em behind the payload (e.g. for metadata). + */ + size_t extra_data_len; + /** + * Pointer to the first node. + */ + void *begin; + /** + * Pointer to the last node. + */ + void *end; +} cx_linked_list; + /** * Allocates a linked list for storing elements with @p elem_size bytes each. * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of - * copies of the added elements and the compare function will be automatically set - * to cx_cmp_ptr(), if none is given. + * copies of the added elements, 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) @@ -58,15 +90,9 @@ extern "C" { * @param elem_size the size of each element in bytes * @return the created list */ -cx_attr_nodiscard -cx_attr_malloc -cx_attr_dealloc(cxListFree, 1) -cx_attr_export -CxList *cxLinkedListCreate( - const CxAllocator *allocator, - cx_compare_func comparator, - size_t elem_size -); +cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1) +CX_EXPORT 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. @@ -76,25 +102,25 @@ CxList *cxLinkedListCreate( * after list creation or use cxLinkedListCreate(). * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of - * copies of the added elements and the compare function will be automatically set + * copies of the added elements, and the compare function will be automatically set * to cx_cmp_ptr(). * * @param elem_size (@c size_t) the size of each element in bytes * @return (@c CxList*) the created list */ #define cxLinkedListCreateSimple(elem_size) \ - cxLinkedListCreate(NULL, NULL, elem_size) + 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). + * If the search index is larger than the start index, @p loc_advance must denote + * the location of a @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). + * (e.g., in cases where traversing a list backwards is faster). + * In that case @p loc_advance must denote the location of a @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 @@ -102,15 +128,9 @@ CxList *cxLinkedListCreate( * @param index the search index * @return the node found at the specified index */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -void *cx_linked_list_at( - const void *start, - size_t start_index, - ptrdiff_t loc_advance, - size_t index -); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT void *cx_linked_list_at(const void *start,size_t start_index, + ptrdiff_t loc_advance, size_t index); /** * Finds the node containing an element within a linked list. @@ -122,18 +142,12 @@ void *cx_linked_list_at( * @param elem a pointer to the element to find * @param found_index an optional pointer where the index of the found node * (given that @p start has index 0) is stored - * @return the index of the element, if found - unspecified if not found + * @return a pointer to the found node or @c NULL if no matching node was found */ cx_attr_nonnull_arg(1, 4, 5) -cx_attr_export -void *cx_linked_list_find( - const void *start, - ptrdiff_t loc_advance, - ptrdiff_t loc_data, - cx_compare_func cmp_func, - const void *elem, - size_t *found_index -); +CX_EXPORT void *cx_linked_list_find(const void *start, ptrdiff_t loc_advance, + ptrdiff_t loc_data, cx_compare_func cmp_func, const void *elem, + size_t *found_index); /** * Finds the first node in a linked list. @@ -146,13 +160,8 @@ void *cx_linked_list_find( * @param loc_prev the location of the @c prev pointer * @return a pointer to the first node */ -cx_attr_nonnull -cx_attr_returns_nonnull -cx_attr_export -void *cx_linked_list_first( - const void *node, - ptrdiff_t loc_prev -); +cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT void *cx_linked_list_first(const void *node, ptrdiff_t loc_prev); /** * Finds the last node in a linked list. @@ -165,13 +174,8 @@ void *cx_linked_list_first( * @param loc_next the location of the @c next pointer * @return a pointer to the last node */ -cx_attr_nonnull -cx_attr_returns_nonnull -cx_attr_export -void *cx_linked_list_last( - const void *node, - ptrdiff_t loc_next -); +cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT void *cx_linked_list_last(const void *node, ptrdiff_t loc_next); /** * Finds the predecessor of a node in case it is not linked. @@ -184,16 +188,11 @@ void *cx_linked_list_last( * @return the node or @c NULL if @p node has no predecessor */ cx_attr_nonnull -cx_attr_export -void *cx_linked_list_prev( - const void *begin, - ptrdiff_t loc_next, - const void *node -); +CX_EXPORT 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. + * The node must not be part of any list yet. * * @remark One of the pointers @p begin or @p end may be @c NULL, but not both. * @@ -204,18 +203,11 @@ void *cx_linked_list_prev( * @param new_node a pointer to the node that shall be appended */ cx_attr_nonnull_arg(5) -cx_attr_export -void cx_linked_list_add( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *new_node -); +CX_EXPORT 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. + * The node must not be part of any list yet. * * @remark One of the pointers @p begin or @p end may be @c NULL, but not both. * @@ -226,14 +218,7 @@ void cx_linked_list_add( * @param new_node a pointer to the node that shall be prepended */ cx_attr_nonnull_arg(5) -cx_attr_export -void cx_linked_list_prepend( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *new_node -); +CX_EXPORT void cx_linked_list_prepend(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node); /** * Links two nodes. @@ -244,13 +229,7 @@ void cx_linked_list_prepend( * @param loc_next the location of a @c next pointer within your node struct (required) */ cx_attr_nonnull -cx_attr_export -void cx_linked_list_link( - void *left, - void *right, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +CX_EXPORT void cx_linked_list_link(void *left, void *right, ptrdiff_t loc_prev, ptrdiff_t loc_next); /** * Unlinks two nodes. @@ -263,17 +242,11 @@ void cx_linked_list_link( * @param loc_next the location of a @c next pointer within your node struct (required) */ cx_attr_nonnull -cx_attr_export -void cx_linked_list_unlink( - void *left, - void *right, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +CX_EXPORT 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. + * The new node must not be part of any list yet. * * @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. @@ -286,19 +259,12 @@ void cx_linked_list_unlink( * @param new_node a pointer to the node that shall be inserted */ cx_attr_nonnull_arg(6) -cx_attr_export -void cx_linked_list_insert( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *node, - void *new_node -); +CX_EXPORT 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. + * The chain must not be part of any list yet. * * If you do not explicitly specify the end of the chain, it will be determined by traversing * the @c next pointer. @@ -317,20 +283,12 @@ void cx_linked_list_insert( * @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined) */ cx_attr_nonnull_arg(6) -cx_attr_export -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 -); +CX_EXPORT 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. + * The new node must not be part of any list yet. * * If the list starting with the node pointed to by @p begin is not sorted * already, the behavior is undefined. @@ -343,26 +301,19 @@ void cx_linked_list_insert_chain( * @param cmp_func a compare function that will receive the node pointers */ cx_attr_nonnull_arg(1, 5, 6) -cx_attr_export -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 -); +CX_EXPORT 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. + * The chain must not be part of any list yet. * * 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 + * will be sorted according to @p cmp_func. That means each node in the source * chain may be re-linked with nodes from the target list. * * @param begin a pointer to the beginning node pointer (required) @@ -373,20 +324,55 @@ void cx_linked_list_insert_sorted( * @param cmp_func a compare function that will receive the node pointers */ cx_attr_nonnull_arg(1, 5, 6) -cx_attr_export -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 -); +CX_EXPORT 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); + +/** + * Inserts a node into a sorted linked list if no other node with the same value already exists. + * The new node must not be part of any list yet. + * + * If the list starting with the node pointed to by @p begin is not sorted + * already, the behavior is undefined. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param new_node a pointer to the node that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + * @retval zero when the node was inserted + * @retval non-zero when a node with the same value already exists + */ +cx_attr_nonnull_arg(1, 5, 6) +CX_EXPORT int cx_linked_list_insert_unique(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, avoiding duplicates. + * The chain must not be part of any list yet. + * + * 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_sorted(), not all nodes of the + * chain might be added. This function returns a new chain consisting of all the duplicates. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param insert_begin a pointer to the first node of the chain that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + * @return a pointer to a new chain with all duplicates that were not inserted (or @c NULL when there were no duplicates) + */ +cx_attr_nonnull_arg(1, 5, 6) cx_attr_nodiscard +CX_EXPORT void *cx_linked_list_insert_unique_chain(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, cx_compare_func cmp_func); /** * Removes a chain of nodes from the linked list. * - * If one of the nodes to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end) + * If one of the nodes to remove is the beginning (resp. end) node of the list, and if @p begin (resp. @p end) * addresses are provided, the pointers are adjusted accordingly. * * The following combinations of arguments are valid (more arguments are optional): @@ -405,20 +391,13 @@ void cx_linked_list_insert_sorted_chain( * @return the actual number of nodes that were removed (can be less when the list did not have enough nodes) */ cx_attr_nonnull_arg(5) -cx_attr_export -size_t cx_linked_list_remove_chain( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *node, - size_t num -); +CX_EXPORT size_t cx_linked_list_remove_chain(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node, size_t num); /** * Removes a node from the linked list. * - * If the node to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end) + * If the node to remove is the beginning (resp. end) node of the list, and if @p begin (resp. @p end) * addresses are provided, the pointers are adjusted accordingly. * * The following combinations of arguments are valid (more arguments are optional): @@ -435,15 +414,8 @@ size_t cx_linked_list_remove_chain( * @param node the node to remove */ cx_attr_nonnull_arg(5) -static inline void cx_linked_list_remove( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *node -) { - cx_linked_list_remove_chain(begin, end, loc_prev, loc_next, node, 1); -} +CX_EXPORT 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. @@ -453,11 +425,7 @@ static inline void cx_linked_list_remove( * @return the size of the list or zero if @p node is @c NULL */ cx_attr_nodiscard -cx_attr_export -size_t cx_linked_list_size( - const void *node, - ptrdiff_t loc_next -); +CX_EXPORT size_t cx_linked_list_size(const void *node, ptrdiff_t loc_next); /** * Sorts a linked list based on a comparison function. @@ -482,21 +450,14 @@ size_t cx_linked_list_size( * @param cmp_func the compare function defining the sort order */ cx_attr_nonnull_arg(1, 6) -cx_attr_export -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 -); +CX_EXPORT void cx_linked_list_sort(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, ptrdiff_t loc_data, cx_compare_func cmp_func); /** * Compares two lists element wise. * - * @attention Both list must have the same structure. + * @attention Both lists must have the same structure. * * @param begin_left the beginning of the left list (@c NULL denotes an empty list) * @param begin_right the beginning of the right list (@c NULL denotes an empty list) @@ -507,14 +468,8 @@ void cx_linked_list_sort( * right list, positive if the left list is larger than the right list, zero if both lists are equal. */ cx_attr_nonnull_arg(5) -cx_attr_export -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 -); +CX_EXPORT 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. @@ -525,13 +480,7 @@ int cx_linked_list_compare( * @param loc_next the location of a @c next pointer within your node struct (required) */ cx_attr_nonnull_arg(1) -cx_attr_export -void cx_linked_list_reverse( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +CX_EXPORT void cx_linked_list_reverse(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next); #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/list.h b/ucx/cx/list.h index 14ddc04..4722261 100644 --- a/ucx/cx/list.h +++ b/ucx/cx/list.h @@ -80,46 +80,41 @@ struct cx_list_class_s { /** * Member function for inserting a single element. - * The data pointer may be @c NULL in which case the function shall only allocate memory. - * Returns a pointer to the data of the inserted element. + * The data pointer may be @c NULL, in which case the function shall only allocate memory. + * Returns a pointer to the allocated memory or @c NULL if allocation fails. */ - void *(*insert_element)( - struct cx_list_s *list, - size_t index, - const void *data - ); + void *(*insert_element)(struct cx_list_s *list, size_t index, const void *data); /** * Member function for inserting multiple elements. * + * The data pointer may be @c NULL, in which case the function shall only allocate memory. + * Returns the number of successfully inserted or allocated elements. + * * @see cx_list_default_insert_array() */ - size_t (*insert_array)( - struct cx_list_s *list, - size_t index, - const void *data, - size_t n - ); + 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. + * Returns the number of successfully inserted elements. * * @see cx_list_default_insert_sorted() */ - size_t (*insert_sorted)( - struct cx_list_s *list, - const void *sorted_data, - size_t n - ); + size_t (*insert_sorted)(struct cx_list_s *list, const void *sorted_data, size_t n); + + /** + * Member function for inserting multiple elements if they do not exist. + * Implementations shall return the number of successfully processed elements + * (including those which were not added because they are already contained). + * @see cx_list_default_insert_unique() + */ + size_t (*insert_unique)(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 - ); + int (*insert_iter)(struct cx_iterator_s *iter, const void *elem, int prepend); /** * Member function for removing elements. @@ -131,12 +126,7 @@ struct cx_list_class_s { * The function SHALL return the actual number of elements removed, which * might be lower than @p num when going out of bounds. */ - size_t (*remove)( - struct cx_list_s *list, - size_t index, - size_t num, - void *targetbuf - ); + size_t (*remove)(struct cx_list_s *list, size_t index, size_t num, void *targetbuf); /** * Member function for removing all elements. @@ -148,28 +138,17 @@ struct cx_list_class_s { * * @see cx_list_default_swap() */ - int (*swap)( - struct cx_list_s *list, - size_t i, - size_t j - ); + 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 - ); + void *(*at)(const struct cx_list_s *list, size_t index); /** * Member function for finding and optionally removing an element. */ - size_t (*find_remove)( - struct cx_list_s *list, - const void *elem, - bool remove - ); + size_t (*find_remove)(struct cx_list_s *list, const void *elem, bool remove); /** * Member function for sorting the list. @@ -181,13 +160,9 @@ struct cx_list_class_s { /** * Optional member function for comparing this list * to another list of the same type. - * If set to @c NULL, comparison won't be optimized. + * If set to @c NULL, the comparison won't be optimized. */ - cx_attr_nonnull - int (*compare)( - const struct cx_list_s *list, - const struct cx_list_s *other - ); + int (*compare)(const struct cx_list_s *list, const struct cx_list_s *other); /** * Member function for reversing the order of the items. @@ -197,11 +172,7 @@ struct cx_list_class_s { /** * 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 - ); + struct cx_iterator_s (*iterator)(const struct cx_list_s *list, size_t index, bool backward); }; /** @@ -214,11 +185,10 @@ typedef struct cx_list_s CxList; * * Writing to that list is not allowed. * - * You can use this is a placeholder for initializing CxList pointers + * You can use this as a placeholder for initializing CxList pointers * for which you do not want to reserve memory right from the beginning. */ -cx_attr_export -extern CxList *const cxEmptyList; +CX_EXPORT extern CxList *const cxEmptyList; /** * Default implementation of an array insert. @@ -235,13 +205,8 @@ extern CxList *const cxEmptyList; * @return the number of elements actually inserted */ cx_attr_nonnull -cx_attr_export -size_t cx_list_default_insert_array( - struct cx_list_s *list, - size_t index, - const void *data, - size_t n -); +CX_EXPORT 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. @@ -260,12 +225,28 @@ size_t cx_list_default_insert_array( * @return the number of elements actually inserted */ cx_attr_nonnull -cx_attr_export -size_t cx_list_default_insert_sorted( - struct cx_list_s *list, - const void *sorted_data, - size_t n -); +CX_EXPORT size_t cx_list_default_insert_sorted(struct cx_list_s *list, + const void *sorted_data, size_t n); + +/** + * Default implementation of an array insert where only elements are inserted when they don't exist in the list. + * + * This function is similar to cx_list_default_insert_sorted(), except it skips elements that are already in the list. + * + * @note The return value of this function denotes the number of elements from the @p sorted_data that are definitely + * contained in the list after completing the call. It is @em not the number of elements that were newly inserted. + * That means, when no error occurred, the return value should be @p n. + * + * 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 from the @p sorted_data that are definitely present in the list after this call + */ +cx_attr_nonnull +CX_EXPORT size_t cx_list_default_insert_unique(struct cx_list_s *list, + const void *sorted_data, size_t n); /** * Default unoptimized sort implementation. @@ -279,8 +260,7 @@ size_t cx_list_default_insert_sorted( * @param list the list that shall be sorted */ cx_attr_nonnull -cx_attr_export -void cx_list_default_sort(struct cx_list_s *list); +CX_EXPORT void cx_list_default_sort(struct cx_list_s *list); /** * Default unoptimized swap implementation. @@ -296,20 +276,19 @@ void cx_list_default_sort(struct cx_list_s *list); * allocation for the temporary buffer fails */ cx_attr_nonnull -cx_attr_export -int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); +CX_EXPORT int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); /** * Initializes a list struct. * * Only use this function if you are creating your own list implementation. * The purpose of this function is to be called in the initialization code - * of your list, to set certain members correctly. + * of your list to set certain members correctly. * * This is particularly important when you want your list to support * #CX_STORE_POINTERS as @p elem_size. This function will wrap the list * class accordingly and make sure that you can implement your list as if - * it was only storing objects and the wrapper will automatically enable + * it was only storing objects, and the wrapper will automatically enable * the feature of storing pointers. * * @par Example @@ -344,14 +323,9 @@ int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); * @param elem_size the size of one element */ cx_attr_nonnull_arg(1, 2, 3) -cx_attr_export -void cx_list_init( - struct cx_list_s *list, - struct cx_list_class_s *cl, - const struct cx_allocator_s *allocator, - cx_compare_func comparator, - size_t elem_size -); +CX_EXPORT void cx_list_init(struct cx_list_s *list, + struct cx_list_class_s *cl, const struct cx_allocator_s *allocator, + cx_compare_func comparator, size_t elem_size); /** * Returns the number of elements currently stored in the list. @@ -360,9 +334,7 @@ void cx_list_init( * @return the number of currently stored elements */ cx_attr_nonnull -static inline size_t cxListSize(const CxList *list) { - return list->collection.size; -} +CX_EXPORT size_t cxListSize(const CxList *list); /** * Adds an item to the end of the list. @@ -375,13 +347,7 @@ static inline size_t cxListSize(const CxList *list) { * @see cxListEmplace() */ cx_attr_nonnull -static inline int cxListAdd( - CxList *list, - const void *elem -) { - list->collection.sorted = false; - return list->cl->insert_element(list, list->collection.size, elem) == NULL; -} +CX_EXPORT int cxListAdd(CxList *list, const void *elem); /** * Adds multiple items to the end of the list. @@ -391,28 +357,22 @@ static inline int cxListAdd( * 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 + * 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 + * @see cxListEmplaceArray() */ cx_attr_nonnull -static inline size_t cxListAddArray( - CxList *list, - const void *array, - size_t n -) { - list->collection.sorted = false; - return list->cl->insert_array(list, list->collection.size, array, n); -} +CX_EXPORT size_t cxListAddArray(CxList *list, const void *array, size_t n); /** * Inserts an item at the specified index. * - * If @p index equals the list @c size, this is effectively cxListAdd(). + * If the @p index equals the list @c size, this is effectively cxListAdd(). * * @param list the list * @param index the index the element shall have @@ -424,14 +384,7 @@ static inline size_t cxListAddArray( * @see cxListEmplaceAt() */ cx_attr_nonnull -static inline int cxListInsert( - CxList *list, - size_t index, - const void *elem -) { - list->collection.sorted = false; - return list->cl->insert_element(list, index, elem) == NULL; -} +CX_EXPORT int cxListInsert(CxList *list, size_t index, const void *elem); /** * Allocates memory for an element at the specified index and returns a pointer to that memory. @@ -442,14 +395,11 @@ static inline int cxListInsert( * @param index the index where to emplace the element * @return a pointer to the allocated memory; @c NULL when the operation fails, or the index is out-of-bounds * @see cxListEmplace() + * @see cxListEmplaceArrayAt() * @see cxListInsert() */ cx_attr_nonnull -static inline void *cxListEmplaceAt(CxList *list, size_t index) { - list->collection.sorted = false; - return list->cl->insert_element(list, index, NULL); -} - +CX_EXPORT void *cxListEmplaceAt(CxList *list, size_t index); /** * Allocates memory for an element at the end of the list and returns a pointer to that memory. @@ -462,10 +412,46 @@ static inline void *cxListEmplaceAt(CxList *list, size_t index) { * @see cxListAdd() */ cx_attr_nonnull -static inline void *cxListEmplace(CxList *list) { - list->collection.sorted = false; - return list->cl->insert_element(list, list->collection.size, NULL); -} +CX_EXPORT void *cxListEmplace(CxList *list); + +/** + * Allocates memory for multiple elements and returns an iterator. + * + * The iterator will only iterate over the successfully allocated elements. + * The @c elem_count attribute is set to that number, and the @c index attribute + * will range from zero to @c elem_count minus one. + * + * @remark When the list is storing pointers, the iterator will iterate over + * the @c void** elements. + * + * @param list the list + * @param index the index where to insert the new data + * @param n the number of elements for which to allocate the memory + * @return an iterator, iterating over the new memory + * @see cxListEmplaceAt() + * @see cxListInsertArray() + */ +cx_attr_nonnull +CX_EXPORT CxIterator cxListEmplaceArrayAt(CxList *list, size_t index, size_t n); + +/** + * Allocates memory for multiple elements and returns an iterator. + * + * The iterator will only iterate over the successfully allocated elements. + * The @c elem_count attribute is set to that number, and the @c index attribute + * will range from zero to @c elem_count minus one. + * + * @remark When the list is storing pointers, the iterator will iterate over + * the @c void** elements. + * + * @param list the list + * @param n the number of elements for which to allocate the memory + * @return an iterator, iterating over the new memory + * @see cxListEmplace() + * @see cxListAddArray() + */ +cx_attr_nonnull +CX_EXPORT CxIterator cxListEmplaceArray(CxList *list, size_t n); /** * Inserts an item into a sorted list. @@ -478,18 +464,27 @@ static inline void *cxListEmplace(CxList *list) { * @retval non-zero memory allocation failure */ cx_attr_nonnull -static inline int cxListInsertSorted( - CxList *list, - const void *elem -) { - list->collection.sorted = true; // guaranteed by definition - const void *data = list->collection.store_pointer ? &elem : elem; - return list->cl->insert_sorted(list, data, 1) == 0; -} +CX_EXPORT int cxListInsertSorted(CxList *list, const void *elem); + +/** + * Inserts an item into a list if it does not exist. + * + * If the list is not sorted already, this function will check all elements + * and append the new element when it was not found. + * It is strongly recommended to use this function only on sorted lists, where + * the element, if it is not contained, is inserted at the correct position. + * + * @param list the list + * @param elem a pointer to the element to add + * @retval zero success (also when the element was already in the list) + * @retval non-zero memory allocation failure + */ +cx_attr_nonnull +CX_EXPORT int cxListInsertUnique(CxList *list, const void *elem); /** * Inserts multiple items to the list at the specified index. - * If @p index equals the list size, this is effectively cxListAddArray(). + * If the @p index equals the list size, this is effectively cxListAddArray(). * * This method is usually more efficient than invoking cxListInsert() * multiple times. @@ -497,7 +492,7 @@ static inline int cxListInsertSorted( * 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 + * If this list is storing pointers instead of objects, @p array is expected to * be an array of pointers. * * @param list the list @@ -505,28 +500,21 @@ static inline int cxListInsertSorted( * @param array a pointer to the elements to add * @param n the number of elements to add * @return the number of added elements + * @see cxListEmplaceArrayAt() */ cx_attr_nonnull -static inline size_t cxListInsertArray( - CxList *list, - size_t index, - const void *array, - size_t n -) { - list->collection.sorted = false; - return list->cl->insert_array(list, index, array, n); -} +CX_EXPORT size_t cxListInsertArray(CxList *list, size_t index, const void *array, size_t n); /** * Inserts a sorted array into a sorted list. * - * This method is usually more efficient than inserting each element separately, + * 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 + * If this list is storing pointers instead of objects, @p array is expected to * be an array of pointers. * * If the list is not sorted already, the behavior is undefined. @@ -537,14 +525,42 @@ static inline size_t cxListInsertArray( * @return the number of added elements */ cx_attr_nonnull -static inline size_t cxListInsertSortedArray( - CxList *list, - const void *array, - size_t n -) { - list->collection.sorted = true; // guaranteed by definition - return list->cl->insert_sorted(list, array, n); -} +CX_EXPORT size_t cxListInsertSortedArray(CxList *list, const void *array, size_t n); + +/** + * Inserts an array into a list, skipping duplicates. + * + * The @p list does not need to be sorted (in contrast to cxListInsertSortedArray()). + * But it is strongly recommended to use this function only on sorted lists, + * because otherwise it will fall back to an inefficient algorithm which inserts + * all elements one by one. + * If the @p list is not sorted, the @p array also does not need to be sorted. + * But when the @p list is sorted, the @p array must also be sorted. + * + * 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. + * + * @note The return value of this function denotes the number of elements + * from the @p sorted_data that are definitely contained in the list after + * completing the call. It is @em not the number of elements that were newly + * inserted. That means, when no error occurred, the return value should + * be @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 + * + * @return the number of elements from the @p sorted_data that are definitely present in the list after this call + */ +cx_attr_nonnull +CX_EXPORT size_t cxListInsertUniqueArray(CxList *list, const void *array, size_t n); /** * Inserts an element after the current location of the specified iterator. @@ -563,14 +579,7 @@ static inline size_t cxListInsertSortedArray( * @see cxListInsertBefore() */ cx_attr_nonnull -static inline int cxListInsertAfter( - CxIterator *iter, - const void *elem -) { - CxList* list = (CxList*)iter->src_handle.m; - list->collection.sorted = false; - return list->cl->insert_iter(iter, elem, 0); -} +CX_EXPORT int cxListInsertAfter(CxIterator *iter, const void *elem); /** * Inserts an element before the current location of the specified iterator. @@ -589,14 +598,7 @@ static inline int cxListInsertAfter( * @see cxListInsertAfter() */ cx_attr_nonnull -static inline int cxListInsertBefore( - CxIterator *iter, - const void *elem -) { - CxList* list = (CxList*)iter->src_handle.m; - list->collection.sorted = false; - return list->cl->insert_iter(iter, elem, 1); -} +CX_EXPORT int cxListInsertBefore(CxIterator *iter, const void *elem); /** * Removes the element at the specified index. @@ -610,12 +612,7 @@ static inline int cxListInsertBefore( * @retval non-zero index out of bounds */ cx_attr_nonnull -static inline int cxListRemove( - CxList *list, - size_t index -) { - return list->cl->remove(list, index, 1, NULL) == 0; -} +CX_EXPORT int cxListRemove(CxList *list, size_t index); /** * Removes and returns the element at the specified index. @@ -630,15 +627,8 @@ static inline int cxListRemove( * @retval zero success * @retval non-zero index out of bounds */ -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cxListRemoveAndGet( - CxList *list, - size_t index, - void *targetbuf -) { - return list->cl->remove(list, index, 1, targetbuf) == 0; -} +cx_attr_nonnull cx_attr_access_w(3) +CX_EXPORT int cxListRemoveAndGet(CxList *list, size_t index, void *targetbuf); /** * Removes and returns the first element of the list. @@ -650,18 +640,12 @@ static inline int cxListRemoveAndGet( * @param list the list * @param targetbuf a buffer where to copy the element * @retval zero success - * @retval non-zero list is empty + * @retval non-zero the list is empty * @see cxListPopFront() * @see cxListRemoveAndGetLast() */ -cx_attr_nonnull -cx_attr_access_w(2) -static inline int cxListRemoveAndGetFirst( - CxList *list, - void *targetbuf -) { - return list->cl->remove(list, 0, 1, targetbuf) == 0; -} +cx_attr_nonnull cx_attr_access_w(2) +CX_EXPORT int cxListRemoveAndGetFirst(CxList *list, void *targetbuf); /** * Removes and returns the first element of the list. @@ -675,7 +659,7 @@ static inline int cxListRemoveAndGetFirst( * @param list (@c CxList*) the list * @param targetbuf (@c void*) a buffer where to copy the element * @retval zero success - * @retval non-zero list is empty + * @retval non-zero the list is empty * @see cxListRemoveAndGetFirst() * @see cxListPop() */ @@ -692,17 +676,10 @@ static inline int cxListRemoveAndGetFirst( * @param list the list * @param targetbuf a buffer where to copy the element * @retval zero success - * @retval non-zero list is empty + * @retval non-zero the list is empty */ -cx_attr_nonnull -cx_attr_access_w(2) -static inline int cxListRemoveAndGetLast( - CxList *list, - void *targetbuf -) { - // note: index may wrap - member function will catch that - return list->cl->remove(list, list->collection.size - 1, 1, targetbuf) == 0; -} +cx_attr_nonnull cx_attr_access_w(2) +CX_EXPORT int cxListRemoveAndGetLast(CxList *list, void *targetbuf); /** * Removes and returns the last element of the list. @@ -716,7 +693,7 @@ static inline int cxListRemoveAndGetLast( * @param list (@c CxList*) the list * @param targetbuf (@c void*) a buffer where to copy the element * @retval zero success - * @retval non-zero list is empty + * @retval non-zero the list is empty * @see cxListRemoveAndGetLast() * @see cxListPopFront() */ @@ -738,13 +715,7 @@ static inline int cxListRemoveAndGetLast( * @return the actual number of removed elements */ cx_attr_nonnull -static inline size_t cxListRemoveArray( - CxList *list, - size_t index, - size_t num -) { - return list->cl->remove(list, index, num, NULL); -} +CX_EXPORT size_t cxListRemoveArray(CxList *list, size_t index, size_t num); /** * Removes and returns multiple elements starting at the specified index. @@ -759,16 +730,8 @@ static inline size_t cxListRemoveArray( * @param targetbuf a buffer where to copy the elements * @return the actual number of removed elements */ -cx_attr_nonnull -cx_attr_access_w(4) -static inline size_t cxListRemoveArrayAndGet( - CxList *list, - size_t index, - size_t num, - void *targetbuf -) { - return list->cl->remove(list, index, num, targetbuf); -} +cx_attr_nonnull cx_attr_access_w(4) +CX_EXPORT size_t cxListRemoveArrayAndGet(CxList *list, size_t index, size_t num, void *targetbuf); /** * Removes all elements from this list. @@ -779,10 +742,7 @@ static inline size_t cxListRemoveArrayAndGet( * @param list the list */ cx_attr_nonnull -static inline void cxListClear(CxList *list) { - list->collection.sorted = true; // empty lists are always sorted - list->cl->clear(list); -} +CX_EXPORT void cxListClear(CxList *list); /** * Swaps two items in the list. @@ -798,14 +758,7 @@ static inline void cxListClear(CxList *list) { * or the swap needed extra memory, but allocation failed */ cx_attr_nonnull -static inline int cxListSwap( - CxList *list, - size_t i, - size_t j -) { - list->collection.sorted = false; - return list->cl->swap(list, i, j); -} +CX_EXPORT int cxListSwap(CxList *list, size_t i, size_t j); /** * Returns a pointer to the element at the specified index. @@ -817,12 +770,7 @@ static inline int cxListSwap( * @return a pointer to the element or @c NULL if the index is out of bounds */ cx_attr_nonnull -static inline void *cxListAt( - const CxList *list, - size_t index -) { - return list->cl->at(list, index); -} +CX_EXPORT void *cxListAt(const CxList *list, size_t index); /** * Returns a pointer to the first element. @@ -833,9 +781,7 @@ static inline void *cxListAt( * @return a pointer to the first element or @c NULL if the list is empty */ cx_attr_nonnull -static inline void *cxListFirst(const CxList *list) { - return list->cl->at(list, 0); -} +CX_EXPORT void *cxListFirst(const CxList *list); /** * Returns a pointer to the last element. @@ -846,12 +792,13 @@ static inline void *cxListFirst(const CxList *list) { * @return a pointer to the last element or @c NULL if the list is empty */ cx_attr_nonnull -static inline void *cxListLast(const CxList *list) { - return list->cl->at(list, list->collection.size - 1); -} +CX_EXPORT void *cxListLast(const CxList *list); /** - * Sets the element at the specified index in the list + * Sets the element at the specified index in the list. + * + * This overwrites the element in-place without calling any destructor + * on the overwritten element. * * @param list the list to set the element in * @param index the index to set the element at @@ -860,91 +807,35 @@ static inline void *cxListLast(const CxList *list) { * @retval non-zero when index is out of bounds */ cx_attr_nonnull -cx_attr_export -int cxListSet( - CxList *list, - size_t index, - const void *elem -); +CX_EXPORT int cxListSet(CxList *list, size_t index, const void *elem); /** * 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. + * If the index is out of range or @p list is @c NULL, a past-the-end iterator will be returned. * * @param list the list * @param index the index where the iterator shall point at * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard -static inline CxIterator cxListIteratorAt( - const CxList *list, - size_t index -) { - return list->cl->iterator(list, index, false); -} +CX_EXPORT CxIterator cxListIteratorAt(const CxList *list, size_t index); /** * Returns a backwards iterator pointing to the item at the specified index. * * The returned iterator is position-aware. * - * If the index is out of range, a past-the-end iterator will be returned. - * - * @param list the list - * @param index the index where the iterator shall point at - * @return a new iterator - */ -cx_attr_nonnull -cx_attr_nodiscard -static inline CxIterator cxListBackwardsIteratorAt( - const CxList *list, - size_t index -) { - return list->cl->iterator(list, index, true); -} - -/** - * Returns a mutating iterator pointing to the item at the specified index. - * - * The returned iterator is position-aware. - * - * If the index is out of range, a past-the-end iterator will be returned. - * - * @param list the list - * @param index the index where the iterator shall point at - * @return a new iterator - */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -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. + * If the index is out of range or @p list is @c NULL, a past-the-end iterator will be returned. * * @param list the list * @param index the index where the iterator shall point at * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard -cx_attr_export -CxIterator cxListMutBackwardsIteratorAt( - CxList *list, - size_t index -); +CX_EXPORT CxIterator cxListBackwardsIteratorAt(const CxList *list, size_t index); /** * Returns an iterator pointing to the first item of the list. @@ -957,27 +848,7 @@ CxIterator cxListMutBackwardsIteratorAt( * @return a new iterator */ cx_attr_nodiscard -static inline CxIterator cxListIterator(const CxList *list) { - if (list == NULL) list = cxEmptyList; - 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 or @c NULL, a past-the-end iterator will be returned. - * - * @param list the list - * @return a new iterator - */ -cx_attr_nodiscard -static inline CxIterator cxListMutIterator(CxList *list) { - if (list == NULL) list = cxEmptyList; - return cxListMutIteratorAt(list, 0); -} - +CX_EXPORT CxIterator cxListIterator(const CxList *list); /** * Returns a backwards iterator pointing to the last item of the list. @@ -990,26 +861,7 @@ static inline CxIterator cxListMutIterator(CxList *list) { * @return a new iterator */ cx_attr_nodiscard -static inline CxIterator cxListBackwardsIterator(const CxList *list) { - if (list == NULL) list = cxEmptyList; - 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 or @c NULL, a past-the-end iterator will be returned. - * - * @param list the list - * @return a new iterator - */ -cx_attr_nodiscard -static inline CxIterator cxListMutBackwardsIterator(CxList *list) { - if (list == NULL) list = cxEmptyList; - return cxListMutBackwardsIteratorAt(list, list->collection.size - 1); -} +CX_EXPORT CxIterator cxListBackwardsIterator(const CxList *list); /** * Returns the index of the first element that equals @p elem. @@ -1022,17 +874,11 @@ static inline CxIterator cxListMutBackwardsIterator(CxList *list) { * @see cxListIndexValid() * @see cxListContains() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline size_t cxListFind( - const CxList *list, - const void *elem -) { - return list->cl->find_remove((CxList*)list, elem, false); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT size_t cxListFind(const CxList *list, const void *elem); /** - * Checks, if the list contains the specified element. + * Checks if the list contains the specified element. * * The elements are compared with the list's comparator function. * @@ -1042,14 +888,8 @@ static inline size_t cxListFind( * @retval false if the element is not contained * @see cxListFind() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline bool cxListContains( - const CxList* list, - const void* elem -) { - return list->cl->find_remove((CxList*)list, elem, false) < list->collection.size; -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT bool cxListContains(const CxList* list, const void* elem); /** * Checks if the specified index is within bounds. @@ -1059,11 +899,8 @@ static inline bool cxListContains( * @retval true if the index is within bounds * @retval false if the index is out of bounds */ -cx_attr_nonnull -cx_attr_nodiscard -static inline bool cxListIndexValid(const CxList *list, size_t index) { - return index < list->collection.size; -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT bool cxListIndexValid(const CxList *list, size_t index); /** * Removes and returns the index of the first element that equals @p elem. @@ -1077,12 +914,7 @@ static inline bool cxListIndexValid(const CxList *list, size_t index) { * @see cxListIndexValid() */ cx_attr_nonnull -static inline size_t cxListFindRemove( - CxList *list, - const void *elem -) { - return list->cl->find_remove(list, elem, true); -} +CX_EXPORT size_t cxListFindRemove(CxList *list, const void *elem); /** * Sorts the list. @@ -1092,11 +924,7 @@ static inline size_t cxListFindRemove( * @param list the list */ cx_attr_nonnull -static inline void cxListSort(CxList *list) { - if (list->collection.sorted) return; - list->cl->sort(list); - list->collection.sorted = true; -} +CX_EXPORT void cxListSort(CxList *list); /** * Reverses the order of the items. @@ -1104,11 +932,7 @@ static inline void cxListSort(CxList *list) { * @param list the list */ cx_attr_nonnull -static inline void cxListReverse(CxList *list) { - // still sorted, but not according to the cmp_func - list->collection.sorted = false; - list->cl->reverse(list); -} +CX_EXPORT void cxListReverse(CxList *list); /** * Compares a list to another list of the same type. @@ -1119,28 +943,22 @@ static inline void cxListReverse(CxList *list) { * @param list the list * @param other the list to compare to * @retval zero both lists are equal element wise - * @retval negative the first list is smaller + * @retval negative the first list is smaller, * or the first non-equal element in the first list is smaller * @retval positive the first list is larger * or the first non-equal element in the first list is larger */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cxListCompare( - const CxList *list, - const CxList *other -); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cxListCompare(const CxList *list, const CxList *other); /** * Deallocates the memory of the specified list structure. * * Also calls the content destructor functions for each element, if specified. * - * @param list the list which shall be freed + * @param list the list that shall be freed */ -cx_attr_export -void cxListFree(CxList *list); +CX_EXPORT void cxListFree(CxList *list); #ifdef __cplusplus diff --git a/ucx/cx/map.h b/ucx/cx/map.h index 36067d5..0b3060d 100644 --- a/ucx/cx/map.h +++ b/ucx/cx/map.h @@ -111,16 +111,7 @@ struct cx_map_iterator_s { /** * Handle for the source map. */ - union { - /** - * Access for mutating iterators. - */ - CxMap *m; - /** - * Access for normal iterators. - */ - const CxMap *c; - } map; + CxMap *map; /** * Handle for the current element. @@ -185,20 +176,16 @@ struct cx_map_class_s { /** * Add or overwrite an element. + * If the @p value is @c NULL, the implementation + * shall only allocate memory instead of adding an existing value to the map. + * Returns a pointer to the allocated memory or @c NULL if allocation fails. */ - int (*put)( - CxMap *map, - CxHashKey key, - void *value - ); + void *(*put)(CxMap *map, CxHashKey key, void *value); /** * Returns an element. */ - void *(*get)( - const CxMap *map, - CxHashKey key - ); + void *(*get)(const CxMap *map, CxHashKey key); /** * Removes an element. @@ -210,11 +197,7 @@ struct cx_map_class_s { * The function SHALL return zero when the @p key was found and * non-zero, otherwise. */ - int (*remove)( - CxMap *map, - CxHashKey key, - void *targetbuf - ); + int (*remove)(CxMap *map, CxHashKey key, void *targetbuf); /** * Creates an iterator for this map. @@ -227,11 +210,10 @@ struct cx_map_class_s { * * Writing to that map is not allowed. * - * You can use this is a placeholder for initializing CxMap pointers + * You can use this as a placeholder for initializing CxMap pointers * for which you do not want to reserve memory right from the beginning. */ -cx_attr_export -extern CxMap *const cxEmptyMap; +CX_EXPORT extern CxMap *const cxEmptyMap; /** * Deallocates the memory of the specified map. @@ -240,8 +222,7 @@ extern CxMap *const cxEmptyMap; * * @param map the map to be freed */ -cx_attr_export -void cxMapFree(CxMap *map); +CX_EXPORT void cxMapFree(CxMap *map); /** @@ -252,9 +233,7 @@ void cxMapFree(CxMap *map); * @param map the map to be cleared */ cx_attr_nonnull -static inline void cxMapClear(CxMap *map) { - map->cl->clear(map); -} +CX_EXPORT void cxMapClear(CxMap *map); /** * Returns the number of elements in this map. @@ -263,9 +242,7 @@ static inline void cxMapClear(CxMap *map) { * @return the number of stored elements */ cx_attr_nonnull -static inline size_t cxMapSize(const CxMap *map) { - return map->collection.size; -} +CX_EXPORT size_t cxMapSize(const CxMap *map); /** * Creates a value iterator for a map. @@ -277,308 +254,66 @@ static inline size_t cxMapSize(const CxMap *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 + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored values */ -cx_attr_nonnull cx_attr_nodiscard -static inline CxMapIterator cxMapIteratorValues(const CxMap *map) { - return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); -} +CX_EXPORT CxMapIterator cxMapIteratorValues(const CxMap *map); /** * Creates a key iterator for a map. * - * The elements of the iterator are keys of type CxHashKey and the pointer returned + * The elements of the iterator are keys of type CxHashKey, and the pointer returned * during iterator shall be treated as @c const @c 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 + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored keys */ -cx_attr_nonnull cx_attr_nodiscard -static inline CxMapIterator cxMapIteratorKeys(const CxMap *map) { - return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); -} +CX_EXPORT CxMapIterator cxMapIteratorKeys(const CxMap *map); /** * Creates an iterator for a map. * - * The elements of the iterator are key/value pairs of type CxMapEntry and the pointer returned + * The elements of the iterator are key/value pairs of type CxMapEntry, and the pointer returned * during iterator shall be treated as @c const @c 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 + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored entries * @see cxMapIteratorKeys() * @see cxMapIteratorValues() */ -cx_attr_nonnull cx_attr_nodiscard -static inline CxMapIterator cxMapIterator(const CxMap *map) { - return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); -} - +CX_EXPORT CxMapIterator cxMapIterator(const CxMap *map); /** - * Creates a mutating iterator over the values of a map. - * - * When the map is storing pointers, those pointers are returned. - * Otherwise, the iterator iterates over pointers to the memory within the map where the - * respective elements are stored. - * - * @note An iterator iterates over all elements successively. Therefore, the order - * highly depends on the map implementation and may change arbitrarily when the contents change. - * - * @param map the map to create the iterator for - * @return an iterator for the currently stored values - */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -CxMapIterator cxMapMutIteratorValues(CxMap *map); - -/** - * Creates a mutating iterator over the keys of a map. - * - * The elements of the iterator are keys of type CxHashKey and the pointer returned - * during iterator shall be treated as @c const @c 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. + * Puts a key/value-pair into the map. * - * @param map the map to create the iterator for - * @return an iterator for the currently stored keys - */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -CxMapIterator cxMapMutIteratorKeys(CxMap *map); - -/** - * Creates a mutating iterator for a map. + * A possible existing value will be overwritten. + * If destructor functions are specified, they are called for + * the overwritten element. * - * The elements of the iterator are key/value pairs of type CxMapEntry and the pointer returned - * during iterator shall be treated as @c const @c CxMapEntry* . + * If this map is storing pointers, the @p value pointer is written + * to the map. Otherwise, the memory is copied from @p value with + * memcpy(). * - * @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. + * The @p key is always copied. * - * @param map the map to create the iterator for - * @return an iterator for the currently stored entries - * @see cxMapMutIteratorKeys() - * @see cxMapMutIteratorValues() - */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -CxMapIterator cxMapMutIterator(CxMap *map); - -#ifdef __cplusplus -} // end the extern "C" block here, because we want to start overloading -cx_attr_nonnull -static inline int cxMapPut( - CxMap *map, - CxHashKey const &key, - void *value -) { - return map->cl->put(map, key, value); -} - -cx_attr_nonnull -static inline int cxMapPut( - CxMap *map, - cxstring const &key, - void *value -) { - return map->cl->put(map, cx_hash_key_cxstr(key), value); -} - -cx_attr_nonnull -static inline int cxMapPut( - CxMap *map, - cxmutstr const &key, - void *value -) { - return map->cl->put(map, cx_hash_key_cxstr(key), value); -} - -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cxMapPut( - CxMap *map, - const char *key, - void *value -) { - return map->cl->put(map, cx_hash_key_str(key), value); -} - -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxMapGet( - const CxMap *map, - CxHashKey const &key -) { - return map->cl->get(map, key); -} - -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxMapGet( - const CxMap *map, - cxstring const &key -) { - return map->cl->get(map, cx_hash_key_cxstr(key)); -} - -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxMapGet( - const CxMap *map, - cxmutstr const &key -) { - return map->cl->get(map, cx_hash_key_cxstr(key)); -} - -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_cstr_arg(2) -static inline void *cxMapGet( - const CxMap *map, - const char *key -) { - return map->cl->get(map, cx_hash_key_str(key)); -} - -cx_attr_nonnull -static inline int cxMapRemove( - CxMap *map, - CxHashKey const &key -) { - return map->cl->remove(map, key, nullptr); -} - -cx_attr_nonnull -static inline int cxMapRemove( - CxMap *map, - cxstring const &key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr); -} - -cx_attr_nonnull -static inline int cxMapRemove( - CxMap *map, - cxmutstr const &key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr); -} - -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cxMapRemove( - CxMap *map, - const char *key -) { - return map->cl->remove(map, cx_hash_key_str(key), nullptr); -} - -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cxMapRemoveAndGet( - CxMap *map, - CxHashKey key, - void *targetbuf -) { - return map->cl->remove(map, key, targetbuf); -} - -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cxMapRemoveAndGet( - CxMap *map, - cxstring key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); -} - -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cxMapRemoveAndGet( - CxMap *map, - cxmutstr key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); -} - -cx_attr_nonnull -cx_attr_access_w(3) -cx_attr_cstr_arg(2) -static inline int cxMapRemoveAndGet( - CxMap *map, - const char *key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_str(key), targetbuf); -} - -#else // __cplusplus - -/** - * @copydoc cxMapPut() - */ -cx_attr_nonnull -static inline int cx_map_put( - CxMap *map, - CxHashKey key, - void *value -) { - return map->cl->put(map, key, value); -} - -/** - * @copydoc cxMapPut() - */ -cx_attr_nonnull -static inline int cx_map_put_cxstr( - CxMap *map, - cxstring key, - void *value -) { - return map->cl->put(map, cx_hash_key_cxstr(key), value); -} - -/** - * @copydoc cxMapPut() - */ -cx_attr_nonnull -static inline int cx_map_put_mustr( - CxMap *map, - cxmutstr key, - void *value -) { - return map->cl->put(map, cx_hash_key_cxstr(key), value); -} - -/** - * @copydoc cxMapPut() + * @param map the map + * @param key the key + * @param value the value + * @retval zero success + * @retval non-zero value on memory allocation failure + * @see cxMapPut() */ cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cx_map_put_str( - CxMap *map, - const char *key, - void *value -) { - return map->cl->put(map, cx_hash_key_str(key), value); -} +CX_EXPORT int cx_map_put(CxMap *map, CxHashKey key, void *value); /** * Puts a key/value-pair into the map. @@ -594,67 +329,71 @@ static inline int cx_map_put_str( * The @p key is always copied. * * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param key (any supported key type) the key * @param value (@c void*) the value * @retval zero success * @retval non-zero value on memory allocation failure + * @see CX_HASH_KEY() */ -#define cxMapPut(map, key, value) _Generic((key), \ - CxHashKey: cx_map_put, \ - cxstring: cx_map_put_cxstr, \ - cxmutstr: cx_map_put_mustr, \ - char*: cx_map_put_str, \ - const char*: cx_map_put_str) \ - (map, key, value) - -/** - * @copydoc cxMapGet() - */ -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cx_map_get( - const CxMap *map, - CxHashKey key -) { - return map->cl->get(map, key); -} +#define cxMapPut(map, key, value) cx_map_put(map, CX_HASH_KEY(key), value) /** - * @copydoc cxMapGet() + * Allocates memory for a value in the map associated with the specified key. + * + * A possible existing value will be overwritten. + * If destructor functions are specified, they are called for + * the overwritten element. + * + * If the map is storing pointers, this function returns a @c void** pointer, + * meaning a pointer to that pointer. + * + * The @p key is always copied. + * + * @param map the map + * @param key the key + * @return the pointer to the allocated memory or @c NULL if allocation fails + * @retval zero success + * @retval non-zero value on memory allocation failure + * @see cxMapEmplace() */ cx_attr_nonnull -cx_attr_nodiscard -static inline void *cx_map_get_cxstr( - const CxMap *map, - cxstring key -) { - return map->cl->get(map, cx_hash_key_cxstr(key)); -} +CX_EXPORT void *cx_map_emplace(CxMap *map, CxHashKey key); /** - * @copydoc cxMapGet() + * Allocates memory for a value in the map associated with the specified key. + * + * A possible existing value will be overwritten. + * If destructor functions are specified, they are called for + * the overwritten element. + * + * If the map is storing pointers, this function returns a @c void** pointer, + * meaning a pointer to that pointer. + * + * The @p key is always copied. + * + * @param map (@c CxMap*) the map + * @param key (any supported key type) the key + * @return the pointer to the allocated memory or @c NULL if allocation fails + * @retval zero success + * @retval non-zero value on memory allocation failure + * @see CX_HASH_KEY() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cx_map_get_mustr( - const CxMap *map, - cxmutstr key -) { - return map->cl->get(map, cx_hash_key_cxstr(key)); -} +#define cxMapEmplace(map, key) cx_map_emplace(map, CX_HASH_KEY(key)) /** - * @copydoc cxMapGet() + * Retrieves a value by using a key. + * + * If this map is storing pointers, the stored pointer is returned. + * Otherwise, a pointer to the element within the map's memory + * is returned (which is valid as long as the element stays in the map). + * + * @param map the map + * @param key the key + * @return the value + * @see cxMapGet() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_cstr_arg(2) -static inline void *cx_map_get_str( - const CxMap *map, - const char *key -) { - return map->cl->get(map, cx_hash_key_str(key)); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT void *cx_map_get(const CxMap *map, CxHashKey key); /** * Retrieves a value by using a key. @@ -664,134 +403,44 @@ static inline void *cx_map_get_str( * is returned (which is valid as long as the element stays in the map). * * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param key (any supported key type) the key * @return (@c void*) the value + * @see CX_HASH_KEY() */ -#define cxMapGet(map, key) _Generic((key), \ - CxHashKey: cx_map_get, \ - cxstring: cx_map_get_cxstr, \ - cxmutstr: cx_map_get_mustr, \ - char*: cx_map_get_str, \ - const char*: cx_map_get_str) \ - (map, key) - -/** - * @copydoc cxMapRemove() - */ -cx_attr_nonnull -static inline int cx_map_remove( - CxMap *map, - CxHashKey key -) { - return map->cl->remove(map, key, NULL); -} - -/** - * @copydoc cxMapRemove() - */ -cx_attr_nonnull -static inline int cx_map_remove_cxstr( - CxMap *map, - cxstring key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); -} - -/** - * @copydoc cxMapRemove() - */ -cx_attr_nonnull -static inline int cx_map_remove_mustr( - CxMap *map, - cxmutstr key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); -} +#define cxMapGet(map, key) cx_map_get(map, CX_HASH_KEY(key)) /** - * @copydoc cxMapRemove() + * Removes a key/value-pair from the map by using the key. + * + * Invokes the destructor functions, if any, on the removed element if and only if the + * @p targetbuf is @c NULL. + * + * @param map the map + * @param key the key + * @param targetbuf the optional buffer where the removed element shall be copied to + * @retval zero success + * @retval non-zero the key was not found + * + * @see cxMapRemove() + * @see cxMapRemoveAndGet() */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cx_map_remove_str( - CxMap *map, - const char *key -) { - return map->cl->remove(map, cx_hash_key_str(key), NULL); -} +cx_attr_nonnull_arg(1) +CX_EXPORT int cx_map_remove(CxMap *map, CxHashKey key, void *targetbuf); /** * Removes a key/value-pair from the map by using the key. * - * Always invokes the destructors functions, if any, on the removed element. + * Always invokes the destructor functions, if any, on the removed element. * * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param key (any supported key type) the key * @retval zero success * @retval non-zero the key was not found * * @see cxMapRemoveAndGet() + * @see CX_HASH_KEY() */ -#define cxMapRemove(map, key) _Generic((key), \ - CxHashKey: cx_map_remove, \ - cxstring: cx_map_remove_cxstr, \ - cxmutstr: cx_map_remove_mustr, \ - char*: cx_map_remove_str, \ - const char*: cx_map_remove_str) \ - (map, key) - -/** - * @copydoc cxMapRemoveAndGet() - */ -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cx_map_remove_and_get( - CxMap *map, - CxHashKey key, - void *targetbuf -) { - return map->cl->remove(map, key, targetbuf); -} - -/** - * @copydoc cxMapRemoveAndGet() - */ -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cx_map_remove_and_get_cxstr( - CxMap *map, - cxstring key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); -} - -/** - * @copydoc cxMapRemoveAndGet() - */ -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cx_map_remove_and_get_mustr( - CxMap *map, - cxmutstr key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); -} - -/** - * @copydoc cxMapRemoveAndGet() - */ -cx_attr_nonnull -cx_attr_access_w(3) -cx_attr_cstr_arg(2) -static inline int cx_map_remove_and_get_str( - CxMap *map, - const char *key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_str(key), targetbuf); -} +#define cxMapRemove(map, key) cx_map_remove(map, CX_HASH_KEY(key), NULL) /** * Removes a key/value-pair from the map by using the key. @@ -805,21 +454,18 @@ static inline int cx_map_remove_and_get_str( * and not the object it points to. * * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param key (any supported key type) the key * @param targetbuf (@c void*) the buffer where the element shall be copied to * @retval zero success * @retval non-zero the key was not found * * @see cxMapRemove() + * @see CX_HASH_KEY() */ -#define cxMapRemoveAndGet(map, key, targetbuf) _Generic((key), \ - CxHashKey: cx_map_remove_and_get, \ - cxstring: cx_map_remove_and_get_cxstr, \ - cxmutstr: cx_map_remove_and_get_mustr, \ - char*: cx_map_remove_and_get_str, \ - const char*: cx_map_remove_and_get_str) \ - (map, key, targetbuf) - -#endif // __cplusplus +#define cxMapRemoveAndGet(map, key, targetbuf) cx_map_remove(map, CX_HASH_KEY(key), targetbuf) + +#ifdef __cplusplus +} // extern "C" +#endif #endif // UCX_MAP_H diff --git a/ucx/cx/mempool.h b/ucx/cx/mempool.h index 5d8d284..6530ea7 100644 --- a/ucx/cx/mempool.h +++ b/ucx/cx/mempool.h @@ -156,8 +156,7 @@ typedef struct cx_mempool_s CxMempool; * * @param pool the memory pool to free */ -cx_attr_export -void cxMempoolFree(CxMempool *pool); +CX_EXPORT void cxMempoolFree(CxMempool *pool); /** * Creates an array-based memory pool. @@ -169,11 +168,8 @@ void cxMempoolFree(CxMempool *pool); * @param type the type of memory pool * @return the created memory pool or @c NULL if allocation failed */ -cx_attr_nodiscard -cx_attr_malloc -cx_attr_dealloc(cxMempoolFree, 1) -cx_attr_export -CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type); +cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMempoolFree, 1) +CX_EXPORT CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type); /** * Creates a basic array-based memory pool. @@ -212,8 +208,7 @@ CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type); * @param fnc the destructor that shall be applied to all memory blocks */ cx_attr_nonnull_arg(1) -cx_attr_export -void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc); +CX_EXPORT void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc); /** * Sets the global destructor for all memory blocks within the specified pool. @@ -223,8 +218,7 @@ void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc); * @param data additional data for the destructor function */ cx_attr_nonnull_arg(1) -cx_attr_export -void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data); +CX_EXPORT void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data); /** * Sets the destructor function for a specific allocated memory object. @@ -237,11 +231,7 @@ void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void * * @param fnc the destructor function */ cx_attr_nonnull -cx_attr_export -void cxMempoolSetDestructor( - void *memory, - cx_destructor_func fnc -); +CX_EXPORT void cxMempoolSetDestructor(void *memory, cx_destructor_func fnc); /** * Sets the destructor function for a specific allocated memory object. @@ -255,12 +245,7 @@ void cxMempoolSetDestructor( * @param data additional data for the destructor function */ cx_attr_nonnull -cx_attr_export -void cxMempoolSetDestructor2( - void *memory, - cx_destructor_func2 fnc, - void *data -); +CX_EXPORT void cxMempoolSetDestructor2(void *memory, cx_destructor_func2 fnc, void *data); /** * Removes the destructor function for a specific allocated memory object. @@ -271,8 +256,7 @@ void cxMempoolSetDestructor2( * @param memory the object allocated in the pool */ cx_attr_nonnull -cx_attr_export -void cxMempoolRemoveDestructor(void *memory); +CX_EXPORT void cxMempoolRemoveDestructor(void *memory); /** * Removes the destructor function for a specific allocated memory object. @@ -283,8 +267,7 @@ void cxMempoolRemoveDestructor(void *memory); * @param memory the object allocated in the pool */ cx_attr_nonnull -cx_attr_export -void cxMempoolRemoveDestructor2(void *memory); +CX_EXPORT void cxMempoolRemoveDestructor2(void *memory); /** * Registers foreign memory with this pool. @@ -302,12 +285,7 @@ void cxMempoolRemoveDestructor2(void *memory); * @retval non-zero failure */ cx_attr_nonnull -cx_attr_export -int cxMempoolRegister( - CxMempool *pool, - void *memory, - cx_destructor_func destr -); +CX_EXPORT int cxMempoolRegister(CxMempool *pool, void *memory, cx_destructor_func destr); /** @@ -330,13 +308,7 @@ int cxMempoolRegister( * @retval non-zero failure */ cx_attr_nonnull -cx_attr_export -int cxMempoolRegister2( - CxMempool *pool, - void *memory, - cx_destructor_func2 destr, - void *data -); +CX_EXPORT int cxMempoolRegister2(CxMempool *pool, void *memory, cx_destructor_func2 destr, void *data); /** * Transfers all the memory managed by one pool to another. @@ -354,11 +326,7 @@ int cxMempoolRegister2( * @retval non-zero allocation failure or incompatible pools */ cx_attr_nonnull -cx_attr_export -int cxMempoolTransfer( - CxMempool *source, - CxMempool *dest -); +CX_EXPORT int cxMempoolTransfer(CxMempool *source, CxMempool *dest); /** * Transfers an object from one pool to another. @@ -375,12 +343,7 @@ int cxMempoolTransfer( * @retval non-zero failure, or the object was not found in the source pool, or the pools are incompatible */ cx_attr_nonnull -cx_attr_export -int cxMempoolTransferObject( - CxMempool *source, - CxMempool *dest, - const void *obj -); +CX_EXPORT int cxMempoolTransferObject(CxMempool *source, CxMempool *dest, const void *obj); #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/printf.h b/ucx/cx/printf.h index 4ffd05f..7311ab4 100644 --- a/ucx/cx/printf.h +++ b/ucx/cx/printf.h @@ -27,7 +27,7 @@ */ /** * @file printf.h - * @brief Wrapper for write functions with a printf-like interface. + * @brief Wrapper for write-functions with a printf-like interface. * @author Mike Becker * @author Olaf Wintermann * @copyright 2-Clause BSD License @@ -56,8 +56,7 @@ extern "C" { /** * The maximum string length that fits into stack memory. */ -cx_attr_export -extern const unsigned cx_printf_sbo_size; +CX_EXPORT extern const unsigned cx_printf_sbo_size; /** * A @c fprintf like function which writes the output to a stream by @@ -69,16 +68,8 @@ extern const unsigned cx_printf_sbo_size; * @param ... additional arguments * @return the total number of bytes written or an error code from stdlib printf implementation */ -cx_attr_nonnull_arg(1, 2, 3) -cx_attr_printf(3, 4) -cx_attr_cstr_arg(3) -cx_attr_export -int cx_fprintf( - void *stream, - cx_write_func wfc, - const char *fmt, - ... -); +cx_attr_nonnull_arg(1, 2, 3) cx_attr_printf(3, 4) cx_attr_cstr_arg(3) +CX_EXPORT 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 @@ -91,18 +82,11 @@ int cx_fprintf( * @return the total number of bytes written or an error code from stdlib printf implementation * @see cx_fprintf() */ -cx_attr_nonnull -cx_attr_cstr_arg(3) -cx_attr_export -int cx_vfprintf( - void *stream, - cx_write_func wfc, - const char *fmt, - va_list ap -); +cx_attr_nonnull cx_attr_cstr_arg(3) +CX_EXPORT 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 + * An @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, @@ -115,18 +99,11 @@ int cx_vfprintf( * @return the formatted string * @see cx_strfree_a() */ -cx_attr_nonnull_arg(1, 2) -cx_attr_printf(2, 3) -cx_attr_cstr_arg(2) -cx_attr_export -cxmutstr cx_asprintf_a( - const CxAllocator *allocator, - const char *fmt, - ... -); +cx_attr_nonnull_arg(1, 2) cx_attr_printf(2, 3) cx_attr_cstr_arg(2) +CX_EXPORT cxmutstr cx_asprintf_a(const CxAllocator *allocator, const char *fmt, ...); /** - * A @c asprintf like function which allocates space for a string + * An @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, @@ -138,8 +115,7 @@ cxmutstr cx_asprintf_a( * @return (@c cxmutstr) the formatted string * @see cx_strfree() */ -#define cx_asprintf(fmt, ...) \ - cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__) +#define cx_asprintf(fmt, ...) cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__) /** * A @c vasprintf like function which allocates space for a string @@ -155,21 +131,15 @@ cxmutstr cx_asprintf_a( * @return the formatted string * @see cx_asprintf_a() */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -cx_attr_export -cxmutstr cx_vasprintf_a( - const CxAllocator *allocator, - const char *fmt, - va_list ap -); +cx_attr_nonnull cx_attr_cstr_arg(2) +CX_EXPORT cxmutstr cx_vasprintf_a(const CxAllocator *allocator, const char *fmt, va_list ap); /** * A @c vasprintf like function which allocates space for a string * the result is written to. * * @note The resulting string is guaranteed to be zero-terminated, - * unless there was in error, in which case the string's pointer + * unless there was an error, in which case the string's pointer * will be @c NULL. * * @param fmt (@c char*) format string @@ -189,8 +159,7 @@ cxmutstr cx_vasprintf_a( * @see cx_fprintf() * @see cxBufferWrite() */ -#define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, \ - cxBufferWriteFunc, fmt, __VA_ARGS__) +#define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, cxBufferWriteFunc, fmt, __VA_ARGS__) /** @@ -204,7 +173,7 @@ cxmutstr cx_vasprintf_a( * @param len (@c size_t*) a pointer to the length of the buffer * @param fmt (@c char*) the format string * @param ... additional arguments - * @return (@c int) the length of produced string or an error code from stdlib printf implementation + * @return (@c int) the length of the produced string or an error code from stdlib printf implementation */ #define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__) @@ -222,19 +191,10 @@ cxmutstr cx_vasprintf_a( * @param len a pointer to the length of the buffer * @param fmt the format string * @param ... additional arguments - * @return the length of produced string or an error code from stdlib printf implementation + * @return the length of the produced string or an error code from stdlib printf implementation */ -cx_attr_nonnull_arg(1, 2, 3, 4) -cx_attr_printf(4, 5) -cx_attr_cstr_arg(4) -cx_attr_export -int cx_sprintf_a( - const CxAllocator *alloc, - char **str, - size_t *len, - const char *fmt, - ... -); +cx_attr_nonnull_arg(1, 2, 3, 4) cx_attr_printf(4, 5) cx_attr_cstr_arg(4) +CX_EXPORT int cx_sprintf_a(const CxAllocator *alloc, char **str, size_t *len, const char *fmt, ...); /** @@ -248,7 +208,7 @@ int cx_sprintf_a( * @param len (@c size_t*) a pointer to the length of the buffer * @param fmt (@c char*) the format string * @param ap (@c va_list) argument list - * @return (@c int) the length of produced string or an error code from stdlib printf implementation + * @return (@c int) the length of the produced string or an error code from stdlib printf implementation */ #define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap) @@ -266,20 +226,10 @@ int cx_sprintf_a( * @param len a pointer to the length of the buffer * @param fmt the format string * @param ap argument list - * @return the length of produced string or an error code from stdlib printf implementation + * @return the length of the produced string or an error code from stdlib printf implementation */ -cx_attr_nonnull -cx_attr_cstr_arg(4) -cx_attr_access_rw(2) -cx_attr_access_rw(3) -cx_attr_export -int cx_vsprintf_a( - const CxAllocator *alloc, - char **str, - size_t *len, - const char *fmt, - va_list ap -); +cx_attr_nonnull cx_attr_cstr_arg(4) cx_attr_access_rw(2) cx_attr_access_rw(3) +CX_EXPORT int cx_vsprintf_a(const CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap); /** @@ -300,7 +250,7 @@ int cx_vsprintf_a( * @param str (@c char**) a pointer where the location of the result shall be stored * @param fmt (@c char*) the format string * @param ... additional arguments - * @return (@c int) the length of produced string or an error code from stdlib printf implementation + * @return (@c int) the length of the produced string or an error code from stdlib printf implementation */ #define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__) @@ -323,23 +273,11 @@ int cx_vsprintf_a( * @param str a pointer where the location of the result shall be stored * @param fmt the format string * @param ... additional arguments - * @return the length of produced string or an error code from stdlib printf implementation + * @return the length of the produced string or an error code from stdlib printf implementation */ -cx_attr_nonnull_arg(1, 2, 4, 5) -cx_attr_printf(5, 6) -cx_attr_cstr_arg(5) -cx_attr_access_rw(2) -cx_attr_access_rw(3) -cx_attr_access_rw(4) -cx_attr_export -int cx_sprintf_sa( - const CxAllocator *alloc, - char *buf, - size_t *len, - char **str, - const char *fmt, - ... -); +cx_attr_nonnull_arg(1, 2, 4, 5) cx_attr_printf(5, 6) cx_attr_cstr_arg(5) +cx_attr_access_rw(2) cx_attr_access_rw(3) cx_attr_access_rw(4) +CX_EXPORT int cx_sprintf_sa(const 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. @@ -359,7 +297,7 @@ int cx_sprintf_sa( * @param str (@c char**) a pointer where the location of the result shall be stored * @param fmt (@c char*) the format string * @param ap (@c va_list) argument list - * @return (@c int) the length of produced string or an error code from stdlib printf implementation + * @return (@c int) the length of the produced string or an error code from stdlib printf implementation */ #define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap) @@ -382,19 +320,10 @@ int cx_sprintf_sa( * @param str a pointer where the location of the result shall be stored * @param fmt the format string * @param ap argument list - * @return the length of produced string or an error code from stdlib printf implementation + * @return the length of the produced string or an error code from stdlib printf implementation */ -cx_attr_nonnull -cx_attr_cstr_arg(5) -cx_attr_export -int cx_vsprintf_sa( - const CxAllocator *alloc, - char *buf, - size_t *len, - char **str, - const char *fmt, - va_list ap -); +cx_attr_nonnull cx_attr_cstr_arg(5) +CX_EXPORT int cx_vsprintf_sa(const CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap); #ifdef __cplusplus diff --git a/ucx/cx/properties.h b/ucx/cx/properties.h index d6df8f5..95a4bbe 100644 --- a/ucx/cx/properties.h +++ b/ucx/cx/properties.h @@ -94,8 +94,7 @@ typedef struct cx_properties_config_s CxPropertiesConfig; /** * Default properties configuration. */ -cx_attr_export -extern const CxPropertiesConfig cx_properties_config_default; +CX_EXPORT extern const CxPropertiesConfig cx_properties_config_default; /** * Status codes for the properties interface. @@ -122,7 +121,7 @@ enum cx_properties_status { * You can use this enumerator to check for all "good" status results * by checking if the status is less than @c CX_PROPERTIES_OK. * - * A "good" status means, that you can refill data and continue parsing. + * A "good" status means that you can refill data and continue parsing. */ CX_PROPERTIES_OK, /** @@ -130,11 +129,11 @@ enum cx_properties_status { */ CX_PROPERTIES_NULL_INPUT, /** - * The line contains a delimiter, but no key. + * The line contains a delimiter but no key. */ CX_PROPERTIES_INVALID_EMPTY_KEY, /** - * The line contains data, but no delimiter. + * The line contains data but no delimiter. */ CX_PROPERTIES_INVALID_MISSING_DELIMITER, /** @@ -200,7 +199,7 @@ typedef struct cx_properties_sink_s CxPropertiesSink; /** * A function that consumes a k/v-pair in a sink. * - * The sink could be e.g. a map and the sink function would be calling + * The sink could be a map, and the sink function would be calling * a map function to store the k/v-pair. * * @param prop the properties interface that wants to sink a k/v-pair @@ -210,7 +209,6 @@ typedef struct cx_properties_sink_s CxPropertiesSink; * @retval zero success * @retval non-zero sinking the k/v-pair failed */ -cx_attr_nonnull typedef int(*cx_properties_sink_func)( CxProperties *prop, CxPropertiesSink *sink, @@ -257,7 +255,6 @@ typedef struct cx_properties_source_s CxPropertiesSource; * @retval zero success * @retval non-zero reading the data failed */ -cx_attr_nonnull typedef int(*cx_properties_read_func)( CxProperties *prop, CxPropertiesSource *src, @@ -272,7 +269,6 @@ typedef int(*cx_properties_read_func)( * @retval zero initialization was successful * @retval non-zero otherwise */ -cx_attr_nonnull typedef int(*cx_properties_read_init_func)( CxProperties *prop, CxPropertiesSource *src @@ -284,7 +280,6 @@ typedef int(*cx_properties_read_init_func)( * @param prop the properties interface that wants to read from the source * @param src the source */ -cx_attr_nonnull typedef void(*cx_properties_read_clean_func)( CxProperties *prop, CxPropertiesSource *src @@ -297,7 +292,7 @@ struct cx_properties_source_s { /** * The source object. * - * For example a file stream or a string. + * For example, a file stream or a string. */ void *src; /** @@ -331,38 +326,32 @@ struct cx_properties_source_s { * @see cxPropertiesInitDefault() */ cx_attr_nonnull -cx_attr_export -void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config); +CX_EXPORT void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config); /** * Destroys the properties interface. * * @note Even when you are certain that you did not use the interface in a * way that caused a memory allocation, you should call this function anyway. - * Future versions of the library might add features that need additional memory - * and you really don't want to search the entire code where you might need - * add call to this function. + * Future versions of the library might add features that need additional memory, + * and you really don't want to search the entire code where you might need to + * add a call to this function. * * @param prop the properties interface */ cx_attr_nonnull -cx_attr_export -void cxPropertiesDestroy(CxProperties *prop); +CX_EXPORT void cxPropertiesDestroy(CxProperties *prop); /** * Destroys and re-initializes the properties interface. * - * You might want to use this, to reset the parser after + * You might want to use this to reset the parser after * encountering a syntax error. * * @param prop the properties interface */ cx_attr_nonnull -static inline void cxPropertiesReset(CxProperties *prop) { - CxPropertiesConfig config = prop->config; - cxPropertiesDestroy(prop); - cxPropertiesInit(prop, config); -} +CX_EXPORT void cxPropertiesReset(CxProperties *prop); /** * Initialize a properties parser with the default configuration. @@ -371,7 +360,7 @@ static inline void cxPropertiesReset(CxProperties *prop) { * @see cxPropertiesInit() */ #define cxPropertiesInitDefault(prop) \ - cxPropertiesInit(prop, cx_properties_config_default) + cxPropertiesInit(prop, cx_properties_config_default) /** * Fills the input buffer with data. @@ -394,44 +383,22 @@ static inline void cxPropertiesReset(CxProperties *prop) { * @retval non-zero a memory allocation was necessary but failed * @see cxPropertiesFill() */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxPropertiesFilln( - CxProperties *prop, - const char *buf, - size_t len -); - -#ifdef __cplusplus -} // extern "C" -cx_attr_nonnull -static inline int cxPropertiesFill( - CxProperties *prop, - cxstring str -) { - return cxPropertiesFilln(prop, str.ptr, str.length); -} +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxPropertiesFilln(CxProperties *prop, const char *buf, size_t len); +/** + * Internal function, do not use. + * + * @param prop the properties interface + * @param str the text to fill in + * @retval zero success + * @retval non-zero a memory allocation was necessary but failed + */ cx_attr_nonnull -static inline int cxPropertiesFill( - CxProperties *prop, - cxmutstr str -) { +CX_INLINE int cx_properties_fill(CxProperties *prop, cxstring str) { return cxPropertiesFilln(prop, str.ptr, str.length); } -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cxPropertiesFill( - CxProperties *prop, - const char *str -) { - return cxPropertiesFilln(prop, str, strlen(str)); -} - -extern "C" { -#else // __cplusplus /** * Fills the input buffer with data. * @@ -452,62 +419,17 @@ extern "C" { * @retval non-zero a memory allocation was necessary but failed * @see cxPropertiesFilln() */ -#define cxPropertiesFill(prop, str) _Generic((str), \ - cxstring: cx_properties_fill_cxstr, \ - cxmutstr: cx_properties_fill_mutstr, \ - char*: cx_properties_fill_str, \ - const char*: cx_properties_fill_str) \ - (prop, str) +#define cxPropertiesFill(prop, str) cx_properties_fill(prop, cx_strcast(str)) /** - * @copydoc cxPropertiesFill() - */ -cx_attr_nonnull -static inline int cx_properties_fill_cxstr( - CxProperties *prop, - cxstring str -) { - return cxPropertiesFilln(prop, str.ptr, str.length); -} - -/** - * @copydoc cxPropertiesFill() - */ -cx_attr_nonnull -static inline int cx_properties_fill_mutstr( - CxProperties *prop, - cxmutstr str -) { - return cxPropertiesFilln(prop, str.ptr, str.length); -} - -/** - * @copydoc cxPropertiesFill() - */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cx_properties_fill_str( - CxProperties *prop, - const char *str -) { - return cxPropertiesFilln(prop, str, strlen(str)); -} -#endif - -/** - * Specifies stack memory that shall be used as internal buffer. + * Specifies stack memory that shall be used as an internal buffer. * * @param prop the properties interface * @param buf a pointer to stack memory * @param capacity the capacity of the stack memory */ cx_attr_nonnull -cx_attr_export -void cxPropertiesUseStack( - CxProperties *prop, - char *buf, - size_t capacity -); +CX_EXPORT void cxPropertiesUseStack(CxProperties *prop, char *buf, size_t capacity); /** * Retrieves the next key/value-pair. @@ -539,14 +461,8 @@ void cxPropertiesUseStack( * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -CxPropertiesStatus cxPropertiesNext( - CxProperties *prop, - cxstring *key, - cxstring *value -); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxPropertiesStatus cxPropertiesNext(CxProperties *prop, cxstring *key, cxstring *value); /** * Creates a properties sink for an UCX map. @@ -562,10 +478,8 @@ CxPropertiesStatus cxPropertiesNext( * @return the sink * @see cxPropertiesLoad() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -CxPropertiesSink cxPropertiesMapSink(CxMap *map); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxPropertiesSink cxPropertiesMapSink(CxMap *map); /** * Creates a properties source based on an UCX string. @@ -575,8 +489,7 @@ CxPropertiesSink cxPropertiesMapSink(CxMap *map); * @see cxPropertiesLoad() */ cx_attr_nodiscard -cx_attr_export -CxPropertiesSource cxPropertiesStringSource(cxstring str); +CX_EXPORT CxPropertiesSource cxPropertiesStringSource(cxstring str); /** * Creates a properties source based on C string with the specified length. @@ -586,11 +499,8 @@ CxPropertiesSource cxPropertiesStringSource(cxstring str); * @return the properties source * @see cxPropertiesLoad() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_access_r(1, 2) -cx_attr_export -CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); +cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1, 2) +CX_EXPORT CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); /** * Creates a properties source based on a C string. @@ -602,11 +512,8 @@ CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); * @return the properties source * @see cxPropertiesLoad() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_cstr_arg(1) -cx_attr_export -CxPropertiesSource cxPropertiesCstrSource(const char *str); +cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) +CX_EXPORT CxPropertiesSource cxPropertiesCstrSource(const char *str); /** * Creates a properties source based on an FILE. @@ -617,11 +524,8 @@ CxPropertiesSource cxPropertiesCstrSource(const char *str); * @return the properties source * @see cxPropertiesLoad() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_access_r(1) -cx_attr_export -CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); +cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1) +CX_EXPORT CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); /** @@ -653,12 +557,8 @@ CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed */ cx_attr_nonnull -cx_attr_export -CxPropertiesStatus cxPropertiesLoad( - CxProperties *prop, - CxPropertiesSink sink, - CxPropertiesSource source -); +CX_EXPORT CxPropertiesStatus cxPropertiesLoad(CxProperties *prop, + CxPropertiesSink sink, CxPropertiesSource source); #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/streams.h b/ucx/cx/streams.h index 3cc35fa..298ca3a 100644 --- a/ucx/cx/streams.h +++ b/ucx/cx/streams.h @@ -54,7 +54,7 @@ extern "C" { * @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 + * @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 @@ -62,19 +62,10 @@ extern "C" { * @return the total number of bytes copied */ cx_attr_nonnull_arg(1, 2, 3, 4) -cx_attr_access_r(1) -cx_attr_access_w(2) -cx_attr_access_w(5) -cx_attr_export -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 -); +cx_attr_access_r(1) cx_attr_access_w(2) cx_attr_access_w(5) +CX_EXPORT 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. @@ -86,7 +77,7 @@ size_t cx_stream_bncopy( * @param buf (@c char*) a pointer to the copy buffer or @c NULL if a buffer * shall be implicitly created on the heap * @param bufsize (@c size_t) the size of the copy buffer - if @p buf is - * @c NULL you can set this to zero to let the implementation decide + * @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) \ @@ -95,7 +86,7 @@ size_t cx_stream_bncopy( /** * Reads data from a stream and writes it to another stream. * - * The data is temporarily stored in a stack allocated buffer. + * The data is temporarily stored in a stack-allocated buffer. * * @param src the source stream * @param dest the destination stream @@ -104,22 +95,14 @@ size_t cx_stream_bncopy( * @param n the maximum number of bytes that shall be copied. * @return total number of bytes copied */ -cx_attr_nonnull -cx_attr_access_r(1) -cx_attr_access_w(2) -cx_attr_export -size_t cx_stream_ncopy( - void *src, - void *dest, - cx_read_func rfnc, - cx_write_func wfnc, - size_t n -); +cx_attr_nonnull cx_attr_access_r(1) cx_attr_access_w(2) +CX_EXPORT 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. + * The data is temporarily stored in a stack-allocated buffer. * * @param src (@c void*) the source stream * @param dest (@c void*) the destination stream diff --git a/ucx/cx/string.h b/ucx/cx/string.h index 9d38f9e..5177ceb 100644 --- a/ucx/cx/string.h +++ b/ucx/cx/string.h @@ -48,8 +48,7 @@ /** * The maximum length of the "needle" in cx_strstr() that can use SBO. */ -cx_attr_export -extern const unsigned cx_strstr_sbo_size; +CX_EXPORT extern const unsigned cx_strstr_sbo_size; /** * The UCX string structure. @@ -112,10 +111,10 @@ struct cx_strtok_ctx_s { */ size_t pos; /** - * Position of next delimiter in the source string. + * Position of the 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 + * is undefined. If the tokenizer reaches the end of the string, this field * contains the length of the source string. */ size_t delim_pos; @@ -174,15 +173,13 @@ extern "C" { * * If you need to wrap a constant string, use cx_str(). * - * @param cstring the string to wrap, must be zero-terminated + * @param cstring the string to wrap (must be zero-terminated) * @return the wrapped string * * @see cx_mutstrn() */ -cx_attr_nodiscard -cx_attr_cstr_arg(1) -cx_attr_export -cxmutstr cx_mutstr(char *cstring); +cx_attr_nodiscard cx_attr_cstr_arg(1) +CX_EXPORT cxmutstr cx_mutstr(char *cstring); /** * Wraps a string that does not need to be zero-terminated. @@ -200,13 +197,8 @@ cxmutstr cx_mutstr(char *cstring); * * @see cx_mutstr() */ -cx_attr_nodiscard -cx_attr_access_rw(1, 2) -cx_attr_export -cxmutstr cx_mutstrn( - char *cstring, - size_t length -); +cx_attr_nodiscard cx_attr_access_rw(1, 2) +CX_EXPORT cxmutstr cx_mutstrn(char *cstring, size_t length); /** * Wraps a string that must be zero-terminated. @@ -220,15 +212,13 @@ cxmutstr cx_mutstrn( * * If you need to wrap a non-constant string, use cx_mutstr(). * - * @param cstring the string to wrap, must be zero-terminated + * @param cstring the string to wrap (must be zero-terminated) * @return the wrapped string * * @see cx_strn() */ -cx_attr_nodiscard -cx_attr_cstr_arg(1) -cx_attr_export -cxstring cx_str(const char *cstring); +cx_attr_nodiscard cx_attr_cstr_arg(1) +CX_EXPORT cxstring cx_str(const char *cstring); /** @@ -247,28 +237,27 @@ cxstring cx_str(const char *cstring); * * @see cx_str() */ -cx_attr_nodiscard -cx_attr_access_r(1, 2) -cx_attr_export -cxstring cx_strn( - const char *cstring, - size_t length -); +cx_attr_nodiscard cx_attr_access_r(1, 2) +CX_EXPORT cxstring cx_strn(const char *cstring, size_t length); #ifdef __cplusplus } // extern "C" cx_attr_nodiscard -static inline cxstring cx_strcast(cxmutstr str) { +CX_CPPDECL cxstring cx_strcast(cxmutstr str) { return cx_strn(str.ptr, str.length); } cx_attr_nodiscard -static inline cxstring cx_strcast(cxstring str) { +CX_CPPDECL cxstring cx_strcast(cxstring str) { return str; } cx_attr_nodiscard -static inline cxstring cx_strcast(const char *str) { +CX_CPPDECL cxstring cx_strcast(const char *str) { return cx_str(str); } +cx_attr_nodiscard +CX_CPPDECL cxstring cx_strcast(const unsigned char *str) { + return cx_str(static_cast(str)); +} extern "C" { #else /** @@ -278,7 +267,7 @@ extern "C" { * @see cx_strcast() */ cx_attr_nodiscard -static inline cxstring cx_strcast_m(cxmutstr str) { +CX_INLINE cxstring cx_strcast_m(cxmutstr str) { return (cxstring) {str.ptr, str.length}; } /** @@ -288,7 +277,7 @@ static inline cxstring cx_strcast_m(cxmutstr str) { * @see cx_strcast() */ cx_attr_nodiscard -static inline cxstring cx_strcast_c(cxstring str) { +CX_INLINE cxstring cx_strcast_c(cxstring str) { return str; } @@ -299,25 +288,32 @@ static inline cxstring cx_strcast_c(cxstring str) { * @see cx_strcast() */ cx_attr_nodiscard -static inline cxstring cx_strcast_z(const char *str) { +CX_INLINE cxstring cx_strcast_u(const unsigned char *str) { + return cx_str((const char*)str); +} + +/** + * Internal function, do not use. + * @param str + * @return + * @see cx_strcast() + */ +cx_attr_nodiscard +CX_INLINE cxstring cx_strcast_z(const char *str) { return cx_str(str); } /** -* Casts a mutable string to an immutable string. -* -* Does nothing for already immutable strings. -* -* @note This is not seriously a cast. Instead, you get a copy -* of the struct with the desired pointer type. Both structs still -* point to the same location, though! -* -* @param str (@c cxstring or @c cxmutstr) the string to cast -* @return (@c cxstring) an immutable copy of the string pointer -*/ + * Wraps any string into an UCX string. + * + * @param str (any supported string type) the string to cast + * @return (@c cxstring) the string wrapped as UCX string + */ #define cx_strcast(str) _Generic((str), \ cxmutstr: cx_strcast_m, \ cxstring: cx_strcast_c, \ + const unsigned char*: cx_strcast_u, \ + unsigned char *: cx_strcast_u, \ const char*: cx_strcast_z, \ char *: cx_strcast_z) (str) #endif @@ -325,37 +321,32 @@ static inline cxstring cx_strcast_z(const char *str) { /** * Passes the pointer in this string to the cxDefaultAllocator's @c free() function. * - * The pointer in the struct is set to @c NULL and the length is set to zero + * The pointer in the struct is set to @c NULL, and the length is set to zero, * which means that this function protects you against double-free. * * @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. + * If you encounter such a situation, you should double-check your code. * * @param str the string to free */ -cx_attr_export -void cx_strfree(cxmutstr *str); +CX_EXPORT void cx_strfree(cxmutstr *str); /** - * Passes the pointer in this string to the allocators free function. + * Passes the pointer in this string to the allocator's free function. * - * The pointer in the struct is set to @c NULL and the length is set to zero + * The pointer in the struct is set to @c NULL, and the length is set to zero, * which means that this function protects you against double-free. * * @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. + * If you encounter such a situation, you should double-check your code. * * @param alloc the allocator * @param str the string to free */ cx_attr_nonnull_arg(1) -cx_attr_export -void cx_strfree_a( - const CxAllocator *alloc, - cxmutstr *str -); +CX_EXPORT void cx_strfree_a(const CxAllocator *alloc, cxmutstr *str); /** * Copies a string. @@ -373,12 +364,7 @@ void cx_strfree_a( * @retval non-zero if re-allocation failed */ cx_attr_nonnull_arg(1) -cx_attr_export -int cx_strcpy_a( - const CxAllocator *alloc, - cxmutstr *dest, - cxstring src -); +CX_EXPORT int cx_strcpy_a(const CxAllocator *alloc, cxmutstr *dest, cxstring src); /** @@ -410,11 +396,7 @@ int cx_strcpy_a( * @return the accumulated length of all strings */ cx_attr_nodiscard -cx_attr_export -size_t cx_strlen( - size_t count, - ... -); +CX_EXPORT size_t cx_strlen(size_t count, ...); /** * Concatenates strings. @@ -438,15 +420,9 @@ size_t cx_strlen( * @param ... all other UCX strings * @return the concatenated string */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -cxmutstr cx_strcat_ma( - const CxAllocator *alloc, - cxmutstr str, - size_t count, - ... -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT cxmutstr cx_strcat_ma(const CxAllocator *alloc, + cxmutstr str, size_t count, ...); /** * Concatenates strings and returns a new string. @@ -467,7 +443,7 @@ cxmutstr cx_strcat_ma( * @return (@c cxmutstr) the concatenated string */ #define cx_strcat_a(alloc, count, ...) \ -cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__) + cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__) /** * Concatenates strings and returns a new string. @@ -487,7 +463,7 @@ cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__) * @return (@c cxmutstr) the concatenated string */ #define cx_strcat(count, ...) \ -cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__) + cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__) /** * Concatenates strings. @@ -511,7 +487,7 @@ cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__) * @return (@c cxmutstr) the concatenated string */ #define cx_strcat_m(str, count, ...) \ -cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__) + cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__) /** * Returns a substring starting at the specified location. @@ -529,11 +505,7 @@ cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__) * @see cx_strsubsl_m() */ cx_attr_nodiscard -cx_attr_export -cxstring cx_strsubs( - cxstring string, - size_t start -); +CX_EXPORT cxstring cx_strsubs(cxstring string, size_t start); /** * Returns a substring starting at the specified location. @@ -555,12 +527,7 @@ cxstring cx_strsubs( * @see cx_strsubsl_m() */ cx_attr_nodiscard -cx_attr_export -cxstring cx_strsubsl( - cxstring string, - size_t start, - size_t length -); +CX_EXPORT cxstring cx_strsubsl(cxstring string, size_t start, size_t length); /** * Returns a substring starting at the specified location. @@ -578,11 +545,7 @@ cxstring cx_strsubsl( * @see cx_strsubsl() */ cx_attr_nodiscard -cx_attr_export -cxmutstr cx_strsubs_m( - cxmutstr string, - size_t start -); +CX_EXPORT cxmutstr cx_strsubs_m(cxmutstr string, size_t start); /** * Returns a substring starting at the specified location. @@ -604,12 +567,7 @@ cxmutstr cx_strsubs_m( * @see cx_strsubsl() */ cx_attr_nodiscard -cx_attr_export -cxmutstr cx_strsubsl_m( - cxmutstr string, - size_t start, - size_t length -); +CX_EXPORT 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 @@ -624,11 +582,7 @@ cxmutstr cx_strsubsl_m( * @see cx_strchr_m() */ cx_attr_nodiscard -cx_attr_export -cxstring cx_strchr( - cxstring string, - int chr -); +CX_EXPORT cxstring cx_strchr(cxstring string, int chr); /** * Returns a substring starting at the location of the first occurrence of the @@ -643,11 +597,7 @@ cxstring cx_strchr( * @see cx_strchr() */ cx_attr_nodiscard -cx_attr_export -cxmutstr cx_strchr_m( - cxmutstr string, - int chr -); +CX_EXPORT cxmutstr cx_strchr_m(cxmutstr string, int chr); /** * Returns a substring starting at the location of the last occurrence of the @@ -662,11 +612,7 @@ cxmutstr cx_strchr_m( * @see cx_strrchr_m() */ cx_attr_nodiscard -cx_attr_export -cxstring cx_strrchr( - cxstring string, - int chr -); +CX_EXPORT cxstring cx_strrchr(cxstring string, int chr); /** * Returns a substring starting at the location of the last occurrence of the @@ -681,11 +627,7 @@ cxstring cx_strrchr( * @see cx_strrchr() */ cx_attr_nodiscard -cx_attr_export -cxmutstr cx_strrchr_m( - cxmutstr string, - int chr -); +CX_EXPORT cxmutstr cx_strrchr_m(cxmutstr string, int chr); /** * Returns a substring starting at the location of the first occurrence of the @@ -704,11 +646,7 @@ cxmutstr cx_strrchr_m( * @see cx_strstr_m() */ cx_attr_nodiscard -cx_attr_export -cxstring cx_strstr( - cxstring haystack, - cxstring needle -); +CX_EXPORT cxstring cx_strstr(cxstring haystack, cxstring needle); /** * Returns a substring starting at the location of the first occurrence of the @@ -727,11 +665,7 @@ cxstring cx_strstr( * @see cx_strstr() */ cx_attr_nodiscard -cx_attr_export -cxmutstr cx_strstr_m( - cxmutstr haystack, - cxstring needle -); +CX_EXPORT cxmutstr cx_strstr_m(cxmutstr haystack, cxstring needle); /** * Splits a given string using a delimiter string. @@ -745,16 +679,9 @@ cxmutstr cx_strstr_m( * @param output a preallocated array of at least @p limit length * @return the actual number of split items */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_access_w(4, 3) -cx_attr_export -size_t cx_strsplit( - cxstring string, - cxstring delim, - size_t limit, - cxstring *output -); +cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(4, 3) +CX_EXPORT size_t cx_strsplit(cxstring string, cxstring delim, + size_t limit, cxstring *output); /** * Splits a given string using a delimiter string. @@ -775,17 +702,10 @@ size_t cx_strsplit( * written to * @return the actual number of split items */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_access_w(5) -cx_attr_export -size_t cx_strsplit_a( - const CxAllocator *allocator, - cxstring string, - cxstring delim, - size_t limit, - cxstring **output -); +cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(5) +CX_EXPORT size_t cx_strsplit_a(const CxAllocator *allocator, + cxstring string, cxstring delim, + size_t limit, cxstring **output); /** @@ -800,16 +720,9 @@ size_t cx_strsplit_a( * @param output a preallocated array of at least @p limit length * @return the actual number of split items */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_access_w(4, 3) -cx_attr_export -size_t cx_strsplit_m( - cxmutstr string, - cxstring delim, - size_t limit, - cxmutstr *output -); +cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(4, 3) +CX_EXPORT size_t cx_strsplit_m(cxmutstr string, cxstring delim, + size_t limit, cxmutstr *output); /** * Splits a given string using a delimiter string. @@ -830,17 +743,10 @@ size_t cx_strsplit_m( * written to * @return the actual number of split items */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_access_w(5) -cx_attr_export -size_t cx_strsplit_ma( - const CxAllocator *allocator, - cxmutstr string, - cxstring delim, - size_t limit, - cxmutstr **output -); +cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(5) +CX_EXPORT size_t cx_strsplit_ma(const CxAllocator *allocator, + cxmutstr string, cxstring delim, size_t limit, + cxmutstr **output); /** * Compares two strings. @@ -851,11 +757,17 @@ size_t cx_strsplit_ma( * than @p s2, zero if both strings equal */ cx_attr_nodiscard -cx_attr_export -int cx_strcmp( - cxstring s1, - cxstring s2 -); +CX_EXPORT int cx_strcmp_(cxstring s1, cxstring s2); + +/** + * 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 + */ +#define cx_strcmp(s1, s2) cx_strcmp_(cx_strcast(s1), cx_strcast(s2)) /** * Compares two strings ignoring case. @@ -866,29 +778,33 @@ int cx_strcmp( * than @p s2, zero if both strings equal ignoring case */ cx_attr_nodiscard -cx_attr_export -int cx_strcasecmp( - cxstring s1, - cxstring s2 -); +CX_EXPORT int cx_strcasecmp_(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 + */ +#define cx_strcasecmp(s1, s2) cx_strcasecmp_(cx_strcast(s1), cx_strcast(s2)) /** * Compares two strings. * * This function has a compatible signature for the use as a cx_compare_func. * + * @attention This function can @em only compare UCX strings. It is unsafe to + * pass normal C-strings to this function. + * * @param s1 the first string * @param s2 the second string * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger * than @p s2, zero if both strings equal */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -int cx_strcmp_p( - const void *s1, - const void *s2 -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT int cx_strcmp_p(const void *s1, const void *s2); /** * Compares two strings ignoring case. @@ -900,13 +816,8 @@ int cx_strcmp_p( * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger * than @p s2, zero if both strings equal ignoring case */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -int cx_strcasecmp_p( - const void *s1, - const void *s2 -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT int cx_strcasecmp_p(const void *s1, const void *s2); /** @@ -921,13 +832,8 @@ int cx_strcasecmp_p( * @return a duplicate of the string * @see cx_strdup() */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -cxmutstr cx_strdup_a_( - const CxAllocator *allocator, - cxstring string -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT cxmutstr cx_strdup_a_(const CxAllocator *allocator, cxstring string); /** * Creates a duplicate of the specified string. @@ -942,8 +848,7 @@ cxmutstr cx_strdup_a_( * @see cx_strdup() * @see cx_strfree_a() */ -#define cx_strdup_a(allocator, string) \ - cx_strdup_a_((allocator), cx_strcast(string)) +#define cx_strdup_a(allocator, string) cx_strdup_a_((allocator), cx_strcast(string)) /** * Creates a duplicate of the specified string. @@ -970,8 +875,7 @@ cxmutstr cx_strdup_a_( * @return the trimmed string */ cx_attr_nodiscard -cx_attr_export -cxstring cx_strtrim(cxstring string); +CX_EXPORT cxstring cx_strtrim(cxstring string); /** * Omits leading and trailing spaces. @@ -983,11 +887,10 @@ cxstring cx_strtrim(cxstring string); * @return the trimmed string */ cx_attr_nodiscard -cx_attr_export -cxmutstr cx_strtrim_m(cxmutstr string); +CX_EXPORT cxmutstr cx_strtrim_m(cxmutstr string); /** - * Checks, if a string has a specific prefix. + * Checks if a string has a specific prefix. * * @param string the string to check * @param prefix the prefix the string should have @@ -995,14 +898,20 @@ cxmutstr cx_strtrim_m(cxmutstr string); * @c false otherwise */ cx_attr_nodiscard -cx_attr_export -bool cx_strprefix( - cxstring string, - cxstring prefix -); +CX_EXPORT bool cx_strprefix_(cxstring string, cxstring prefix); + +/** + * 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 + */ +#define cx_strprefix(string, prefix) cx_strprefix_(cx_strcast(string), cx_strcast(prefix)) /** - * Checks, if a string has a specific suffix. + * Checks if a string has a specific suffix. * * @param string the string to check * @param suffix the suffix the string should have @@ -1010,14 +919,20 @@ bool cx_strprefix( * @c false otherwise */ cx_attr_nodiscard -cx_attr_export -bool cx_strsuffix( - cxstring string, - cxstring suffix -); +CX_EXPORT bool cx_strsuffix_(cxstring string, cxstring suffix); + +/** + * 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 + */ +#define cx_strsuffix(string, suffix) cx_strsuffix_(cx_strcast(string), cx_strcast(suffix)) /** - * Checks, if a string has a specific prefix, ignoring the case. + * 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 @@ -1025,11 +940,17 @@ bool cx_strsuffix( * @c false otherwise */ cx_attr_nodiscard -cx_attr_export -bool cx_strcaseprefix( - cxstring string, - cxstring prefix -); +CX_EXPORT bool cx_strcaseprefix_(cxstring string, cxstring prefix); + +/** + * Checks if a string has a specific prefix, ignoring the case. + * + * @param string the string to check + * @param prefix the prefix the string should have + * @return @c true, if and only if the string has the specified prefix, + * @c false otherwise + */ +#define cx_strcaseprefix(string, prefix) cx_strcaseprefix_(cx_strcast(string), cx_strcast(prefix)) /** * Checks, if a string has a specific suffix, ignoring the case. @@ -1040,16 +961,22 @@ bool cx_strcaseprefix( * @c false otherwise */ cx_attr_nodiscard -cx_attr_export -bool cx_strcasesuffix( - cxstring string, - cxstring suffix -); +CX_EXPORT bool cx_strcasesuffix_(cxstring string, cxstring suffix); + +/** + * Checks, if a string has a specific suffix, ignoring the case. + * + * @param string the string to check + * @param suffix the suffix the string should have + * @return @c true, if and only if the string has the specified suffix, + * @c false otherwise + */ +#define cx_strcasesuffix(string, suffix) cx_strcasesuffix_(cx_strcast(string), cx_strcast(suffix)) /** * Replaces a string with another string. * - * Replaces at most @p replmax occurrences. + * The function replaces at most @p replmax occurrences. * * The returned string will be allocated by @p allocator and is guaranteed * to be zero-terminated. @@ -1064,21 +991,14 @@ bool cx_strcasesuffix( * @param replmax maximum number of replacements * @return the resulting string after applying the replacements */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -cxmutstr cx_strreplacen_a( - const CxAllocator *allocator, - cxstring str, - cxstring search, - cxstring replacement, - size_t replmax -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT cxmutstr cx_strreplacen_a(const CxAllocator *allocator, + cxstring str, cxstring search, cxstring replacement, size_t replmax); /** * Replaces a string with another string. * - * Replaces at most @p replmax occurrences. + * The function replaces at most @p replmax occurrences. * * The returned string will be allocated by the cxDefaultAllocator and is guaranteed * to be zero-terminated. @@ -1093,7 +1013,7 @@ cxmutstr cx_strreplacen_a( * @return (@c cxmutstr) the resulting string after applying the replacements */ #define cx_strreplacen(str, search, replacement, replmax) \ -cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, replmax) + cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, replmax) /** * Replaces a string with another string. @@ -1111,7 +1031,7 @@ cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, replmax) * @return (@c cxmutstr) the resulting string after applying the replacements */ #define cx_strreplace_a(allocator, str, search, replacement) \ -cx_strreplacen_a(allocator, str, search, replacement, SIZE_MAX) + cx_strreplacen_a(allocator, str, search, replacement, SIZE_MAX) /** * Replaces a string with another string. @@ -1128,7 +1048,7 @@ cx_strreplacen_a(allocator, str, search, replacement, SIZE_MAX) * @return (@c cxmutstr) the resulting string after applying the replacements */ #define cx_strreplace(str, search, replacement) \ -cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, SIZE_MAX) + cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, SIZE_MAX) /** * Creates a string tokenization context. @@ -1139,12 +1059,7 @@ cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, SIZE_MAX) * @return a new string tokenization context */ cx_attr_nodiscard -cx_attr_export -CxStrtokCtx cx_strtok_( - cxstring str, - cxstring delim, - size_t limit -); +CX_EXPORT CxStrtokCtx cx_strtok_(cxstring str, cxstring delim, size_t limit); /** * Creates a string tokenization context. @@ -1155,7 +1070,7 @@ CxStrtokCtx cx_strtok_( * @return (@c CxStrtokCtx) a new string tokenization context */ #define cx_strtok(str, delim, limit) \ - cx_strtok_(cx_strcast(str), cx_strcast(delim), (limit)) + cx_strtok_(cx_strcast(str), cx_strcast(delim), (limit)) /** * Returns the next token. @@ -1167,14 +1082,8 @@ CxStrtokCtx cx_strtok_( * @return true if successful, false if the limit or the end of the string * has been reached */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_access_w(2) -cx_attr_export -bool cx_strtok_next( - CxStrtokCtx *ctx, - cxstring *token -); +cx_attr_nonnull cx_attr_nodiscard cx_attr_access_w(2) +CX_EXPORT bool cx_strtok_next(CxStrtokCtx *ctx, cxstring *token); /** * Returns the next token of a mutable string. @@ -1190,14 +1099,8 @@ bool cx_strtok_next( * @return true if successful, false if the limit or the end of the string * has been reached */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_access_w(2) -cx_attr_export -bool cx_strtok_next_m( - CxStrtokCtx *ctx, - cxmutstr *token -); +cx_attr_nonnull cx_attr_nodiscard cx_attr_access_w(2) +CX_EXPORT bool cx_strtok_next_m(CxStrtokCtx *ctx, cxmutstr *token); /** * Defines an array of more delimiters for the specified tokenization context. @@ -1206,14 +1109,8 @@ bool cx_strtok_next_m( * @param delim array of more delimiters * @param count number of elements in the array */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -void cx_strtok_delim( - CxStrtokCtx *ctx, - const cxstring *delim, - size_t count -); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT void cx_strtok_delim(CxStrtokCtx *ctx, const cxstring *delim, size_t count); /* ------------------------------------------------------------------------- * * string to number conversion functions * @@ -1229,12 +1126,12 @@ void cx_strtok_delim( * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1246,12 +1143,12 @@ int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep); * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1263,12 +1160,12 @@ int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep); * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1280,12 +1177,12 @@ int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep); * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1297,12 +1194,12 @@ int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groups * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1314,12 +1211,12 @@ int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep) * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1331,12 +1228,12 @@ int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1348,12 +1245,12 @@ int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1365,12 +1262,12 @@ int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1382,12 +1279,12 @@ int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *g * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1399,12 +1296,12 @@ int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *grou * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1416,12 +1313,12 @@ int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *gr * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1433,12 +1330,12 @@ int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const ch * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1450,12 +1347,12 @@ int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1467,12 +1364,12 @@ int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groups * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1484,12 +1381,12 @@ int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groups * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1501,15 +1398,15 @@ int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groups * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep); /** - * Converts a string to a single precision floating point number. + * Converts a string to a single precision floating-point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. @@ -1518,15 +1415,15 @@ int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep); * @param str the string to convert * @param output a pointer to the float variable where the result shall be stored * @param decsep the decimal separator - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep); /** - * Converts a string to a double precision floating point number. + * Converts a string to a double precision floating-point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. @@ -1535,12 +1432,12 @@ int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep * @param str the string to convert * @param output a pointer to the float variable where the result shall be stored * @param decsep the decimal separator - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep); /** * Converts a string to a number. @@ -1552,7 +1449,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1568,7 +1465,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1584,7 +1481,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1600,7 +1497,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1616,7 +1513,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1632,7 +1529,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1648,7 +1545,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1664,7 +1561,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1680,7 +1577,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1696,7 +1593,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1712,7 +1609,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1728,7 +1625,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1744,7 +1641,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1760,7 +1657,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1776,7 +1673,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1792,7 +1689,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1808,7 +1705,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1821,7 +1718,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1839,7 +1736,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1857,7 +1754,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1875,7 +1772,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1893,7 +1790,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1911,7 +1808,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1929,7 +1826,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1947,7 +1844,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1965,7 +1862,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1983,7 +1880,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2001,7 +1898,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2019,7 +1916,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2037,7 +1934,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2055,7 +1952,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2073,7 +1970,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2091,7 +1988,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2109,7 +2006,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2121,7 +2018,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse #define cx_strtou64(str, output, base) cx_strtou64_lc_(cx_strcast(str), output, base, ",") /** - * Converts a string to a single precision floating point number. + * Converts a string to a single precision floating-point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. @@ -2130,14 +2027,14 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the float variable where the result shall be stored * @param decsep the decimal separator - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ #define cx_strtof_lc(str, output, decsep, groupsep) cx_strtof_lc_(cx_strcast(str), output, decsep, groupsep) /** - * Converts a string to a double precision floating point number. + * Converts a string to a double precision floating-point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. @@ -2145,21 +2042,21 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse * @param str the string to convert * @param output a pointer to the double variable where the result shall be stored * @param decsep the decimal separator - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ #define cx_strtod_lc(str, output, decsep, groupsep) cx_strtod_lc_(cx_strcast(str), output, decsep, groupsep) /** - * Converts a string to a single precision floating point number. + * Converts a string to a single precision floating-point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. * * The decimal separator is assumed to be a dot character. - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose a different format, use cx_strtof_lc(). * * @param str the string to convert @@ -2170,13 +2067,13 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse #define cx_strtof(str, output) cx_strtof_lc_(cx_strcast(str), output, '.', ",") /** - * Converts a string to a double precision floating point number. + * Converts a string to a double precision floating-point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. * * The decimal separator is assumed to be a dot character. - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose a different format, use cx_strtof_lc(). * * @param str the string to convert diff --git a/ucx/cx/test.h b/ucx/cx/test.h index fdd9269..ff86b33 100644 --- a/ucx/cx/test.h +++ b/ucx/cx/test.h @@ -56,7 +56,7 @@ * } * * - * @attention Do not call own functions within a test, that use + * @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 @@ -136,10 +136,7 @@ struct CxTestSuite { * @param name optional name of the suite * @return a new test suite */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_cstr_arg(1) -cx_attr_malloc +cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) cx_attr_malloc static inline CxTestSuite* cx_test_suite_new(const char *name) { CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite)); if (suite != NULL) { @@ -157,7 +154,7 @@ static inline CxTestSuite* cx_test_suite_new(const char *name) { * * @param suite the test suite to free */ -static inline void cx_test_suite_free(CxTestSuite* suite) { +CX_INLINE void cx_test_suite_free(CxTestSuite* suite) { if (suite == NULL) return; CxTestSet *l = suite->tests; while (l != NULL) { @@ -171,13 +168,13 @@ static inline void cx_test_suite_free(CxTestSuite* suite) { /** * Registers a test function with the specified test suite. * - * @param suite the suite, the test function shall be added to + * @param suite the suite the test function shall be added to * @param test the test function to register * @retval zero success * @retval non-zero failure */ cx_attr_nonnull -static inline int cx_test_register(CxTestSuite* suite, CxTest test) { +CX_INLINE int cx_test_register(CxTestSuite* suite, CxTest test) { CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet)); if (t) { t->test = test; @@ -204,8 +201,7 @@ static inline int cx_test_register(CxTestSuite* suite, CxTest test) { * @param out_writer the write function writing to @p out_target */ cx_attr_nonnull -static inline void cx_test_run(CxTestSuite *suite, - void *out_target, cx_write_func out_writer) { +CX_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 { @@ -263,7 +259,7 @@ static inline void cx_test_run(CxTestSuite *suite, * } * @endcode * - * @attention Any CX_TEST_ASSERT() calls must be performed in scope of + * @attention Any CX_TEST_ASSERT() calls must be performed in the scope of * #CX_TEST_DO. */ #define CX_TEST_DO _writefnc_("Running ", 1, 8, _output_);\ diff --git a/ucx/cx/tree.h b/ucx/cx/tree.h index 14832db..a1412ca 100644 --- a/ucx/cx/tree.h +++ b/ucx/cx/tree.h @@ -49,12 +49,12 @@ extern "C" { * * 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. + * 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. + * 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). * @@ -71,7 +71,7 @@ typedef struct cx_tree_iterator_s { bool skip; /** * Set to true, when the iterator shall visit a node again - * when all it's children have been processed. + * when all its children have been processed. */ bool visit_on_exit; /** @@ -97,7 +97,7 @@ typedef struct cx_tree_iterator_s { */ void *node; /** - * Stores a copy of the next pointer of the visited node. + * Stores a copy of the pointer to the successor of the visited node. * Allows freeing a node on exit without corrupting the iteration. */ void *node_next; @@ -155,12 +155,12 @@ struct cx_tree_visitor_queue_s { * * 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. + * 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. + * 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). * @@ -212,24 +212,14 @@ typedef struct cx_tree_visitor_s { * @param iter the iterator */ cx_attr_nonnull -static inline void cxTreeIteratorDispose(CxTreeIterator *iter) { - cxFreeDefault(iter->stack); - iter->stack = NULL; -} +CX_EXPORT void cxTreeIteratorDispose(CxTreeIterator *iter); /** * Releases internal memory of the given tree visitor. * @param visitor the visitor */ cx_attr_nonnull -static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) { - struct cx_tree_visitor_queue_s *q = visitor->queue_next; - while (q != NULL) { - struct cx_tree_visitor_queue_s *next = q->next; - cxFreeDefault(q); - q = next; - } -} +CX_EXPORT void cxTreeVisitorDispose(CxTreeVisitor *visitor); /** * Advises the iterator to skip the subtree below the current node and @@ -250,7 +240,7 @@ static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) { /** * Links a node to a (new) parent. * - * If the node has already a parent, it is unlinked, first. + * If the node already has 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. * @@ -265,16 +255,9 @@ static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) { * @see cx_tree_unlink() */ cx_attr_nonnull -cx_attr_export -void cx_tree_link( - void *parent, - void *node, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +CX_EXPORT void cx_tree_link(void *parent, void *node, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next); /** * Unlinks a node from its parent. @@ -291,15 +274,9 @@ void cx_tree_link( * @see cx_tree_link() */ cx_attr_nonnull -cx_attr_export -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 -); +CX_EXPORT void cx_tree_unlink(void *node, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next); /** * Macro that can be used instead of the magic value for infinite search depth. @@ -318,8 +295,8 @@ void cx_tree_unlink( * Zero means exact match and a positive number is an implementation defined * measure for the distance to an exact match. * - * For example if a tree stores file path information, a node that is - * describing a parent directory of a filename that is searched, shall + * For example, consider a tree that stores file path information. + * A node which is describing a parent directory of a searched file 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 @@ -330,9 +307,8 @@ void cx_tree_unlink( * * @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 + * negative if neither the node nor the children contains the data */ -cx_attr_nonnull typedef int (*cx_tree_search_data_func)(const void *node, const void *data); @@ -348,8 +324,8 @@ typedef int (*cx_tree_search_data_func)(const void *node, const void *data); * Zero means exact match and a positive number is an implementation defined * measure for the distance to an exact match. * - * For example if a tree stores file path information, a node that is - * describing a parent directory of a filename that is searched, shall + * For example, consider a tree that stores file path information. + * A node which is describing a parent directory of a searched file 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 @@ -360,19 +336,18 @@ typedef int (*cx_tree_search_data_func)(const void *node, const void *data); * * @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 + * negative if neither the node nor the children contains the data */ -cx_attr_nonnull typedef int (*cx_tree_search_func)(const void *node, const void *new_node); /** * Searches for data in a tree. * - * When the data cannot be found exactly, the search function might return a - * closest result which might be a good starting point for adding a new node + * When the data cannot be found exactly, the search function might return the + * 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 + * 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 @@ -389,27 +364,19 @@ typedef int (*cx_tree_search_func)(const void *node, const void *new_node); * could contain the node (but doesn't right now), negative if the tree does not * contain any node that might be related to the searched data */ -cx_attr_nonnull -cx_attr_access_w(5) -cx_attr_export -int cx_tree_search_data( - const void *root, - size_t depth, - const void *data, - cx_tree_search_data_func sfunc, - void **result, - ptrdiff_t loc_children, - ptrdiff_t loc_next -); +cx_attr_nonnull cx_attr_access_w(5) +CX_EXPORT int cx_tree_search_data(const void *root, size_t depth, + const void *data, cx_tree_search_data_func sfunc, + void **result, ptrdiff_t loc_children, ptrdiff_t loc_next); /** * Searches for a node in a tree. * * When no node with the same data can be found, the search function might - * return a closest result which might be a good starting point for adding the + * return the 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 + * 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 @@ -426,18 +393,10 @@ int cx_tree_search_data( * could contain the node (but doesn't right now), negative if the tree does not * contain any node that might be related to the searched data */ -cx_attr_nonnull -cx_attr_access_w(5) -cx_attr_export -int cx_tree_search( - const void *root, - size_t depth, - const void *node, - cx_tree_search_func sfunc, - void **result, - ptrdiff_t loc_children, - ptrdiff_t loc_next -); +cx_attr_nonnull cx_attr_access_w(5) +CX_EXPORT int cx_tree_search(const void *root, size_t depth, + const void *node, cx_tree_search_func sfunc, + void **result, ptrdiff_t loc_children, ptrdiff_t loc_next); /** * Creates a depth-first iterator for a tree with the specified root node. @@ -460,13 +419,8 @@ int cx_tree_search( * @see cxTreeIteratorDispose() */ cx_attr_nodiscard -cx_attr_export -CxTreeIterator cx_tree_iterator( - void *root, - bool visit_on_exit, - ptrdiff_t loc_children, - ptrdiff_t loc_next -); +CX_EXPORT 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. @@ -487,24 +441,19 @@ CxTreeIterator cx_tree_iterator( * @see cxTreeVisitorDispose() */ cx_attr_nodiscard -cx_attr_export -CxTreeVisitor cx_tree_visitor( - void *root, - ptrdiff_t loc_children, - ptrdiff_t loc_next -); +CX_EXPORT 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). + * The first argument points to the data the node shall contain, and + * the second argument may be used for additional data (e.g., an allocator). * Functions of this type shall either return a new pointer to a newly * created node or @c NULL when allocation fails. * * @note the function may leave the node pointers in the struct uninitialized. * The caller is responsible to set them according to the intended use case. */ -cx_attr_nonnull_arg(1) typedef void *(*cx_tree_node_create_func)(const void *, void *); /** @@ -513,8 +462,7 @@ typedef void *(*cx_tree_node_create_func)(const void *, void *); * This variable is used by #cx_tree_add_array() and #cx_tree_add_iter() to * implement optimized insertion of multiple elements into a tree. */ -cx_attr_export -extern unsigned int cx_tree_add_look_around_depth; +CX_EXPORT extern unsigned int cx_tree_add_look_around_depth; /** * Adds multiple elements efficiently to a tree. @@ -554,23 +502,12 @@ extern unsigned int cx_tree_add_look_around_depth; * @return the number of nodes created and added * @see cx_tree_add() */ -cx_attr_nonnull_arg(1, 3, 4, 6, 7) -cx_attr_access_w(6) -cx_attr_export -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 -); +cx_attr_nonnull_arg(1, 3, 4, 6, 7) cx_attr_access_w(6) +CX_EXPORT 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. @@ -609,24 +546,12 @@ size_t cx_tree_add_iter( * @return the number of array elements successfully processed * @see cx_tree_add() */ -cx_attr_nonnull_arg(1, 4, 5, 7, 8) -cx_attr_access_w(7) -cx_attr_export -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 -); +cx_attr_nonnull_arg(1, 4, 5, 7, 8) cx_attr_access_w(7) +CX_EXPORT 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. @@ -637,17 +562,17 @@ size_t cx_tree_add_array( * 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 + * When @p sfunc returns 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 + * When @p sfunc returns zero and the found node has a parent, the new + * node will be added as a 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. + * When @p sfunc returns 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. * @@ -673,22 +598,12 @@ size_t cx_tree_add_array( * @return zero when a new node was created and added to the tree, * non-zero otherwise */ -cx_attr_nonnull_arg(1, 2, 3, 5, 6) -cx_attr_access_w(5) -cx_attr_export -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 -); +cx_attr_nonnull_arg(1, 2, 3, 5, 6) cx_attr_access_w(5) +CX_EXPORT 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); /** @@ -747,9 +662,9 @@ struct cx_tree_s { * A function to create new nodes. * * Invocations to this function will receive a pointer to this tree - * structure as second argument. + * structure as the second argument. * - * Nodes MAY use #cx_tree_node_base_s as base layout, but do not need to. + * Nodes MAY use #cx_tree_node_base_s as the base layout, but do not need to. */ cx_tree_node_create_func node_create; @@ -814,7 +729,7 @@ struct cx_tree_s { * Macro to roll out the #cx_tree_node_base_s structure with a custom * node type. * - * Must be used as first member in your custom tree struct. + * Must be used as the first member in your custom tree struct. * * @param type the data type for the nodes */ @@ -850,32 +765,20 @@ struct cx_tree_class_s { * 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 - ); + 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 + * Implementations SHALL avoid performing 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 - ); + size_t (*insert_many)(struct cx_tree_s *tree, struct cx_iterator_base_s *iter, size_t n); /** * Member function for finding a node. */ - void *(*find)( - struct cx_tree_s *tree, - const void *subtree, - const void *data, - size_t depth - ); + void *(*find)(struct cx_tree_s *tree, const void *subtree, const void *data, size_t depth); }; /** @@ -885,7 +788,7 @@ typedef struct cx_tree_s CxTree; /** - * Destroys a node and it's subtree. + * Destroys a node and its subtree. * * It is guaranteed that the simple destructor is invoked before * the advanced destructor, starting with the leaf nodes of the subtree. @@ -906,8 +809,7 @@ typedef struct cx_tree_s CxTree; * @see cxTreeFree() */ cx_attr_nonnull -cx_attr_export -void cxTreeDestroySubtree(CxTree *tree, void *node); +CX_EXPORT void cxTreeDestroySubtree(CxTree *tree, void *node); /** @@ -921,7 +823,7 @@ void cxTreeDestroySubtree(CxTree *tree, void *node); * * @attention Be careful when calling this function when no destructor function * is registered that actually frees the memory of nodes. In that case you will - * need a reference to the (former) root node of the tree somewhere or + * need a reference to the (former) root node of the tree somewhere, or * otherwise you will be leaking memory. * * @param tree the tree @@ -945,8 +847,7 @@ void cxTreeDestroySubtree(CxTree *tree, void *node); * * @param tree the tree to free */ -cx_attr_export -void cxTreeFree(CxTree *tree); +CX_EXPORT void cxTreeFree(CxTree *tree); /** * Creates a new tree structure based on the specified layout. @@ -972,29 +873,18 @@ void cxTreeFree(CxTree *tree); * @see cxTreeCreateSimple() * @see cxTreeCreateWrapped() */ -cx_attr_nonnull_arg(2, 3, 4) -cx_attr_nodiscard -cx_attr_malloc -cx_attr_dealloc(cxTreeFree, 1) -cx_attr_export -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 -); +cx_attr_nonnull_arg(2, 3, 4) cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxTreeFree, 1) +CX_EXPORT 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 + * Nodes created by @p create_func MUST contain #cx_tree_node_base_s as the first * member (or at least respect the default offsets specified in the tree - * struct) and they MUST be allocated with the specified allocator. + * 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. @@ -1006,10 +896,8 @@ CxTree *cxTreeCreate( * @return (@c CxTree*) the new tree * @see cxTreeCreate() */ -#define cxTreeCreateSimple(\ - allocator, create_func, search_func, search_data_func \ -) cxTreeCreate(allocator, create_func, search_func, search_data_func, \ -cx_tree_node_base_layout) +#define cxTreeCreateSimple(allocator, create_func, search_func, search_data_func) \ + cxTreeCreate(allocator, create_func, search_func, search_data_func, cx_tree_node_base_layout) /** * Creates a new tree structure based on an existing tree. @@ -1019,7 +907,7 @@ cx_tree_node_base_layout) * @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. + * tree, you need to specify those functions afterward. * * @param allocator the allocator that was used for nodes of the wrapped tree * (if @c NULL, the cxDefaultAllocator is assumed) @@ -1033,20 +921,10 @@ cx_tree_node_base_layout) * @return the new tree * @see cxTreeCreate() */ -cx_attr_nonnull_arg(2) -cx_attr_nodiscard -cx_attr_malloc -cx_attr_dealloc(cxTreeFree, 1) -cx_attr_export -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 -); +cx_attr_nonnull_arg(2) cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxTreeFree, 1) +CX_EXPORT CxTree *cxTreeCreateWrapped(const CxAllocator *allocator, void *root, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next); /** * Inserts data into the tree. @@ -1061,12 +939,7 @@ CxTree *cxTreeCreateWrapped( * @retval non-zero failure */ cx_attr_nonnull -static inline int cxTreeInsert( - CxTree *tree, - const void *data -) { - return tree->cl->insert_element(tree, data); -} +CX_EXPORT int cxTreeInsert(CxTree *tree, const void *data); /** * Inserts elements provided by an iterator efficiently into the tree. @@ -1081,13 +954,7 @@ static inline int cxTreeInsert( * @return the number of elements that could be successfully inserted */ cx_attr_nonnull -static inline size_t cxTreeInsertIter( - CxTree *tree, - CxIteratorBase *iter, - size_t n -) { - return tree->cl->insert_many(tree, iter, n); -} +CX_EXPORT size_t cxTreeInsertIter(CxTree *tree, CxIteratorBase *iter, size_t n); /** * Inserts an array of data efficiently into the tree. @@ -1103,17 +970,7 @@ static inline size_t cxTreeInsertIter( * @return the number of elements that could be successfully inserted */ cx_attr_nonnull -static inline size_t cxTreeInsertArray( - CxTree *tree, - const void *data, - size_t elem_size, - size_t n -) { - if (n == 0) return 0; - if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0; - CxIterator iter = cxIterator(data, elem_size, n); - return cxTreeInsertIter(tree, cxIteratorRef(iter), n); -} +CX_EXPORT size_t cxTreeInsertArray(CxTree *tree, const void *data, size_t elem_size, size_t n); /** * Searches the data in the specified tree. @@ -1126,14 +983,8 @@ static inline size_t cxTreeInsertArray( * @param data the data to search for * @return the first matching node, or @c NULL when the data cannot be found */ -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxTreeFind( - CxTree *tree, - const void *data -) { - return tree->cl->find(tree, tree->root, data, 0); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT void *cxTreeFind(CxTree *tree, const void *data); /** * Searches the data in the specified subtree. @@ -1154,16 +1005,8 @@ static inline void *cxTreeFind( * @param max_depth the maximum search depth * @return the first matching node, or @c NULL when the data cannot be found */ -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxTreeFindInSubtree( - CxTree *tree, - const void *data, - void *subtree_root, - size_t max_depth -) { - return tree->cl->find(tree, subtree_root, data, max_depth); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT void *cxTreeFindInSubtree(CxTree *tree, const void *data, void *subtree_root, size_t max_depth); /** * Determines the size of the specified subtree. @@ -1172,10 +1015,8 @@ static inline void *cxTreeFindInSubtree( * @param subtree_root the root node of the subtree * @return the number of nodes in the specified subtree */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root); /** * Determines the depth of the specified subtree. @@ -1184,10 +1025,8 @@ size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root); * @param subtree_root the root node of the subtree * @return the tree depth including the @p subtree_root */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); /** * Determines the size of the entire tree. @@ -1195,11 +1034,8 @@ size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); * @param tree the tree * @return the tree size, counting the root as one */ -cx_attr_nonnull -cx_attr_nodiscard -static inline size_t cxTreeSize(CxTree *tree) { - return tree->size; -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT size_t cxTreeSize(CxTree *tree); /** * Determines the depth of the entire tree. @@ -1207,10 +1043,8 @@ static inline size_t cxTreeSize(CxTree *tree) { * @param tree the tree * @return the tree depth, counting the root as one */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -size_t cxTreeDepth(CxTree *tree); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT size_t cxTreeDepth(CxTree *tree); /** * Creates a depth-first iterator for the specified tree starting in @p node. @@ -1224,18 +1058,8 @@ size_t cxTreeDepth(CxTree *tree); * @return a tree iterator (depth-first) * @see cxTreeVisit() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline CxTreeIterator cxTreeIterateSubtree( - CxTree *tree, - void *node, - bool visit_on_exit -) { - return cx_tree_iterator( - node, visit_on_exit, - tree->loc_children, tree->loc_next - ); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxTreeIterator cxTreeIterateSubtree(CxTree *tree, void *node, bool visit_on_exit); /** * Creates a breadth-first iterator for the specified tree starting in @p node. @@ -1247,13 +1071,8 @@ static inline CxTreeIterator cxTreeIterateSubtree( * @return a tree visitor (a.k.a. breadth-first iterator) * @see cxTreeIterate() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) { - return cx_tree_visitor( - node, tree->loc_children, tree->loc_next - ); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node); /** * Creates a depth-first iterator for the specified tree. @@ -1264,14 +1083,8 @@ static inline CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) { * @return a tree iterator (depth-first) * @see cxTreeVisit() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline CxTreeIterator cxTreeIterate( - CxTree *tree, - bool visit_on_exit -) { - return cxTreeIterateSubtree(tree, tree->root, visit_on_exit); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxTreeIterator cxTreeIterate(CxTree *tree, bool visit_on_exit); /** * Creates a breadth-first iterator for the specified tree. @@ -1280,16 +1093,13 @@ static inline CxTreeIterator cxTreeIterate( * @return a tree visitor (a.k.a. breadth-first iterator) * @see cxTreeIterate() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline CxTreeVisitor cxTreeVisit(CxTree *tree) { - return cxTreeVisitSubtree(tree, tree->root); -} +cx_attr_nonnull cx_attr_nodiscard +CxTreeVisitor cxTreeVisit(CxTree *tree); /** * Sets the (new) parent of the specified child. * - * If the @p child is not already member of the tree, this function behaves + * If the @p child is not already a member of the tree, this function behaves * as #cxTreeAddChildNode(). * * @param tree the tree @@ -1298,21 +1108,16 @@ static inline CxTreeVisitor cxTreeVisit(CxTree *tree) { * @see cxTreeAddChildNode() */ cx_attr_nonnull -cx_attr_export -void cxTreeSetParent( - CxTree *tree, - void *parent, - void *child -); +CX_EXPORT void cxTreeSetParent(CxTree *tree, void *parent, void *child); /** * Adds a new node to the tree. * - * If the @p child is already member of the tree, the behavior is undefined. + * If the @p child is already a member of the tree, the behavior is undefined. * Use #cxTreeSetParent() if you want to move a subtree to another location. * * @attention The node may be externally created, but MUST obey the same rules - * as if it was created by the tree itself with #cxTreeAddChild() (e.g. use + * as if it was created by the tree itself with #cxTreeAddChild() (e.g., use * the same allocator). * * @param tree the tree @@ -1321,12 +1126,7 @@ void cxTreeSetParent( * @see cxTreeSetParent() */ cx_attr_nonnull -cx_attr_export -void cxTreeAddChildNode( - CxTree *tree, - void *parent, - void *child -); +CX_EXPORT void cxTreeAddChildNode(CxTree *tree, void *parent, void *child); /** * Creates a new node and adds it to the tree. @@ -1336,7 +1136,7 @@ void cxTreeAddChildNode( * 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 + * 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 @@ -1346,12 +1146,7 @@ void cxTreeAddChildNode( * @see cxTreeInsert() */ cx_attr_nonnull -cx_attr_export -int cxTreeAddChild( - CxTree *tree, - void *parent, - const void *data -); +CX_EXPORT 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. @@ -1365,7 +1160,6 @@ int cxTreeAddChild( * @param old_parent the old parent of the node * @param new_parent the new parent of the node */ -cx_attr_nonnull typedef void (*cx_tree_relink_func)( void *node, const void *old_parent, @@ -1387,15 +1181,10 @@ typedef void (*cx_tree_relink_func)( * @return zero on success, non-zero if @p node is the root node of the tree */ cx_attr_nonnull_arg(1, 2) -cx_attr_export -int cxTreeRemoveNode( - CxTree *tree, - void *node, - cx_tree_relink_func relink_func -); +CX_EXPORT int cxTreeRemoveNode(CxTree *tree, void *node, cx_tree_relink_func relink_func); /** - * Removes a node and it's subtree from the tree. + * Removes a node and its subtree from the tree. * * If the node is not part of the tree, the behavior is undefined. * @@ -1406,8 +1195,7 @@ int cxTreeRemoveNode( * @param node the node to remove */ cx_attr_nonnull -cx_attr_export -void cxTreeRemoveSubtree(CxTree *tree, void *node); +CX_EXPORT void cxTreeRemoveSubtree(CxTree *tree, void *node); /** * Destroys a node and re-links its children to its former parent. @@ -1428,12 +1216,7 @@ void cxTreeRemoveSubtree(CxTree *tree, void *node); * @return zero on success, non-zero if @p node is the root node of the tree */ cx_attr_nonnull_arg(1, 2) -cx_attr_export -int cxTreeDestroyNode( - CxTree *tree, - void *node, - cx_tree_relink_func relink_func -); +CX_EXPORT int cxTreeDestroyNode(CxTree *tree, void *node, cx_tree_relink_func relink_func); #ifdef __cplusplus } // extern "C" diff --git a/ucx/hash_key.c b/ucx/hash_key.c index fac29bb..25f8c4b 100644 --- a/ucx/hash_key.c +++ b/ucx/hash_key.c @@ -27,6 +27,7 @@ */ #include "cx/hash_key.h" +#include "cx/compare.h" #include void cx_hash_murmur(CxHashKey *key) { @@ -62,14 +63,14 @@ void cx_hash_murmur(CxHashKey *key) { switch (len) { case 3: h ^= (data[i + 2] & 0xFF) << 16; - __attribute__((__fallthrough__)); + cx_attr_fallthrough; case 2: h ^= (data[i + 1] & 0xFF) << 8; - __attribute__((__fallthrough__)); + cx_attr_fallthrough; case 1: h ^= (data[i + 0] & 0xFF); h *= m; - __attribute__((__fallthrough__)); + cx_attr_fallthrough; default: // do nothing ; } @@ -81,6 +82,21 @@ void cx_hash_murmur(CxHashKey *key) { key->hash = h; } + +uint32_t cx_hash_u32(uint32_t x) { + x = ((x >> 16) ^ x) * 0x45d9f3bu; + x = ((x >> 16) ^ x) * 0x45d9f3bu; + x = (x >> 16) ^ x; + return x; +} + +uint64_t cx_hash_u64(uint64_t x) { + x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); + x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb); + x = x ^ (x >> 31); + return x; +} + CxHashKey cx_hash_key_str(const char *str) { CxHashKey key; key.data = str; @@ -89,6 +105,22 @@ CxHashKey cx_hash_key_str(const char *str) { return key; } +CxHashKey cx_hash_key_ustr(unsigned const char *str) { + CxHashKey key; + key.data = str; + key.len = str == NULL ? 0 : strlen((const char*)str); + cx_hash_murmur(&key); + return key; +} + +CxHashKey cx_hash_key_cxstr(cxstring str) { + return cx_hash_key(str.ptr, str.length); +} + +CxHashKey cx_hash_key_mutstr(cxmutstr str) { + return cx_hash_key(str.ptr, str.length); +} + CxHashKey cx_hash_key_bytes( const unsigned char *bytes, size_t len @@ -110,3 +142,29 @@ CxHashKey cx_hash_key( cx_hash_murmur(&key); return key; } + +CxHashKey cx_hash_key_u32(uint32_t x) { + CxHashKey key; + key.data = NULL; + key.len = 0; + key.hash = cx_hash_u32(x); + return key; +} + +CxHashKey cx_hash_key_u64(uint64_t x) { + CxHashKey key; + key.data = NULL; + key.len = 0; + key.hash = cx_hash_u64(x); + return key; +} + +int cx_hash_key_cmp(const CxHashKey *left, const CxHashKey *right) { + int d; + d = cx_vcmp_uint64(left->hash, right->hash); + if (d != 0) return d; + d = cx_vcmp_size(left->len, right->len); + if (d != 0) return d; + if (left->len == 0) return 0; + return memcmp(left->data, right->data, left->len); +} diff --git a/ucx/hash_map.c b/ucx/hash_map.c index 1721e8c..82ae21e 100644 --- a/ucx/hash_map.c +++ b/ucx/hash_map.c @@ -78,7 +78,7 @@ static void cx_hash_map_destructor(struct cx_map_s *map) { cxFree(map->collection.allocator, map); } -static int cx_hash_map_put( +static void *cx_hash_map_put( CxMap *map, CxHashKey key, void *value @@ -101,11 +101,12 @@ static int cx_hash_map_put( elm = elm->next; } - if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len && - memcmp(elm->key.data, key.data, key.len) == 0) { + if (elm != NULL && cx_hash_key_cmp(&elm->key, &key) == 0) { // overwrite existing element, but call destructors first cx_invoke_destructor(map, elm->data); - if (map->collection.store_pointer) { + if (value == NULL) { + memset(elm->data, 0, map->collection.elem_size); + } else if (map->collection.store_pointer) { memcpy(elm->data, &value, sizeof(void *)); } else { memcpy(elm->data, value, map->collection.elem_size); @@ -116,10 +117,12 @@ static int cx_hash_map_put( allocator, sizeof(struct cx_hash_map_element_s) + map->collection.elem_size ); - if (e == NULL) return -1; + if (e == NULL) return NULL; // write the value - if (map->collection.store_pointer) { + if (value == NULL) { + memset(e->data, 0, map->collection.elem_size); + } else if (map->collection.store_pointer) { memcpy(e->data, &value, sizeof(void *)); } else { memcpy(e->data, value, map->collection.elem_size); @@ -127,7 +130,10 @@ static int cx_hash_map_put( // copy the key void *kd = cxMalloc(allocator, key.len); - if (kd == NULL) return -1; + if (kd == NULL) { + cxFree(allocator, e); + return NULL; + } memcpy(kd, key.data, key.len); e->key.data = kd; e->key.len = key.len; @@ -140,12 +146,14 @@ static int cx_hash_map_put( prev->next = e; } e->next = elm; + elm = e; // increase the size map->collection.size++; } - return 0; + // return pointer to the element + return elm->data; } static void cx_hash_map_unlink( @@ -205,27 +213,25 @@ static int cx_hash_map_get_remove( struct cx_hash_map_element_s *elm = hash_map->buckets[slot]; struct cx_hash_map_element_s *prev = NULL; while (elm && elm->key.hash <= hash) { - if (elm->key.hash == hash && elm->key.len == key.len) { - if (memcmp(elm->key.data, key.data, key.len) == 0) { - if (remove) { - if (targetbuf == NULL) { - cx_invoke_destructor(map, elm->data); - } else { - memcpy(targetbuf, elm->data, map->collection.elem_size); - } - cx_hash_map_unlink(hash_map, slot, prev, elm); + if (cx_hash_key_cmp(&elm->key, &key) == 0) { + if (remove) { + if (targetbuf == NULL) { + cx_invoke_destructor(map, elm->data); + } else { + memcpy(targetbuf, elm->data, map->collection.elem_size); + } + cx_hash_map_unlink(hash_map, slot, prev, elm); + } else { + assert(targetbuf != NULL); + void *data = NULL; + if (map->collection.store_pointer) { + data = *(void **) elm->data; } else { - assert(targetbuf != NULL); - void *data = NULL; - if (map->collection.store_pointer) { - data = *(void **) elm->data; - } else { - data = elm->data; - } - memcpy(targetbuf, &data, sizeof(void *)); + data = elm->data; } - return 0; + memcpy(targetbuf, &data, sizeof(void *)); } + return 0; } prev = elm; elm = prev->next; @@ -260,19 +266,12 @@ static void *cx_hash_map_iter_current_entry(const void *it) { static void *cx_hash_map_iter_current_key(const void *it) { const CxMapIterator *iter = it; - struct cx_hash_map_element_s *elm = iter->elem; - return &elm->key; + return (void*) iter->entry.key; } static void *cx_hash_map_iter_current_value(const void *it) { const CxMapIterator *iter = it; - const CxMap *map = iter->map.c; - struct cx_hash_map_element_s *elm = iter->elem; - if (map->collection.store_pointer) { - return *(void **) elm->data; - } else { - return elm->data; - } + return iter->entry.value; } static bool cx_hash_map_iter_valid(const void *it) { @@ -282,7 +281,7 @@ static bool cx_hash_map_iter_valid(const void *it) { static void cx_hash_map_iter_next(void *it) { CxMapIterator *iter = it; - CxMap *map = iter->map.m; + CxMap *map = iter->map; struct cx_hash_map_s *hmap = (struct cx_hash_map_s *) map; struct cx_hash_map_element_s *elm = iter->elem; @@ -309,6 +308,7 @@ static void cx_hash_map_iter_next(void *it) { // unlink cx_hash_map_unlink(hmap, iter->slot, prev, elm); + iter->elem_count--; // advance elm = next; @@ -329,7 +329,7 @@ static void cx_hash_map_iter_next(void *it) { // must not modify the iterator (the parameter is const) if (elm != NULL) { iter->entry.key = &elm->key; - if (iter->map.c->collection.store_pointer) { + if (map->collection.store_pointer) { iter->entry.value = *(void **) elm->data; } else { iter->entry.value = elm->data; @@ -343,7 +343,7 @@ static CxMapIterator cx_hash_map_iterator( ) { CxMapIterator iter; - iter.map.c = map; + iter.map = (CxMap*) map; iter.elem_count = map->collection.size; switch (type) { @@ -366,7 +366,7 @@ static CxMapIterator cx_hash_map_iterator( 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.base.allow_remove = true; iter.slot = 0; iter.index = 0; diff --git a/ucx/iterator.c b/ucx/iterator.c index 6a78a69..292c11e 100644 --- a/ucx/iterator.c +++ b/ucx/iterator.c @@ -53,7 +53,7 @@ static void cx_iter_next_fast(void *it) { // 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) + void *last = ((char *) iter->src_handle) + iter->elem_count * iter->elem_size; memcpy(iter->elem_handle, last, iter->elem_size); } @@ -84,8 +84,8 @@ static void cx_iter_next_slow(void *it) { } } -CxIterator cxMutIterator( - void *array, +CxIterator cxIterator( + const void *array, size_t elem_size, size_t elem_count, bool remove_keeps_order @@ -93,44 +93,25 @@ CxIterator cxMutIterator( CxIterator iter; iter.index = 0; - iter.src_handle.m = array; - iter.elem_handle = array; + iter.src_handle = (void*) array; + iter.elem_handle = (void*) 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; + iter.base.allow_remove = true; return iter; } -CxIterator cxIterator( +CxIterator cxIteratorPtr( const void *array, - size_t elem_size, - size_t elem_count -) { - CxIterator iter = cxMutIterator((void*)array, elem_size, elem_count, false); - iter.base.mutating = false; - return iter; -} - -CxIterator cxMutIteratorPtr( - void *array, size_t elem_count, bool remove_keeps_order ) { - CxIterator iter = cxMutIterator(array, sizeof(void*), elem_count, remove_keeps_order); + CxIterator iter = cxIterator(array, sizeof(void*), elem_count, remove_keeps_order); iter.base.current = cx_iter_current_ptr; return iter; } - -CxIterator cxIteratorPtr( - const void *array, - size_t elem_count -) { - CxIterator iter = cxMutIteratorPtr((void*) array, elem_count, false); - iter.base.mutating = false; - return iter; -} diff --git a/ucx/json.c b/ucx/json.c index 484c7d3..caf1c72 100644 --- a/ucx/json.c +++ b/ucx/json.c @@ -32,6 +32,7 @@ #include #include #include +#include /* * RFC 8259 @@ -46,22 +47,17 @@ static int json_cmp_objvalue(const void *l, const void *r) { return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name)); } -static CxJsonObjValue *json_find_objvalue(const CxJsonValue *obj, cxstring name) { +static size_t json_find_objvalue(const CxJsonValue *obj, cxstring name) { assert(obj->type == CX_JSON_OBJECT); CxJsonObjValue kv_dummy; kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length); - size_t index = cx_array_binary_search( + return cx_array_binary_search( obj->value.object.values, obj->value.object.values_size, sizeof(CxJsonObjValue), &kv_dummy, json_cmp_objvalue ); - if (index == obj->value.object.values_size) { - return NULL; - } else { - return &obj->value.object.values[index]; - } } static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) { @@ -132,16 +128,6 @@ static void token_destroy(CxJsonToken *token) { } } -static bool json_isdigit(char c) { - // TODO: remove once UCX has public API for this - return c >= '0' && c <= '9'; -} - -static bool json_isspace(char c) { - // TODO: remove once UCX has public API for this - return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f'; -} - static int num_isexp(const char *content, size_t length, size_t pos) { if (pos >= length) { return 0; @@ -150,7 +136,7 @@ static int num_isexp(const char *content, size_t length, size_t pos) { int ok = 0; for (size_t i = pos; i < length; i++) { char c = content[i]; - if (json_isdigit(c)) { + if (isdigit((unsigned char)c)) { ok = 1; } else if (i == pos) { if (!(c == '+' || c == '-')) { @@ -167,7 +153,7 @@ static int num_isexp(const char *content, size_t length, size_t pos) { static CxJsonTokenType token_numbertype(const char *content, size_t length) { if (length == 0) return CX_JSON_TOKEN_ERROR; - if (content[0] != '-' && !json_isdigit(content[0])) { + if (content[0] != '-' && !isdigit((unsigned char)content[0])) { return CX_JSON_TOKEN_ERROR; } @@ -180,7 +166,7 @@ static CxJsonTokenType token_numbertype(const char *content, size_t length) { type = CX_JSON_TOKEN_NUMBER; } else if (content[i] == 'e' || content[i] == 'E') { return num_isexp(content, length, i + 1) ? CX_JSON_TOKEN_NUMBER : CX_JSON_TOKEN_ERROR; - } else if (!json_isdigit(content[i])) { + } else if (!isdigit((unsigned char)content[i])) { return CX_JSON_TOKEN_ERROR; // char is not a digit, decimal separator or exponent sep } } @@ -244,7 +230,7 @@ static CxJsonTokenType char2ttype(char c) { return CX_JSON_TOKEN_STRING; } default: { - if (json_isspace(c)) { + if (isspace((unsigned char)c)) { return CX_JSON_TOKEN_SPACE; } } @@ -644,6 +630,12 @@ void cxJsonDestroy(CxJson *json) { } } +void cxJsonReset(CxJson *json) { + const CxAllocator *allocator = json->allocator; + cxJsonDestroy(json); + cxJsonInit(json, allocator); +} + int cxJsonFilln(CxJson *json, const char *buf, size_t size) { if (cxBufferEof(&json->buffer)) { // reinitialize the buffer @@ -1126,10 +1118,53 @@ CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) { return value->value.array.array[index]; } +CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index) { + if (index >= value->value.array.array_size) { + return NULL; + } + CxJsonValue *ret = value->value.array.array[index]; + // TODO: replace with a low level cx_array_remove() + size_t count = value->value.array.array_size - index - 1; + if (count > 0) { + memmove(value->value.array.array + index, value->value.array.array + index + 1, count * sizeof(CxJsonValue*)); + } + value->value.array.array_size--; + return ret; +} + +char *cxJsonAsString(const CxJsonValue *value) { + return value->value.string.ptr; +} + +cxstring cxJsonAsCxString(const CxJsonValue *value) { + return cx_strcast(value->value.string); +} + +cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { + return value->value.string; +} + +double cxJsonAsDouble(const CxJsonValue *value) { + if (value->type == CX_JSON_INTEGER) { + return (double) value->value.integer; + } else { + return value->value.number; + } +} + +int64_t cxJsonAsInteger(const CxJsonValue *value) { + if (value->type == CX_JSON_INTEGER) { + return value->value.integer; + } else { + return (int64_t) value->value.number; + } +} + CxIterator cxJsonArrIter(const CxJsonValue *value) { return cxIteratorPtr( value->value.array.array, - value->value.array.array_size + value->value.array.array_size, + true // arrays need to keep order ); } @@ -1137,16 +1172,31 @@ CxIterator cxJsonObjIter(const CxJsonValue *value) { return cxIterator( value->value.object.values, sizeof(CxJsonObjValue), - value->value.object.values_size + value->value.object.values_size, + true // TODO: objects do not always need to keep order ); } -CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) { - CxJsonObjValue *member = json_find_objvalue(value, name); - if (member == NULL) { +CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name) { + size_t index = json_find_objvalue(value, name); + if (index >= value->value.object.values_size) { return &cx_json_value_nothing; } else { - return member->value; + return value->value.object.values[index].value; + } +} + +CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name) { + size_t index = json_find_objvalue(value, name); + if (index >= value->value.object.values_size) { + return NULL; + } else { + CxJsonObjValue kv = value->value.object.values[index]; + cx_strfree_a(value->allocator, &kv.name); + // TODO: replace with cx_array_remove() / cx_array_remove_fast() + value->value.object.values_size--; + memmove(value->value.object.values + index, value->value.object.values + index + 1, (value->value.object.values_size - index) * sizeof(CxJsonObjValue)); + return kv.value; } } diff --git a/ucx/kv_list.c b/ucx/kv_list.c new file mode 100644 index 0000000..d4e19f2 --- /dev/null +++ b/ucx/kv_list.c @@ -0,0 +1,705 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 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/kv_list.h" +#include "cx/hash_map.h" +#include "cx/linked_list.h" + +#include +#include + +typedef struct cx_kv_list_s cx_kv_list; + +struct cx_kv_list_map_s { + struct cx_hash_map_s map_base; + /** Back-reference to the list. */ + cx_kv_list *list; +}; + +struct cx_kv_list_s { + struct cx_linked_list_s list; + /** The lookup map - stores pointers to the nodes. */ + struct cx_kv_list_map_s *map; + const cx_list_class *list_methods; + const cx_map_class *map_methods; + cx_destructor_func list_destr; + cx_destructor_func2 list_destr2; + void *list_destr_data; + cx_destructor_func map_destr; + cx_destructor_func2 map_destr2; + void *map_destr_data; +}; + +static void cx_kv_list_destructor_wrapper(void *list_ptr, void *elem) { + const cx_kv_list *kv_list = list_ptr; + // list destructor is already called with proper deref of the elem + if (kv_list->list_destr) { + kv_list->list_destr(elem); + } + if (kv_list->list_destr2) { + kv_list->list_destr2(kv_list->list_destr_data, elem); + } + if (kv_list->map_destr) { + kv_list->map_destr(elem); + } + if (kv_list->map_destr2) { + kv_list->map_destr2(kv_list->map_destr_data, elem); + } +} + +static void cx_kv_list_update_destructors(cx_kv_list *list) { + // we copy the destructors to our custom fields and register + // an own destructor function which invokes all these + if (list->list.base.collection.simple_destructor != NULL) { + list->list_destr = list->list.base.collection.simple_destructor; + list->list.base.collection.simple_destructor = NULL; + } + if (list->list.base.collection.advanced_destructor != cx_kv_list_destructor_wrapper) { + list->list_destr2 = list->list.base.collection.advanced_destructor; + list->list_destr_data = list->list.base.collection.destructor_data; + list->list.base.collection.advanced_destructor = cx_kv_list_destructor_wrapper; + list->list.base.collection.destructor_data = list; + } + if (list->map->map_base.base.collection.simple_destructor != NULL) { + list->map_destr = list->map->map_base.base.collection.simple_destructor; + list->map->map_base.base.collection.simple_destructor = NULL; + } + if (list->map->map_base.base.collection.advanced_destructor != NULL) { + list->map_destr2 = list->map->map_base.base.collection.advanced_destructor; + list->map_destr_data = list->map->map_base.base.collection.destructor_data; + list->map->map_base.base.collection.advanced_destructor = NULL; + list->map->map_base.base.collection.destructor_data = NULL; + } +} + +static CxHashKey *cx_kv_list_loc_key(cx_kv_list *list, void *node_data) { + return (CxHashKey*)((char*)node_data + list->list.base.collection.elem_size); +} + +static void cx_kvl_deallocate(struct cx_list_s *list) { + cx_kv_list *kv_list = (cx_kv_list*)list; + // patch the destructors + cx_kv_list_update_destructors(kv_list); + kv_list->map_methods->deallocate(&kv_list->map->map_base.base); + // then free the list, now the destructors may be called + kv_list->list_methods->deallocate(list); +} + +static void *cx_kvl_insert_element( + struct cx_list_s *list, + size_t index, + const void *data +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->insert_element(list, index, data); +} + +static size_t cx_kvl_insert_array( + struct cx_list_s *list, + size_t index, + const void *data, + size_t n +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->insert_array(list, index, data, n); +} + +static size_t cx_kvl_insert_sorted( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->insert_sorted(list, sorted_data, n); +} + +static size_t cx_kvl_insert_unique( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->insert_unique(list, sorted_data, n); +} + +static int cx_kvl_insert_iter( + struct cx_iterator_s *iter, + const void *elem, + int prepend +) { + cx_kv_list *kv_list = iter->src_handle; + return kv_list->list_methods->insert_iter(iter, elem, prepend); +} + +static size_t cx_kvl_remove( + struct cx_list_s *list, + size_t index, + size_t num, + void *targetbuf +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + // patch the destructors + // we also have to do that when targetbuf is NULL, + // because we do not want wrong destructors to be called when we remove keys from the map + cx_kv_list_update_destructors(kv_list); + // iterate through the elements first and remove their keys from the map + CxIterator iter = kv_list->list_methods->iterator(list, index, false); + for (size_t i = 0; i < num && cxIteratorValid(iter); i++) { + void *node_data = cxIteratorCurrent(iter); + CxHashKey *key = cx_kv_list_loc_key(kv_list, node_data); + // when the hash is zero, there is no key assigned to that element + if (key->hash != 0) { + kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); + } + cxIteratorNext(iter); + } + return kv_list->list_methods->remove(list, index, num, targetbuf); +} + +static void cx_kvl_clear(struct cx_list_s *list) { + cx_kv_list *kv_list = (cx_kv_list*)list; + // patch the destructors + cx_kv_list_update_destructors(kv_list); + // clear the list + kv_list->list_methods->clear(list); + // then clear the map + kv_list->map_methods->clear(&kv_list->map->map_base.base); +} + +static int cx_kvl_swap( + struct cx_list_s *list, + size_t i, + size_t j +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->swap(list, i, j); +} + +static void *cx_kvl_at( + const struct cx_list_s *list, + size_t index +) { + const cx_kv_list *kv_list = (const cx_kv_list*)list; + return kv_list->list_methods->at(list, index); +} + +static size_t cx_kvl_find_remove( + struct cx_list_s *list, + const void *elem, + bool remove +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + // we do not use the original list methods, + // because that would need two passes for removal + // (the first to find the index, the second to get a pointer) + if (list->collection.size == 0) return 0; + + size_t index; + cx_linked_list *ll = &kv_list->list; + char *node = cx_linked_list_find( + ll->begin, + ll->loc_next, ll->loc_data, + list->collection.cmpfunc, elem, + &index + ); + if (node == NULL) { + return list->collection.size; + } + if (remove) { + cx_kv_list_update_destructors(kv_list); + cx_invoke_advanced_destructor(list, node + ll->loc_data); + cx_linked_list_remove(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node); + CxHashKey *key = cx_kv_list_loc_key(kv_list, node + ll->loc_data); + if (key->hash != 0) { + kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); + } + list->collection.size--; + cxFree(list->collection.allocator, node); + } + return index; +} + +static void cx_kvl_sort(struct cx_list_s *list) { + cx_kv_list *kv_list = (cx_kv_list*)list; + kv_list->list_methods->sort(list); +} + +static void cx_kvl_reverse(struct cx_list_s *list) { + cx_kv_list *kv_list = (cx_kv_list*)list; + kv_list->list_methods->reverse(list); +} + +static void cx_kvl_list_iter_next(void *it) { + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + // remove the assigned key from the map before calling the actual function + cx_kv_list *kv_list = iter->src_handle; + cx_kv_list_update_destructors(kv_list); + char *node = iter->elem_handle; + CxHashKey *key = cx_kv_list_loc_key(kv_list, node + kv_list->list.loc_data); + if (key->hash != 0) { + kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); + } + } + // note that we do not clear the remove flag, because the next_impl will do that + iter->base.next_impl(it); +} + +static struct cx_iterator_s cx_kvl_iterator( + const struct cx_list_s *list, + size_t index, + bool backward +) { + const cx_kv_list *kv_list = (const cx_kv_list*)list; + struct cx_iterator_s iter = kv_list->list_methods->iterator(list, index, backward); + iter.base.next_impl = iter.base.next; + iter.base.next = cx_kvl_list_iter_next; + return iter; +} + +static void cx_kvl_map_deallocate(struct cx_map_s *map) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + kv_list->map_methods->deallocate(map); + kv_list->list_methods->deallocate(&kv_list->list.base); +} + +static void cx_kvl_map_clear(struct cx_map_s *map) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + cx_kv_list_update_destructors(kv_list); + kv_list->list_methods->clear(&kv_list->list.base); + kv_list->map_methods->clear(map); +} + +static void *cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + // if the hash has not yet been computed, do it now + if (key.hash == 0) { + cx_hash_murmur(&key); + } + + // reserve memory in the map first + void **map_data = kv_list->map_methods->put(map, key, NULL); + if (map_data == NULL) return NULL; // LCOV_EXCL_LINE + + // insert the data into the list (which most likely destroys the sorted property) + kv_list->list.base.collection.sorted = false; + void *node_data = kv_list->list_methods->insert_element( + &kv_list->list.base, kv_list->list.base.collection.size, + kv_list->list.base.collection.store_pointer ? &value : value); + if (node_data == NULL) { // LCOV_EXCL_START + // non-destructively remove the key again + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); + return NULL; + } // LCOV_EXCL_STOP + + // write the node pointer to the map entry + *map_data = node_data; + + // copy the key to the node data + CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); + *key_ptr = key; + + // we must return node_data here and not map_data, + // because the node_data is the actual element of this collection + return node_data; +} + +void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + void *node_data = kv_list->map_methods->get(map, key); + if (node_data == NULL) return NULL; // LCOV_EXCL_LINE + // return the node data + return kv_list->list.base.collection.store_pointer ? *(void**)node_data : node_data; +} + +int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + + void *node_data; + if (kv_list->map_methods->remove(map, key, &node_data)) { + return 1; + } + // we cannot just call a list method (because we don't have the index) + // and tbh. we also don't want to (because it's not performant when we + // can have the node ptr directly instead) + // therefore, we re-implement the logic ourselves + + // check if the outside caller want's us to return or to destroy the element + if (targetbuf == NULL) { + // patch the destructors and invoke them through the wrapper + cx_kv_list_update_destructors(kv_list); + cx_invoke_advanced_destructor(&kv_list->list.base, node_data); + } else { + // copy the element to the target buffer + memcpy(targetbuf, node_data, kv_list->list.base.collection.elem_size); + } + + // calculate the address of the node + void *node_ptr = (char*)node_data - kv_list->list.loc_data; + + // unlink the node + cx_linked_list_remove( + &kv_list->list.begin, + &kv_list->list.end, + kv_list->list.loc_prev, + kv_list->list.loc_next, + node_ptr + ); + + // decrement the list's size + kv_list->list.base.collection.size--; + + // deallocate the node + cxFree(kv_list->list.base.collection.allocator, node_ptr); + + return 0; +} + +static void *cx_kvl_iter_current_entry(const void *it) { + const CxMapIterator *iter = it; + return (void*)&iter->entry; +} + +static void *cx_kvl_iter_current_key(const void *it) { + const CxMapEntry *entry = cx_kvl_iter_current_entry(it); + return (void*)entry->key; +} + +static void *cx_kvl_iter_current_value(const void *it) { + const CxMapEntry *entry = cx_kvl_iter_current_entry(it); + return entry->value; +} + +static void cx_kvl_iter_next(void *it) { + CxMapIterator *iter = it; + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)iter->map)->list; + + // find the next list entry that has a key assigned + CxHashKey *key = NULL; + char *next = iter->elem; + while (true) { + next = *(char**)(next + kv_list->list.loc_next); + if (next == NULL) break; + key = cx_kv_list_loc_key(kv_list, next + kv_list->list.loc_data); + if (key->hash != 0) break; + } + + // remove previous element if requested + if (iter->base.remove) { + iter->base.remove = false; + cx_kv_list_update_destructors(kv_list); + char *elem = iter->elem; + char *elem_data = elem + kv_list->list.loc_data; + CxHashKey *elem_key = cx_kv_list_loc_key(kv_list, elem_data); + // key is guaranteed to exist because iterator only iterates over elems with a key + kv_list->map_methods->remove(&kv_list->map->map_base.base, *elem_key, NULL); + cx_invoke_advanced_destructor(&kv_list->list.base, elem_data); + cx_linked_list_remove( + &kv_list->list.begin, + &kv_list->list.end, + kv_list->list.loc_prev, + kv_list->list.loc_next, + elem + ); + cxFree(kv_list->list.base.collection.allocator, elem); + kv_list->list.base.collection.size--; + iter->index--; + iter->elem_count--; + } + + // advance to the next element, if any + if (next == NULL) { + iter->index = kv_list->list.base.collection.size; + iter->elem = NULL; + iter->entry = (CxMapEntry){NULL, NULL}; + return; + } + iter->index++; + iter->elem = next; + iter->entry.key = key; + if (kv_list->list.base.collection.store_pointer) { + iter->entry.value = *(void**)(next + kv_list->list.loc_data); + } else { + iter->entry.value = (void*)(next + kv_list->list.loc_data); + } +} + +static bool cx_kvl_iter_valid(const void *it) { + const CxMapIterator *iter = it; + return iter->elem != NULL; +} + +CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type type) { + CxMapIterator iter = {0}; + + iter.type = type; + iter.map = (CxMap*)map; + // although we iterate over the list, we only report that many elements that have a key in the map + iter.elem_count = map->collection.size; + + switch (type) { + case CX_MAP_ITERATOR_PAIRS: + iter.elem_size = sizeof(CxMapEntry); + iter.base.current = cx_kvl_iter_current_entry; + break; + case CX_MAP_ITERATOR_KEYS: + iter.elem_size = sizeof(CxHashKey); + iter.base.current = cx_kvl_iter_current_key; + break; + case CX_MAP_ITERATOR_VALUES: + iter.elem_size = map->collection.elem_size; + iter.base.current = cx_kvl_iter_current_value; + break; + default: + assert(false); // LCOV_EXCL_LINE + } + + iter.base.allow_remove = true; + iter.base.next = cx_kvl_iter_next; + iter.base.valid = cx_kvl_iter_valid; + + // find the first list entry that has a key assigned + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + CxHashKey *key = NULL; + char *next = kv_list->list.begin; + while (next != NULL) { + key = cx_kv_list_loc_key(kv_list, next + kv_list->list.loc_data); + if (key->hash != 0) break; + next = *(char**)(next + kv_list->list.loc_next); + } + if (next == NULL) { + iter.elem = NULL; + iter.entry = (CxMapEntry){NULL, NULL}; + } else { + iter.elem = next; + iter.entry.key = key; + if (kv_list->list.base.collection.store_pointer) { + iter.entry.value = *(void**)(next + kv_list->list.loc_data); + } else { + iter.entry.value = (void*)(next + kv_list->list.loc_data); + } + } + + return iter; +} + +static cx_list_class cx_kv_list_class = { + cx_kvl_deallocate, + cx_kvl_insert_element, + cx_kvl_insert_array, + cx_kvl_insert_sorted, + cx_kvl_insert_unique, + cx_kvl_insert_iter, + cx_kvl_remove, + cx_kvl_clear, + cx_kvl_swap, + cx_kvl_at, + cx_kvl_find_remove, + cx_kvl_sort, + NULL, + cx_kvl_reverse, + cx_kvl_iterator, +}; + +static cx_map_class cx_kv_map_class = { + cx_kvl_map_deallocate, + cx_kvl_map_clear, + cx_kvl_map_put, + cx_kvl_map_get, + cx_kvl_map_remove, + cx_kvl_map_iterator, +}; + +CxList *cxKvListCreate( + const CxAllocator *allocator, + cx_compare_func comparator, + size_t elem_size +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + + // create a normal linked list and a normal hash map, first + CxList *list = cxLinkedListCreate(allocator, comparator, elem_size); + if (list == NULL) return NULL; // LCOV_EXCL_LINE + cx_linked_list *ll = (cx_linked_list*)list; + ll->extra_data_len = sizeof(CxHashKey); + CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 0); + if (map == NULL) { // LCOV_EXCL_START + cxListFree(list); + return NULL; + } // LCOV_EXCL_STOP + + // patch the kv-list class with the compare function of the linked list + // this allows cxListCompare() to optimize comparisons between linked lists and kv-list + cx_kv_list_class.compare = list->cl->compare; + + // reallocate the map to add memory for the list back-reference + struct cx_kv_list_map_s *kv_map = cxRealloc(allocator, map, sizeof(struct cx_kv_list_map_s)); + + // reallocate the list to add memory for storing the metadata + cx_kv_list *kv_list = cxRealloc(allocator, list, sizeof(struct cx_kv_list_s)); + + // if any of the reallocations failed, we bail out + if (kv_map != NULL && kv_list != NULL) { + map = (CxMap*) kv_map; + list = (CxList*) kv_list; + } else { // LCOV_EXCL_START + cxListFree(list); + cxMapFree(map); + return NULL; + } // LCOV_EXCL_STOP + + // zero the custom destructor information + memset((char*)kv_list + offsetof(cx_kv_list, list_destr), 0, sizeof(void*)*6); + + // combine the list and the map aspect + kv_list->map = kv_map; + kv_map->list = kv_list; + + // remember the base methods and override them + kv_list->map_methods = map->cl; + map->cl = &cx_kv_map_class; + if (list->climpl == NULL) { + kv_list->list_methods = list->cl; + list->cl = &cx_kv_list_class; + } else { + kv_list->list_methods = list->climpl; + list->climpl = &cx_kv_list_class; + } + + return list; +} + +CxMap *cxKvListCreateAsMap( + const CxAllocator *allocator, + cx_compare_func comparator, + size_t elem_size +) { + CxList *list = cxKvListCreate(allocator, comparator, elem_size); + return list == NULL ? NULL : cxKvListAsMap(list); +} + +CxList *cxKvListAsList(CxMap *map) { + return &((struct cx_kv_list_map_s*)map)->list->list.base; +} + +CxMap *cxKvListAsMap(CxList *list) { + return &((cx_kv_list*)list)->map->map_base.base; +} + +int cx_kv_list_set_key(CxList *list, size_t index, CxHashKey key) { + cx_kv_list *kv_list = (cx_kv_list*)list; + void *node_data = kv_list->list_methods->at(list, index); + if (node_data == NULL) { + return 1; + } + // if the hash has not yet been computed, do it now + if (key.hash == 0) { + cx_hash_murmur(&key); + } + + // check if the key is already assigned + void *existing = kv_list->map_methods->get(&kv_list->map->map_base.base, key); + if (existing == node_data) { + return 0; // nothing to do + } + if (existing != NULL) { + // the key is already assigned to another node, we disallow re-assignment + return 1; + } + + // add the key to the map; + if (NULL == kv_list->map_methods->put(&kv_list->map->map_base.base, key, node_data)) { + return 1; // LCOV_EXCL_LINE + } + + // write the key to the list's node + CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); + *loc_key = key; + + return 0; +} + +int cxKvListRemoveKey(CxList *list, size_t index) { + cx_kv_list *kv_list = (cx_kv_list*)list; + void *node_data = kv_list->list_methods->at(list, index); + if (node_data == NULL) { + return 1; + } + CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); + if (loc_key->hash == 0) { + return 0; + } + kv_list->map_methods->remove(&kv_list->map->map_base.base, *loc_key, NULL); + // also zero the memory in the list node, + // but don't free the key data (that was done by the map remove) + memset(loc_key, 0, sizeof(CxHashKey)); + return 0; +} + +const CxHashKey *cxKvListGetKey(CxList *list, size_t index) { + cx_kv_list *kv_list = (cx_kv_list*)list; + void *node_data = kv_list->list_methods->at(list, index); + if (node_data == NULL) { + return NULL; + } + CxHashKey *key = cx_kv_list_loc_key(kv_list, node_data); + if (key->hash == 0) { + return NULL; + } + return key; +} + +int cx_kv_list_insert(CxList *list, size_t index, CxHashKey key, void *value) { + // assume we are losing the sorted property + list->collection.sorted = false; + + cx_kv_list *kv_list = (cx_kv_list*)list; + + // reserve memory in the map + void **map_data = kv_list->map_methods->put(&kv_list->map->map_base.base, key, NULL); + if (map_data == NULL) return 1; // LCOV_EXCL_LINE + + // insert the node + void *node_data = kv_list->list_methods->insert_element(&kv_list->list.base, index, + kv_list->list.base.collection.store_pointer ? &value : value); + if (node_data == NULL) { // LCOV_EXCL_START + // non-destructively remove the key again + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); + return 1; + } // LCOV_EXCL_STOP + *map_data = node_data; + + // write the key to the node + CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); + *loc_key = key; + + return 0; +} diff --git a/ucx/linked_list.c b/ucx/linked_list.c index 7cd2ff2..370b349 100644 --- a/ucx/linked_list.c +++ b/ucx/linked_list.c @@ -244,80 +244,184 @@ void cx_linked_list_insert_sorted( begin, end, loc_prev, loc_next, new_node, cmp_func); } -void cx_linked_list_insert_sorted_chain( +static void *cx_linked_list_insert_sorted_chain_impl( void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, - cx_compare_func cmp_func + cx_compare_func cmp_func, + bool allow_duplicates ) { 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); + // strategy: build completely new chains from scratch + void *source_original = *begin; + void *source_argument = insert_begin; + void *new_begin = NULL; + void *new_end = NULL; + void *dup_begin = NULL; + void *dup_end = NULL; + + // determine the new start + { + int d = source_original == NULL ? 1 : cmp_func(source_original, source_argument); + if (d <= 0) { + // the new chain starts with the original chain + new_begin = new_end = source_original; + source_original = ll_next(source_original); + if (d == 0) { + if (allow_duplicates) { + // duplicate allowed, append it to the chain + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = source_argument; + } else { + // duplicate is not allowed, start a duplicate chain with the argument + dup_begin = dup_end = source_argument; + } + source_argument = ll_next(source_argument); + } + } else { + // input is smaller, or there is no original chain; + // start the new chain with the source argument + new_begin = new_end = source_argument; + source_argument = ll_next(source_argument); } - 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; + // now successively compare the elements and add them to the correct chains + while (source_original != NULL && source_argument != NULL) { + int d = cmp_func(source_original, source_argument); + if (d <= 0) { + // the original is not larger, add it to the chain + cx_linked_list_link(new_end, source_original, loc_prev, loc_next); + new_end = source_original; + source_original = ll_next(source_original); + if (d == 0) { + if (allow_duplicates) { + // duplicate allowed, append it to the chain + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = source_argument; + } else { + // duplicate is not allowed, append it to the duplicate chain + if (dup_end == NULL) { + dup_begin = dup_end = source_argument; + } else { + cx_linked_list_link(dup_end, source_argument, loc_prev, loc_next); + dup_end = source_argument; + } + } + source_argument = ll_next(source_argument); + } + } else { + // the original is larger, append the source argument to the chain + // check if we must discard the source argument as duplicate + if (!allow_duplicates && cmp_func(new_end, source_argument) == 0) { + if (dup_end == NULL) { + dup_begin = dup_end = source_argument; + } else { + cx_linked_list_link(dup_end, source_argument, loc_prev, loc_next); + dup_end = source_argument; + } + } else { + // no duplicate or duplicates allowed + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = source_argument; } - // otherwise, we can insert one more - end_of_chain = next_in_chain; - next_in_chain = ll_next(next_in_chain); + source_argument = ll_next(source_argument); } + } - // insert the elements - if (dest_prev == NULL) { - // new begin - *begin = src; + if (source_original != NULL) { + // something is left from the original chain, append it + cx_linked_list_link(new_end, source_original, loc_prev, loc_next); + new_end = cx_linked_list_last(source_original, loc_next); + } else if (source_argument != NULL) { + // something is left from the input chain; + // when we allow duplicates, append it + if (allow_duplicates) { + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = cx_linked_list_last(source_argument, loc_next); } else { - cx_linked_list_link(dest_prev, src, loc_prev, loc_next); + // otherwise we must check one-by-one + while (source_argument != NULL) { + if (cmp_func(new_end, source_argument) == 0) { + if (dup_end == NULL) { + dup_begin = dup_end = source_argument; + } else { + cx_linked_list_link(dup_end, source_argument, loc_prev, loc_next); + dup_end = source_argument; + } + } else { + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = source_argument; + } + source_argument = ll_next(source_argument); + } } - 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); + // null the next pointers at the end of the chain + ll_next(new_end) = NULL; + if (dup_end != NULL) { + ll_next(dup_end) = NULL; } - // insert remaining items - if (src != NULL) { - cx_linked_list_link(dest_prev, src, loc_prev, loc_next); + // null the optional prev pointers + if (loc_prev >= 0) { + ll_prev(new_begin) = NULL; + if (dup_begin != NULL) { + ll_prev(dup_begin) = NULL; + } } - // determine new end of list, if requested + // output + *begin = new_begin; if (end != NULL) { - *end = cx_linked_list_last( - dest != NULL ? dest : dest_prev, loc_next); + *end = new_end; } + return dup_begin; +} + +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 +) { + cx_linked_list_insert_sorted_chain_impl( + begin, end, loc_prev, loc_next, + insert_begin, cmp_func, true); +} + +int cx_linked_list_insert_unique( + 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); + return NULL != cx_linked_list_insert_unique_chain( + begin, end, loc_prev, loc_next, new_node, cmp_func); +} + +void *cx_linked_list_insert_unique_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *insert_begin, + cx_compare_func cmp_func +) { + return cx_linked_list_insert_sorted_chain_impl( + begin, end, loc_prev, loc_next, + insert_begin, cmp_func, false); } size_t cx_linked_list_remove_chain( @@ -370,6 +474,16 @@ size_t cx_linked_list_remove_chain( return removed; } +void cx_linked_list_remove( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node +) { + cx_linked_list_remove_chain(begin, end, loc_prev, loc_next, node, 1); +} + size_t cx_linked_list_size( const void *node, ptrdiff_t loc_next @@ -564,64 +678,46 @@ void cx_linked_list_reverse( // 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( +static void *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); + return cx_linked_list_at(list->end, list->base.collection.size - 1, list->loc_prev, index); } else { - return cx_linked_list_at(list->begin, 0, CX_LL_LOC_NEXT, index); + return cx_linked_list_at(list->begin, 0, list->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 void *cx_ll_malloc_node(const cx_linked_list *list) { + return cxZalloc(list->base.collection.allocator, + list->loc_data + list->base.collection.elem_size + list->extra_data_len); } static int cx_ll_insert_at( struct cx_list_s *list, - cx_linked_list_node *node, + void *node, const void *elem ) { + cx_linked_list *ll = (cx_linked_list *) list; // create the new new_node - cx_linked_list_node *new_node = cx_ll_malloc_node(list); + void *new_node = cx_ll_malloc_node(ll); // sortir if failed if (new_node == NULL) return 1; - // initialize new new_node - new_node->prev = new_node->next = NULL; + // copy the data if (elem != NULL) { - memcpy(new_node->payload, elem, list->collection.elem_size); + memcpy((char*)new_node + ll->loc_data, 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, + &ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node, new_node, new_node ); @@ -640,7 +736,7 @@ static size_t cx_ll_insert_array( 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); + void *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; @@ -649,14 +745,17 @@ static size_t cx_ll_insert_array( if (n == 1) return 1; // we now know exactly where we are - node = node == NULL ? ((cx_linked_list *) list)->begin : node->next; + cx_linked_list *ll = (cx_linked_list *) list; + node = node == NULL ? ((cx_linked_list *) list)->begin : CX_LL_PTR(node, ll->loc_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 (source != NULL) { + source += list->collection.elem_size; + } if (0 != cx_ll_insert_at(list, node, source)) return i; - node = node->next; + node = CX_LL_PTR(node, ll->loc_next); } return n; } @@ -670,76 +769,113 @@ static void *cx_ll_insert_element( if (index > list->collection.size) return NULL; // find position efficiently - cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); + void *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); // perform first insert if (cx_ll_insert_at(list, node, element)) return NULL; // return a pointer to the data of the inserted node + cx_linked_list *ll = (cx_linked_list *) list; if (node == NULL) { - return ((cx_linked_list *) list)->begin->payload; + return (char*)(ll->begin) + ll->loc_data; } else { - return node->next->payload; + char *next = CX_LL_PTR(node, ll->loc_next); + return next + ll->loc_data; } } static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func; +static _Thread_local off_t cx_ll_insert_sorted_loc_data; 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); + const char *left = (const char*)l + cx_ll_insert_sorted_loc_data; + const char *right = (const char*)r + cx_ll_insert_sorted_loc_data; + return cx_ll_insert_sorted_cmp_func(left, right); } -static size_t cx_ll_insert_sorted( +static size_t cx_ll_insert_sorted_impl( struct cx_list_s *list, const void *array, - size_t n + size_t n, + bool allow_duplicates ) { + cx_linked_list *ll = (cx_linked_list *) list; + // special case if (n == 0) return 0; // create a new chain of nodes - cx_linked_list_node *chain = cx_ll_malloc_node(list); + void *chain = cx_ll_malloc_node(ll); if (chain == NULL) return 0; - memcpy(chain->payload, array, list->collection.elem_size); - chain->prev = NULL; - chain->next = NULL; + memcpy((char*)chain + ll->loc_data, array, list->collection.elem_size); // add all elements from the array to that chain - cx_linked_list_node *prev = chain; + void *prev = chain; const char *src = array; size_t inserted = 1; for (; inserted < n; inserted++) { - cx_linked_list_node *next = cx_ll_malloc_node(list); + void *next = cx_ll_malloc_node(ll); if (next == NULL) break; src += list->collection.elem_size; - memcpy(next->payload, src, list->collection.elem_size); - prev->next = next; - next->prev = prev; + memcpy((char*)next + ll->loc_data, src, list->collection.elem_size); + CX_LL_PTR(prev, ll->loc_next) = next; + CX_LL_PTR(next, ll->loc_prev) = prev; prev = next; } - prev->next = NULL; + CX_LL_PTR(prev, ll->loc_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; + cx_ll_insert_sorted_loc_data = ll->loc_data; + if (allow_duplicates) { + cx_linked_list_insert_sorted_chain( + &ll->begin, + &ll->end, + ll->loc_prev, + ll->loc_next, + chain, + cx_ll_insert_sorted_cmp_helper + ); + list->collection.size += inserted; + } else { + void *duplicates = cx_linked_list_insert_unique_chain( + &ll->begin, + &ll->end, + ll->loc_prev, + ll->loc_next, + chain, + cx_ll_insert_sorted_cmp_helper + ); + list->collection.size += inserted; + // free the nodes that did not make it into the list + while (duplicates != NULL) { + void *next = CX_LL_PTR(duplicates, ll->loc_next); + cxFree(list->collection.allocator, duplicates); + duplicates = next; + list->collection.size--; + } + } return inserted; } +static size_t cx_ll_insert_sorted( + struct cx_list_s *list, + const void *array, + size_t n +) { + return cx_ll_insert_sorted_impl(list, array, n, true); +} + +static size_t cx_ll_insert_unique( + struct cx_list_s *list, + const void *array, + size_t n +) { + return cx_ll_insert_sorted_impl(list, array, n, false); +} + static size_t cx_ll_remove( struct cx_list_s *list, size_t index, @@ -747,7 +883,7 @@ static size_t cx_ll_remove( void *targetbuf ) { cx_linked_list *ll = (cx_linked_list *) list; - cx_linked_list_node *node = cx_ll_node_at(ll, index); + void *node = cx_ll_node_at(ll, index); // out-of-bounds check if (node == NULL) return 0; @@ -756,8 +892,8 @@ static size_t cx_ll_remove( size_t removed = cx_linked_list_remove_chain( (void **) &ll->begin, (void **) &ll->end, - CX_LL_LOC_PREV, - CX_LL_LOC_NEXT, + ll->loc_prev, + ll->loc_next, node, num ); @@ -767,28 +903,28 @@ static size_t cx_ll_remove( // copy or destroy the removed chain if (targetbuf == NULL) { - cx_linked_list_node *n = node; + char *n = node; for (size_t i = 0; i < removed; i++) { // element destruction - cx_invoke_destructor(list, n->payload); + cx_invoke_destructor(list, n + ll->loc_data); // free the node and advance - void *next = n->next; + void *next = CX_LL_PTR(n, ll->loc_next); cxFree(list->collection.allocator, n); n = next; } } else { char *dest = targetbuf; - cx_linked_list_node *n = node; + char *n = node; for (size_t i = 0; i < removed; i++) { // copy payload - memcpy(dest, n->payload, list->collection.elem_size); + memcpy(dest, n + ll->loc_data, list->collection.elem_size); // advance target buffer dest += list->collection.elem_size; // free the node and advance - void *next = n->next; + void *next = CX_LL_PTR(n, ll->loc_next); cxFree(list->collection.allocator, n); n = next; } @@ -801,10 +937,10 @@ 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; + char *node = ll->begin; while (node != NULL) { - cx_invoke_destructor(list, node->payload); - cx_linked_list_node *next = node->next; + cx_invoke_destructor(list, node + ll->loc_data); + void *next = CX_LL_PTR(node, ll->loc_next); cxFree(list->collection.allocator, node); node = next; } @@ -831,14 +967,14 @@ static int cx_ll_swap( left = j; right = i; } - cx_linked_list_node *nleft = NULL, *nright = NULL; + void *nleft = NULL, *nright = NULL; if (left < mid && right < mid) { // case 1: both items left from mid nleft = cx_ll_node_at(ll, left); assert(nleft != NULL); nright = nleft; for (size_t c = left; c < right; c++) { - nright = nright->next; + nright = CX_LL_PTR(nright, ll->loc_next); } } else if (left >= mid && right >= mid) { // case 2: both items right from mid @@ -846,7 +982,7 @@ static int cx_ll_swap( assert(nright != NULL); nleft = nright; for (size_t c = right; c > left; c--) { - nleft = nleft->prev; + nleft = CX_LL_PTR(nleft, ll->loc_prev); } } else { // case 3: one item left, one item right @@ -872,12 +1008,12 @@ static int cx_ll_swap( if (closest == left) { nright = nleft; for (size_t c = left; c < right; c++) { - nright = nright->next; + nright = CX_LL_PTR(nright, ll->loc_next); } } else { nleft = nright; for (size_t c = right; c > left; c--) { - nleft = nleft->prev; + nleft = CX_LL_PTR(nleft, ll->loc_prev); } } } else { @@ -890,33 +1026,33 @@ static int cx_ll_swap( } } - 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; + void *prev = CX_LL_PTR(nleft, ll->loc_prev); + void *next = CX_LL_PTR(nright, ll->loc_next); + void *midstart = CX_LL_PTR(nleft, ll->loc_next); + void *midend = CX_LL_PTR(nright, ll->loc_prev); if (prev == NULL) { ll->begin = nright; } else { - prev->next = nright; + CX_LL_PTR(prev, ll->loc_next) = nright; } - nright->prev = prev; + CX_LL_PTR(nright, ll->loc_prev) = prev; if (midstart == nright) { // special case: both nodes are adjacent - nright->next = nleft; - nleft->prev = nright; + CX_LL_PTR(nright, ll->loc_next) = nleft; + CX_LL_PTR(nleft, ll->loc_prev) = nright; } else { // likely case: a chain is between the two nodes - nright->next = midstart; - midstart->prev = nright; - midend->next = nleft; - nleft->prev = midend; + CX_LL_PTR(nright, ll->loc_next) = midstart; + CX_LL_PTR(midstart, ll->loc_prev) = nright; + CX_LL_PTR(midend, ll->loc_next) = nleft; + CX_LL_PTR(nleft, ll->loc_prev) = midend; } - nleft->next = next; + CX_LL_PTR(nleft, ll->loc_next) = next; if (next == NULL) { ll->end = nleft; } else { - next->prev = nleft; + CX_LL_PTR(next, ll->loc_prev) = nleft; } return 0; @@ -927,8 +1063,8 @@ static void *cx_ll_at( 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; + char *node = cx_ll_node_at(ll, index); + return node == NULL ? NULL : node + ll->loc_data; } static size_t cx_ll_find_remove( @@ -939,10 +1075,10 @@ static size_t cx_ll_find_remove( if (list->collection.size == 0) return 0; size_t index; - cx_linked_list *ll = ((cx_linked_list *) list); - cx_linked_list_node *node = cx_linked_list_find( + cx_linked_list *ll = (cx_linked_list *) list; + char *node = cx_linked_list_find( ll->begin, - CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + ll->loc_next, ll->loc_data, list->collection.cmpfunc, elem, &index ); @@ -950,9 +1086,9 @@ static size_t cx_ll_find_remove( return list->collection.size; } if (remove) { - 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); + cx_invoke_destructor(list, node + ll->loc_data); + cx_linked_list_remove(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node); list->collection.size--; cxFree(list->collection.allocator, node); } @@ -961,14 +1097,14 @@ static size_t cx_ll_find_remove( 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, + cx_linked_list_sort(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, 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); + cx_linked_list_reverse(&ll->begin, &ll->end, ll->loc_prev, ll->loc_next); } static int cx_ll_compare( @@ -977,8 +1113,10 @@ static int cx_ll_compare( ) { cx_linked_list *left = (cx_linked_list *) list; cx_linked_list *right = (cx_linked_list *) other; + assert(left->loc_next == right->loc_next); + assert(left->loc_data == right->loc_data); return cx_linked_list_compare(left->begin, right->begin, - CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + left->loc_next, left->loc_data, list->collection.cmpfunc); } @@ -989,49 +1127,52 @@ static bool cx_ll_iter_valid(const void *it) { static void cx_ll_iter_next(void *it) { struct cx_iterator_s *iter = it; + cx_linked_list *ll = iter->src_handle; 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); + struct cx_list_s *list = iter->src_handle; + char *node = iter->elem_handle; + iter->elem_handle = CX_LL_PTR(node, ll->loc_next); + cx_invoke_destructor(list, node + ll->loc_data); + cx_linked_list_remove(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node); list->collection.size--; + iter->elem_count--; cxFree(list->collection.allocator, node); } else { iter->index++; - cx_linked_list_node *node = iter->elem_handle; - iter->elem_handle = node->next; + void *node = iter->elem_handle; + iter->elem_handle = CX_LL_PTR(node, ll->loc_next); } } static void cx_ll_iter_prev(void *it) { struct cx_iterator_s *iter = it; + cx_linked_list *ll = iter->src_handle; 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; + struct cx_list_s *list = iter->src_handle; + char *node = iter->elem_handle; + iter->elem_handle = CX_LL_PTR(node, ll->loc_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); + cx_invoke_destructor(list, node + ll->loc_data); + cx_linked_list_remove(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node); list->collection.size--; + iter->elem_count--; cxFree(list->collection.allocator, node); } else { iter->index--; - cx_linked_list_node *node = iter->elem_handle; - iter->elem_handle = node->prev; + char *node = iter->elem_handle; + iter->elem_handle = CX_LL_PTR(node, ll->loc_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; + const cx_linked_list *ll = iter->src_handle; + char *node = iter->elem_handle; + return node + ll->loc_data; } static CxIterator cx_ll_iterator( @@ -1041,14 +1182,14 @@ static CxIterator cx_ll_iterator( ) { CxIterator iter; iter.index = index; - iter.src_handle.c = list; + iter.src_handle = (void*)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.allow_remove = true; iter.base.remove = false; return iter; } @@ -1058,11 +1199,12 @@ static int cx_ll_insert_iter( const void *elem, int prepend ) { - struct cx_list_s *list = iter->src_handle.m; - cx_linked_list_node *node = iter->elem_handle; + struct cx_list_s *list = iter->src_handle; + cx_linked_list *ll = iter->src_handle; + void *node = iter->elem_handle; if (node != NULL) { assert(prepend >= 0 && prepend <= 1); - cx_linked_list_node *choice[2] = {node, node->prev}; + void *choice[2] = {node, CX_LL_PTR(node, ll->loc_prev)}; int result = cx_ll_insert_at(list, choice[prepend], elem); if (result == 0) { iter->elem_count++; @@ -1084,10 +1226,10 @@ static int cx_ll_insert_iter( static void cx_ll_destructor(CxList *list) { cx_linked_list *ll = (cx_linked_list *) list; - cx_linked_list_node *node = ll->begin; + char *node = ll->begin; while (node) { - cx_invoke_destructor(list, node->payload); - void *next = node->next; + cx_invoke_destructor(list, node + ll->loc_data); + void *next = CX_LL_PTR(node, ll->loc_next); cxFree(list->collection.allocator, node); node = next; } @@ -1100,6 +1242,7 @@ static cx_list_class cx_linked_list_class = { cx_ll_insert_element, cx_ll_insert_array, cx_ll_insert_sorted, + cx_ll_insert_unique, cx_ll_insert_iter, cx_ll_remove, cx_ll_clear, @@ -1123,6 +1266,10 @@ CxList *cxLinkedListCreate( cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list)); if (list == NULL) return NULL; + list->extra_data_len = 0; + list->loc_prev = 0; + list->loc_next = sizeof(void*); + list->loc_data = sizeof(void*)*2; cx_list_init((CxList*)list, &cx_linked_list_class, allocator, comparator, elem_size); diff --git a/ucx/list.c b/ucx/list.c index 7fbbb8b..d4a8b22 100644 --- a/ucx/list.c +++ b/ucx/list.c @@ -29,6 +29,7 @@ #include "cx/list.h" #include +#include // @@ -38,10 +39,18 @@ static int cx_pl_cmpfunc( const void *l, const void *r ) { + // l and r are guaranteed to be non-NULL pointing to the list's memory void *const *lptr = l; void *const *rptr = r; - const void *left = lptr == NULL ? NULL : *lptr; - const void *right = rptr == NULL ? NULL : *rptr; + const void *left = *lptr; + const void *right = *rptr; + if (left == NULL) { + // NULL is smaller than any value except NULL + return right == NULL ? 0 : -1; + } else if (right == NULL) { + // any value is larger than NULL + return 1; + } return cx_pl_cmpfunc_impl(left, right); } @@ -90,12 +99,23 @@ static size_t cx_pl_insert_sorted( return result; } +static size_t cx_pl_insert_unique( + struct cx_list_s *list, + const void *array, + size_t n +) { + cx_pl_hack_cmpfunc(list); + size_t result = list->climpl->insert_unique(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; + struct cx_list_s *list = iter->src_handle; return list->climpl->insert_iter(iter, &elem, prepend); } @@ -181,6 +201,7 @@ static cx_list_class cx_pointer_list_class = { cx_pl_insert_element, cx_pl_insert_array, cx_pl_insert_sorted, + cx_pl_insert_unique, cx_pl_insert_iter, cx_pl_remove, cx_pl_clear, @@ -225,7 +246,7 @@ static CxIterator cx_emptyl_iterator( cx_attr_unused bool backwards ) { CxIterator iter = {0}; - iter.src_handle.c = list; + iter.src_handle = (void*) list; iter.index = index; iter.base.valid = cx_emptyl_iter_valid; return iter; @@ -238,6 +259,7 @@ static cx_list_class cx_empty_list_class = { NULL, NULL, NULL, + NULL, cx_emptyl_noop, NULL, cx_emptyl_at, @@ -278,21 +300,26 @@ size_t cx_list_default_insert_array( 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 (NULL == invoke_list_func( - insert_element, list, index + i, - src + (i * elem_size))) return i; + insert_element, list, index + i, src) + ) { + return i; // LCOV_EXCL_LINE + } + if (src != NULL) { + src += list->collection.elem_size; + } } return i; } -size_t cx_list_default_insert_sorted( +static size_t cx_list_default_insert_sorted_impl( struct cx_list_s *list, const void *sorted_data, - size_t n + size_t n, + bool allow_duplicates ) { // corner case if (n == 0) return 0; @@ -302,22 +329,54 @@ size_t cx_list_default_insert_sorted( const char *src = sorted_data; // track indices and number of inserted items - size_t di = 0, si = 0, inserted = 0; + size_t di = 0, si = 0, processed = 0; // search the list for insertion points - for (; di < list->collection.size; di++) { + while (di < list->collection.size) { 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; + // compare the current list element with the first source element + // if less, skip the list elements + // if equal, skip the list elements and optionally the source elements + { + int d = cmp(list_elm, src); + if (d <= 0) { + if (!allow_duplicates && d == 0) { + src += elem_size; + si++; + processed++; // we also count duplicates for the return value + while (si < n && cmp(list_elm, src) == 0) { + src += elem_size; + si++; + processed++; + } + if (processed == n) { + return processed; + } + } + di++; + continue; + } } - // determine number of consecutive elements that can be inserted - size_t ins = 1; + // determine the number of consecutive elements that can be inserted + size_t ins = 1, skip = 0; const char *next = src; while (++si < n) { + if (!allow_duplicates) { + // skip duplicates within the source + if (cmp(next, next + elem_size) == 0) { + next += elem_size; + skip++; + continue; + } else { + if (skip > 0) { + // if we had to skip something, we must wait for the next run + next += elem_size; + break; + } + } + } next += elem_size; // once we become larger than the list elem, break if (cmp(list_elm, next) <= 0) { @@ -329,33 +388,70 @@ size_t cx_list_default_insert_sorted( // insert the elements at location si if (ins == 1) { - if (NULL == invoke_list_func( - insert_element, list, di, src)) return inserted; + if (NULL == invoke_list_func(insert_element, list, di, src)) { + return processed; // LCOV_EXCL_LINE + } } else { size_t r = invoke_list_func(insert_array, list, di, src, ins); - if (r < ins) return inserted + r; + if (r < ins) { + return processed + r; // LCOV_EXCL_LINE + } } - inserted += ins; + processed += ins + skip; di += ins; // everything inserted? - if (inserted == n) return inserted; + if (processed == n) { + return processed; + } src = next; } // insert remaining items if (si < n) { - inserted += invoke_list_func(insert_array, list, di, src, n - si); + if (allow_duplicates) { + processed += invoke_list_func(insert_array, list, di, src, n - si); + } else { + const void *last = di == 0 ? NULL : invoke_list_func(at, list, di - 1); + for (; si < n; si++) { + // skip duplicates within the source + if (last == NULL || cmp(last, src) != 0) { + if (NULL == invoke_list_func(insert_element, list, di, src)) { + return processed; // LCOV_EXCL_LINE + } + last = src; + di++; + } + processed++; + src += elem_size; + } + } } - return inserted; + return processed; +} + +size_t cx_list_default_insert_sorted( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + return cx_list_default_insert_sorted_impl(list, sorted_data, n, true); +} + +size_t cx_list_default_insert_unique( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + return cx_list_default_insert_sorted_impl(list, sorted_data, n, false); } 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 = cxMallocDefault(elem_size * list_size); - if (tmp == NULL) abort(); + if (tmp == NULL) abort(); // LCOV_EXCL_LINE // copy elements from source array char *loc = tmp; @@ -388,7 +484,7 @@ int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j) { size_t elem_size = list->collection.elem_size; void *tmp = cxMallocDefault(elem_size); - if (tmp == NULL) return 1; + if (tmp == NULL) return 1; // LCOV_EXCL_LINE void *ip = invoke_list_func(at, list, i); void *jp = invoke_list_func(at, list, j); @@ -472,34 +568,174 @@ int cxListCompare( } } -CxIterator cxListMutIteratorAt( - CxList *list, - size_t index -) { - CxIterator it = list->cl->iterator(list, index, false); - it.base.mutating = true; - return it; +size_t cxListSize(const CxList *list) { + return list->collection.size; } -CxIterator cxListMutBackwardsIteratorAt( - CxList *list, - size_t index -) { - CxIterator it = list->cl->iterator(list, index, true); - it.base.mutating = true; - return it; +int cxListAdd(CxList *list, const void *elem) { + list->collection.sorted = false; + return list->cl->insert_element(list, list->collection.size, elem) == NULL; } -void cxListFree(CxList *list) { - if (list == NULL) return; - list->cl->deallocate(list); +size_t cxListAddArray(CxList *list, const void *array, size_t n) { + list->collection.sorted = false; + return list->cl->insert_array(list, list->collection.size, array, n); } -int cxListSet( - CxList *list, - size_t index, - const void *elem -) { +int cxListInsert(CxList *list, size_t index, const void *elem) { + list->collection.sorted = false; + return list->cl->insert_element(list, index, elem) == NULL; +} + +void *cxListEmplaceAt(CxList *list, size_t index) { + list->collection.sorted = false; + return list->cl->insert_element(list, index, NULL); +} + +void *cxListEmplace(CxList *list) { + list->collection.sorted = false; + return list->cl->insert_element(list, list->collection.size, NULL); +} + +static bool cx_list_emplace_iterator_valid(const void *it) { + const CxIterator *iter = it; + return iter->index < iter->elem_count; +} + +CxIterator cxListEmplaceArrayAt(CxList *list, size_t index, size_t n) { + list->collection.sorted = false; + size_t c = list->cl->insert_array(list, index, NULL, n); + CxIterator iter = list->cl->iterator(list, index, false); + // tweak the fields of this iterator + iter.elem_count = c; + iter.index = 0; + // replace the valid function to abort iteration when c is reached + iter.base.valid = cx_list_emplace_iterator_valid; + // if we are storing pointers, we want to return the pure pointers. + // therefore, we must unwrap the "current" method + if (list->collection.store_pointer) { + iter.base.current = iter.base.current_impl; + } + return iter; +} + +CxIterator cxListEmplaceArray(CxList *list, size_t n) { + return cxListEmplaceArrayAt(list, list->collection.size, n); +} + +int cxListInsertSorted(CxList *list, const void *elem) { + assert(cxCollectionSorted(list)); + list->collection.sorted = true; + const void *data = list->collection.store_pointer ? &elem : elem; + return list->cl->insert_sorted(list, data, 1) == 0; +} + +int cxListInsertUnique(CxList *list, const void *elem) { + if (cxCollectionSorted(list)) { + list->collection.sorted = true; + const void *data = list->collection.store_pointer ? &elem : elem; + return list->cl->insert_unique(list, data, 1) == 0; + } else { + if (cxListContains(list, elem)) { + return 0; + } else { + return cxListAdd(list, elem); + } + } +} + +size_t cxListInsertArray(CxList *list, size_t index, const void *array, size_t n) { + list->collection.sorted = false; + return list->cl->insert_array(list, index, array, n); +} + +size_t cxListInsertSortedArray(CxList *list, const void *array, size_t n) { + assert(cxCollectionSorted(list)); + list->collection.sorted = true; + return list->cl->insert_sorted(list, array, n); +} + +size_t cxListInsertUniqueArray(CxList *list, const void *array, size_t n) { + if (cxCollectionSorted(list)) { + list->collection.sorted = true; + return list->cl->insert_unique(list, array, n); + } else { + const char *source = array; + for (size_t i = 0 ; i < n; i++) { + // note: this also checks elements added in a previous iteration + const void *data = list->collection.store_pointer ? + *((const void**)source) : source; + if (!cxListContains(list, data)) { + if (cxListAdd(list, data)) { + return i; // LCOV_EXCL_LINE + } + } + source += list->collection.elem_size; + } + return n; + } +} + +int cxListInsertAfter(CxIterator *iter, const void *elem) { + CxList* list = (CxList*)iter->src_handle; + list->collection.sorted = false; + return list->cl->insert_iter(iter, elem, 0); +} + +int cxListInsertBefore(CxIterator *iter, const void *elem) { + CxList* list = (CxList*)iter->src_handle; + list->collection.sorted = false; + return list->cl->insert_iter(iter, elem, 1); +} + +int cxListRemove(CxList *list, size_t index) { + return list->cl->remove(list, index, 1, NULL) == 0; +} + +int cxListRemoveAndGet(CxList *list, size_t index, void *targetbuf) { + return list->cl->remove(list, index, 1, targetbuf) == 0; +} + +int cxListRemoveAndGetFirst(CxList *list, void *targetbuf) { + return list->cl->remove(list, 0, 1, targetbuf) == 0; +} + +int cxListRemoveAndGetLast(CxList *list, void *targetbuf) { + // note: index may wrap - member function will catch that + return list->cl->remove(list, list->collection.size - 1, 1, targetbuf) == 0; +} + +size_t cxListRemoveArray(CxList *list, size_t index, size_t num) { + return list->cl->remove(list, index, num, NULL); +} + +size_t cxListRemoveArrayAndGet(CxList *list, size_t index, size_t num, void *targetbuf) { + return list->cl->remove(list, index, num, targetbuf); +} + +void cxListClear(CxList *list) { + list->cl->clear(list); + list->collection.sorted = true; // empty lists are always sorted +} + +int cxListSwap(CxList *list, size_t i, size_t j) { + list->collection.sorted = false; + return list->cl->swap(list, i, j); +} + +void *cxListAt(const CxList *list, size_t index) { + return list->cl->at(list, index); +} + +void *cxListFirst(const CxList *list) { + return list->cl->at(list, 0); +} + +void *cxListLast(const CxList *list) { + return list->cl->at(list, list->collection.size - 1); +} + +int cxListSet(CxList *list, size_t index, const void *elem) { if (index >= list->collection.size) { return 1; } @@ -515,3 +751,56 @@ int cxListSet( return 0; } + +CxIterator cxListIteratorAt(const CxList *list, size_t index) { + if (list == NULL) list = cxEmptyList; + return list->cl->iterator(list, index, false); +} + +CxIterator cxListBackwardsIteratorAt(const CxList *list, size_t index) { + if (list == NULL) list = cxEmptyList; + return list->cl->iterator(list, index, true); +} + +CxIterator cxListIterator(const CxList *list) { + if (list == NULL) list = cxEmptyList; + return list->cl->iterator(list, 0, false); +} + +CxIterator cxListBackwardsIterator(const CxList *list) { + if (list == NULL) list = cxEmptyList; + return list->cl->iterator(list, list->collection.size - 1, true); +} + +size_t cxListFind(const CxList *list, const void *elem) { + return list->cl->find_remove((CxList*)list, elem, false); +} + +bool cxListContains(const CxList* list, const void* elem) { + return list->cl->find_remove((CxList*)list, elem, false) < list->collection.size; +} + +bool cxListIndexValid(const CxList *list, size_t index) { + return index < list->collection.size; +} + +size_t cxListFindRemove(CxList *list, const void *elem) { + return list->cl->find_remove(list, elem, true); +} + +void cxListSort(CxList *list) { + if (list->collection.sorted) return; + list->cl->sort(list); + list->collection.sorted = true; +} + +void cxListReverse(CxList *list) { + // still sorted, but not according to the cmp_func + list->collection.sorted = false; + list->cl->reverse(list); +} + +void cxListFree(CxList *list) { + if (list == NULL) return; + list->cl->deallocate(list); +} diff --git a/ucx/map.c b/ucx/map.c index 49223f5..d0c41f4 100644 --- a/ucx/map.c +++ b/ucx/map.c @@ -51,7 +51,7 @@ static CxMapIterator cx_empty_map_iterator( cx_attr_unused enum cx_map_iterator_type type ) { CxMapIterator iter = {0}; - iter.map.c = map; + iter.map = (CxMap*) map; iter.base.valid = cx_empty_map_iter_valid; return iter; } @@ -84,22 +84,43 @@ CxMap *const cxEmptyMap = &cx_empty_map; // -CxMapIterator cxMapMutIteratorValues(CxMap *map) { - CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); - it.base.mutating = true; - return it; +void cxMapClear(CxMap *map) { + map->cl->clear(map); } -CxMapIterator cxMapMutIteratorKeys(CxMap *map) { - CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); - it.base.mutating = true; - return it; +size_t cxMapSize(const CxMap *map) { + return map->collection.size; } -CxMapIterator cxMapMutIterator(CxMap *map) { - CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); - it.base.mutating = true; - return it; +CxMapIterator cxMapIteratorValues(const CxMap *map) { + if (map == NULL) map = cxEmptyMap; + return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); +} + +CxMapIterator cxMapIteratorKeys(const CxMap *map) { + if (map == NULL) map = cxEmptyMap; + return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); +} + +CxMapIterator cxMapIterator(const CxMap *map) { + if (map == NULL) map = cxEmptyMap; + return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); +} + +int cx_map_put(CxMap *map, CxHashKey key, void *value) { + return map->cl->put(map, key, value) == NULL; +} + +void *cx_map_emplace(CxMap *map, CxHashKey key) { + return map->cl->put(map, key, NULL); +} + +void *cx_map_get(const CxMap *map, CxHashKey key) { + return map->cl->get(map, key); +} + +int cx_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { + return map->cl->remove(map, key, targetbuf); } void cxMapFree(CxMap *map) { diff --git a/ucx/mempool.c b/ucx/mempool.c index 8711439..fab6427 100644 --- a/ucx/mempool.c +++ b/ucx/mempool.c @@ -633,13 +633,19 @@ int cxMempoolTransfer( new_source_allocator->data = source; // transfer all the data - memcpy(&dest->data[dest->size], source->data, sizeof(void*)*source->size); - dest->size += source->size; + if (source->size > 0) { + memcpy(&dest->data[dest->size], source->data, + sizeof(void*)*source->size); + dest->size += source->size; + } // transfer all registered memory - memcpy(&dest->registered[dest->registered_size], source->registered, - sizeof(struct cx_mempool_foreign_memory_s) * source->size); - dest->registered_size += source->registered_size; + if (source->registered_size > 0) { + memcpy(&dest->registered[dest->registered_size], source->registered, + sizeof(struct cx_mempool_foreign_memory_s) + * source->registered_size); + dest->registered_size += source->registered_size; + } // register the old allocator with the new pool // we have to remove const-ness for this, but that's okay here diff --git a/ucx/properties.c b/ucx/properties.c index 4593602..3583532 100644 --- a/ucx/properties.c +++ b/ucx/properties.c @@ -51,6 +51,12 @@ void cxPropertiesDestroy(CxProperties *prop) { cxBufferDestroy(&prop->buffer); } +void cxPropertiesReset(CxProperties *prop) { + CxPropertiesConfig config = prop->config; + cxPropertiesDestroy(prop); + cxPropertiesInit(prop, config); +} + int cxPropertiesFilln( CxProperties *prop, const char *buf, @@ -244,7 +250,7 @@ static int cx_properties_sink_map( CxMap *map = sink->sink; CxAllocator *alloc = sink->data; cxmutstr v = cx_strdup_a(alloc, value); - int r = cx_map_put_cxstr(map, key, v.ptr); + int r = cxMapPut(map, key, v.ptr); if (r != 0) cx_strfree_a(alloc, &v); return r; } diff --git a/ucx/string.c b/ucx/string.c index a969779..52ce290 100644 --- a/ucx/string.c +++ b/ucx/string.c @@ -25,6 +25,10 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ +#ifdef MEMRCHR_NEED_GNU +#define _GNU_SOURCE +#endif + #include "cx/string.h" #include @@ -33,6 +37,7 @@ #include #include #include +#include #ifdef _WIN32 #define cx_strcasecmp_impl _strnicmp @@ -231,19 +236,24 @@ cxmutstr cx_strchr_m( } cxstring cx_strrchr( - cxstring string, - int chr + cxstring string, + int chr ) { +#ifdef WITH_MEMRCHR + char *ret = memrchr(string.ptr, 0xFF & chr, string.length); + if (ret == NULL) return (cxstring) {NULL, 0}; + return (cxstring) {ret, string.length - (ret - string.ptr)}; +#else 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}; +#endif } cxmutstr cx_strrchr_m( @@ -451,7 +461,7 @@ size_t cx_strsplit_ma( delim, limit, (cxstring **) output); } -int cx_strcmp( +int cx_strcmp_( cxstring s1, cxstring s2 ) { @@ -468,7 +478,7 @@ int cx_strcmp( } } -int cx_strcasecmp( +int cx_strcasecmp_( cxstring s1, cxstring s2 ) { @@ -520,19 +530,13 @@ cxmutstr cx_strdup_a_( return result; } -static bool str_isspace(char c) { - // TODO: remove once UCX has public API for this - return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f'; -} - cxstring cx_strtrim(cxstring string) { cxstring result = string; - // TODO: optimize by comparing multiple bytes at once - while (result.length > 0 && str_isspace(*result.ptr)) { + while (result.length > 0 && isspace((unsigned char)(result.ptr[0]))) { result.ptr++; result.length--; } - while (result.length > 0 && str_isspace(result.ptr[result.length - 1])) { + while (result.length > 0 && isspace((unsigned char)result.ptr[result.length - 1])) { result.length--; } return result; @@ -543,7 +547,7 @@ cxmutstr cx_strtrim_m(cxmutstr string) { return (cxmutstr) {(char *) result.ptr, result.length}; } -bool cx_strprefix( +bool cx_strprefix_( cxstring string, cxstring prefix ) { @@ -551,7 +555,7 @@ bool cx_strprefix( return memcmp(string.ptr, prefix.ptr, prefix.length) == 0; } -bool cx_strsuffix( +bool cx_strsuffix_( cxstring string, cxstring suffix ) { @@ -560,7 +564,7 @@ bool cx_strsuffix( suffix.ptr, suffix.length) == 0; } -bool cx_strcaseprefix( +bool cx_strcaseprefix_( cxstring string, cxstring prefix ) { @@ -572,7 +576,7 @@ bool cx_strcaseprefix( #endif } -bool cx_strcasesuffix( +bool cx_strcasesuffix_( cxstring string, cxstring suffix ) { @@ -957,11 +961,6 @@ int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep return 0; } -static bool str_isdigit(char c) { - // TODO: remove once UCX has public API for this - return c >= '0' && c <= '9'; -} - int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep) { // TODO: overflow check // TODO: increase precision @@ -994,7 +993,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse // parse all digits until we find the decsep size_t pos = 0; do { - if (str_isdigit(str.ptr[pos])) { + if (isdigit((unsigned char)str.ptr[pos])) { result = result * 10 + (str.ptr[pos] - '0'); } else if (strchr(groupsep, str.ptr[pos]) == NULL) { break; @@ -1023,7 +1022,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse // parse everything until exponent or end double factor = 1.; do { - if (str_isdigit(str.ptr[pos])) { + if (isdigit((unsigned char)str.ptr[pos])) { factor *= 0.1; result = result + factor * (str.ptr[pos] - '0'); } else if (strchr(groupsep, str.ptr[pos]) == NULL) { @@ -1064,7 +1063,7 @@ int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupse // parse the exponent unsigned int exp = 0; do { - if (str_isdigit(str.ptr[pos])) { + if (isdigit((unsigned char)str.ptr[pos])) { exp = 10 * exp + (str.ptr[pos] - '0'); } else if (strchr(groupsep, str.ptr[pos]) == NULL) { errno = EINVAL; diff --git a/ucx/tree.c b/ucx/tree.c index 1b2b38e..d948a4c 100644 --- a/ucx/tree.c +++ b/ucx/tree.c @@ -375,7 +375,7 @@ CxTreeIterator cx_tree_iterator( iter.skip = false; // assign base iterator functions - iter.base.mutating = false; + iter.base.allow_remove = false; iter.base.remove = false; iter.base.current_impl = NULL; iter.base.valid = cx_tree_iter_valid; @@ -496,7 +496,7 @@ CxTreeVisitor cx_tree_visitor( iter.queue_last = NULL; // assign base iterator functions - iter.base.mutating = false; + iter.base.allow_remove = false; iter.base.remove = false; iter.base.current_impl = NULL; iter.base.valid = cx_tree_visitor_valid; @@ -717,7 +717,7 @@ size_t cx_tree_add_array( } // otherwise, create iterator and hand over to other function - CxIterator iter = cxIterator(src, elem_size, num); + CxIterator iter = cxIterator(src, elem_size, num, false); return cx_tree_add_iter(cxIteratorRef(iter), num, sfunc, cfunc, cdata, failed, root, loc_parent, loc_children, loc_last_child, @@ -804,16 +804,12 @@ static cx_tree_class cx_tree_default_class = { cx_tree_default_find }; -CxTree *cxTreeCreate( - const CxAllocator *allocator, +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 + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next ) { if (allocator == NULL) { allocator = cxDefaultAllocator; @@ -852,15 +848,9 @@ void cxTreeFree(CxTree *tree) { cxFree(tree->allocator, tree); } -CxTree *cxTreeCreateWrapped( - const CxAllocator *allocator, - void *root, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -) { +CxTree *cxTreeCreateWrapped(const CxAllocator *allocator, void *root, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next) { if (allocator == NULL) { allocator = cxDefaultAllocator; } @@ -888,11 +878,7 @@ CxTree *cxTreeCreateWrapped( return tree; } -void cxTreeSetParent( - CxTree *tree, - void *parent, - void *child -) { +void cxTreeSetParent(CxTree *tree, void *parent, void *child) { size_t loc_parent = tree->loc_parent; if (tree_parent(child) == NULL) { tree->size++; @@ -900,19 +886,12 @@ void cxTreeSetParent( cx_tree_link(parent, child, cx_tree_node_layout(tree)); } -void cxTreeAddChildNode( - CxTree *tree, - void *parent, - void *child -) { +void cxTreeAddChildNode(CxTree *tree, void *parent, void *child) { cx_tree_link(parent, child, cx_tree_node_layout(tree)); tree->size++; } -int cxTreeAddChild( - CxTree *tree, - void *parent, - const void *data) { +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)); @@ -921,6 +900,29 @@ int cxTreeAddChild( return 0; } +int cxTreeInsert(CxTree *tree, const void *data) { + return tree->cl->insert_element(tree, data); +} + +size_t cxTreeInsertIter(CxTree *tree, CxIteratorBase *iter, size_t n) { + return tree->cl->insert_many(tree, iter, n); +} + +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, false); + return cxTreeInsertIter(tree, cxIteratorRef(iter), n); +} + +void *cxTreeFind( CxTree *tree, const void *data) { + return tree->cl->find(tree, tree->root, data, 0); +} + +void *cxTreeFindInSubtree(CxTree *tree, const void *data, void *subtree_root, size_t max_depth) { + return tree->cl->find(tree, subtree_root, data, max_depth); +} + size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root) { CxTreeVisitor visitor = cx_tree_visitor( subtree_root, @@ -945,6 +947,10 @@ size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root) { return visitor.depth; } +size_t cxTreeSize(CxTree *tree) { + return tree->size; +} + size_t cxTreeDepth(CxTree *tree) { CxTreeVisitor visitor = cx_tree_visitor( tree->root, tree->loc_children, tree->loc_next @@ -1052,3 +1058,38 @@ void cxTreeDestroySubtree(CxTree *tree, void *node) { tree->root = NULL; } } + +void cxTreeIteratorDispose(CxTreeIterator *iter) { + cxFreeDefault(iter->stack); + iter->stack = NULL; +} + +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; + cxFreeDefault(q); + q = next; + } +} + +CxTreeIterator cxTreeIterateSubtree(CxTree *tree, void *node, bool visit_on_exit) { + return cx_tree_iterator( + node, visit_on_exit, + tree->loc_children, tree->loc_next + ); +} + +CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) { + return cx_tree_visitor( + node, tree->loc_children, tree->loc_next + ); +} + +CxTreeIterator cxTreeIterate(CxTree *tree, bool visit_on_exit) { + return cxTreeIterateSubtree(tree, tree->root, visit_on_exit); +} + +CxTreeVisitor cxTreeVisit(CxTree *tree) { + return cxTreeVisitSubtree(tree, tree->root); +} diff --git a/ui/.qmake.stash b/ui/.qmake.stash new file mode 100644 index 0000000..5808fc8 --- /dev/null +++ b/ui/.qmake.stash @@ -0,0 +1,22 @@ +QMAKE_CXX.QT_COMPILER_STDCXX = 201703L +QMAKE_CXX.QMAKE_GCC_MAJOR_VERSION = 15 +QMAKE_CXX.QMAKE_GCC_MINOR_VERSION = 2 +QMAKE_CXX.QMAKE_GCC_PATCH_VERSION = 1 +QMAKE_CXX.COMPILER_MACROS = \ + QT_COMPILER_STDCXX \ + QMAKE_GCC_MAJOR_VERSION \ + QMAKE_GCC_MINOR_VERSION \ + QMAKE_GCC_PATCH_VERSION +QMAKE_CXX.INCDIRS = \ + /usr/include/c++/15 \ + /usr/include/c++/15/x86_64-redhat-linux \ + /usr/include/c++/15/backward \ + /usr/lib/gcc/x86_64-redhat-linux/15/include \ + /usr/local/include \ + /usr/include +QMAKE_CXX.LIBDIRS = \ + /usr/lib/gcc/x86_64-redhat-linux/15 \ + /usr/lib64 \ + /lib64 \ + /usr/lib \ + /lib diff --git a/ui/Makefile b/ui/Makefile new file mode 100644 index 0000000..ad5aad0 --- /dev/null +++ b/ui/Makefile @@ -0,0 +1,48 @@ +# +# 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/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) +UI_SHLIB = ../build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(SHLIB_EXT) + +include $(TOOLKIT)/objs.mk +OBJ = $(TOOLKITOBJS) $(COMMONOBJS) + +all: $(UI_LIB) $(UI_SHLIB) + +include $(TOOLKIT)/Makefile + +$(COMMON_OBJPRE)uic_%$(OBJ_EXT): common/%.c + $(CC) -o $@ -c -I../ucx/ $(CFLAGS) $(SHLIB_CFLAGS) $(TK_CFLAGS) $< + diff --git a/ui/cocoa/BoxContainer.h b/ui/cocoa/BoxContainer.h new file mode 100644 index 0000000..a80ae7d --- /dev/null +++ b/ui/cocoa/BoxContainer.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#import "Container.h" +#import "GridLayout.h" + +@interface BoxContainer : GridLayout + +@property NSUserInterfaceLayoutOrientation orientation; + +- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing; + +@end + diff --git a/ui/cocoa/BoxContainer.m b/ui/cocoa/BoxContainer.m new file mode 100644 index 0000000..e4b8f7f --- /dev/null +++ b/ui/cocoa/BoxContainer.m @@ -0,0 +1,30 @@ + + +#import "BoxContainer.h" + +@implementation BoxContainer + +- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing { + self = [super init]; + _orientation = orientation; + self.columnspacing = spacing; + self.rowspacing = spacing; + + return self; +} + +- (void) addView:(NSView*)view layout:(UiLayout*)layout { + if(_orientation == NSUserInterfaceLayoutOrientationVertical) { + layout->hexpand = TRUE; + layout->hfill = TRUE; + } else { + layout->vexpand = TRUE; + layout->vfill = TRUE; + } + [super addView:view layout:layout]; + if(_orientation == NSUserInterfaceLayoutOrientationVertical) { + self.container->newline = TRUE; + } +} + +@end diff --git a/ui/cocoa/EventData.h b/ui/cocoa/EventData.h new file mode 100644 index 0000000..4ee89f3 --- /dev/null +++ b/ui/cocoa/EventData.h @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#import "../ui/toolkit.h" +#import "../common/context.h" + +typedef void(*get_eventdata_func)(id sender, UiVar *var, void **eventdata, int *value); + +@interface EventData : NSObject +@property UiObject *obj; +@property UiVar *var; +@property int vartype; +@property ui_callback callback; +@property void *userdata; +@property void *data; +@property int value; +@property get_eventdata_func get_eventdata; + +- (EventData*)init:(ui_callback)cb userdata:(void*)userdata; + +- (void)handleEvent:(id)sender; + +- (void)handleEventWithEventData:(id)sender; + +@end + diff --git a/ui/cocoa/EventData.m b/ui/cocoa/EventData.m new file mode 100644 index 0000000..1df6d9b --- /dev/null +++ b/ui/cocoa/EventData.m @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#import "EventData.h" + +#import + + +@implementation EventData + +- (EventData*)init:(ui_callback)cb userdata:(void*)userdata { + _callback = cb; + _userdata = userdata; + return self; +} + +- (void)handleEvent:(id)sender { + if(_callback) { + UiEvent event; + event.obj = self.obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = self.data; + event.intval = self.value; + event.set = ui_get_setop(); + self.callback(&event, self.userdata); + } +} + +- (void)handleEventWithEventData:(id)sender { + UiEvent event; + event.obj = self.obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.intval = 0; + event.set = ui_get_setop(); + if(_get_eventdata) { + _get_eventdata(sender, _var, &event.eventdata, &event.intval); + } + if(self.callback) { + self.callback(&event, self.userdata); + } +} + +@end diff --git a/ui/cocoa/GridLayout.h b/ui/cocoa/GridLayout.h new file mode 100644 index 0000000..362c9b2 --- /dev/null +++ b/ui/cocoa/GridLayout.h @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#import "toolkit.h" + +#import "Container.h" + +#import + +typedef struct GridElm { + NSView *view; + NSEdgeInsets margin; + int x; + int y; + int colspan; + int rowspan; + int preferred_width; + int preferred_height; + BOOL hexpand; + BOOL vexpand; + BOOL hfill; + BOOL vfill; +} GridElm; + +typedef struct GridDef { + int size; + int pos; + int preferred_size; + BOOL expand; +} GridDef; + +@interface GridLayout : NSView + +@property UiContainerX *container; + +@property int columnspacing; +@property int rowspacing; +@property CxList *children; +@property NSSize preferredSize; + +@property int x; +@property int y; +@property int cols; +@property int rows; + +- (GridLayout*)init; + +@end diff --git a/ui/cocoa/GridLayout.m b/ui/cocoa/GridLayout.m new file mode 100644 index 0000000..c27ba1d --- /dev/null +++ b/ui/cocoa/GridLayout.m @@ -0,0 +1,361 @@ +/* + * 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. + */ + +#import "GridLayout.h" + + + +@implementation GridLayout + +@synthesize container = _container; + +- (GridLayout*)init { + self = [super init]; + _columnspacing = 0; + _rowspacing = 0; + _children = cxArrayListCreateSimple(sizeof(GridElm), 32); + _preferredSize.width = -1; + _preferredSize.height = -1; + + return self; +} + +/* +- (void) layout { + [super layout]; + + NSRect r1 = _test.frame; + NSSize s1 = _test.intrinsicContentSize; + NSEdgeInsets e1 = _test.alignmentRectInsets; + +} + */ + +- (BOOL)isFlipped { + return YES; +} + +- (void) layout { + int ncols = _cols+1; + int nrows = _rows+1; + + GridDef *cols = calloc(ncols, sizeof(GridDef)); + GridDef *rows = calloc(nrows, sizeof(GridDef)); + + //NSRect viewFrame = self.frame; + NSRect viewFrame = self.bounds; + + int colspacing = _columnspacing; + int rowspacing = _rowspacing; + + int span_max = 1; + for(int r=0;r<2;r++) { + CxIterator i = cxListIterator(_children); + cx_foreach(GridElm *, elm, i) { + int x = elm->x; + int y = elm->y; + GridDef *col = &cols[x]; + GridDef *row = &rows[y]; + + NSSize size = elm->view.intrinsicContentSize; + NSSize size2 = elm->view.fittingSize; + if(size.width == NSViewNoIntrinsicMetric) { + size.width = size2.width; + } + if(size.height == NSViewNoIntrinsicMetric) { + size.height = size2.height; + } + if(size.width != NSViewNoIntrinsicMetric) { + CGFloat width = size.width + elm->margin.left + elm->margin.right; + if(width > cols[elm->x].preferred_size && elm->colspan <= 1 && span_max == 1) { + cols[elm->x].preferred_size = width; + } + elm->preferred_width = width; + } + if(size.height != NSViewNoIntrinsicMetric) { + CGFloat height = size.height + elm->margin.top + elm->margin.bottom; + if(height > rows[elm->y].preferred_size && elm->rowspan <= 1 && span_max == 1) { + rows[elm->y].preferred_size = height; + } + elm->preferred_height = height; + } + + + if(elm->rowspan > span_max || elm->colspan > span_max) { + continue; + } + + int end_col = x+elm->colspan; + if(end_col > ncols) { + end_col = ncols; + } + int end_row = y+elm->rowspan; + if(end_row > nrows) { + end_row = nrows; + } + + // are all columns in the span > preferred_width? + if(elm->colspan > 1) { + int span_width = 0; + GridDef *last_col = col; + for(int c=x;cpreferred_width) { + last_col->size += elm->preferred_width - span_width; + } + } + // are all rows in the span > preferred_height? + if(elm->rowspan > 1) { + int span_height = 0; + GridDef *last_row = row; + for(int c=x;cpreferred_height) { + last_row->size += elm->preferred_height - span_height; + } + } + + if(elm->hexpand) { + if(elm->colspan > 1) { + // check if any column in the span is expanding + // if not, make the last column expanding + GridDef *last_col = col; + for(int c=x;cexpand) { + break; + } + } + last_col->expand = TRUE; + } else { + col->expand = TRUE; + } + } + if(elm->vexpand) { + if(elm->rowspan > 1) { + // same as colspan + GridDef *last_row = row; + for(int c=x;cexpand) { + break; + } + } + last_row->expand = TRUE; + } else { + row->expand = TRUE; + } + } + } + span_max = 50000; // not sure if this is unreasonable low or high + } + + + int col_ext = 0; + int row_ext = 0; + + int preferred_width = 0; + int preferred_height = 0; + for(int j=0;j 0) { + preferred_width += (ncols-1) * colspacing; + } + if(nrows > 0) { + preferred_height += (nrows-1) * rowspacing; + } + + _preferredSize.width = preferred_width; + _preferredSize.height = preferred_height; + + + int hremaining = viewFrame.size.width - preferred_width; + int vremaining = viewFrame.size.height - preferred_height; + int hext = hremaining/col_ext; + int vext = vremaining/row_ext; + + for(int j=0;jexpand) { + col->size = col->preferred_size + hext; + } else { + col->size = col->preferred_size; + } + } + for(int j=0;jexpand) { + row->size = row->preferred_size + vext; + } else { + row->size = row->preferred_size; + } + } + + int pos = 0; + for(int j=0;jview.intrinsicContentSize; + GridDef *col = &cols[elm->x]; + GridDef *row = &rows[elm->y]; + + NSRect frame; + if(elm->hfill) { + if(elm->colspan > 1) { + int cwidth = 0; + int end_col = elm->x + elm->colspan; + if(end_col > ncols) { + end_col = ncols; + } + int real_span = 0; + for(int c=elm->x;c 0) { + cwidth += (real_span-1) * colspacing; + } + frame.size.width = cwidth; + } else { + frame.size.width = col->size; + } + } else { + frame.size.width = elm->preferred_width; + } + frame.size.width -= elm->margin.left + elm->margin.right; + if(elm->vfill) { + if(elm->rowspan > 1) { + int rheight = 0; + int end_row = elm->y + elm->rowspan; + if(end_row > nrows) { + end_row = nrows; + } + int real_span = 0; + for(int r=elm->y;r 0) { + rheight += (real_span-1) * rowspacing; + } + frame.size.height = rheight; + } + frame.size.height = row->size; + } else { + frame.size.height = elm->preferred_height; + } + frame.size.height -= elm->margin.top + elm->margin.bottom; + + frame.origin.x = col->pos + elm->margin.left; + frame.origin.y = row->pos + elm->margin.top; + NSRect viewFrame = [elm->view frameForAlignmentRect:frame]; + elm->view.frame = viewFrame; + } + + free(cols); + free(rows); +} + + +- (NSSize)intrinsicContentSize { + if(_preferredSize.width == -1) { + [self layout]; + } + return self.preferredSize; +} + +- (void) addView:(NSView*)view layout:(UiLayout*)layout { + _preferredSize.width = -1; + _preferredSize.height = -1; + + if(self.container != nil && self.container->newline) { + _y++; + _x = 0; + self.container->newline = FALSE; + } + + GridElm elm; + elm.x = _x; + elm.y = _y; + elm.margin = NSEdgeInsetsMake(layout->margin_top, layout->margin_left, layout->margin_bottom, layout->margin_right); + elm.colspan = layout->colspan; + elm.rowspan = layout->rowspan; + if(layout->fill) { + elm.hfill = TRUE; + elm.vfill = TRUE; + elm.hexpand = TRUE; + elm.vexpand = TRUE; + } else { + elm.hfill = layout->hfill; + elm.vfill = layout->vfill; + elm.hexpand = layout->hexpand; + elm.vexpand = layout->vexpand; + } + elm.view = view; + cxListAdd(_children, &elm); + + [self addSubview:view]; + self.needsLayout = YES; + + if(_x > _cols) { + _cols = _x; + } + if(_y > _rows) { + _rows = _y; + } + _x++; +} + +- (void) dealloc { + cxListFree(_children); +} + +@end diff --git a/ui/cocoa/ListDataSource.h b/ui/cocoa/ListDataSource.h new file mode 100644 index 0000000..4dc142f --- /dev/null +++ b/ui/cocoa/ListDataSource.h @@ -0,0 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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/tree.h" + +@interface ListDataSource : NSObject + +@property NSArray *columns; +@property UiVar *var; +@property ui_getvaluefunc2 getvalue; +@property void *getvaluedata; +@property UiModel *model; + +- (id) init:(NSArray*) columns var:(UiVar*)var getvalue:(ui_getvaluefunc2) getvaluefunc getvaluedata:(void*)userdata; + +@end diff --git a/ui/cocoa/ListDataSource.m b/ui/cocoa/ListDataSource.m new file mode 100644 index 0000000..8894b20 --- /dev/null +++ b/ui/cocoa/ListDataSource.m @@ -0,0 +1,116 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "ListDataSource.h" + +@implementation ListDataSource + +- (id) init:(NSArray*) columns var:(UiVar*)var getvalue:(ui_getvaluefunc2) getvaluefunc getvaluedata:(void*)userdata { + _columns = columns; + _var = var; + _getvalue = getvaluefunc; + _getvaluedata = userdata; + return self; +} + +- (NSInteger) numberOfRowsInTableView:(NSTableView *) tableView { + if(_var) { + UiList *list = _var->value; + if(list->count) { + return list->count(list); + } + } + return 0; +} + +- (id) tableView:(NSTableView *) tableView +objectValueForTableColumn:(NSTableColumn *) tableColumn + row:(NSInteger) row +{ + id ret = nil; + UiList *list = _var->value; + void *elm = list->get(list, (int)row); + if(elm) { + // get column index + NSUInteger colIndex = [_columns indexOfObject:tableColumn]; + if(colIndex == NSNotFound) { + return nil; + } + + // get UI model type for this column + UiModelType type = UI_STRING; + UiModel *model = _model; + if(model) { + if(colIndex >= model->columns) { + return nil; + } + type = model->types[colIndex]; + } + + // convert the list element + UiBool freeResult = FALSE; + void *data = _getvalue(list, elm, (int)row, (int)colIndex, _getvaluedata, &freeResult); + + switch(type) { + case UI_STRING: { + ret = [[NSString alloc] initWithUTF8String:data]; + break; + } + case UI_STRING_FREE: { + ret = [[NSString alloc] initWithUTF8String:data]; + freeResult = TRUE; + break; + } + case UI_INTEGER: { + break; + } + case UI_ICON: { + break; + } + case UI_ICON_TEXT: { + break; + } + case UI_ICON_TEXT_FREE: { + break; + } + case UI_STRING_EDITABLE: { + break; + } + case UI_BOOL_EDITABLE: { + break; + } + } + + if(freeResult) { + free(data); + } + } + return ret; +} + +@end diff --git a/ui/cocoa/ListDelegate.h b/ui/cocoa/ListDelegate.h new file mode 100644 index 0000000..27762f7 --- /dev/null +++ b/ui/cocoa/ListDelegate.h @@ -0,0 +1,46 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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" + +@interface ListDelegate : NSObject + +@property (weak) NSTableView *tableview; +@property UiObject *obj; +@property ui_callback onselection; +@property void *onselectiondata; +@property ui_callback onactivate; +@property void *onactivatedata; + +- (id)init:(NSTableView*) tableview obj:(UiObject*)obj; + +- (void)activateEvent:(id)sender; + +@end + +UiListSelection ui_tableview_selection(NSTableView *tableview); diff --git a/ui/cocoa/ListDelegate.m b/ui/cocoa/ListDelegate.m new file mode 100644 index 0000000..7e15f3d --- /dev/null +++ b/ui/cocoa/ListDelegate.m @@ -0,0 +1,99 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "ListDelegate.h" + +@implementation ListDelegate + +- (id)init:(NSTableView*) tableview obj:(UiObject*)obj { + _tableview = tableview; + _obj = obj; + return self; +} + +- (void)activateEvent:(id)sender { + NSTableView *table = sender; + if(_onactivate) { + int row = (int)table.clickedRow; + + UiListSelection sel; + sel.count = 1; + sel.rows = &row; + + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &sel; + event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; + event.intval = row; + event.set = ui_get_setop(); + + _onactivate(&event, _onactivatedata); + } +} + +- (void) tableViewSelectionDidChange:(NSNotification *) notification { + if(_onselection) { + UiListSelection sel = ui_tableview_selection(_tableview); + + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &sel; + event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; + event.intval = 0; + event.set = ui_get_setop(); + + _onselection(&event, _onselectiondata); + } +} + +@end + +UiListSelection ui_tableview_selection(NSTableView *tableview) { + NSIndexSet *indexSet = tableview.selectedRowIndexes; + NSUInteger count = [indexSet count]; + + if(count == 0) { + return (UiListSelection){0, NULL}; + } + + int *rows = calloc(count, sizeof(int)); + + __block NSUInteger i = 0; + [indexSet enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { + rows[i++] = (int)index; + }]; + + UiListSelection sel; + sel.count = (int)count; + sel.rows = rows; + return sel; +} diff --git a/ui/cocoa/MainWindow.h b/ui/cocoa/MainWindow.h new file mode 100644 index 0000000..7c8cbf6 --- /dev/null +++ b/ui/cocoa/MainWindow.h @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#import "toolkit.h" +#import "../ui/window.h" + +@interface MainWindow : NSWindow + +@property UiObject *obj; +@property (strong) NSSplitView *splitview; +@property (strong) NSView *sidebar; +@property (strong) NSView *leftPanel; +@property (strong) NSView *rightPanel; +@property int topOffset; + +- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)hasSidebar withSplitview:(BOOL)hasSplitview; + +@end + + +@interface MainWindowController : NSWindowController + +@property UiObject *uiobj; +@property NSMutableDictionary *checkItemStates; +@property NSMutableDictionary *radioItems; + +- (MainWindowController*)initWithWindow:(UiObject*)obj window:(NSWindow*)window; + +- (void) windowDidLoad; + +- (void)menuItemAction:(id)sender; + +- (BOOL) validateMenuItem:(NSMenuItem *) menuItem; + +@end + +@interface MenuItemState : NSObject +@property (weak) MainWindowController *mainWindow; +@property UiVar *var; +@property int state; +@end + + +int64_t ui_menu_check_item_get(UiInteger *i); +void ui_menu_check_item_set(UiInteger *i, int64_t value); + +int64_t ui_menu_radio_item_get(UiInteger *i); +void ui_menu_radio_item_set(UiInteger *i, int64_t value); diff --git a/ui/cocoa/MainWindow.m b/ui/cocoa/MainWindow.m new file mode 100644 index 0000000..c62079f --- /dev/null +++ b/ui/cocoa/MainWindow.m @@ -0,0 +1,415 @@ +/* + * 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. + */ + +#import "MainWindow.h" +#import "Container.h" +#import "GridLayout.h" +#import "BoxContainer.h" +#import "../common/object.h" +#import "../ui/properties.h" +#import + +#import "EventData.h" +#import "menu.h" +#import "Toolbar.h" + +@implementation MainWindow + +- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)hasSidebar withSplitview:(BOOL)hasSplitview{ + NSRect frame = NSMakeRect(300, 200, 600, 500); + + self = [self initWithContentRect:frame + styleMask:NSWindowStyleMaskTitled | + NSWindowStyleMaskResizable | + NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable + backing:NSBackingStoreBuffered + defer:false]; + _obj = obj; + + + int top = 4; + NSView *content = self.contentView; + + // A sidebar or splitview window need a NSSplitView + NSSplitView *splitview; + if(hasSidebar || hasSplitview) { + self.styleMask |= NSWindowStyleMaskFullSizeContentView; + self.titleVisibility = NSWindowTitleHidden; + self.titlebarAppearsTransparent = YES; + + splitview = [[NSSplitView alloc]init]; + splitview.vertical = YES; + splitview.dividerStyle = NSSplitViewDividerStyleThin; + splitview.translatesAutoresizingMaskIntoConstraints = false; + [self.contentView addSubview:splitview]; + _splitview = splitview; + + [NSLayoutConstraint activateConstraints:@[ + [splitview.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:0], + [splitview.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor], + [splitview.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], + [splitview.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor] + ]]; + + top = 34; + } + + if(hasSidebar) { + // add the sidebar + const char *sidebarMaterialProperty = ui_get_property("ui.cocoa.sidebar.usematerial"); + BOOL useMaterial = YES; + if(sidebarMaterialProperty && (sidebarMaterialProperty[0] == 'f' || sidebarMaterialProperty[0] == 'F')) { + useMaterial = NO; + } + + if(useMaterial) { + NSVisualEffectView *v = [[NSVisualEffectView alloc] initWithFrame:NSMakeRect(0,0,0,0)]; + v.material = NSVisualEffectMaterialSidebar; + v.blendingMode = NSVisualEffectBlendingModeBehindWindow; + v.state = NSVisualEffectStateActive; + _sidebar = v; + } else { + _sidebar = [[NSView alloc]initWithFrame:NSMakeRect(0,0,0,0)]; + } + _sidebar.translatesAutoresizingMaskIntoConstraints = NO; + [splitview addArrangedSubview:_sidebar]; + [_sidebar.widthAnchor constraintGreaterThanOrEqualToConstant:250].active = YES; + } + if(hasSplitview) { + // add the splitview window left/right panels + _leftPanel = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)]; + [splitview addArrangedSubview:_leftPanel]; + _rightPanel = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)]; + [splitview addArrangedSubview:_rightPanel]; + } else if(hasSidebar) { + // sidebar only window: add content view + content = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)]; + [splitview addArrangedSubview:content]; + } + + // normal or sidebar-only windows get a container + if(!hasSplitview) { + // create a vertical stackview as default container + BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; + //GridLayout *vbox = [[GridLayout alloc] init]; + vbox.translatesAutoresizingMaskIntoConstraints = false; + [content addSubview:vbox]; + [NSLayoutConstraint activateConstraints:@[ + [vbox.topAnchor constraintEqualToAnchor:content.topAnchor constant:top], + [vbox.leadingAnchor constraintEqualToAnchor:content.leadingAnchor], + [vbox.trailingAnchor constraintEqualToAnchor:content.trailingAnchor], + [vbox.bottomAnchor constraintEqualToAnchor:content.bottomAnchor], + ]]; + UiContainerX *container = ui_create_container(obj, vbox); + vbox.container = container; + uic_object_push_container(obj, container); + } + _topOffset = top; + + if(uic_toolbar_isenabled()) { + UiToolbar *toolbar = [[UiToolbar alloc]initWithWindow:self]; + [self setToolbar:toolbar]; + } + + + return self; +} + +- (BOOL) getIsVisible { + return [self isVisible]; +} + +- (void) setVisible:(BOOL)visible { + if(visible) { + [self makeKeyAndOrderFront:nil]; + } else { + [self close]; + } +} + + +@end + + +@implementation MainWindowController + +- (MainWindowController*)initWithWindow:(UiObject*)obj window:(NSWindow*)window { + self = [super initWithWindow:window]; + _uiobj = obj; + + self.checkItemStates = [[NSMutableDictionary alloc] init]; + self.radioItems = [[NSMutableDictionary alloc] init]; + + // bind all stateful menu items (checkbox, radiobuttons, lists) + NSArray *menuBindItems = ui_get_binding_items(); // returns all items that require binding + for(MenuItem *item in menuBindItems) { + if(item.checkItem || item.radioItem) { + // simple check item (ui_menu_toggleitem_create) + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, NULL, item.checkItem ? item.checkItem->varname : item.radioItem->varname, UI_VAR_INTEGER); + // create the state object for this item/window + MenuItemState *state = [[MenuItemState alloc] init]; + state.mainWindow = self; + state.var = var; + if(var) { + UiInteger *i = var->value; + if(item.checkItem) { + // bind toggle item + state.state = (int)i->value; + i->obj = (__bridge void*)state; + i->get = ui_menu_check_item_get; + i->set = ui_menu_check_item_set; + } else { + // bind radio item + NSMutableArray *rgroup = nil; + if(i->obj) { + rgroup = (__bridge NSMutableArray*)i->obj; + } else { + // create a new rgroup array and register it in the window + rgroup = [[NSMutableArray alloc] init]; + NSString *varname = [[NSString alloc] initWithUTF8String:item.radioItem->varname]; + [_radioItems setObject:rgroup forKey:varname]; + i->obj = (__bridge void*)rgroup; + } + i->get = ui_menu_radio_item_get; + i->set = ui_menu_radio_item_set; + [rgroup addObject:state]; // add this item state to the radio group + // i->value can contain a non-zero value, which means a specific radiobutton + // should be pre-selected + if(i->value == rgroup.count) { + state.state = NSControlStateValueOn; + } + } + } else { + state.state = 0; + } + [_checkItemStates setObject:state forKey:item.itemId]; + } + } + + return self; +} + +- (void) windowDidLoad { + [self.window setNextResponder:self]; +} + +- (void)menuItemAction:(id)sender { + EventData *event = objc_getAssociatedObject(sender, "eventdata"); + if(event) { + if(event.obj) { + [event handleEvent:sender]; + } else { + event.obj = self.uiobj; + [event handleEvent:sender]; + event.obj = NULL; + } + } +} + +- (void)menuCheckItemAction:(id)sender { + NSMenuItem *menuItem = sender; + MenuItem *item = objc_getAssociatedObject(sender, "menuitem"); + if(!item || !item.checkItem) { + return; + } + + MenuItemState *state = [_checkItemStates objectForKey:item.itemId]; + state.state = state.state == NSControlStateValueOff ? NSControlStateValueOn : NSControlStateValueOff; + menuItem.state = state.state; + + UiMenuCheckItem *it = item.checkItem; + if(it->callback) { + UiEvent event; + event.obj = _uiobj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = state.var ? state.var->value : NULL; + event.intval = state.state; + it->callback(&event, it->userdata); + } +} + +- (void)menuRadioItemAction:(id)sender { + NSMenuItem *menuItem = sender; + MenuItem *item = objc_getAssociatedObject(sender, "menuitem"); + if(!item || !item.radioItem) { + return; + } + + UiMenuRadioItem *it = item.radioItem; + if(!it->varname) { + return; + } + + MenuItemState *state = [_checkItemStates objectForKey:item.itemId]; // current state of this menu item + + NSString *varname = [[NSString alloc] initWithUTF8String:it->varname]; + NSArray *radioGroup = [_radioItems objectForKey:varname]; + if(!radioGroup) { + return; + } + int index = 1; + int value = 0; + for(MenuItemState *g in radioGroup) { + if(g == state) { + menuItem.state = NSControlStateValueOn; + g.state = NSControlStateValueOn; + value = index; + } else { + menuItem.state = NSControlStateValueOff; + g.state = NSControlStateValueOff; + } + } + + if(it->callback) { + UiEvent event; + event.obj = _uiobj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = state.var ? state.var->value : NULL; + event.intval = value; + it->callback(&event, it->userdata); + } +} + + +- (BOOL) validateMenuItem:(NSMenuItem *) menuItem { + MenuItem *item = objc_getAssociatedObject(menuItem, "menuitem"); + if(item) { + MenuItemState *state = [_checkItemStates objectForKey:item.itemId]; + if(state) { + menuItem.state = state.state; + } else { + menuItem.state = NSControlStateValueOff; + } + } + + return YES; +} + +@end + +@implementation MenuItemState + +@end + +int64_t ui_menu_check_item_get(UiInteger *i) { + MenuItemState *state = (__bridge MenuItemState*)i->obj; + i->value = state.state; + return i->value; +} + +void ui_menu_check_item_set(UiInteger *i, int64_t value) { + MenuItemState *state = (__bridge MenuItemState*)i->obj; + i->value = value; + state.state = (int)value; +} + +int64_t ui_menu_radio_item_get(UiInteger *i) { + NSArray *rgroup = (__bridge NSArray*)i->obj; + i->value = 0; + int index = 1; + for(MenuItemState *state in rgroup) { + if(state.state == NSControlStateValueOn) { + i->value = index; + break; + } + index++; + } + return i->value; +} + +void ui_menu_radio_item_set(UiInteger *i, int64_t value) { + NSArray *rgroup = (__bridge NSArray*)i->obj; + i->value = 0; + int index = 1; + for(MenuItemState *state in rgroup) { + state.state = value == index; + index++; + } +} + + +UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) { + MainWindow *window = (__bridge MainWindow*)obj->wobj; + if(window.sidebar == nil) { + return NULL; + } + NSView *sidebar = window.sidebar; + + // create a vertical stackview as default container + BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:args->spacing]; + vbox.container = ui_create_container(obj, vbox); + //GridLayout *vbox = [[GridLayout alloc] init]; + vbox.translatesAutoresizingMaskIntoConstraints = false; + [sidebar addSubview:vbox]; + [NSLayoutConstraint activateConstraints:@[ + [vbox.topAnchor constraintEqualToAnchor:sidebar.topAnchor constant:34], + [vbox.leadingAnchor constraintEqualToAnchor:sidebar.leadingAnchor], + [vbox.trailingAnchor constraintEqualToAnchor:sidebar.trailingAnchor], + [vbox.bottomAnchor constraintEqualToAnchor:sidebar.bottomAnchor] + ]]; + uic_object_push_container(obj, vbox.container); + + return NULL; +} + +static UIWIDGET splitview_window_add_panel(UiObject *obj, NSView *panel, UiSidebarArgs *args) { + MainWindow *window = (__bridge MainWindow*)obj->wobj; + BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; + //GridLayout *vbox = [[GridLayout alloc] init]; + vbox.container = ui_create_container(obj, vbox); + vbox.translatesAutoresizingMaskIntoConstraints = false; + [panel addSubview:vbox]; + [NSLayoutConstraint activateConstraints:@[ + [vbox.topAnchor constraintEqualToAnchor:panel.topAnchor constant:window.topOffset], + [vbox.leadingAnchor constraintEqualToAnchor:panel.leadingAnchor], + [vbox.trailingAnchor constraintEqualToAnchor:panel.trailingAnchor], + [vbox.bottomAnchor constraintEqualToAnchor:panel.bottomAnchor], + ]]; + uic_object_push_container(obj, vbox.container); + return (__bridge void*)vbox; +} + +UIWIDGET ui_left_panel_create(UiObject *obj, UiSidebarArgs *args) { + MainWindow *window = (__bridge MainWindow*)obj->wobj; + if(window.leftPanel == nil) { + return NULL; + } + return splitview_window_add_panel(obj, window.leftPanel, args); +} + +UIWIDGET ui_right_panel_create(UiObject *obj, UiSidebarArgs *args) { + MainWindow *window = (__bridge MainWindow*)obj->wobj; + if(window.rightPanel == nil) { + return NULL; + } + return splitview_window_add_panel(obj, window.rightPanel, args); +} + diff --git a/ui/cocoa/Makefile b/ui/cocoa/Makefile new file mode 100644 index 0000000..a772b55 --- /dev/null +++ b/ui/cocoa/Makefile @@ -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. +# + +$(COCOA_OBJPRE)%.o: cocoa/%.m + $(CC) -o $@ -c -I../ucx -fobjc-arc $(CFLAGS) $(SHLIB_CFLAGS) $(TK_CFLAGS) $< + +$(UI_LIB): $(OBJ) + $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ) + +$(UI_SHLIB): $(OBJ) + $(CC) -o $(UI_SHLIB) $(LDFLAGS) $(SHLIB_LDFLAGS) $(TK_LDFLAGS) $(OBJ) -L../build/lib -lucx diff --git a/ui/cocoa/TabView.h b/ui/cocoa/TabView.h new file mode 100644 index 0000000..d6e4b0d --- /dev/null +++ b/ui/cocoa/TabView.h @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#import "Container.h" + +@protocol TabView + +- (NSView*) createTab:(int)index title:(NSString*)title; +- (void) selectTab:(int)index; +- (void) removeTab:(int)index; +- (UiObject*) addTab:(int)index title:(NSString*)title; + +@end + +@interface UiTopTabView : NSTabView + +@property UiObject *obj; +@property UiSubContainerType subcontainer; +@property int padding; +@property int spacing; +@property int columnspacing; +@property int rowspacing; +@property ui_callback onchange; +@property void *onchangedata; +@property UiVar *var; + +- (id)init:(UiObject*)obj args:(UiTabViewArgs*)args; + +@end + +int64_t ui_nstabview_get(UiInteger *i); +void ui_nstabview_set(UiInteger *i, int64_t value); diff --git a/ui/cocoa/TabView.m b/ui/cocoa/TabView.m new file mode 100644 index 0000000..5b50048 --- /dev/null +++ b/ui/cocoa/TabView.m @@ -0,0 +1,134 @@ +/* + * 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. + */ + +#import "TabView.h" +#import "BoxContainer.h" +#import "GridLayout.h" + +@implementation UiTopTabView + +@synthesize container = _container; + +- (id)init:(UiObject*)obj args:(UiTabViewArgs*)args { + self = [super init]; + _obj = obj; + _subcontainer = args->subcontainer; + _padding = args->padding; + _spacing = args->spacing; + _columnspacing = args->columnspacing; + _rowspacing = args->rowspacing; + _onchange = args->onchange; + _onchangedata = args->onchangedata; + _var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + + if(args->tabview == UI_TABVIEW_INVISIBLE || args->tabview == UI_TABVIEW_NAVIGATION_SIDE) { + self.tabViewType = NSNoTabsNoBorder; + } + + if(_var) { + UiInteger *i = _var->value; + i->obj = (__bridge void*)self; + i->get = ui_nstabview_get; + i->set = ui_nstabview_set; + } + + return self; +} + +- (void) addView:(NSView*)view layout:(UiLayout*)layout { + // noop +} + +- (NSView*) createTab:(int)index title:(NSString*)title { + NSTabViewItem *item = [[NSTabViewItem alloc]initWithIdentifier:nil]; + [item setLabel:title]; + if(index < 0) { + [self addTabViewItem:item]; + } else { + [self insertTabViewItem:item atIndex:index]; + } + + BoxContainer *content = [[BoxContainer alloc]init]; + item.view = content; + + GridLayout *sub; + switch(_subcontainer) { + default: sub = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:_spacing]; break; + case UI_CONTAINER_HBOX: sub = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationHorizontal spacing:_spacing]; break; + case UI_CONTAINER_GRID: { + sub = [[GridLayout alloc] init]; + sub.columnspacing = _columnspacing; + sub.rowspacing = _rowspacing; + break; + } + } + UiLayout layout = { + .margin = _padding, + .margin_left = _padding, .margin_right = _padding, .margin_top = _padding, .margin_bottom = _padding, + .fill = TRUE }; + [content addView:sub layout:&layout]; + + return sub; +} + +- (void) selectTab:(int)index { + [self selectTabViewItemAtIndex:index]; +} + +- (void) removeTab:(int)index { + NSTabViewItem *item = [self tabViewItemAtIndex:index]; + if(item != nil) { + [self removeTabViewItem:item]; + } +} + +- (UiObject*) addTab:(int)index title:(NSString*)title { + NSView *sub = [self createTab:index title:title]; + + UiObject *newobj = uic_object_new_toplevel(); + newobj->widget = (__bridge void*)sub; + + UiContainerX *container = ui_create_container(newobj, sub); + uic_object_push_container(newobj, container); + + return newobj; +} + +@end + +int64_t ui_nstabview_get(UiInteger *i) { + UiTopTabView *tabview = (__bridge UiTopTabView*)i->obj; + i->value = [tabview indexOfTabViewItem:tabview.selectedTabViewItem]; + return i->value; +} + +void ui_nstabview_set(UiInteger *i, int64_t value) { + UiTopTabView *tabview = (__bridge UiTopTabView*)i->obj; + [tabview selectTab:(int)value]; + i->value = [tabview indexOfTabViewItem:tabview.selectedTabViewItem]; +} diff --git a/ui/cocoa/Toolbar.h b/ui/cocoa/Toolbar.h new file mode 100644 index 0000000..7ec7bbd --- /dev/null +++ b/ui/cocoa/Toolbar.h @@ -0,0 +1,79 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "../common/toolbar.h" +#import "MainWindow.h" + +/* + * UiToolbarDelegate + */ +@interface UiToolbarDelegate : NSObject { + NSMutableArray *allowedItems; + NSMutableArray *defaultItems; +} + +- (UiToolbarDelegate*) init; + +@end + +/* + * UiToolbar + */ +@interface UiToolbar : NSToolbar { + NSMutableArray *allowedItems; + NSMutableArray *defaultItems; +} + +@property MainWindow *window; +@property UiObject *obj; + +- (UiToolbar*) initWithWindow:(MainWindow*)window; + +@end + + +@interface UiToolbarToggleEventHandler : NSObject +@property UiObject *obj; +@property UiVar *var; +@property ui_callback callback; +@property void *userdata; + +- (UiToolbarToggleEventHandler*)init; +- (void)handleEvent:(id)sender; + +@end + +void ui_toolbar_init(void); + +NSToolbarItem* ui_nstoolbaritem_create_item(UiObject *obj, UiToolbarItem *item, NSString *identifier); +NSToolbarItem* ui_nstoolbaritem_create_toggle(UiObject *obj, UiToolbarToggleItem *item, NSString *identifier); +NSToolbarItem* ui_nstoolbaritem_create_menu(UiObject *obj, UiToolbarMenuItem *item, NSString *identifier); + +int64_t ui_toolbar_seg_toggleitem_get(UiInteger *i); +void ui_toolbar_seg_toggleitem_set(UiInteger *i, int64_t value); diff --git a/ui/cocoa/Toolbar.m b/ui/cocoa/Toolbar.m new file mode 100644 index 0000000..8ab2ad8 --- /dev/null +++ b/ui/cocoa/Toolbar.m @@ -0,0 +1,307 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "Toolbar.h" +#import "EventData.h" +#import "image.h" +#import "menu.h" +#import + +#include "../common/toolbar.h" + + +void ui_toolbar_init(void) { + +} + + + +/* --------------------- UiToolbar --------------------- */ + +@implementation UiToolbar + +- (UiToolbar*) initWithWindow:(MainWindow*)window { + self = [super initWithIdentifier:@"UiToolbar"]; + _window = window; + _obj = window.obj; + + allowedItems = [[NSMutableArray alloc]initWithCapacity:16]; + defaultItems = [[NSMutableArray alloc]initWithCapacity:16]; + + if(window.sidebar) { + [allowedItems addObject:@"sidebar_separator"]; + } + if(window.leftPanel) { + [allowedItems addObject:@"splitview_separator"]; + } + + CxMap *toolbarItems = uic_get_toolbar_items(); + CxMapIterator i = cxMapIteratorKeys(toolbarItems); + cx_foreach(CxHashKey *, key, i) { + NSString *s = [[NSString alloc]initWithBytes:key->data length:key->len encoding:NSUTF8StringEncoding]; + [allowedItems addObject:s]; + } + [allowedItems addObject: NSToolbarFlexibleSpaceItemIdentifier]; + [allowedItems addObject: NSToolbarSpaceItemIdentifier]; + + // UI_TOOLBAR_LEFT = 0, + // UI_TOOLBAR_CENTER, + // UI_TOOLBAR_RIGHT, + // UI_TOOLBAR_SIDEBAR_LEFT, + // UI_TOOLBAR_SIDEBAR_RIGHT, + // UI_TOOLBAR_RIGHTPANEL_LEFT, + // UI_TOOLBAR_RIGHTPANEL_CENTER, + // UI_TOOLBAR_RIGHTPANEL_RIGHT + CxList *tbitems[8]; + for(int i=0;i<8;i++) { + tbitems[i] = uic_get_toolbar_defaults(i); + } + + if(window.sidebar) { + CxIterator iter = cxListIterator(tbitems[UI_TOOLBAR_SIDEBAR_LEFT]); + cx_foreach(char *, name, iter) { + NSString *s = [[NSString alloc] initWithUTF8String:name]; + [defaultItems addObject:s]; + } + + CxList *sidebarRight = tbitems[UI_TOOLBAR_SIDEBAR_RIGHT]; + if(cxListSize(sidebarRight) > 0) { + [defaultItems addObject:NSToolbarFlexibleSpaceItemIdentifier]; + iter = cxListIterator(sidebarRight); + cx_foreach(char *, name, iter) { + NSString *s = [[NSString alloc] initWithUTF8String:name]; + [defaultItems addObject:s]; + } + } + + [defaultItems addObject:@"sidebar_separator"]; + } + + int start_pos = UI_TOOLBAR_LEFT; + for(int x=0;x<2;x++) { + for(int t=start_pos;t 0) { + [defaultItems addObject:NSToolbarFlexibleSpaceItemIdentifier]; + } + } + + if(x == 0 && window.rightPanel) { + [defaultItems addObject:@"splitview_separator"]; + } + start_pos = UI_TOOLBAR_RIGHTPANEL_LEFT; + } + + + [self setDelegate:self]; + [self setAllowsUserCustomization:YES]; + return self; +} + +// implementation of NSToolbarDelegate methods + +- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar { + return allowedItems; +} + +- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar { + return defaultItems; +} + +- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar + itemForItemIdentifier:(NSString *)itemIdentifier + willBeInsertedIntoToolbar:(BOOL)flag { + CxMap *items = uic_get_toolbar_items(); + UiToolbarItemI *item = cxMapGet(items, itemIdentifier.UTF8String); + if(!item) { + if([itemIdentifier isEqualToString:@"sidebar_separator"]) { + NSTrackingSeparatorToolbarItem *sep = [NSTrackingSeparatorToolbarItem trackingSeparatorToolbarItemWithIdentifier:itemIdentifier + splitView:_window.splitview + dividerIndex:0]; + return sep; + } else if([itemIdentifier isEqualToString:@"splitview_separator"]) { + int dividerIndex = _window.sidebar != nil ? 1 : 0; + NSTrackingSeparatorToolbarItem *sep = [NSTrackingSeparatorToolbarItem trackingSeparatorToolbarItemWithIdentifier:itemIdentifier + splitView:_window.splitview + dividerIndex:dividerIndex]; + return sep; + } + return nil; + } + + switch(item->type) { + default: return nil; + case UI_TOOLBAR_ITEM: { + return ui_nstoolbaritem_create_item(_obj, (UiToolbarItem*)item, itemIdentifier); + } + case UI_TOOLBAR_TOGGLEITEM: { + return ui_nstoolbaritem_create_toggle(_obj, (UiToolbarToggleItem*)item, itemIdentifier); + } + case UI_TOOLBAR_MENU: { + return ui_nstoolbaritem_create_menu(_obj, (UiToolbarMenuItem*)item, itemIdentifier); + } + } + + return nil; +} + +@end + +@implementation UiToolbarToggleEventHandler + +- (UiToolbarToggleEventHandler*)init { + return self; +} + +- (void)handleEvent:(id)sender { + if(_callback == nil) { + return; + } + + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = 0; + event.set = ui_get_setop(); + + if([sender isKindOfClass:[NSSegmentedControl class]]) { + NSSegmentedControl *seg = (NSSegmentedControl*)sender; + event.intval = [seg isSelectedForSegment:0]; + } + + _callback(&event, _userdata); +} + +@end + +NSToolbarItem* ui_nstoolbaritem_create_item(UiObject *obj, UiToolbarItem *item, NSString *identifier) { + NSToolbarItem *button = [[NSToolbarItem alloc] initWithItemIdentifier: identifier]; + button.bordered = YES; + + if(item->args.label) { + NSString *label = [[NSString alloc] initWithUTF8String:item->args.label]; + button.paletteLabel = label; + button.label = label; + } + if(item->args.icon) { + button.image = ui_cocoa_named_icon(item->args.icon); + } + + if(item->args.onclick) { + EventData *event = [[EventData alloc] init:item->args.onclick userdata:item->args.onclickdata]; + event.obj = obj; + button.target = event; + button.action = @selector(handleEvent:); + objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + return button; +} + +NSToolbarItem* ui_nstoolbaritem_create_toggle(UiObject *obj, UiToolbarToggleItem *item, NSString *identifier) { + UiToolbarToggleEventHandler *event = [[UiToolbarToggleEventHandler alloc]init]; + event.obj = obj; + event.callback = item->args.onchange; + event.userdata = item->args.onchangedata; + + NSToolbarItem *button = [[NSToolbarItem alloc] initWithItemIdentifier:identifier]; + if(item->args.label) { + NSString *label = [[NSString alloc] initWithUTF8String:item->args.label]; + button.paletteLabel = label; + button.label = label; + } + objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + + NSSegmentedControl *seg; + if(!item->args.icon) { + NSArray *labels = @[[[NSString alloc] initWithUTF8String:item->args.label]]; + seg = [NSSegmentedControl segmentedControlWithLabels:labels trackingMode:NSSegmentSwitchTrackingSelectAny target:event action:@selector(handleEvent:)]; + button.view = seg; + } else { + NSArray *images = @[ui_cocoa_named_icon(item->args.icon)]; + seg = [NSSegmentedControl segmentedControlWithImages:images trackingMode:NSSegmentSwitchTrackingSelectAny target:event action:@selector(handleEvent:)]; + } + button.view = seg; + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, item->args.varname, UI_VAR_INTEGER); + if(var) { + event.var = var; + UiInteger *i = var->value; + if(i->get) { + if(ui_get(i) != 0) { + [seg setEnabled:YES forSegment:0]; + } + + } + i->obj = (__bridge void*)seg; + printf("seg: %p\n", seg); + i->get = ui_toolbar_seg_toggleitem_get; + i->set = ui_toolbar_seg_toggleitem_set; + } + + return button; +} + +int64_t ui_toolbar_seg_toggleitem_get(UiInteger *i) { + NSSegmentedControl *seg = (__bridge NSSegmentedControl*)i->obj; + i->value = [seg isSelectedForSegment:0]; + return i->value; +} + +void ui_toolbar_seg_toggleitem_set(UiInteger *i, int64_t value) { + NSSegmentedControl *seg = (__bridge NSSegmentedControl*)i->obj; + i->value = value; + [seg setSelected:value != 0 forSegment:0]; +} + +NSToolbarItem* ui_nstoolbaritem_create_menu(UiObject *obj, UiToolbarMenuItem *item, NSString *identifier) { + NSMenuToolbarItem *button = [[NSMenuToolbarItem alloc] initWithItemIdentifier: identifier]; + button.bordered = YES; + + if(item->args.label) { + NSString *label = [[NSString alloc] initWithUTF8String:item->args.label]; + button.paletteLabel = label; + button.label = label; + } + if(item->args.icon) { + button.image = ui_cocoa_named_icon(item->args.icon); + } + + NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; + ui_add_menu_items(obj, menu, 0, &item->menu); + + button.menu = menu; + + return button; +} diff --git a/ui/cocoa/UiThread.h b/ui/cocoa/UiThread.h new file mode 100644 index 0000000..1c4120d --- /dev/null +++ b/ui/cocoa/UiThread.h @@ -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. + */ + +#import "toolkit.h" + +@interface UiThread : NSObject + +@property UiObject *obj; +@property ui_threadfunc job; +@property void *jobdata; +@property ui_callback finish_callback; +@property void *finish_userdata; + +- (id)init:(UiObject*)obj jobfunc:(ui_threadfunc)job jobdata:(void*)jobdata; + +- (void)start; +- (void) runJob:(id)n; +- (void) finish:(id)n; + +@end diff --git a/ui/cocoa/UiThread.m b/ui/cocoa/UiThread.m new file mode 100644 index 0000000..2916d2f --- /dev/null +++ b/ui/cocoa/UiThread.m @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#import "UiThread.h" + + +@implementation UiThread + +- (id) init:(UiObject*)obj jobfunc:(ui_threadfunc)job jobdata:(void*)jobdata { + _obj = obj; + _job = job; + _jobdata = jobdata; + return self; +} + +- (void) start { + [NSThread detachNewThreadSelector:@selector(runJob:) + toTarget:self + withObject:nil]; +} + + +- (void) runJob:(id)n { + int result = _job(_jobdata); + if(!result) { + [self performSelectorOnMainThread:@selector(finish:) + withObject:nil + waitUntilDone:NO]; + } +} + +- (void) finish:(id)n { + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.intval = 0; + _finish_callback(&event, _finish_userdata); +} + +@end + diff --git a/ui/cocoa/WindowManager.h b/ui/cocoa/WindowManager.h new file mode 100644 index 0000000..7f1b225 --- /dev/null +++ b/ui/cocoa/WindowManager.h @@ -0,0 +1,41 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "toolkit.h" + +@interface WindowManager : NSObject + +@property NSMutableArray *windows; + ++ (WindowManager*) sharedWindowManager; + +- (WindowManager*)init; + +- (void)addWindow:(NSWindow*)win; + +@end diff --git a/ui/cocoa/WindowManager.m b/ui/cocoa/WindowManager.m new file mode 100644 index 0000000..ca03182 --- /dev/null +++ b/ui/cocoa/WindowManager.m @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#import "WindowManager.h" + +@implementation WindowManager + +static WindowManager *instance = nil; + ++ (WindowManager*) sharedWindowManager { + if(instance == nil) { + instance = [[WindowManager alloc] init]; + } + return instance; +} + +- (WindowManager*)init { + _windows = [[NSMutableArray alloc] initWithCapacity:16]; + return self; +} + +- (void)addWindow:(NSWindow*)win { + [_windows addObject:win]; + [win setDelegate:self]; +} + +- (void) windowWillClose:(NSNotification *) notification { + NSWindow *window = notification.object; + [_windows removeObject:window]; +} + +@end diff --git a/ui/cocoa/appdelegate.h b/ui/cocoa/appdelegate.h new file mode 100644 index 0000000..7c2ce1e --- /dev/null +++ b/ui/cocoa/appdelegate.h @@ -0,0 +1,37 @@ +/* + * 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 "../ui/toolkit.h" +#include "../common/context.h" +#include "../common/object.h" + +@interface AppDelegate : NSObject + + +@end diff --git a/ui/cocoa/appdelegate.m b/ui/cocoa/appdelegate.m new file mode 100644 index 0000000..f0967ed --- /dev/null +++ b/ui/cocoa/appdelegate.m @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#import "AppDelegate.h" + +#import "toolkit.h" +#import "menu.h" + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + ui_menu_init(); + NSLog(@"toolkit applicationDidFinishLaunching"); + ui_cocoa_onstartup(); +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + ui_cocoa_onexit(); +} + + +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app { + return YES; +} + + +@end diff --git a/ui/cocoa/button.h b/ui/cocoa/button.h new file mode 100644 index 0000000..29534fa --- /dev/null +++ b/ui/cocoa/button.h @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#import "toolkit.h" + +#import "../ui/button.h" + +@interface UiRadioButton : NSButton + +@property UiVar *var; +@property Boolean direct_state; + +- (UiRadioButton*)init; +- (void)setState:(NSControlStateValue)state; + +@end + +@interface UiLinkButtonData : NSObject +@property UiObject *obj; +@property (weak) NSTextField *textfield; +@property (strong) NSString *label; +@property (strong) NSString *uri; +@property BOOL visited; +@property ui_callback onclick; +@property void *onclickdata; + +- (id)init:(UiObject*)obj textfield:(NSTextField*)textfield; +- (void)setLinkDataFromJson:(const char*)jsonStr; +- (void)buildLink; + +@end + + +int64_t ui_togglebutton_get(UiInteger *i); +void ui_togglebutton_set(UiInteger *i, int64_t value); + +int64_t ui_switch_get(UiInteger *i); +void ui_switch_set(UiInteger *i, int64_t value); + +int64_t ui_radiobuttons_get(UiInteger *i); +void ui_radiobuttons_set(UiInteger *i, int64_t value); + +char* ui_linkbutton_get(UiString *s); +void ui_linkbutton_set(UiString *s, const char *str); diff --git a/ui/cocoa/button.m b/ui/cocoa/button.m new file mode 100644 index 0000000..4a7f259 --- /dev/null +++ b/ui/cocoa/button.m @@ -0,0 +1,462 @@ +/* + * 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. + */ + +#import "button.h" +#import "EventData.h" +#import "Container.h" +#import + +#import +#import + +UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs *args) { + NSButton *button = [[NSButton alloc] init]; + button.translatesAutoresizingMaskIntoConstraints = NO; + if(args->label) { + NSString *label = [[NSString alloc] initWithUTF8String:args->label]; + button.title = label; + } + + if(args->onclick) { + EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata]; + event.obj = obj; + button.target = event; + button.action = @selector(handleEvent:); + objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, button, &layout); + + return (__bridge void*)button; +} + + +static void togglebutton_eventdata(id button, UiVar *var, void **eventdata, int *value) { + NSButton *btn = (NSButton*)button; + NSControlStateValue state = btn.state; + *value = (int)state; +} + +UIWIDGET togglebutton_create(UiObject* obj, UiToggleArgs *args, enum NSButtonType type) { + NSButton *button = [[NSButton alloc] init]; + [button setButtonType:type]; + //[button setAllowsMixedState:YES]; + + if(args->label) { + NSString *label = [[NSString alloc] initWithUTF8String:args->label]; + button.title = label; + } + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + if(var) { + UiInteger *i = var->value; + i->obj = (__bridge void*)button; + i->get = ui_togglebutton_get; + i->set = ui_togglebutton_set; + } + + if(args->onchange) { + EventData *event = [[EventData alloc] init:args->onchange userdata:args->onchangedata]; + event.get_eventdata = togglebutton_eventdata; + event.obj = obj; + event.var = var; + event.vartype = UI_VAR_INTEGER; + button.target = event; + button.action = @selector(handleEventWithEventData:); + objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, button, &layout); + + return (__bridge void*)button; +} + +int64_t ui_togglebutton_get(UiInteger *i) { + NSButton *button = (__bridge NSButton*)i->obj; + NSControlStateValue state = button.state; + i->value = (int64_t)state; + return (int64_t)state; +} + +void ui_togglebutton_set(UiInteger *i, int64_t value) { + NSButton *button = (__bridge NSButton*)i->obj; + NSControlStateValue state; + switch(value) { + case 0: state = NSControlStateValueOff; break; + case 1: state = NSControlStateValueOff; break; + default: state = NSControlStateValueMixed; + } + i->value = (int64_t)state; + button.state = state; +} + +UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs *args) { + return togglebutton_create(obj, args, NSButtonTypePushOnPushOff); +} + +UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args) { + return togglebutton_create(obj, args, NSButtonTypeSwitch); +} + +static void switch_eventdata(id button, UiVar *var, void **eventdata, int *value) { + NSSwitch *btn = (NSSwitch*)button; + NSControlStateValue state = btn.state; + *value = (int)state; +} + +UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { + NSSwitch *button = [[NSSwitch alloc] init]; + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + if(var) { + UiInteger *i = var->value; + i->obj = (__bridge void*)button; + i->get = ui_switch_get; + i->set = ui_switch_set; + } + + if(args->onchange) { + EventData *event = [[EventData alloc] init:args->onchange userdata:args->onchangedata]; + event.get_eventdata = switch_eventdata; + event.obj = obj; + event.var = var; + event.vartype = UI_VAR_INTEGER; + button.target = event; + button.action = @selector(handleEventWithEventData:); + objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, button, &layout); + + return (__bridge void*)button; +} + +int64_t ui_switch_get(UiInteger *i) { + NSSwitch *button = (__bridge NSSwitch*)i->obj; + NSControlStateValue state = button.state; + i->value = (int64_t)state; + return (int64_t)state; +} + +void ui_switch_set(UiInteger *i, int64_t value) { + NSSwitch *button = (__bridge NSSwitch*)i->obj; + NSControlStateValue state; + switch(value) { + case 0: state = NSControlStateValueOff; break; + case 1: state = NSControlStateValueOff; break; + default: state = NSControlStateValueMixed; + } + i->value = (int64_t)state; + button.state = state; +} + + +@implementation UiRadioButton + +- (UiRadioButton*)init { + self = [super init]; + _direct_state = NO; + [self setButtonType:NSButtonTypeRadio]; + return self; +} + +- (void)setState:(NSControlStateValue)state { + // NOOP +} + +@end + +static void radiobutton_eventdata(id button, UiVar *var, void **eventdata, int *value) { + if(var) { + UiInteger *value = var->value; + NSMutableArray *buttons = (__bridge NSMutableArray*)value->obj; + for(UiRadioButton *b in buttons) { + if(b != button) { + b.direct_state = YES; + [[b cell] setState:0]; + b.direct_state = NO; + } + } + } +} + +UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs *args) { + UiRadioButton *button = [[UiRadioButton alloc] init]; + + if(args->label) { + button.title = [[NSString alloc] initWithUTF8String:args->label]; + } + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + button.var = var; + NSMutableArray *buttons = nil; + if(var) { + UiInteger *i = var->value; + buttons = (__bridge NSMutableArray*)i->obj; + if(!buttons) { + buttons = [[NSMutableArray alloc] init]; + i->obj = (__bridge void*)buttons; + i->get = ui_radiobuttons_get; + i->set = ui_radiobuttons_set; + } + [buttons addObject:button]; + objc_setAssociatedObject(button, "radiogroup", buttons, OBJC_ASSOCIATION_RETAIN); + } + + if(args->onchange || var) { + EventData *event = [[EventData alloc] init:args->onchange userdata:args->onchangedata]; + event.get_eventdata = radiobutton_eventdata; + event.obj = obj; + event.var = var; + event.vartype = UI_VAR_INTEGER; + button.target = event; + + + button.action = @selector(handleEventWithEventData:); + + objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, button, &layout); + + return (__bridge void*)button; +} + +int64_t ui_radiobuttons_get(UiInteger *i) { + NSMutableArray *buttons = (__bridge NSMutableArray*)i->obj; + int64_t index = 0; + for(UiRadioButton *b in buttons) { + if([b cell].state != 0) { + i->value = index + 1; + break; + } + index++; + } + return i->value; +} + +void ui_radiobuttons_set(UiInteger *i, int64_t value) { + NSMutableArray *buttons = (__bridge NSMutableArray*)i->obj; + int64_t index = 1; + for(UiRadioButton *b in buttons) { + if(index == value) { + [b cell].state = NSControlStateValueOn; + } else { + [b cell].state = NSControlStateValueOff; + } + index++; + } +} + + +/* --------------------------- Link Button --------------------------- */ + +@implementation UiLinkButtonData + +- (id)init:(UiObject*)obj textfield:(NSTextField*)textfield { + _obj = obj; + _textfield = textfield; + return self; +} + +- (void)setLinkDataFromJson:(const char*)jsonStr { + CxJson json; + cxJsonInit(&json, NULL); + cxJsonFill(&json, jsonStr); + + CxJsonValue *value; + if(cxJsonNext(&json, &value) == CX_JSON_NO_ERROR) { + if(cxJsonIsObject(value)) { + CxJsonValue *label = cxJsonObjGet(value, "label"); + CxJsonValue *uri = cxJsonObjGet(value, "uri"); + CxJsonValue *visited = cxJsonObjGet(value, "visited"); + if(label) { + char *str = cxJsonIsString(label) ? cxJsonAsString(label) : NULL; + if(str) { + _label = [[NSString alloc]initWithUTF8String:str]; + } else { + _label = nil; + } + } + if(uri) { + char *str = cxJsonIsString(uri) ? cxJsonAsString(uri) : NULL; + if(str) { + _uri = [[NSString alloc]initWithUTF8String:str]; + } else { + _uri = nil; + } + } + if(visited) { + _visited = cxJsonIsBool(visited) ? cxJsonAsBool(visited) : FALSE; + } + } + cxJsonValueFree(value); + } + cxJsonDestroy(&json); + + [self buildLink]; +} + +- (void)buildLink { + NSString *label = _label ? _label : @""; + + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:label]; + [attrString beginEditing]; + if(_uri) { + [attrString addAttribute:NSLinkAttributeName value:_uri range:NSMakeRange(0, attrString.length)]; + } + [attrString addAttribute:NSForegroundColorAttributeName value:[NSColor systemBlueColor] range:NSMakeRange(0, attrString.length)]; + [attrString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:NSMakeRange(0, attrString.length)]; + [attrString endEditing]; + + [_textfield setAttributedStringValue:attrString]; +} + +@end + +static char* create_linkbutton_jsonvalue(const char *label, const char *uri, UiBool include_null, UiBool visited, UiBool set_visited) { + CxJsonValue *obj = cxJsonCreateObj(NULL); + if(label) { + cxJsonObjPutString(obj, CX_STR("label"), label); + } else if(include_null) { + cxJsonObjPutLiteral(obj, CX_STR("label"), CX_JSON_NULL); + } + + if(uri) { + cxJsonObjPutString(obj, CX_STR("uri"), uri); + } else if(include_null) { + cxJsonObjPutLiteral(obj, CX_STR("uri"), CX_JSON_NULL); + } + + if(set_visited) { + cxJsonObjPutLiteral(obj, CX_STR("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE); + } + + CxJsonWriter writer = cxJsonWriterCompact(); + CxBuffer buf; + cxBufferInit(&buf, NULL, 128, NULL, CX_BUFFER_AUTO_EXTEND); + cxJsonWrite(&buf, obj, (cx_write_func)cxBufferWrite, &writer); + cxJsonValueFree(obj); + cxBufferTerminate(&buf); + + return buf.space; +} + +UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args) { + NSTextField *label = [[NSTextField alloc] init]; + label.editable = NO; + label.bezeled = NO; + label.drawsBackground = NO; + label.allowsEditingTextAttributes = YES; + label.selectable = YES; + + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, label, &layout); + + UiLinkButtonData *data = [[UiLinkButtonData alloc]init:obj textfield:label]; + objc_setAssociatedObject(label, "linkdata", data, OBJC_ASSOCIATION_RETAIN); + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); + if(var) { + UiString *s = var->value; + s->obj = (__bridge void*)data; + s->get = ui_linkbutton_get; + s->set = ui_linkbutton_set; + + if(s->value.ptr) { + [data setLinkDataFromJson:s->value.ptr]; + } + } + + return (__bridge void*)label; +} + +char* ui_linkbutton_get(UiString *s) { + return NULL; // TODO +} + +void ui_linkbutton_set(UiString *s, const char *str) { + UiLinkButtonData *data = (__bridge UiLinkButtonData*)s->obj; + [data setLinkDataFromJson:str]; +} + + + +void ui_linkbutton_value_set(UiString *str, const char *label, const char *uri) { + char *value = create_linkbutton_jsonvalue(label, uri, TRUE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_label(UiString *str, const char *label) { + char *value = create_linkbutton_jsonvalue(label, NULL, FALSE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_uri(UiString *str, const char *uri) { + char *value = create_linkbutton_jsonvalue(NULL, uri, FALSE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_visited(UiString *str, UiBool visited) { + char *value = create_linkbutton_jsonvalue(NULL, NULL, FALSE, visited, TRUE); + ui_set(str, value); + free(value); +} + +// TODO + +void ui_linkbutton_set_label(UIWIDGET button, const char *label) { + +} + +void ui_linkbutton_set_uri(UIWIDGET button, const char *label) { + +} + +void ui_linkbutton_set_visited(UIWIDGET button, UiBool visited) { + +} + +char* ui_linkbutton_get_label(UIWIDGET button) { + return NULL; +} + +char* ui_linkbutton_get_uri(UIWIDGET button) { + return NULL; +} + +UiBool ui_linkbutton_get_visited(UIWIDGET button) { + return FALSE; +} diff --git a/ui/cocoa/container.h b/ui/cocoa/container.h new file mode 100644 index 0000000..ed2dc67 --- /dev/null +++ b/ui/cocoa/container.h @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#import "toolkit.h" + +#import "../ui/container.h" + + +typedef struct UiLayout UiLayout; + + +#define UI_INIT_LAYOUT(args) (UiLayout) {\ + .fill = args->fill, \ + .hexpand = args->hexpand, \ + .vexpand = args->vexpand, \ + .hfill = args->hfill, \ + .vfill = args->vfill, \ + .margin = args->margin, \ + .margin_left = args->margin_left, \ + .margin_right = args->margin_right, \ + .margin_top = args->margin_top, \ + .margin_bottom = args->margin_bottom, \ + .colspan = args->colspan, \ + .rowspan = args->rowspan } + + +@protocol Container + +@property UiContainerX *container; + +- (void) addView:(NSView*)view layout:(UiLayout*)layout; + +@end + + +@interface FrameContainer : NSBox + +- (id)init:(NSString*)title; + +@end + +@interface ScrollViewContainer : NSScrollView + +@property NSView *child; + +- (id)init:(UiSubContainerType)subContainer columnSpacing:(int)columnSpacing rowSpacing:(int)rowSpacing; + +@end + + +UiContainerX* ui_create_container(UiObject *obj, id container); + +void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout); diff --git a/ui/cocoa/container.m b/ui/cocoa/container.m new file mode 100644 index 0000000..d9b2e2d --- /dev/null +++ b/ui/cocoa/container.m @@ -0,0 +1,332 @@ +/* + * 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. + */ + +#import "Container.h" +#import "GridLayout.h" +#import "BoxContainer.h" +#import "TabView.h" + +/* -------------------- public container functions --------------------- */ + +static UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs *args, NSUserInterfaceLayoutOrientation orientation) { + BoxContainer *box = [[BoxContainer alloc] init:orientation spacing:args->spacing]; + box.translatesAutoresizingMaskIntoConstraints = false; + UiContainerX *container = ui_create_container(obj, box); + + // add box to the parent + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, box, &layout); + + // add new box to the obj container chain + uic_object_push_container(obj, container); + + return (__bridge void*)box; +} + +UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs *args) { + return ui_box_create(obj, args, NSUserInterfaceLayoutOrientationVertical); +} + +UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs *args) { + return ui_box_create(obj, args, NSUserInterfaceLayoutOrientationHorizontal); +} + +UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { + GridLayout *grid = [[GridLayout alloc] init]; + grid.translatesAutoresizingMaskIntoConstraints = false; + grid.columnspacing = args->columnspacing; + grid.rowspacing = args->rowspacing; + UiContainerX *container = ui_create_container(obj, grid); + grid.container = container; + + // add box to the parent + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, grid, &layout); + + // add new box to the obj container chain + uic_object_push_container(obj, container); + + return (__bridge void*)grid; +} + +UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) { + NSString *title = args->label ? [[NSString alloc]initWithUTF8String:args->label] : nil; + FrameContainer *frame = [[FrameContainer alloc] init:title]; + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, frame, &layout); + + // add container to the chain + UiContainerX *container; + UiLayout subLayout = {0}; + switch(args->subcontainer) { + default: { + // UI_CONTAINER_NO_SUB + container = ui_create_container(obj, frame); + break; + } + case UI_CONTAINER_VBOX: { + BoxContainer *box = [[BoxContainer alloc]init:NSUserInterfaceLayoutOrientationVertical spacing:args->spacing]; + box.translatesAutoresizingMaskIntoConstraints = false; + [frame addView:box layout:&subLayout]; + container = ui_create_container(obj, box); + break; + } + case UI_CONTAINER_HBOX: { + BoxContainer *box = [[BoxContainer alloc]init:NSUserInterfaceLayoutOrientationHorizontal spacing:args->spacing]; + box.translatesAutoresizingMaskIntoConstraints = false; + [frame addView:box layout:&subLayout]; + container = ui_create_container(obj, box); + break; + } + case UI_CONTAINER_GRID: { + GridLayout *grid = [[GridLayout alloc] init]; + grid.translatesAutoresizingMaskIntoConstraints = false; + grid.columnspacing = args->columnspacing; + grid.rowspacing = args->rowspacing; + [frame addView:grid layout:&subLayout]; + container = ui_create_container(obj, grid); + break; + } + } + + uic_object_push_container(obj, container); + + return (__bridge void*)frame; +} + +UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs *args) { + return ui_frame_create(obj, args); // TODO +} + +UIWIDGET ui_scrolledwindow_create(UiObject *obj, UiFrameArgs *args) { + int colspacing = args->spacing; + int rowspacing = args->spacing; + if(args->subcontainer == UI_CONTAINER_GRID) { + colspacing = args->columnspacing; + rowspacing = args->rowspacing; + } + ScrollViewContainer *scrollview = [[ScrollViewContainer alloc]init:args->subcontainer columnSpacing:colspacing rowSpacing:rowspacing]; + scrollview.hasVerticalScroller = YES; + scrollview.scrollerStyle = NSScrollerStyleOverlay; + scrollview.autohidesScrollers = YES; + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, scrollview, &layout); + + UiContainerX *container = ui_create_container(obj, scrollview); + uic_object_push_container(obj, container); + + return (__bridge void*)scrollview; +} + +UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs *args) { + NSView *tabview; + switch(args->tabview) { + default: tabview = [[UiTopTabView alloc]init:obj args:args]; break; + } + + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, tabview, &layout); + + UiContainerX *container = ui_create_container(obj, tabview); + uic_object_push_container(obj, container); + + return (__bridge void*)tabview; +} + +void ui_tab_create(UiObject *obj, const char* title) { + UiContainerX *ctn = obj->container_end; + id tabview = (__bridge id)ctn->container; + NSString *s = title ? [[NSString alloc]initWithUTF8String:title] : @""; + NSView *sub = [tabview createTab:-1 title:s]; + + UiContainerX *container = ui_create_container(obj, sub); + uic_object_push_container(obj, container); +} + +void ui_tabview_select(UIWIDGET tabview, int tab) { + id tabv = (__bridge id)tabview; + [tabv selectTab:tab]; +} + +void ui_tabview_remove(UIWIDGET tabview, int tab) { + id tabv = (__bridge id)tabview; + [tabv removeTab:tab]; +} + +UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) { + id tabv = (__bridge id)tabview; + NSString *s = name ? [[NSString alloc]initWithUTF8String:name] : @""; + return [tabv addTab:tab_index title:s]; +} + + +UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs *args) { + return NULL; // TODO +} + +UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs *args) { + return NULL; // TODO +} + +UIWIDGET ui_hsplitpane_create(UiObject *obj, UiSplitPaneArgs *args) { + return NULL; // TODO +} + +UIWIDGET ui_vsplitpane_create(UiObject *obj, UiSplitPaneArgs *args) { + return NULL; // TODO +} + + + +void ui_container_begin_close(UiObject *obj) { + UiContainerX *ct = obj->container_end; + ct->close = 1; +} + +int ui_container_finish(UiObject *obj) { + UiContainerX *ct = obj->container_end; + if(ct->close) { + ui_end_new(obj); + return 0; + } + return 1; +} + +/* -------------------------- Frame Container -------------------------- */ + +@implementation FrameContainer + +@synthesize container = _container; + +- (id)init:(NSString*)title { + self = [super init]; + self.title = title; + self.boxType = NSBoxPrimary; + if(title != nil) { + self.titlePosition = NSAtTop; + } + return self; +} + +- (void) addView:(NSView*)view layout:(UiLayout*)layout { + [self.contentView addSubview:view]; + view.translatesAutoresizingMaskIntoConstraints = NO; + [NSLayoutConstraint activateConstraints:@[ + [view.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:0], + [view.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:0], + [view.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-0], + [view.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-0] + ]]; +} + +@end + + +/* -------------------------- Expander Container -------------------------- */ + +// TODO + + +/* ------------------------ ScrollView Container ------------------------ */ + +@implementation ScrollViewContainer + +@synthesize container = _container; + +- (id)init:(UiSubContainerType)subContainer columnSpacing:(int)columnSpacing rowSpacing:(int)rowSpacing { + self = [super init]; + + if(subContainer != UI_CONTAINER_NO_SUB) { + GridLayout *child; + switch(subContainer) { + default: + case UI_CONTAINER_VBOX: { + child = [[BoxContainer alloc]init:NSUserInterfaceLayoutOrientationVertical spacing:columnSpacing]; + break; + } + case UI_CONTAINER_HBOX: { + child = [[BoxContainer alloc]init:NSUserInterfaceLayoutOrientationHorizontal spacing:columnSpacing]; + break; + } + case UI_CONTAINER_GRID: { + child = [[GridLayout alloc]init]; + child.columnspacing = columnSpacing; + child.rowspacing = rowSpacing; + break; + } + } + child.translatesAutoresizingMaskIntoConstraints = NO; + + self.documentView = child; + [child.widthAnchor constraintEqualToAnchor:self.contentView.widthAnchor].active = YES; + + _child = child; + } + + + return self; +} + +- (void) addView:(NSView*)view layout:(UiLayout*)layout { + if(_child != nil) { + _child.container = self.container; // required, otherwise child has no container and can't access the newline property + view.translatesAutoresizingMaskIntoConstraints = NO; + [_child addView:view layout:layout]; + } else { + self.documentView = view; + [view.widthAnchor constraintEqualToAnchor:self.contentView.widthAnchor].active = YES; + } +} + +@end + +/* ------------------------- private functions ------------------------- */ + +UiContainerX* ui_create_container(UiObject *obj, id container) { + UiContainerX *ctn = ui_malloc(obj->ctx, sizeof(UiContainerX)); + ctn->container = (__bridge void*)container; + ctn->close = 0; + ctn->prev = NULL; + ctn->next = NULL; + container.container = ctn; + return ctn; +} + +void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout) { + UiContainerX *ctn = obj->container_end; + id container = (__bridge id)ctn->container; + UiLayout adjustedLayout = *layout; + if(adjustedLayout.margin > 0) { + adjustedLayout.margin_left = adjustedLayout.margin; + adjustedLayout.margin_right = adjustedLayout.margin; + adjustedLayout.margin_top = adjustedLayout.margin; + adjustedLayout.margin_bottom = adjustedLayout.margin; + } + [container addView:view layout:&adjustedLayout]; +} + diff --git a/ui/cocoa/entry.h b/ui/cocoa/entry.h new file mode 100644 index 0000000..b0cb2fd --- /dev/null +++ b/ui/cocoa/entry.h @@ -0,0 +1,61 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "Container.h" + +#import "../ui/entry.h" + +@interface UiSpinBox : NSObject + +@property UiObject *obj; +@property (weak) NSTextField *textfield; +@property (weak) NSStepper *stepper; +@property BOOL isInteger; +@property ui_callback onchange; +@property void* onchangedata; +@property UiObserver **observers; + +- (UiSpinBox*)init; + +- (void)valueChanged; +- (void)stepperChanged:(id)sender; +- (void) controlTextDidChange:(NSNotification *) obj; + +@end + +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); diff --git a/ui/cocoa/entry.m b/ui/cocoa/entry.m new file mode 100644 index 0000000..9bccc06 --- /dev/null +++ b/ui/cocoa/entry.m @@ -0,0 +1,272 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "entry.h" +#import + +@implementation UiSpinBox + +- (UiSpinBox*)init { + return self; +} + +- (void)valueChanged { + float value = _stepper.doubleValue; + UiEvent e; + e.obj = _obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = NULL; + e.eventdatatype = 0; + e.intval = (int)value; + e.set = ui_get_setop(); + + if(_onchange) { + _onchange(&e, _onchangedata); + } + + if(_observers) { + UiObserver *observer = *_observers; + ui_notify_evt(observer, &e); + } +} + +- (void)stepperChanged:(id)sender { + if(_isInteger) { + _textfield.integerValue = _stepper.integerValue; + } else { + _textfield.doubleValue = _stepper.doubleValue; + } + [self valueChanged]; +} + +- (void) controlTextDidChange:(NSNotification *)obj { + if(_isInteger) { + _stepper.integerValue = _textfield.integerValue; + } else { + _stepper.doubleValue = _textfield.doubleValue; + } + [self valueChanged]; +} + +@end + +UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { + double min = args->min; + double max = args->max != 0 ? args->max : 1000; + + UiVar *var = NULL; + UiVarType vartype = 0; + if(args->varname) { + var = uic_get_var(obj->ctx, args->varname); + if(var) { + vartype = var->type; + } else { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, args->varname, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(!var) { + if(args->intvalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER); + vartype = UI_VAR_INTEGER; + } else if(args->doublevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE); + vartype = UI_VAR_DOUBLE; + } else if(args->rangevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(vartype == UI_VAR_RANGE) { + UiRange *r = var->value; + min = r->min; + max = r->max; + } + if(args->step == 0) { + args->step = 1; + } + + // create and setup textfield for number input + NSTextField *textfield = [[NSTextField alloc] init]; + textfield.translatesAutoresizingMaskIntoConstraints = NO; + + if(!args->hfill || args->width > 0) { + textfield.translatesAutoresizingMaskIntoConstraints = NO; + int width = args->width > 0 ? args->width : 100; + [[textfield.widthAnchor constraintEqualToConstant:width] setActive:YES]; + } + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + formatter.numberStyle = NSNumberFormatterDecimalStyle; + formatter.allowsFloats = vartype != UI_VAR_INTEGER; + formatter.minimumFractionDigits = args->digits; + formatter.maximumFractionDigits = args->digits; + + textfield.formatter = formatter; + + // create view containing the textfield and stepper + NSView *view = [[NSView alloc]init]; + view.translatesAutoresizingMaskIntoConstraints = NO; + + NSStepper *stepper = [[NSStepper alloc] init]; + stepper.translatesAutoresizingMaskIntoConstraints = NO; + + [view addSubview:textfield]; + [view addSubview:stepper]; + + [NSLayoutConstraint activateConstraints:@[ + [textfield.leadingAnchor constraintEqualToAnchor:view.leadingAnchor], + [textfield.topAnchor constraintEqualToAnchor:view.topAnchor], + [textfield.bottomAnchor constraintEqualToAnchor:view.bottomAnchor], + + [stepper.trailingAnchor constraintEqualToAnchor:view.trailingAnchor], + [stepper.topAnchor constraintEqualToAnchor:view.topAnchor], + [stepper.bottomAnchor constraintEqualToAnchor:view.bottomAnchor], + + [textfield.trailingAnchor constraintEqualToAnchor:stepper.leadingAnchor] + ]]; + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, view, &layout); + + // create the spinbox object, that handles all textfield and stepper events + UiSpinBox *spinbox = [[UiSpinBox alloc]init]; + spinbox.obj = obj; + spinbox.textfield = textfield; + spinbox.stepper = stepper; + spinbox.onchange = args->onchange; + spinbox.onchangedata = args->onchangedata; + spinbox.isInteger = vartype == UI_VAR_INTEGER; + objc_setAssociatedObject(stepper, "ui_spinbox", spinbox, OBJC_ASSOCIATION_RETAIN); + + stepper.minValue = min; + stepper.maxValue = max; + stepper.increment = args->step; + stepper.target = spinbox; + stepper.action = @selector(stepperChanged:); + textfield.delegate = spinbox; + + UiObserver **obs = NULL; + if(var) { + void *varObj = (__bridge void*)spinbox; + switch(vartype) { + default: break; + case UI_VAR_INTEGER: { + UiInteger *i = var->value; + i->get = ui_spinbutton_getint; + i->set = ui_spinbutton_setint; + i->obj = varObj; + obs = &i->observers; + + stepper.integerValue = i->value; + textfield.integerValue = i->value; + break; + } + case UI_VAR_DOUBLE: { + UiDouble *d = var->value; + d->get = ui_spinbutton_getdouble; + d->set = ui_spinbutton_setdouble; + d->obj = varObj; + obs = &d->observers; + + stepper.doubleValue = d->value; + textfield.doubleValue = d->value; + 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 = varObj; + obs = &r->observers; + + stepper.doubleValue = r->value; + textfield.doubleValue = r->value; + break; + } + } + } + spinbox.observers = obs; + + return (__bridge void*)textfield; +} + +int64_t ui_spinbutton_getint(UiInteger *i) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)i->obj; + i->value = spinbox.stepper.integerValue; + return i->value; +} + +void ui_spinbutton_setint(UiInteger *i, int64_t val) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)i->obj; + i->value = val; + spinbox.stepper.integerValue = val; + spinbox.textfield.integerValue = val; +} + +double ui_spinbutton_getdouble(UiDouble *d) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)d->obj; + d->value = spinbox.stepper.doubleValue; + return d->value; +} + +void ui_spinbutton_setdouble(UiDouble *d, double val) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)d->obj; + d->value = val; + spinbox.stepper.doubleValue = val; + spinbox.textfield.doubleValue = val; +} + +double ui_spinbutton_getrangeval(UiRange *r) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; + r->value = spinbox.stepper.doubleValue; + return r->value; +} + +void ui_spinbutton_setrangeval(UiRange *r, double val) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; + r->value = val; + spinbox.stepper.doubleValue = val; + spinbox.textfield.doubleValue = val; +} + +void ui_spinbutton_setrange(UiRange *r, double min, double max) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; + spinbox.stepper.minValue = min; + spinbox.stepper.maxValue = max; +} + +void ui_spinbutton_setextent(UiRange *r, double extent) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; + spinbox.stepper.increment = extent; +} diff --git a/ui/cocoa/image.h b/ui/cocoa/image.h new file mode 100644 index 0000000..2e1a05b --- /dev/null +++ b/ui/cocoa/image.h @@ -0,0 +1,35 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/image.h" + +#include "Container.h" + +void ui_icon_init(void); + +NSImage* ui_cocoa_named_icon(const char *name); diff --git a/ui/cocoa/image.m b/ui/cocoa/image.m new file mode 100644 index 0000000..e00c39a --- /dev/null +++ b/ui/cocoa/image.m @@ -0,0 +1,67 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "image.h" + +static NSDictionary *standardIconNames; + +void ui_icon_init(void) { + standardIconNames = @{ + @"NSImageNameActionTemplate": NSImageNameActionTemplate, + @"NSImageNameAddTemplate": NSImageNameAddTemplate, + @"NSImageNameAdvanced": NSImageNameAdvanced, + @"NSImageNameApplicationIcon": NSImageNameApplicationIcon, + @"NSImageNameBluetoothTemplate": NSImageNameBluetoothTemplate, + @"NSImageNameBonjour": NSImageNameBonjour, + @"NSImageNameBookmarksTemplate": NSImageNameBookmarksTemplate, + @"NSImageNameCaution": NSImageNameCaution, + // TODO + @"NSImageNameRefreshTemplate": NSImageNameRefreshTemplate, + @"NSImageNameFolder": NSImageNameFolder, + @"NSImageNameGoForwardTemplate": NSImageNameGoForwardTemplate, + @"NSImageNameGoBackTemplate": NSImageNameGoBackTemplate + }; +} + +NSImage* ui_cocoa_named_icon(const char *name) { + NSString *imageName = [[NSString alloc] initWithUTF8String:name]; + NSString *imgName = [standardIconNames objectForKey:imageName]; + if(imgName) { + imageName = imgName; + } + return [NSImage imageNamed:imageName]; +} + + +void ui_image_ref(UIIMAGE img) { + // TODO +} + +void ui_image_unref(UIIMAGE img) { + // TODO +} diff --git a/ui/cocoa/label.h b/ui/cocoa/label.h new file mode 100644 index 0000000..bb7e97c --- /dev/null +++ b/ui/cocoa/label.h @@ -0,0 +1,33 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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/display.h" + +char* ui_label_get(UiString *s); +void ui_label_set(UiString *s, const char *str); diff --git a/ui/cocoa/label.m b/ui/cocoa/label.m new file mode 100644 index 0000000..4e75e01 --- /dev/null +++ b/ui/cocoa/label.m @@ -0,0 +1,104 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "label.h" +#import "Container.h" + +#import + +static UIWIDGET create_label(UiObject *obj, UiLabelArgs *args) { + NSTextField *label = [[NSTextField alloc] init]; + label.editable = NO; + label.bezeled = NO; + label.drawsBackground = NO; + label.selectable = NO; + + NSTextAlignment alignment; + switch(args->align) { + case UI_ALIGN_LEFT: alignment = NSTextAlignmentLeft; break; + case UI_ALIGN_RIGHT: alignment = NSTextAlignmentRight; break; + default: alignment = NSTextAlignmentCenter; + } + label.alignment = alignment; + + if(args->label) { + NSString *str = [[NSString alloc] initWithUTF8String:args->label]; + label.stringValue = str; + } + + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, label, &layout); + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); + if(var) { + UiString *s = var->value; + s->obj = (__bridge void*)label; + s->get = ui_label_get; + s->set = ui_label_set; + + if(s->value.ptr) { + label.stringValue = [[NSString alloc]initWithUTF8String:s->value.ptr]; + } + } + + return (__bridge void*)label; +} + + +UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs *args) { + return create_label(obj, args); +} + +UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs *args) { + args->align = UI_ALIGN_LEFT; + return create_label(obj, args); +} + +UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs *args) { + args->align = UI_ALIGN_RIGHT; + return create_label(obj, args); +} + + +char* ui_label_get(UiString *s) { + NSTextField *label = (__bridge NSTextField*)s->obj; + if(s->value.free) { + s->value.free(s->value.ptr); + } + s->value.ptr = strdup(label.stringValue.UTF8String); + return s->value.ptr; +} + +void ui_label_set(UiString *s, const char *str) { + NSTextField *label = (__bridge NSTextField*)s->obj; + if(s->value.free) { + s->value.free(s->value.ptr); + } + s->value.ptr = NULL; + label.stringValue = [[NSString alloc] initWithUTF8String:str]; +} diff --git a/ui/cocoa/list.h b/ui/cocoa/list.h new file mode 100644 index 0000000..c498de5 --- /dev/null +++ b/ui/cocoa/list.h @@ -0,0 +1,121 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "Container.h" +#import "../ui/tree.h" + +#import "ListDataSource.h" + +@interface UiDropDown : NSObject + +@property UiObject *obj; +@property ui_callback onactivate; +@property void *onactivatedata; +@property ui_callback onselection; +@property void *onselectiondata; +@property ui_getvaluefunc2 getvalue; +@property void *getvaluedata; +@property UiVar *var; +@property (weak) NSComboBox *combobox; + +- (id)init:(UiObject*)obj; + +@end + +void ui_tableview_update(UiList *list, int i); +UiListSelection ui_tableview_getselection(UiList *list); +void ui_tableview_setselection(UiList *list, UiListSelection selection); + +void ui_dropdown_update(UiList *list, int i); +UiListSelection ui_dropdown_getselection(UiList *list); +void ui_dropdown_setselection(UiList *list, UiListSelection selection); + +@class UiSourceList; + +@interface UiSourceListItem : NSObject +@property (weak) UiSourceList *sourcelist; +@property (weak) UiSourceListItem *parent; +@property (strong) NSString *label; +@property (strong) NSString *badge; + +@property (strong) NSMutableArray *items; +@property UiVar *var; +@property UiSubList *sublist; + +@property int sublistIndex; +@property int sublistStartRow; +@property int rownum; + +@property void *eventdata; + +/* + * Initialize a section item + */ +- (id)init:(UiSubListItem*)item parent:(UiSourceListItem*)parent; +/* + * Initialize a child item + */ +- (id)init:(UiSourceList*)sourcelist sublist:(UiSubList*)sublist; +- (BOOL)isSection; +- (void)update:(int)row; + +@end + + +@interface UiSourceList : NSObject + +@property UiObject *obj; +@property (weak) NSOutlineView *outlineView; +@property CxList *sublists; +@property UiVar *dynamic_sublists; +@property ui_sublist_getvalue_func getvalue; +@property void *getvaluedata; +@property ui_callback onactivate; +@property void *onactivatedata; +@property ui_callback onbuttonclick; +@property void *onbuttonclickdata; + +@property (strong) NSMutableArray *sections; + +- (id)init:(UiObject*)obj outline:(NSOutlineView*)view; + +- (void)update:(int)row; + +@end + +@interface UiSourceListRow : NSTableRowView + +@property NSTrackingArea *trackingArea; +@property NSView *disclosureButton; +@property BOOL hover; +@property BOOL showDisclosureButton; + +@end + +void ui_sourcelist_update(UiList *list, int row); diff --git a/ui/cocoa/list.m b/ui/cocoa/list.m new file mode 100644 index 0000000..e776eeb --- /dev/null +++ b/ui/cocoa/list.m @@ -0,0 +1,819 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "list.h" +#import "ListDelegate.h" +#import + +#import +#import + +#import + +static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata; + return getvalue(elm, col); +} + +static void* str_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + return elm; +} + +/* --------------------------- ListView --------------------------- */ + +/* + * adds a NSTableViewDelegate that handles all events and calls + * callbacks specified in the UiListArgs + */ +static void add_listdelegate(UiObject *obj, NSTableView *tableview, UiListArgs *args) { + ListDelegate *delegate = [[ListDelegate alloc] init:tableview obj:obj]; + delegate.onactivate = args->onactivate; + delegate.onactivatedata = args->onactivatedata; + delegate.onselection = args->onselection; + delegate.onselectiondata = args->onselectiondata; + tableview.delegate = delegate; + objc_setAssociatedObject(tableview, "ui_listdelegate", delegate, OBJC_ASSOCIATION_RETAIN); + tableview.doubleAction = @selector(activateEvent:); + tableview.target = delegate; +} + +static void bind_list_to_tableview(UiList *list, NSTableView *tableview) { + list->obj = (__bridge void*)tableview; + list->update = ui_tableview_update; + list->getselection = ui_tableview_getselection; + list->setselection = ui_tableview_setselection; +} + +UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { + NSScrollView *scrollview = [[NSScrollView alloc] init]; + + NSTableView *tableview = [[NSTableView alloc] init]; + tableview.autoresizingMask = NSViewWidthSizable; + tableview.headerView = nil; + + if(args->multiselection) { + tableview.allowsMultipleSelection = YES; + } + + scrollview.documentView = tableview; + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, scrollview, &layout); + + add_listdelegate(obj, tableview, args); + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + if(var) { + UiList *list = var->value; + bind_list_to_tableview(list, tableview); + + ui_getvaluefunc2 getvalue = args->getvalue2; + void *getvaluedata = args->getvalue2data; + if(!getvalue) { + if(args->getvalue) { + getvalue = getvalue_wrapper; + getvaluedata = (void*)args->getvalue; + } else { + getvalue = str_getvalue; // by default list values are interpreted as strings + } + } + + NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"column"]; + [tableview addTableColumn:column]; + + ListDataSource *dataSource = [[ListDataSource alloc] init:tableview.tableColumns var:var getvalue:getvalue getvaluedata:getvaluedata]; + + tableview.dataSource = dataSource; + [tableview reloadData]; + + objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN); + } + + return (__bridge void*)scrollview; +} + +/* --------------------------- TableView --------------------------- */ + +UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) { + NSScrollView *scrollview = [[NSScrollView alloc] init]; + + NSTableView *tableview = [[NSTableView alloc] init]; + tableview.autoresizingMask = NSViewWidthSizable; + tableview.columnAutoresizingStyle = NSTableViewSequentialColumnAutoresizingStyle; + + if(args->multiselection) { + tableview.allowsMultipleSelection = YES; + } + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, scrollview, &layout); + + add_listdelegate(obj, tableview, args); + + // convert model + NSMutableArray *cols = [[NSMutableArray alloc] init]; + UiModel *model = args->model; + if(model) { + for(int i=0;icolumns;i++) { + char *title = model->titles[i]; + UiModelType type = model->types[i]; + int width = model->columnsize[i]; + NSString *identifier = [[NSString alloc] initWithUTF8String:title]; + NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:identifier]; + column.title = identifier; + column.resizingMask = NSTableColumnUserResizingMask; + if(width > 0) { + column.width = width; + } else if(width < 0) { + column.resizingMask = NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask; + } + if(type >= UI_ICON) { + // TODO + } + [tableview addTableColumn:column]; + [cols addObject:column]; + } + } + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + if(var) { + UiList *list = var->value; + bind_list_to_tableview(list, tableview); + + ui_getvaluefunc2 getvalue = args->getvalue2; + void *getvaluedata = args->getvalue2data; + if(!getvalue) { + if(args->getvalue) { + getvalue = getvalue_wrapper; + getvaluedata = (void*)args->getvalue; + } else { + fprintf(stderr, "Error: tableview requires getvalue or getvalue2 func\n"); + return (__bridge void*)scrollview; + } + } + + ListDataSource *dataSource = [[ListDataSource alloc] init:cols var:var getvalue:getvalue getvaluedata:getvaluedata]; + if(model) { + dataSource.model = ui_model_copy(obj->ctx, model); + } + + tableview.dataSource = dataSource; + [tableview reloadData]; + + objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN); + } + + scrollview.documentView = tableview; + + return (__bridge void*)scrollview; +} + +/* ------ common functions ------ */ + +void ui_tableview_update(UiList *list, int i) { + NSTableView *tableview = (__bridge NSTableView*)list->obj; + if(i < 0) { + [tableview reloadData]; + } else { + [tableview reloadData]; // TODO: optimize + } +} + +UiListSelection ui_tableview_getselection(UiList *list) { + NSTableView *tableview = (__bridge NSTableView*)list->obj; + return ui_tableview_selection(tableview); +} + +void ui_tableview_setselection(UiList *list, UiListSelection selection) { + NSTableView *tableview = (__bridge NSTableView*)list->obj; + NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet]; + for(int i=0;ivalue; + if(index >= 0) { + eventdata = list->get(list, index); + } + } else { + NSString *str = _combobox.objectValueOfSelectedItem; + if(str) { + eventdata = (void*)str.UTF8String; + } + } + + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = eventdata; + event.eventdatatype = UI_EVENT_DATA_LIST_ELM; + event.intval = index; + + if(_onselection) { + _onselection(&event, _onselectiondata); + } + + if(_onactivate) { + _onactivate(&event, _onactivatedata); + } +} + +@end + +UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { + NSComboBox *dropdown = [[NSComboBox alloc] init]; + dropdown.editable = NO; + + UiDropDown *uidropdown = [[UiDropDown alloc] init:obj]; + objc_setAssociatedObject(dropdown, "ui_dropdown", uidropdown, OBJC_ASSOCIATION_RETAIN); + uidropdown.onactivate = args->onactivate; + uidropdown.onactivatedata = args->onactivatedata; + uidropdown.onselection = args->onselection; + uidropdown.onselectiondata = args->onselectiondata; + uidropdown.combobox = dropdown; + + if(!args->getvalue2) { + if(args->getvalue) { + args->getvalue2 = getvalue_wrapper; + args->getvalue2data = (void*)args->getvalue; + } else { + args->getvalue2 = str_getvalue; + } + } + uidropdown.getvalue = args->getvalue2; + uidropdown.getvaluedata = args->getvalue2data; + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, dropdown, &layout); + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + if(var) { + UiList *list = var->value; + list->obj = (__bridge void*)dropdown; + list->update = ui_dropdown_update; + list->getselection = ui_dropdown_getselection; + list->setselection = ui_dropdown_setselection; + ui_dropdown_update(list, -1); + } else { + for(int i=0;istatic_nelm;i++) { + char *str = args->static_elements[i]; + NSString *item = [[NSString alloc] initWithUTF8String:str]; + [dropdown addItemWithObjectValue:item]; + } + } + + uidropdown.var = var; + + return (__bridge void*)dropdown; +} + +void ui_dropdown_update(UiList *list, int i) { + NSComboBox *combobox = (__bridge NSComboBox*)list->obj; + UiDropDown *dropdown = objc_getAssociatedObject(combobox, "ui_dropdown"); + if(dropdown) { + [combobox removeAllItems]; + + ui_getvaluefunc2 getvalue = dropdown.getvalue; + void *getvaluedata = dropdown.getvaluedata; + + int index = 0; + void *elm = list->first(list); + while(elm) { + UiBool freeResult = FALSE; + char *str = getvalue(list, elm, index, 0, getvaluedata, &freeResult); + if(str) { + NSString *item = [[NSString alloc] initWithUTF8String:str]; + [combobox addItemWithObjectValue:item]; + } + if(freeResult) { + free(str); + } + elm = list->next(list); + index++; + } + } else { + fprintf(stderr, "Error: obj is not a dropdown\n"); + } +} + +UiListSelection ui_dropdown_getselection(UiList *list) { + UiListSelection sel = { 0, NULL }; + NSComboBox *combobox = (__bridge NSComboBox*)list->obj; + NSInteger index = combobox.indexOfSelectedItem; + if(index >= 0) { + sel.rows = malloc(sizeof(int)); + sel.count = 1; + sel.rows[0] = (int)index; + } + return sel; +} + +void ui_dropdown_setselection(UiList *list, UiListSelection selection) { + NSComboBox *combobox = (__bridge NSComboBox*)list->obj; + if(selection.count > 0) { + [combobox selectItemAtIndex:selection.rows[0]]; + } else { + [combobox selectItemAtIndex: -1]; + } +} + + +/* --------------------------- SourceList --------------------------- */ + +static ui_sourcelist_update_func sclist_update_callback = NULL; + +void ui_sourcelist_set_update_callback(ui_sourcelist_update_func cb) { + sclist_update_callback = cb; +} + +void ui_sourcelist_updated(void) { + if(sclist_update_callback) { + sclist_update_callback(); + } +} + +static void sublist_free(const CxAllocator *a, UiSubList *sl) { + cxFree(a, (char*)sl->varname); + cxFree(a, (char*)sl->header); +} + +static UiSubList copy_sublist(const CxAllocator *a, UiSubList *sl) { + UiSubList new_sl; + new_sl.value = sl->value; + new_sl.varname = sl->varname ? cx_strdup_a(a, cx_str(sl->varname)).ptr : NULL; + new_sl.header = sl->header ? cx_strdup_a(a, cx_str(sl->header)).ptr : NULL; + new_sl.separator = sl->separator; + new_sl.userdata = sl->userdata; + return new_sl; +} + +static CxList* copy_sublists(const CxAllocator *a, UiSourceListArgs *args) { + if(args->sublists) { + size_t max = args->numsublists; + if(max == 0) { + max = INT_MAX; + } + + CxList *sublists = cxArrayListCreate(a, NULL, sizeof(UiSubList), args->numsublists); + sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_free; + + for(int i=0;isublists[i]; + if(sl->value == NULL && sl->varname == NULL) { + break; + } + + UiSubList new_sl = copy_sublist(a, sl); + cxListAdd(sublists, &new_sl); + } + + return sublists; + } + return NULL; +} + +UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) { + // create views + NSScrollView *scrollview = [[NSScrollView alloc] init]; + scrollview.autoresizingMask = NSViewWidthSizable; + scrollview.hasVerticalScroller = YES; + scrollview.hasHorizontalScroller = NO; + scrollview.autohidesScrollers = YES; + + NSOutlineView *outline = [[NSOutlineView alloc]init]; + NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"x"]; + [outline addTableColumn:column]; + outline.outlineTableColumn = column; + outline.headerView = NULL; + outline.rowSizeStyle = NSTableViewRowSizeStyleDefault; + outline.usesAutomaticRowHeights = YES; + outline.indentationPerLevel = 0; + + outline.style = NSTableViewStyleSourceList; + + // Make background transparent so vibrancy shows through + scrollview.drawsBackground = NO; + + scrollview.documentView = outline; + + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, scrollview, &layout); + + // datasource and delegate + UiSourceList *data = [[UiSourceList alloc] init:obj outline:outline]; + data.sublists = copy_sublists(obj->ctx->allocator, args); + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->dynamic_sublist, args->varname, UI_VAR_LIST); + if(var) { + UiList *list = var->value; + list->obj = (__bridge void*)data; + list->update = ui_sourcelist_update; + } + data.dynamic_sublists = var; + data.getvalue = args->getvalue; + data.getvaluedata = args->getvaluedata; + data.onactivate = args->onactivate; + data.onactivatedata = args->onactivatedata; + data.onbuttonclick = args->onbuttonclick; + data.onactivatedata = args->onbuttonclickdata; + [data update:-1]; + + outline.dataSource = data; + outline.delegate = data; + + [data update:-1]; + + objc_setAssociatedObject(outline, "ui_datasource", data, OBJC_ASSOCIATION_RETAIN); + + return (__bridge void*)scrollview; +} + +void ui_sourcelist_update(UiList *list, int row) { + UiSourceList *sourcelist = (__bridge UiSourceList*)list->obj; + [sourcelist update:row]; +} + + +/* + * Data Source and Delegate for the sourcelist NSOutlineView + */ +@implementation UiSourceList + +- (id)init:(UiObject*)obj outline:(NSOutlineView*)view { + _obj = obj; + _outlineView = view; + _sections = [[NSMutableArray alloc] initWithCapacity:16]; + return self; +} + +- (void)dealloc { + cxListFree(_sublists); +} + +- (void)update:(int)row { + // TODO: check row + + [_sections removeAllObjects]; + + CxIterator i = cxListIterator(_sublists); + int index = 0; + int rownum = 0; + cx_foreach(UiSubList *, sl, i) { + UiSourceListItem *section = [[UiSourceListItem alloc] init:self sublist:sl]; + section.sublistIndex = index; + section.rownum = rownum; + section.sublistStartRow = rownum; + [section update:-1]; + [_sections addObject:section]; + index++; + rownum += 1 + section.items.count; + } + + [_outlineView reloadData]; + [_outlineView expandItem:nil expandChildren:YES]; +} + +// NSOutlineViewDataSource implementation + +- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { + if(item == nil) { + return _sections.count; + } else { + UiSourceListItem *i = item; + return i.items.count; + } +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { + UiSourceListItem *i = item; + return [i isSection] ? YES : NO; +} + +- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { + UiSourceListItem *i = item; + if(i) { + return [i.items objectAtIndex:index]; + } + return [_sections objectAtIndex:index]; +} + +- (void)outlineView:(NSOutlineView *)outlineView + setObjectValue:(id)object + forTableColumn:(NSTableColumn *)tableColumn + byItem:(id)item +{ + +} + +// NSOutlineViewDelegate implementation + +- (NSView *)outlineView:(NSOutlineView *)outlineView + viewForTableColumn:(NSTableColumn *)tableColumn + item:(id)item +{ + UiSourceListItem *i = item; + + NSTableCellView *cell = [[NSTableCellView alloc] init]; + cell.identifier = @"cell"; + // Icon + NSImageView *iconView = [[NSImageView alloc] initWithFrame:NSZeroRect]; + iconView.translatesAutoresizingMaskIntoConstraints = NO; + [cell addSubview:iconView]; + cell.imageView = iconView; + + // Label + //NSTextField *textField = [NSTextField labelWithString:@""]; + NSTextField *textField = [[NSTextField alloc] initWithFrame:NSZeroRect]; + textField.translatesAutoresizingMaskIntoConstraints = NO; + textField.bezeled = NO; + textField.editable = NO; + textField.drawsBackground = NO; + textField.selectable = NO; + textField.lineBreakMode = NSLineBreakByTruncatingTail; + + + [cell addSubview:textField]; + cell.textField = textField; + + if([i isSection]) { + NSFont *font = [NSFont boldSystemFontOfSize:[NSFont systemFontSize]*0.85]; + //NSFont *font = [NSFont preferredFontForTextStyle:NSFontTextStyleCaption1 options:@{}]; + NSDictionary *attrs = @{ + NSFontAttributeName: font, + NSForegroundColorAttributeName: [NSColor tertiaryLabelColor] + }; + textField.attributedStringValue = [[NSAttributedString alloc] initWithString:i.label attributes:attrs]; + + // Layout constraints + [NSLayoutConstraint activateConstraints:@[ + [iconView.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0], + [iconView.bottomAnchor constraintEqualToAnchor:cell.bottomAnchor constant:-1], + + [textField.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0], + [textField.bottomAnchor constraintEqualToAnchor:cell.bottomAnchor constant:-1], + [textField.trailingAnchor constraintEqualToAnchor:cell.trailingAnchor constant:0], + ]]; + } else { + textField.stringValue = i.label; + + // Layout constraints + [NSLayoutConstraint activateConstraints:@[ + [iconView.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0], + [iconView.centerYAnchor constraintEqualToAnchor:cell.centerYAnchor], + + [textField.leadingAnchor constraintEqualToAnchor:cell.leadingAnchor constant:0], + [textField.centerYAnchor constraintEqualToAnchor:cell.centerYAnchor], + [textField.trailingAnchor constraintEqualToAnchor:cell.trailingAnchor constant:0], + ]]; + } + + return cell; +} + +- (NSTableRowView *) outlineView:(NSOutlineView *) outlineView + rowViewForItem:(id)item { + UiSourceListItem *it = item; + UiSourceListRow *row = [[UiSourceListRow alloc]init]; + if([it isSection] && it.sublist->header) { + row.showDisclosureButton = YES; + } + return row; +} + +- (BOOL) outlineView:(NSOutlineView *) outlineView + shouldSelectItem:(id)item +{ + UiSourceListItem *i = item; + return [i isSection] ? NO : YES; +} + +- (CGFloat) outlineView:(NSOutlineView *) outlineView + heightOfRowByItem:(id) item +{ + UiSourceListItem *i = item; + CGFloat rowHeight = outlineView.rowHeight; + if([i isSection]) { + if(i.sublist->header) { + rowHeight += i.sublistIndex == 0 ? -12 : 4; + } else { + rowHeight = i.sublistIndex == 0 ? 0.1 : 12; + } + } + return rowHeight; +} + +- (void) outlineViewSelectionDidChange:(NSNotification *) notification { + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = 0; + event.set = ui_get_setop(); + + UiSubListEventData sublistEvent; + + NSInteger selectedRow = _outlineView.selectedRow; + if(selectedRow >= 0) { + UiSourceListItem *item = [_outlineView itemAtRow:selectedRow]; + UiSourceListItem *parent = item.parent; + UiSubList *sublist = parent != nil ? parent.sublist : item.sublist; + UiVar *var = parent != nil ? parent.var : item.var; + if(item && var) { + sublistEvent.list = var->value; + sublistEvent.sublist_index = parent ? parent.sublistIndex : item.sublistIndex; + sublistEvent.row_index = (int)selectedRow - item.sublistStartRow - 1; + sublistEvent.sublist_userdata = sublist ? sublist->userdata : NULL; + sublistEvent.event_data = item.eventdata; + sublistEvent.row_data = sublistEvent.list->get(sublistEvent.list, sublistEvent.row_index); + + event.eventdata = &sublistEvent; + event.eventdatatype = UI_EVENT_DATA_SUBLIST; + } + } + + if(_onactivate) { + _onactivate(&event, _onactivatedata); + } +} + +@end + +/* + * Outline datasource item + * Is used for sections (sublists) and individual items + */ +@implementation UiSourceListItem + +- (id)init:(UiSourceList*)sourcelist sublist:(UiSubList*)sublist { + _sourcelist = sourcelist; + _sublist = sublist; + _items = [[NSMutableArray alloc]initWithCapacity:16]; + if(sublist->header) { + _label = [[NSString alloc]initWithUTF8String:sublist->header]; + } else { + _label = @""; + } + UiVar *var = uic_widget_var(sourcelist.obj->ctx, + sourcelist.obj->ctx, + sublist->value, + sublist->varname, + UI_VAR_LIST); + _var = var; + return self; +} + +- (id)init:(UiSubListItem*)item parent:(UiSourceListItem*)parent { + _parent = parent; + if(item->label) { + _label = [[NSString alloc]initWithUTF8String:item->label]; + } else { + _label = @""; + } + _eventdata = item->eventdata; + return self; +} + +- (BOOL)isSection { + return _sublist != NULL; +} + +- (void)update:(int)row { + // TODO: check row + + [_items removeAllObjects]; + if(_var == NULL) { + return; + } + UiList *list = _var->value; + void *elm = list->first(list); + int index = 0; + while(elm) { + UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; + if(_sourcelist.getvalue) { + _sourcelist.getvalue(list, _sublist->userdata, elm, index, &item, _sourcelist.getvaluedata); + } else { + item.label = strdup(elm); + } + + UiSourceListItem *it = [[UiSourceListItem alloc] init:&item parent:self]; + it.sublistIndex = index; + it.rownum = self.rownum + index; + it.sublistStartRow = _parent ? _parent.sublistStartRow : _sublistStartRow; + [_items addObject:it]; + + elm = list->next(list); + index++; + } +} + +@end + +/* + * Custom NSTableRowView implementation + * Moves the disclosure button to the right side + * Handles mouse hover events (for hiding the disclosure button) + */ +@implementation UiSourceListRow + +- (void)layout { + [super layout]; + + for (NSView *subview in self.subviews) { + if ([subview.identifier isEqualToString:NSOutlineViewDisclosureButtonKey] || + [subview.identifier isEqualToString:NSOutlineViewShowHideButtonKey]) + { + NSRect frame = subview.frame; + frame.origin.x = self.bounds.size.width - frame.size.width - 16.0; + subview.frame = frame; + + if(!_hover) { + subview.hidden = YES; + } + + if(subview != _disclosureButton) { + // init disclosure button + _disclosureButton = (NSButton*)subview; + if ([subview isKindOfClass:[NSButton class]]) { + NSButton *button = (NSButton*)subview; + button.contentTintColor = [NSColor tertiaryLabelColor]; + } + } + + + } else if ([subview.identifier isEqualToString:@"cell"]) { + NSRect frame = subview.frame; + frame.origin.x = 16; + subview.frame = frame; + } + } +} + +- (void)updateTrackingAreas { + [super updateTrackingAreas]; + if(_trackingArea != nil) { + [self removeTrackingArea:_trackingArea]; + } + _trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds + options:NSTrackingMouseEnteredAndExited | + NSTrackingActiveInActiveApp | + NSTrackingInVisibleRect + owner:self + userInfo:nil]; + [self addTrackingArea:_trackingArea]; +} + +- (void)mouseEntered:(NSEvent *)event { + _hover = YES; + _disclosureButton.hidden = _showDisclosureButton ? NO : YES; +} + +- (void)mouseExited:(NSEvent *)event { + _hover = NO; + _disclosureButton.hidden = YES; +} + +@end diff --git a/ui/cocoa/menu.h b/ui/cocoa/menu.h new file mode 100644 index 0000000..d1cd280 --- /dev/null +++ b/ui/cocoa/menu.h @@ -0,0 +1,70 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "../ui/menu.h" +#import "toolkit.h" + +#import "../common/menu.h" + +@interface MenuItem : NSObject + +@property (weak) NSMenuItem *menuItem; +@property (strong) NSString *itemId; +@property UiMenuCheckItem *checkItem; +@property UiMenuRadioItem *radioItem; +@property ui_callback callback; +@property void *userdata; +@property (strong) NSString *varname; +@property UiObject *obj; +@property UiVar *var; +@property BOOL state; + +- (MenuItem*)init:(int)itId; + +- (void)handleToggleEvent:(id)sender; + +@end + +void ui_menu_init(void); + +typedef void(*ui_menu_add_f)(UiObject*, NSMenu*, int, UiMenuItemI*); + +void add_menu_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item); +void add_menuitem_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item); +void add_menuseparator_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item); +void add_checkitem_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item); +void add_radioitem_widget(UiObject *obj, NSMenu *parent, int index, UiMenuItemI *item); +void add_checkitemnv_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item); +void add_menuitem_list_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item); + +void ui_add_menu_items(UiObject *obj, NSMenu *parent, int i, UiMenu *menu); + +NSArray* ui_get_binding_items(void); + +int64_t ui_menu_toggleitem_get(UiInteger *i); +void ui_menu_toggleitem_set(UiInteger *i, int64_t value); diff --git a/ui/cocoa/menu.m b/ui/cocoa/menu.m new file mode 100644 index 0000000..eae8a50 --- /dev/null +++ b/ui/cocoa/menu.m @@ -0,0 +1,228 @@ +/* + * 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 "menu.h" +#import "window.h" +#import "EventData.h" + +#pragma GCC diagnostic ignored "-Wundeclared-selector" +#pragma clang diagnostic ignored "-Wundeclared-selector" + +// holds all items that need bindings +// value type: MenuItem* +static NSMutableArray *bindingItems; + +@implementation MenuItem + +- (MenuItem*)init:(int)itId { + self.itemId = [[NSString alloc] initWithFormat:@"item%d", itId]; + return self; +} + +- (void)handleToggleEvent:(id)sender { + NSMenuItem *item = (NSMenuItem*)sender; + if(!_state) { + item.state = NSControlStateValueOn; + } else { + item.state = NSControlStateValueOff; + } + _state = !_state; + + if(_callback) { + UiEvent event; + event.obj = _obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = _state; + event.set = ui_get_setop(); + _callback(&event, _userdata); + } +} + +@end + +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 +}; + +void ui_add_menu_items(UiObject *obj, NSMenu *parent, int i, UiMenu *menu) { + UiMenuItemI *it = menu->items_begin; + int index = 0; + while(it) { + createMenuItem[it->type](obj, parent, index, it); + it = it->next; + index++; + } +} + +void add_menu_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) { + UiMenu *it = (UiMenu*)item; + NSString *str = [[NSString alloc] initWithUTF8String:it->label]; + NSMenu *menu = [[NSMenu alloc] initWithTitle: str]; + NSMenuItem *menuItem = [parent addItemWithTitle:str action:nil keyEquivalent:@""]; + [parent setSubmenu:menu forItem:menuItem]; + + ui_add_menu_items(obj, menu, i, it); +} + +void add_menuitem_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) { + UiMenuItem *it = (UiMenuItem*)item; + + NSString *str = [[NSString alloc] initWithUTF8String:it->label]; + NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuItemAction) keyEquivalent:@""]; + + if(it->callback) { + EventData *event = [[EventData alloc] init:it->callback userdata:it->userdata]; + if(obj) { + event.obj = obj; + menuItem.target = event; + menuItem.action = @selector(handleEvent:); + } + objc_setAssociatedObject(menuItem, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } +} + +void add_menuseparator_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) { + NSMenuItem *menuItem = [NSMenuItem separatorItem]; + [parent addItem:menuItem]; +} + +static int nItem = 0; + +void add_checkitem_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) { + UiMenuCheckItem *it = (UiMenuCheckItem*)item; + + NSString *str = [[NSString alloc] initWithUTF8String:it->label]; + NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuCheckItemAction:) keyEquivalent:@""]; + + MenuItem *mItem = [[MenuItem alloc] init:nItem++]; + mItem.menuItem = menuItem; + mItem.obj = obj; + mItem.callback = it->callback; + mItem.userdata = it->userdata; + mItem.checkItem = it; + if(it->varname) { + mItem.varname = [[NSString alloc] initWithUTF8String:it->varname]; + } + + objc_setAssociatedObject(menuItem, "menuitem", mItem, OBJC_ASSOCIATION_RETAIN); + + if(!obj) { + [bindingItems addObject:mItem]; + } else { + mItem.var = uic_widget_var(obj->ctx, obj->ctx, NULL, it->varname, UI_VAR_INTEGER); + if(mItem.var) { + UiInteger *i = mItem.var->value; + i->obj = (__bridge void*)mItem; + i->get = ui_menu_toggleitem_get; + i->set = ui_menu_toggleitem_set; + } + menuItem.target = mItem; + menuItem.action = @selector(handleToggleEvent:); + } +} + +void add_radioitem_widget(UiObject *obj, NSMenu *parent, int index, UiMenuItemI *item) { + UiMenuRadioItem *it = (UiMenuRadioItem*)item; + + NSString *str = [[NSString alloc] initWithUTF8String:it->label]; + NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuRadioItemAction:) keyEquivalent:@""]; + + MenuItem *mItem = [[MenuItem alloc] init:nItem++]; + mItem.callback = it->callback; + mItem.userdata = it->userdata; + mItem.radioItem = it; + + objc_setAssociatedObject(menuItem, "menuitem", mItem, OBJC_ASSOCIATION_RETAIN); + [bindingItems addObject:mItem]; +} + +void add_checkitemnv_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) { + +} + +void add_menuitem_list_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) { + +} + + +void ui_menu_init(void) { + bindingItems = [[NSMutableArray alloc] init]; + + UiMenu *menus_begin = uic_get_menu_list(); + UiMenu *ls = menus_begin; + int index = 1; + while(ls) { + if(ls->item.type == UI_MENU) { + NSString *str = [[NSString alloc] initWithUTF8String:ls->label]; + NSMenu *menu = [[NSMenu alloc] initWithTitle: str]; + NSMenuItem *menuItem = [[NSApp mainMenu] insertItemWithTitle:str action:nil keyEquivalent:@"" atIndex:index]; + [[NSApp mainMenu] setSubmenu:menu forItem:menuItem]; + + ui_add_menu_items(NULL, menu, 0, ls); + } + ls = (UiMenu*)ls->item.next; + index++; + } +} + +NSArray* ui_get_binding_items(void) { + return bindingItems; +} + + +int64_t ui_menu_toggleitem_get(UiInteger *i) { + MenuItem *item = (__bridge MenuItem*)i->obj; + i->value = item.state; + return i->value; +} + +void ui_menu_toggleitem_set(UiInteger *i, int64_t value) { + MenuItem *item = (__bridge MenuItem*)i->obj; + i->value = value; + if(value != 0) { + item.menuItem.state = NSControlStateValueOn; + } else { + item.menuItem.state = NSControlStateValueOff; + } +} diff --git a/ui/cocoa/objs.mk b/ui/cocoa/objs.mk new file mode 100644 index 0000000..d4d0e61 --- /dev/null +++ b/ui/cocoa/objs.mk @@ -0,0 +1,56 @@ +# +# 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 += AppDelegate.o +COCOAOBJ += GridLayout.o +COCOAOBJ += BoxContainer.o +COCOAOBJ += EventData.o +COCOAOBJ += UiThread.o +COCOAOBJ += MainWindow.o +COCOAOBJ += WindowManager.o +COCOAOBJ += window.o +COCOAOBJ += Container.o +COCOAOBJ += button.o +COCOAOBJ += text.o +COCOAOBJ += menu.o +COCOAOBJ += Toolbar.o +COCOAOBJ += ListDelegate.o +COCOAOBJ += ListDataSource.o +COCOAOBJ += label.o +COCOAOBJ += list.o +COCOAOBJ += widget.o +COCOAOBJ += image.o +COCOAOBJ += entry.o +COCOAOBJ += TabView.o + +TOOLKITOBJS += $(COCOAOBJ:%=$(COCOA_OBJPRE)%) +TOOLKITSOURCE += $(COCOAOBJ:%.o=cocoa/%.m) diff --git a/ui/cocoa/text.h b/ui/cocoa/text.h new file mode 100644 index 0000000..23bc438 --- /dev/null +++ b/ui/cocoa/text.h @@ -0,0 +1,49 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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/text.h" + + +void ui_textarea_save(UiText *text); +void ui_textarea_destroy(UiText *text); +void ui_textarea_restore(UiText *text); +void ui_textarea_set(UiText *text, const char *str); +char* ui_textarea_get(UiText *text); +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_setselection(UiText *text, int begin, int end); +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 *s); +void ui_textfield_set(UiString *s, const char *value); diff --git a/ui/cocoa/text.m b/ui/cocoa/text.m new file mode 100644 index 0000000..324d6e7 --- /dev/null +++ b/ui/cocoa/text.m @@ -0,0 +1,250 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "text.h" +#import "EventData.h" +#import "Container.h" +#import + +UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs *args) { + NSTextView *textview = [[NSTextView alloc] init]; + textview.autoresizingMask = NSViewWidthSizable; + textview.minSize = NSMakeSize(0, 0); + textview.maxSize = NSMakeSize(FLT_MAX, FLT_MAX); + + NSScrollView *scrollview = [[NSScrollView alloc] init]; + scrollview.hasVerticalScroller = YES; + scrollview.documentView = textview; + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, scrollview, &layout); + + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_TEXT); + if(var) { + UiText *text = var->value; + text->obj = (__bridge void*)textview; + ui_textarea_restore(text); + + text->save = ui_textarea_save; + text->destroy = ui_textarea_destroy; + text->restore = ui_textarea_restore; + text->set = ui_textarea_set; + text->get = ui_textarea_get; + text->getsubstr = ui_textarea_getsubstr; + text->insert = ui_textarea_insert; + text->setposition = ui_textarea_setposition; + text->position = ui_textarea_position; + text->setselection = ui_textarea_setselection; + text->selection = ui_textarea_selection; + text->length = ui_textarea_length; + text->remove = ui_textarea_remove; + } + + return (__bridge void*)scrollview; +} + + + + +void ui_textarea_save(UiText *text) { + +} + +void ui_textarea_destroy(UiText *text) { + (void)(__bridge_transfer NSTextStorage*)text->data1; +} + +void ui_textarea_restore(UiText *text) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSTextStorage *textStorage; + if(text->data1) { + textStorage = (__bridge NSTextStorage*)text->data1; + } else { + textStorage = [[NSTextStorage alloc] init]; + } + [textview.layoutManager replaceTextStorage:textStorage]; + text->data1 = (__bridge_retained void*)textStorage; +} + +void ui_textarea_set(UiText *text, const char *str) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + if(text->value.free) { + text->value.free(text->value.ptr); + } + text->value.ptr = strdup(str); + text->value.free = free; + textview.string = [[NSString alloc] initWithUTF8String:str]; +} + +char* ui_textarea_get(UiText *text) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + if(text->value.free) { + text->value.free(text->value.ptr); + } + text->value.ptr = strdup(textview.string.UTF8String); + text->value.free = free; + return text->value.ptr; +} + +char* ui_textarea_getsubstr(UiText *text, int begin, int end) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSString *str = textview.string; + NSRange range = NSMakeRange(begin, end-begin); + NSString *sub = [str substringWithRange:range]; + return strdup(sub.UTF8String); +} + +void ui_textarea_insert(UiText *text, int pos, char *str) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSString *s = [[NSString alloc] initWithUTF8String:str]; + NSAttributedString *attributedStr = [[NSAttributedString alloc] initWithString:s]; + [textview.textStorage insertAttributedString:attributedStr atIndex:pos]; +} + +void ui_textarea_setposition(UiText *text, int pos) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSRange range = NSMakeRange(pos, 0); + [textview setSelectedRange:range]; +} + +int ui_textarea_position(UiText *text) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSRange range = textview.selectedRange; + return (int)range.location; +} + +void ui_textarea_setselection(UiText *text, int begin, int end) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSRange range = NSMakeRange(begin, end-begin); + [textview setSelectedRange:range]; +} + +void ui_textarea_selection(UiText *text, int *begin, int *end) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSRange range = textview.selectedRange; + if(begin) { + *begin = (int)range.location; + } + if(end) { + *end = (int)(range.location+range.length); + } +} + +int ui_textarea_length(UiText *text) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + return (int)textview.string.length; +} + +void ui_textarea_remove(UiText *text, int begin, int end) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + + if (begin < 0 || end < begin || end > textview.string.length) { + return; + } + + NSRange range = NSMakeRange(begin, end - begin); + [[textview textStorage] deleteCharactersInRange:range]; +} + + + +/* -------------------------- TextField -------------------------- */ + +static UIWIDGET textfield_create(UiObject *obj, UiTextFieldArgs *args, BOOL password, BOOL frameless) { + NSTextField *textfield; + if(password) { + textfield = [[NSSecureTextField alloc] init]; + } else { + textfield = [[NSTextField alloc] init]; + } + + if(!args->hfill || args->width > 0) { + textfield.translatesAutoresizingMaskIntoConstraints = NO; + int width = args->width > 0 ? args->width : 100; + [[textfield.widthAnchor constraintEqualToConstant:width] setActive:YES]; + } + + + if(frameless) { + [textfield setBezeled: NO]; + } + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, textfield, &layout); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); + if(var) { + UiString *s = var->value; + if(s->value.ptr) { + textfield.stringValue = [[NSString alloc] initWithUTF8String:s->value.ptr]; + if(s->value.free) { + s->value.free(s->value.ptr); + } + } + s->obj = (__bridge void*)textfield; + s->get = ui_textfield_get; + s->set = ui_textfield_set; + } + + return (__bridge void*)textfield; +} + +UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs *args) { + return textfield_create(obj, args, FALSE, FALSE); +} + +UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs *args) { + return textfield_create(obj, args, FALSE, TRUE); +} + +UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs *args) { + return textfield_create(obj, args, TRUE, FALSE); +} + +char* ui_textfield_get(UiString *s) { + NSTextField *textfield = (__bridge NSTextField*)s->obj; + NSString *str = textfield.stringValue; + const char *cstr = str.UTF8String; + if(s->value.free) { + s->value.free(s->value.ptr); + } + s->value.ptr = strdup(cstr); + s->value.free = free; + return s->value.ptr; +} + +void ui_textfield_set(UiString *s, const char *value) { + if(s->value.free) { + s->value.free(s->value.ptr); + } + s->value.ptr = NULL; + s->value.free = NULL; + NSTextField *textfield = (__bridge NSTextField*)s->obj; + textfield.stringValue = [[NSString alloc] initWithUTF8String:value]; +} diff --git a/ui/cocoa/toolkit.h b/ui/cocoa/toolkit.h new file mode 100644 index 0000000..9a377b3 --- /dev/null +++ b/ui/cocoa/toolkit.h @@ -0,0 +1,57 @@ +/* + * 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 "../ui/toolkit.h" +#include "../common/context.h" +#include "../common/object.h" + +@interface UiAppCallback : NSObject + +@property ui_threadfunc callback; +@property void *userdata; + +- (id) initWithCallback:(ui_threadfunc)func userdata:(void*)userdata; + +- (void) callMainThread; + +- (void) mainThread:(id)n; + +@end + +@protocol UiToplevelObject + +- (BOOL) getIsVisible; +- (void) setVisible:(BOOL)visible; + +@end + +void ui_cocoa_onstartup(void); +void ui_cocoa_onopen(const char *file); +void ui_cocoa_onexit(void); + diff --git a/ui/cocoa/toolkit.m b/ui/cocoa/toolkit.m new file mode 100644 index 0000000..fd97b2c --- /dev/null +++ b/ui/cocoa/toolkit.m @@ -0,0 +1,206 @@ +/* + * 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. + */ + +#import "toolkit.h" + +#include "../common/document.h" +#include "../common/properties.h" +#include "../common/menu.h" +#include "../common/toolbar.h" +#include "../common/threadpool.h" + +#import "image.h" +#import "menu.h" +#import "Toolbar.h" +#import "UiThread.h" + +#import "AppDelegate.h" + +static const char *application_name; + +static int app_argc; +static const char **app_argv; + +static ui_callback startup_func; +static void *startup_data; +static ui_callback open_func; +static void *open_data; +static ui_callback exit_func; +static void *exit_data; + +static UiBool exit_on_shutdown; + +/* ------------------- App Init / Event Loop functions ------------------- */ + + +void ui_init(const char *appname, int argc, char **argv) { + application_name = appname; + app_argc = argc; + app_argv = (const char**)argv; + + uic_init_global_context(); + + uic_menu_init(); + uic_toolbar_init(); + + uic_load_app_properties(); + + NSApplication *app = [NSApplication sharedApplication]; + //[app setActivationPolicy:NSApplicationActivationPolicyRegular]; + + //[NSBundle loadNibNamed:@"MainMenu" owner:NSApp ]; + //[[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:NSApp topLevelObjects:&topLevelObjects]; + + ui_icon_init(); + ui_toolbar_init(); + +} + +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_app_exit_on_shutdown(UiBool exitapp) { + exit_on_shutdown = exitapp; +} + +void ui_cocoa_onstartup(void) { + UiEvent e; + e.obj = NULL; + e.window = NULL; + e.document = NULL; + e.eventdata = NULL; + e.intval = 0; + if(startup_func) { + startup_func(&e, startup_data); + } +} + +void ui_cocoa_onopen(const char *file) { + UiEvent e; + e.obj = NULL; + e.window = NULL; + e.document = NULL; + e.eventdata = NULL; + e.intval = 0; + if(open_func) { + open_func(&e, open_data); + } +} + +void ui_cocoa_onexit(void) { + UiEvent e; + e.obj = NULL; + e.window = NULL; + e.document = NULL; + e.eventdata = NULL; + e.intval = 0; + if(exit_func) { + exit_func(&e, exit_data); + } +} + +void ui_main(void) { + NSApplicationMain(app_argc, app_argv); + if(exit_on_shutdown) { + exit(0); + } +} + +/* ------------------- Window Visibility functions ------------------- */ + +void ui_show(UiObject *obj) { + if(obj->wobj) { + id window = (__bridge id)obj->wobj; + + if(![window getIsVisible]) { + obj->ref++; + } + + [window setVisible:YES]; + } +} + +void ui_close(UiObject *obj) { + // TODO: unref, window close, ... + if(obj->wobj) { + id window = (__bridge id)obj->wobj; + [window setVisible:NO]; + } +} + +/* ------------------- Job Control / Threadpool functions ------------------- */ + +void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) { + UiThread *thread = [[UiThread alloc]init:obj jobfunc:tf jobdata:td]; + thread.finish_callback = f; + thread.finish_userdata = fd; + [thread start]; +} + +@implementation UiAppCallback + +- (id) initWithCallback:(ui_threadfunc)func userdata:(void*)userdata { + _callback = func; + _userdata = userdata; + return self; +} + +- (void) callMainThread { + [self performSelectorOnMainThread:@selector(mainThread:) + withObject:nil + waitUntilDone:NO]; +} + +- (void) mainThread:(id)n { + if(_callback) { + _callback(_userdata); + } +} + +@end + +void ui_call_mainthread(ui_threadfunc tf, void* td) { + UiAppCallback *cb = [[UiAppCallback alloc]initWithCallback:tf userdata:td]; + [cb callMainThread]; +} diff --git a/ui/cocoa/webview.h b/ui/cocoa/webview.h new file mode 100644 index 0000000..479905b --- /dev/null +++ b/ui/cocoa/webview.h @@ -0,0 +1,58 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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/webview.h" + +#import + + +enum WebViewDataType { + WEBVIEW_CONTENT_URL, + WEBVIEW_CONTENT_CONTENT +}; + +struct UiWebViewData { + void *webview; + char *uri; + char *mimetype; + char *encoding; + char *content; + size_t contentlength; + enum WebViewDataType type; + + double zoom; + UiBool javascript; +}; + +UiWebViewData* ui_webview_data_clone(UiWebViewData *data); + +void* ui_webview_get(UiGeneric *g); +const char* ui_webview_get_type(UiGeneric *g); +int ui_webview_set(UiGeneric *g, void *data, const char *type); +void ui_webview_destroy(UiGeneric *g); diff --git a/ui/cocoa/webview.m b/ui/cocoa/webview.m new file mode 100644 index 0000000..088ad5f --- /dev/null +++ b/ui/cocoa/webview.m @@ -0,0 +1,273 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "webview.h" +#import "Container.h" + +UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args) { + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_GENERIC); + + WKWebView *webview = [[WKWebView alloc]init]; + + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, webview, &layout); + + if(var) { + UiGeneric *value = var->value; + value->get = ui_webview_get; + value->get_type = ui_webview_get_type; + value->set = ui_webview_set; + value->destroy = ui_webview_destroy; + value->obj = (__bridge void*)webview; + if(value->value) { + ui_webview_set(value, value->value, UI_WEBVIEW_OBJECT_TYPE); + } else { + UiWebViewData *data = malloc(sizeof(UiWebViewData)); + memset(data, 0, sizeof(UiWebViewData)); + data->webview = (__bridge void*)webview; + data->javascript = TRUE; + data->zoom = 1; + value->value = value; + } + } + + return (__bridge void*)webview; +} + +UiWebViewData* ui_webview_data_clone(UiWebViewData *data) { + UiWebViewData *newdata = malloc(sizeof(UiWebViewData)); + memset(newdata, 0, sizeof(UiWebViewData)); + newdata->zoom = 1; + newdata->javascript = TRUE; + + if(data) { + newdata->uri = data->uri ? strdup(data->uri) : NULL; + newdata->mimetype = data->mimetype ? strdup(data->mimetype) : NULL; + newdata->encoding = data->encoding ? strdup(data->encoding) : NULL; + newdata->type = data->type; + newdata->javascript = data->javascript; + newdata->zoom = data->zoom; + if(data->content && data->contentlength > 0) { + newdata->content = malloc(data->contentlength + 1); + memcpy(newdata->content, data->content, data->contentlength); + newdata->content[data->contentlength] = 0; + newdata->contentlength = data->contentlength; + } + } + + return newdata; +} + +void ui_webview_data_free(UiWebViewData *data) { + if(!data) { + return; + } + free(data->uri); + free(data->mimetype); + free(data->encoding); + free(data->content); + free(data); +} + +void* ui_webview_get(UiGeneric *g) { + UiWebViewData *data = g->value; + WKWebView *webview = (__bridge WKWebView*)g->obj; + + if(data->type == WEBVIEW_CONTENT_URL) { + (void)ui_webview_get_uri(g); // this updates data->uri + } + data->zoom = webview.pageZoom; + + return ui_webview_data_clone(g->value); +} + +const char* ui_webview_get_type(UiGeneric *g) { + return UI_WEBVIEW_OBJECT_TYPE; +} + +int ui_webview_set(UiGeneric *g, void *data, const char *type) { + if(!data || !type) { + return 1; + } + if(strcmp(type, UI_WEBVIEW_OBJECT_TYPE)) { + return 1; + } + ui_webview_data_free(g->value); + g->value = ui_webview_data_clone(data); + + WKWebView *webview = (__bridge WKWebView*)g->obj; + UiWebViewData *webd = data; + if(webd->type == WEBVIEW_CONTENT_URL) { + const char *uri = webd->uri ? webd->uri : "about:blank"; + NSURL *url = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:uri]]; + if(url) { + NSURLRequest *req = [NSURLRequest requestWithURL:url]; + if(req) { + [webview loadRequest:req]; + } + } + } else { + NSString *mimetype = [[NSString alloc]initWithUTF8String: webd->mimetype ? webd->mimetype : "text/plain"]; + NSString *encoding = [[NSString alloc]initWithUTF8String: webd->encoding ? webd->encoding : "UTF-8"]; + NSString *urlStr = [[NSString alloc]initWithUTF8String: webd->uri ? webd->uri : "file:///"]; + NSURL *url = [NSURL URLWithString:urlStr]; + NSData *content = [NSData dataWithBytes:webd->content length:webd->contentlength]; + if(!url) { + url = [NSURL URLWithString:@"about:blank"]; + } + [webview loadData:content MIMEType:mimetype characterEncodingName:encoding baseURL:url]; + } + + webview.pageZoom = webd->zoom; + + return 1; +} + +void ui_webview_destroy(UiGeneric *g) { + ui_webview_data_free(g->value); + g->value = NULL; +} + + +void ui_webview_load_url(UiGeneric *g, const char *url) { + WKWebView *webview = (__bridge WKWebView*)g->obj; + UiWebViewData *data = g->value; + data->type = WEBVIEW_CONTENT_URL; + + if(!url) { + url = "about:blank"; + } + + NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]]; + if(nsurl) { + NSURLRequest *req = [NSURLRequest requestWithURL:nsurl]; + if(req) { + [webview loadRequest:req]; + } + } +} + +void ui_webview_load_content( + UiGeneric *g, + const char *uri, + const char *content, + size_t contentlength, + const char *mimetype, + const char *encoding) +{ + UiWebViewData *data = g->value; + WKWebView *webview = (__bridge WKWebView*)g->obj; + + data->type = WEBVIEW_CONTENT_CONTENT; + + free(data->uri); + data->uri = NULL; + free(data->mimetype); + free(data->encoding); + free(data->content); + data->type = WEBVIEW_CONTENT_URL; + + data->content = malloc(contentlength+1); + memcpy(data->content, content, contentlength); + data->content[contentlength] = 0; + + if(!mimetype) { + mimetype = "text/plain"; + } + if(!encoding) { + encoding = "UTF-8"; + } + + data->mimetype = strdup(mimetype); + data->encoding = strdup(encoding); + + NSString *mtype = [[NSString alloc]initWithUTF8String:mimetype]; + NSString *enc = [[NSString alloc]initWithUTF8String:encoding]; + NSData *ct = [NSData dataWithBytes:content length:contentlength]; + NSURL *url; + if(uri) { + NSString *uriStr = [[NSString alloc]initWithUTF8String:uri]; + url = [NSURL URLWithString:uriStr]; + } else { + url = [NSURL URLWithString:@"file:///"]; + } + [webview loadData:ct MIMEType:mtype characterEncodingName:enc baseURL:url]; +} + + +void ui_webview_reload(UiGeneric *g) { + WKWebView *webview = (__bridge WKWebView*)g->obj; + [webview reload]; +} + +UiBool ui_webview_can_go_back(UiGeneric *g) { + return FALSE; +} + +UiBool ui_webview_can_go_forward(UiGeneric *g) { + return FALSE; +} + +void ui_webview_go_back(UiGeneric *g) { + +} + +void ui_webview_go_forward(UiGeneric *g) { + +} + +const char * ui_webview_get_uri(UiGeneric *g) { + UiWebViewData *data = g->value; + WKWebView *webview = (__bridge WKWebView*)g->obj; + + free(data->uri); + data->uri = NULL; + + NSURL *url = webview.URL; + if(url) { + NSString *s = [url absoluteString]; + if(s) { + data->uri = strdup(s.UTF8String); + } + } + return data->uri; +} + +void ui_webview_enable_javascript(UiGeneric *g, UiBool enable) { + // unsupported +} + +void ui_webview_set_zoom(UiGeneric *g, double zoom) { + WKWebView *webview = (__bridge WKWebView*)g->obj; + webview.pageZoom = zoom; +} + +double ui_webview_get_zoom(UiGeneric *g) { + WKWebView *webview = (__bridge WKWebView*)g->obj; + return webview.pageZoom; +} diff --git a/ui/cocoa/widget.h b/ui/cocoa/widget.h new file mode 100644 index 0000000..772bf88 --- /dev/null +++ b/ui/cocoa/widget.h @@ -0,0 +1,31 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "Container.h" +#import "../ui/widget.h" diff --git a/ui/cocoa/widget.m b/ui/cocoa/widget.m new file mode 100644 index 0000000..edc78c1 --- /dev/null +++ b/ui/cocoa/widget.m @@ -0,0 +1,64 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 "widget.h" + +/* genereal widget functions */ + +void ui_set_enabled(UIWIDGET widget, int enabled) { + NSControl *control = (__bridge NSControl*)widget; + control.enabled = enabled; +} + +void ui_set_show_all(UIWIDGET widget, int value) { + // TODO: is this relevant? +} + +void ui_set_visible(UIWIDGET widget, int visible) { + NSView *view = (__bridge NSView*)widget; + view.hidden = !visible; +} + + +UIWIDGET ui_separator_create(UiObject *obj, UiWidgetArgs *args) { + // TODO + return NULL; +} + + +/* custom widget */ + +UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) { + UIWIDGET widget = create_widget(obj, args, userdata); + + NSView *view = (__bridge NSView*)widget; + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, view, &layout); + + return widget; +} diff --git a/ui/cocoa/window.h b/ui/cocoa/window.h new file mode 100644 index 0000000..329d38c --- /dev/null +++ b/ui/cocoa/window.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#import "toolkit.h" +#import "../ui/window.h" + +@interface UiDialogWindow : NSPanel + +@property UiObject *obj; +@property NSWindow *parent; +@property UiTri modal; +@property ui_callback onclick; +@property void *onclickdata; + +@end diff --git a/ui/cocoa/window.m b/ui/cocoa/window.m new file mode 100644 index 0000000..c4efb6b --- /dev/null +++ b/ui/cocoa/window.m @@ -0,0 +1,435 @@ +/* + * 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. + */ + +#import "window.h" + +#import "MainWindow.h" +#import "WindowManager.h" +#import "BoxContainer.h" +#import "EventData.h" + +#import + +#include "../ui/window.h" +#include "../ui/properties.h" +#include "../common/context.h" +#include "../common/menu.h" +#include "../common/toolbar.h" +#include "../common/object.h" + +#include + +static int window_default_width = 650; +static int window_default_height = 550; + +static int splitview_window_default_pos = -1; +static UiBool splitview_window_use_prop = TRUE; + +static UiObject* create_window(const char *title, BOOL simple, BOOL sidebar, BOOL splitview) { + UiObject *obj = uic_object_new_toplevel(); + + MainWindow *window = [[MainWindow alloc] init:obj withSidebar:sidebar withSplitview:splitview]; + [[WindowManager sharedWindowManager] addWindow:window]; + window.releasedWhenClosed = false; // TODO: we still need a cleanup strategy + + obj->wobj = (__bridge void*)window; + + MainWindowController *controller = [[MainWindowController alloc] initWithWindow:obj window:window]; + window.windowController = controller; + [window setNextResponder:(NSResponder*)controller]; + objc_setAssociatedObject(window, "windowcontroller", controller, OBJC_ASSOCIATION_RETAIN); + + return obj; +} + +UiObject* ui_window(const char *title, void *window_data) { + UiObject *obj = create_window(title, FALSE, FALSE, FALSE); + obj->window = window_data; + return obj; +} + +UiObject* ui_simple_window(const char *title, void *window_data) { + UiObject *obj = create_window(title, TRUE, FALSE, FALSE); + obj->window = window_data; + return obj; +} + +UiObject* ui_sidebar_window(const char *title, void *window_data) { + UiObject *obj = create_window(title, FALSE, TRUE, FALSE); + obj->window = window_data; + return obj; +} + +UiObject* ui_splitview_window(const char *title, UiBool sidebar) { + sleep(1); + return create_window(title, FALSE, sidebar, TRUE); +} + +void ui_window_size(UiObject *obj, int width, int height) { + // TODO +} + +void ui_window_default_size(int width, int height) { + window_default_width = width; + window_default_height = height; +} + +/* --------------------------------- File Dialogs --------------------------------- */ + +void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + if((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) { + openPanel.allowsMultipleSelection = YES; + } + if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) { + openPanel.canChooseFiles = NO; + openPanel.canChooseDirectories = YES; + } + + NSWindow *window = (__bridge NSWindow*)obj->wobj; + [openPanel beginSheetModalForWindow:window completionHandler:^(NSModalResponse result) { + UiEvent event; + event.obj = obj; + event.window = obj->window; + event.document = obj->ctx->document; + event.intval = 0; + event.set = 0; + + UiFileList flist = { NULL, 0 }; + event.eventdata = &flist; + event.eventdatatype = UI_EVENT_DATA_FILE_LIST; + + if(result == NSModalResponseOK) { + NSArray *urls = [openPanel URLs]; + flist.files = calloc(urls.count, sizeof(char*)); + for(NSURL *url in urls) { + if([url isFileURL]) { + flist.files[flist.nfiles++] = strdup(url.path.UTF8String); + } + } + } + + if(file_selected_callback) { + file_selected_callback(&event, cbdata); + } + ui_filelist_free(flist); + }]; +} + +void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) { + NSSavePanel *savePanel = [NSSavePanel savePanel]; + if(name) { + NSString *nameStr = [[NSString alloc] initWithUTF8String:name]; + [savePanel setNameFieldStringValue: nameStr]; + } + + NSWindow *window = (__bridge NSWindow*)obj->wobj; + [savePanel beginSheetModalForWindow:window completionHandler:^(NSModalResponse result) { + UiEvent event; + event.obj = obj; + event.window = obj->window; + event.document = obj->ctx->document; + event.intval = 0; + event.set = 0; + + UiFileList flist = { NULL, 0 }; + event.eventdata = &flist; + event.eventdatatype = UI_EVENT_DATA_FILE_LIST; + + if(result == NSModalResponseOK) { + NSURL *url = [savePanel URL]; + if([url isFileURL]) { + NSString *path = url.path; + flist.files = malloc(sizeof(char*)); + flist.files[0] = strdup(path.UTF8String); + flist.nfiles = 1; + } + file_selected_callback(NULL, NULL); + } + if(file_selected_callback) { + file_selected_callback(&event, cbdata); + } + ui_filelist_free(flist); + }]; +} + +/* ------------------------------------- Dialog ------------------------------------- */ + +void ui_dialog_create(UiObject *parent, UiDialogArgs *args) { + NSAlert *dialog = [[NSAlert alloc] init]; + + if(args->title) { + dialog.messageText = [[NSString alloc]initWithUTF8String:args->title]; + } + if(args->content) { + dialog.informativeText = [[NSString alloc]initWithUTF8String:args->content]; + } + NSTextField *textfield = nil; + if(args->input) { + NSRect frame = NSMakeRect(0,0,300,22); + textfield = args->password ? [[NSSecureTextField alloc] initWithFrame:frame] : [[NSTextField alloc]initWithFrame:frame]; + if(args->input_value) { + textfield.stringValue = [[NSString alloc]initWithUTF8String:args->input_value]; + } + dialog.accessoryView = textfield; + } + + int b = 0; + int b1 = -1; + int b2 = -1; + if(args->button1_label) { + [dialog addButtonWithTitle:[[NSString alloc]initWithUTF8String:args->button1_label]]; + b1 = b++; + } + if(args->button2_label) { + [dialog addButtonWithTitle:[[NSString alloc]initWithUTF8String:args->button2_label]]; + b2 = b; + } + if(args->closebutton_label) { + [dialog addButtonWithTitle:[[NSString alloc]initWithUTF8String:args->closebutton_label]]; + } + + ui_callback callback = args->result; + void *userdata = args->resultdata; + + NSWindow *window = (__bridge NSWindow*)parent->wobj; + [dialog beginSheetModalForWindow:window completionHandler:^(NSModalResponse returnCode) { + UiEvent event; + event.obj = parent; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.set = 0; + event.intval = 0; + + long ret = returnCode - NSAlertFirstButtonReturn; + if(ret == b1) { + event.intval = 1; + } else if(ret == b2) { + event.intval = 2; + } + + NSString *value = nil; + if(textfield) { + value = textfield.stringValue; + event.eventdata = (void*)value.UTF8String; + event.eventdatatype = UI_EVENT_DATA_STRING; + } + + if(callback) { + callback(&event, userdata); + } + }]; + +} + +/* ------------------------------------- Dialog Window ------------------------------------- */ + +@implementation UiDialogWindow + +- (BOOL) getIsVisible { + return self.isVisible; +} + +- (void) setVisible:(BOOL)visible { + //[self makeKeyAndOrderFront:nil]; + if(visible) { + [_parent beginSheet:self completionHandler:^(NSModalResponse returnCode) { + // TODO: close event + }]; + } else { + [self.sheetParent endSheet:self returnCode:NSModalResponseCancel]; + } +} + +- (void)cancelOperation:(id)sender { + [self.sheetParent endSheet:self returnCode:NSModalResponseCancel]; + // TODO: close event +} + +@end + +UiObject *ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args) { + UiObject *obj = uic_object_new_toplevel(); + UiDialogWindow *panel = [[UiDialogWindow alloc] initWithContentRect:NSMakeRect(0, 0, args->width, args->height) + styleMask:(NSWindowStyleMaskTitled | + NSWindowStyleMaskClosable | + NSWindowStyleMaskResizable | + NSWindowStyleMaskUtilityWindow) + backing:NSBackingStoreBuffered + defer:NO]; + panel.parent = (__bridge NSWindow*)parent->wobj; + panel.obj = obj; + panel.modal = args->modal; + panel.onclick = args->onclick; + panel.onclickdata = args->onclickdata; + [panel center]; + [[WindowManager sharedWindowManager] addWindow:panel]; + obj->wobj = (__bridge void*)panel; + + NSView *content = panel.contentView; + + // Create a view for the dialog window buttons (lbutton1, lbutton2, rbutton3, rbutton4) + NSView *buttonArea = [[NSView alloc]init]; + buttonArea.translatesAutoresizingMaskIntoConstraints = NO; + [content addSubview:buttonArea]; + [NSLayoutConstraint activateConstraints:@[ + [buttonArea.bottomAnchor constraintEqualToAnchor:content.bottomAnchor constant:-10], + [buttonArea.leadingAnchor constraintEqualToAnchor:content.leadingAnchor constant:10], + [buttonArea.trailingAnchor constraintEqualToAnchor:content.trailingAnchor constant:-10], + [buttonArea.heightAnchor constraintEqualToConstant:20] + ]]; + + NSButton *lbutton1 = nil; + if(args->lbutton1) { + lbutton1 = [[NSButton alloc]init]; + lbutton1.title = [[NSString alloc]initWithUTF8String:args->lbutton1]; + lbutton1.translatesAutoresizingMaskIntoConstraints = NO; + [buttonArea addSubview:lbutton1]; + [NSLayoutConstraint activateConstraints:@[ + [lbutton1.topAnchor constraintEqualToAnchor:buttonArea.topAnchor constant:0], + [lbutton1.leadingAnchor constraintEqualToAnchor:buttonArea.leadingAnchor constant:0] + ]]; + + EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata]; + event.obj = obj; + event.value = 1; + lbutton1.target = event; + lbutton1.action = @selector(handleEvent:); + objc_setAssociatedObject(lbutton1, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + NSButton *lbutton2 = nil; + if(args->lbutton2) { + lbutton2 = [[NSButton alloc]init]; + lbutton2.title = [[NSString alloc]initWithUTF8String:args->lbutton2]; + lbutton2.translatesAutoresizingMaskIntoConstraints = NO; + [buttonArea addSubview:lbutton2]; + NSLayoutXAxisAnchor *anchor = lbutton1 != nil ? lbutton1.trailingAnchor : buttonArea.leadingAnchor; + int off = lbutton1 != nil ? 4 : 0; + [NSLayoutConstraint activateConstraints:@[ + [lbutton2.topAnchor constraintEqualToAnchor:buttonArea.topAnchor constant:0], + [lbutton2.leadingAnchor constraintEqualToAnchor:anchor constant:off] + ]]; + + EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata]; + event.obj = obj; + event.value = 2; + lbutton2.target = event; + lbutton2.action = @selector(handleEvent:); + objc_setAssociatedObject(lbutton2, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + + NSButton *rbutton4 = nil; + if(args->rbutton4) { + rbutton4 = [[NSButton alloc]init]; + rbutton4.title = [[NSString alloc]initWithUTF8String:args->rbutton4]; + rbutton4.translatesAutoresizingMaskIntoConstraints = NO; + [buttonArea addSubview:rbutton4]; + [NSLayoutConstraint activateConstraints:@[ + [rbutton4.topAnchor constraintEqualToAnchor:buttonArea.topAnchor constant:0], + [rbutton4.trailingAnchor constraintEqualToAnchor:buttonArea.trailingAnchor constant:0] + ]]; + + EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata]; + event.obj = obj; + event.value = 2; + rbutton4.target = event; + rbutton4.action = @selector(handleEvent:); + objc_setAssociatedObject(rbutton4, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + NSButton *rbutton3 = nil; + if(args->rbutton3) { + rbutton3 = [[NSButton alloc]init]; + rbutton3.title = [[NSString alloc]initWithUTF8String:args->rbutton3]; + rbutton3.translatesAutoresizingMaskIntoConstraints = NO; + [buttonArea addSubview:rbutton3]; + NSLayoutXAxisAnchor *anchor = rbutton4 != nil ? rbutton4.leadingAnchor : buttonArea.trailingAnchor; + int off = rbutton4 != nil ? -4 : 0; + [NSLayoutConstraint activateConstraints:@[ + [rbutton3.topAnchor constraintEqualToAnchor:buttonArea.topAnchor constant:0], + [rbutton3.trailingAnchor constraintEqualToAnchor:anchor constant:off] + ]]; + + EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata]; + event.obj = obj; + event.value = 2; + rbutton3.target = event; + rbutton3.action = @selector(handleEvent:); + objc_setAssociatedObject(rbutton3, "eventdata", event, OBJC_ASSOCIATION_RETAIN); + } + switch(args->default_button) { + default: break; + case 1: if(lbutton1 != nil) lbutton1.keyEquivalent = @"\r"; break; + case 2: if(lbutton2 != nil) lbutton2.keyEquivalent = @"\r"; break; + case 3: if(rbutton3 != nil) rbutton3.keyEquivalent = @"\r"; break; + case 4: if(rbutton4 != nil) rbutton4.keyEquivalent = @"\r"; break; + } + + // space between left and right buttons + NSView *space = [[NSView alloc]init]; + space.translatesAutoresizingMaskIntoConstraints = NO; + [buttonArea addSubview:space]; + NSLayoutXAxisAnchor *leftAnchor = buttonArea.leadingAnchor; + NSLayoutXAxisAnchor *rightAnchor = buttonArea.trailingAnchor; + if(lbutton2 != nil) { + leftAnchor = lbutton2.trailingAnchor; + } else if(lbutton1 != nil) { + leftAnchor = lbutton1.trailingAnchor; + } + + if(rbutton3 != nil) { + rightAnchor = rbutton3.leadingAnchor; + } else if(rbutton4 != nil) { + rightAnchor = rbutton4.leadingAnchor; + } + [NSLayoutConstraint activateConstraints:@[ + [space.topAnchor constraintEqualToAnchor:buttonArea.topAnchor], + [space.leadingAnchor constraintEqualToAnchor:leftAnchor constant:10], + [space.trailingAnchor constraintEqualToAnchor:rightAnchor constant:-10] + ]]; + + // dialog window main content + BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; + vbox.translatesAutoresizingMaskIntoConstraints = NO; + [content addSubview:vbox]; + + [NSLayoutConstraint activateConstraints:@[ + [vbox.topAnchor constraintEqualToAnchor:content.topAnchor constant:0], + [vbox.leadingAnchor constraintEqualToAnchor:content.leadingAnchor], + [vbox.trailingAnchor constraintEqualToAnchor:content.trailingAnchor], + [vbox.bottomAnchor constraintEqualToAnchor:buttonArea.topAnchor constant:0] + ]]; + + UiContainerX *container = ui_create_container(obj, vbox); + vbox.container = container; + uic_object_push_container(obj, container); + + return obj; +} + diff --git a/ui/common/args.c b/ui/common/args.c new file mode 100644 index 0000000..796919b --- /dev/null +++ b/ui/common/args.c @@ -0,0 +1,2430 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "args.h" + +#include +#include + +#include "../ui/container.h" + + +/* ---------------------------- UiDialogArgs ---------------------------- */ + +UiDialogArgs* ui_dialog_args_new(void) { + UiDialogArgs *args = malloc(sizeof(UiDialogArgs)); + memset(args, 0, sizeof(UiDialogArgs)); + return args; +} + +void ui_dialog_args_set_title(UiDialogArgs *args, const char *title) { + args->title = strdup(title); +} + +void ui_dialog_args_set_content(UiDialogArgs *args, const char *str) { + args->content = strdup(str); +} + +void ui_dialog_args_set_button1_label(UiDialogArgs *args, const char *label) { + args->button1_label = strdup(label); +} + +void ui_dialog_args_set_button2_label(UiDialogArgs *args, const char *label) { + args->button2_label = strdup(label); +} + +void ui_dialog_args_set_closebutton_label(UiDialogArgs *args, const char *label) { + args->closebutton_label = strdup(label); +} + +void ui_dialog_args_set_input_value(UiDialogArgs *args, const char *value) { + args->input_value = strdup(value); +} + +void ui_dialog_args_set_input(UiDialogArgs *args, UiBool input) { + args->input = input; +} + +void ui_dialog_args_set_password(UiDialogArgs *args, UiBool password) { + args->password = password; +} + +void ui_dialog_args_set_result(UiDialogArgs *args, ui_callback cb) { + args->result = cb; +} + +void ui_dialog_args_set_resultdata(UiDialogArgs *args, void *userdata) { + args->resultdata = userdata; +} + +void ui_dialog_args_free(UiDialogArgs *args) { + free((void*)args->title); + free((void*)args->button1_label); + free((void*)args->button2_label); + free((void*)args->content); + free((void*)args->closebutton_label); + free((void*)args->input_value); + free(args); +} + + +/* -------------------------- UiDialogWindowArgs -------------------------- */ + +UiDialogWindowArgs* ui_dialogwindow_args_new(void) { + UiDialogWindowArgs *args = malloc(sizeof(UiDialogWindowArgs)); + memset(args, 0, sizeof(UiDialogWindowArgs)); + return args; +} + +void ui_dialogwindow_args_set_modal(UiDialogWindowArgs *args, UiTri value) { + args->modal = value; +} + +void ui_dialogwindow_args_set_titlebar_buttons(UiDialogWindowArgs *args, UiTri value) { + args->titlebar_buttons = value; +} + +void ui_dialogwindow_args_set_show_closebutton(UiDialogWindowArgs *args, UiTri value) { + args->show_closebutton = value; +} + +void ui_dialogwindow_args_set_title(UiDialogWindowArgs *args, const char *title) { + args->title = strdup(title); +} + +void ui_dialogwindow_args_set_lbutton1(UiDialogWindowArgs *args, const char *label) { + args->lbutton1 = strdup(label); +} + +void ui_dialogwindow_args_set_lbutton2(UiDialogWindowArgs *args, const char *label) { + args->lbutton2 = strdup(label); +} + +void ui_dialogwindow_args_set_rbutton3(UiDialogWindowArgs *args, const char *label) { + args->rbutton3 = strdup(label); +} + +void ui_dialogwindow_args_set_rbutton4(UiDialogWindowArgs *args, const char *label) { + args->rbutton4 = strdup(label); +} + +void ui_dialogwindow_args_set_lbutton1_states(UiDialogWindowArgs *args, const int *states) { + // TODO +} + +void ui_dialogwindow_args_set_lbutton2_states(UiDialogWindowArgs *args, const int *states) { + // TODO +} + +void ui_dialogwindow_args_set_rbutton3_states(UiDialogWindowArgs *args, const int *states) { + // TODO +} + +void ui_dialogwindow_args_set_rbutton4_states(UiDialogWindowArgs *args, const int *states) { + // TODO +} + +void ui_dialogwindow_args_set_default_button(UiDialogWindowArgs *args, int button) { + args->default_button = button; +} + +void ui_dialogwindow_args_set_width(UiDialogWindowArgs *args, int width) { + args->width = width; +} + +void ui_dialogwindow_args_set_height(UiDialogWindowArgs *args, int height) { + args->height = height; +} + +void ui_dialogwindow_args_set_onclick(UiDialogWindowArgs *args, ui_callback cb) { + args->onclick = cb; +} + +void ui_dialogwindow_args_set_onclickdata(UiDialogWindowArgs *args, void *userdata) { + args->onclickdata = userdata; +} + +void ui_dialogwindow_args_free(UiDialogWindowArgs *args) { + free((void*)args->title); + free((void*)args->lbutton1); + free((void*)args->lbutton2); + free((void*)args->rbutton3); + free((void*)args->rbutton4); + free((void*)args->lbutton1_groups); + free((void*)args->lbutton2_groups); + free((void*)args->rbutton3_groups); + free((void*)args->rbutton4_groups); + free(args); +} + + +/* ---------------------------- UiMenuItemArgs ---------------------------- */ + +UiMenuItemArgs* ui_menuitem_args_new(void) { + UiMenuItemArgs *args = malloc(sizeof(UiMenuItemArgs)); + memset(args, 0, sizeof(UiMenuItemArgs)); + return args; +} + +void ui_menuitem_args_set_label(UiMenuItemArgs *args, const char *label) { + args->label = strdup(label); +} + +void ui_menuitem_args_set_icon(UiMenuItemArgs *args, const char *icon) { + args->icon = strdup(icon); +} + +void ui_menuitem_args_set_onclick(UiMenuItemArgs *args, ui_callback callback) { + args->onclick = callback; +} + +void ui_menuitem_args_set_onclickdata(UiMenuItemArgs *args, void *onclickdata) { + args->onclickdata = onclickdata; +} + +void ui_menuitem_args_free(UiMenuItemArgs *args) { + free((void*)args->label); + free((void*)args->icon); + free(args); +} + + +/* ---------------------------- UiMenuToggleItemArgs ---------------------------- */ + +UiMenuToggleItemArgs* ui_menutoggleitem_args_new(void) { + UiMenuToggleItemArgs *args = malloc(sizeof(UiMenuToggleItemArgs)); + memset(args, 0, sizeof(UiMenuToggleItemArgs)); + return args; +} + +void ui_menutoggleitem_args_set_label(UiMenuToggleItemArgs *args, const char *label) { + args->label = strdup(label); +} + +void ui_menutoggleitem_args_set_icon(UiMenuToggleItemArgs *args, const char *icon) { + args->icon = strdup(icon); +} + +void ui_menutoggleitem_args_set_varname(UiMenuToggleItemArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_menutoggleitem_args_set_onchange(UiMenuToggleItemArgs *args, ui_callback callback) { + args->onchange = callback; +} + +void ui_menutoggleitem_args_set_onchangedata(UiMenuToggleItemArgs *args, void *onclickdata) { + args->onchangedata = onclickdata; +} + +void ui_menutoggleitem_args_free(UiMenuToggleItemArgs *args) { + free((void*)args->label); + free((void*)args->icon); + free((void*)args->varname); + free(args); +} + +/* --------------------------- UiMenuItemListArgs --------------------------- */ + +UiMenuItemListArgs* ui_menuitemlist_args_new(void) { + UiMenuItemListArgs *args = malloc(sizeof(UiMenuItemListArgs)); + memset(args, 0, sizeof(UiMenuItemListArgs)); + return args; +} + +void ui_menuitemlist_args_set_varname(UiMenuItemListArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_menuitemlist_args_set_getvalue(UiMenuItemListArgs *args, ui_getvaluefunc func) { + args->getvalue = func; +} + +void ui_menuitemlist_args_set_onselect(UiMenuItemListArgs *args, ui_callback callback) { + args->onselect = callback; +} + +void ui_menuitemlist_args_set_onselectdata(UiMenuItemListArgs *args, void *data){ + args->onselectdata = data; +} + +void ui_menuitemlist_args_set_addseparator(UiMenuItemListArgs *args, UiBool value) { + args->addseparator = value; +} + +void ui_menuitemlist_args_free(UiMenuItemListArgs *args){ + free((void*)args->varname); + free(args); +} + +/* --------------------------- UiToolbarItemArgs --------------------------- */ + +UiToolbarItemArgs* ui_toolbar_item_args_new(void) { + UiToolbarItemArgs *args = malloc(sizeof(UiToolbarItemArgs)); + memset(args, 0, sizeof(UiToolbarItemArgs)); + return args; +} + +void ui_toolbar_item_args_set_label(UiToolbarItemArgs *args, const char *label) { + args->label = strdup(label); +} + +void ui_toolbar_item_args_set_icon(UiToolbarItemArgs *args, const char *icon) { + args->icon = strdup(icon); +} + +void ui_toolbar_item_args_set_tooltip(UiToolbarItemArgs *args, const char *tooltip) { + args->tooltip = strdup(tooltip); +} + +void ui_toolbar_item_args_set_onclick(UiToolbarItemArgs *args, ui_callback callback) { + args->onclick = callback; +} + +void ui_toolbar_item_args_set_onclickdata(UiToolbarItemArgs *args, void *onclickdata) { + args->onclickdata = onclickdata; +} + +void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; +} +void ui_toolbar_item_args_free(UiToolbarItemArgs *args) { + free((void*)args->label); + free((void*)args->icon); + free((void*)args->tooltip); + free((void*)args->groups); + free(args); +} + +/* ---------------------------- UiToolbarToggleItemArgs ---------------------------- */ + +UiToolbarToggleItemArgs* ui_toolbar_toggleitem_args_new(void) { + UiToolbarToggleItemArgs *args = malloc(sizeof(UiToolbarToggleItemArgs)); + memset(args, 0, sizeof(UiToolbarToggleItemArgs)); + return args; +} + +void ui_toolbar_toggleitem_args_set_label(UiToolbarToggleItemArgs *args, const char *label) { + args->label = strdup(label); +} + +void ui_toolbar_toggleitem_args_set_icon(UiToolbarToggleItemArgs *args, const char *icon) { + args->icon = strdup(icon); +} + +void ui_toolbar_toggleitem_args_set_tooltip(UiToolbarToggleItemArgs *args, const char *tooltip) { + args->tooltip = strdup(tooltip); +} + +void ui_toolbar_toggleitem_args_set_varname(UiToolbarToggleItemArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_toolbar_toggleitem_args_set_onchange(UiToolbarToggleItemArgs *args, ui_callback callback) { + args->onchange = callback; +} + +void ui_toolbar_toggleitem_args_set_onchangedata(UiToolbarToggleItemArgs *args, void *onchangedata) { + args->onchangedata = onchangedata; +} + +void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args,int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; +} + +void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args) { + free((void*)args->label); + free((void*)args->icon); + free((void*)args->tooltip); + free((void*)args->varname); + free((void*)args->groups); + free(args); +} + +/* ---------------------------- UiToolbarMenuArgs ---------------------------- */ + + +UiToolbarMenuArgs* ui_toolbar_menu_args_new(void) { + UiToolbarMenuArgs *args = malloc(sizeof(UiToolbarMenuArgs)); + memset(args, 0, sizeof(UiToolbarMenuArgs)); + return args; +} + +void ui_toolbar_menu_args_set_label(UiToolbarMenuArgs *args, const char *label) { + args->label = strdup(label); +} + +void ui_toolbar_menu_args_set_icon(UiToolbarMenuArgs *args, const char *icon) { + args->icon = strdup(icon); +} + +void ui_toolbar_menu_args_set_tooltip(UiToolbarMenuArgs *args, const char *tooltip) { + args->tooltip = strdup(tooltip); +} + +void ui_toolbar_menu_args_free(UiToolbarMenuArgs *args) { + free((void*)args->label); + free((void*)args->icon); + free((void*)args->tooltip); + free(args); +} + + +/* ---------------------------- UiContainerArgs ---------------------------- */ + +UiContainerArgs* ui_container_args_new(void) { + UiContainerArgs *args = malloc(sizeof(UiContainerArgs)); + memset(args, 0, sizeof(UiContainerArgs)); + return args; +} + +void ui_container_args_set_fill(UiContainerArgs *args, UiBool fill) { + args->fill = fill; +} + +void ui_container_args_set_hexpand(UiContainerArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_container_args_set_vexpand(UiContainerArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_container_args_set_hfill(UiContainerArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_container_args_set_vfill(UiContainerArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_container_args_set_override_defaults(UiContainerArgs *args, UiBool value) { + args->override_defaults = value; +} + + +void ui_container_args_set_colspan(UiContainerArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_container_args_set_rowspan(UiContainerArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_container_args_set_def_hexpand(UiContainerArgs *args, UiBool value) { + args->def_hexpand = value; +} + + +void ui_container_args_set_def_vexpand(UiContainerArgs *args, UiBool value) { + args->def_vexpand = value; +} + + +void ui_container_args_set_def_hfill(UiContainerArgs *args, UiBool value) { + args->def_hfill = value; +} + + +void ui_container_args_set_def_vfill(UiContainerArgs *args, UiBool value) { + args->def_vfill = value; +} + + +void ui_container_args_set_name(UiContainerArgs *args, const char *name) { + args->name = strdup(name); +} + + +void ui_container_args_set_style_class(UiContainerArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + + +void ui_container_args_set_margin(UiContainerArgs *args, int value) { + args->margin = value; +} + +void ui_container_args_set_margin_left(UiContainerArgs *args, int value) { + args->margin_left = value; +} + +void ui_container_args_set_margin_right(UiContainerArgs *args, int value) { + args->margin_right = value; +} + +void ui_container_args_set_margin_top(UiContainerArgs *args, int value) { + args->margin_top = value; +} + +void ui_container_args_set_margin_bottom(UiContainerArgs *args, int value) { + args->margin_bottom = value; +} + + +void ui_container_args_set_spacing(UiContainerArgs *args, int value) { + args->spacing = value; +} + + +void ui_container_args_set_columnspacing(UiContainerArgs *args, int value) { + args->columnspacing = value; +} + + +void ui_container_args_set_rowspacing(UiContainerArgs *args, int value) { + args->rowspacing = value; +} + + +void ui_container_args_free(UiContainerArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free(args); +} + + +/* ------------------------------- UiFrameArgs ------------------------------*/ + +UiFrameArgs* ui_frame_args_new(void) { + UiFrameArgs *args = malloc(sizeof(UiFrameArgs)); + memset(args, 0, sizeof(UiContainerArgs)); + return args; +} + + +void ui_frame_args_set_fill(UiFrameArgs *args, UiBool fill) { + args->fill = fill; +} + + +void ui_frame_args_set_hexpand(UiFrameArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_frame_args_set_vexpand(UiFrameArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_frame_args_set_hfill(UiFrameArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_frame_args_set_vfill(UiFrameArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_frame_args_set_override_defaults(UiFrameArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_frame_args_set_margin(UiFrameArgs *args, int value) { + args->margin = value; +} + +void ui_frame_args_set_margin_left(UiFrameArgs *args, int value) { + args->margin_left = value; +} + +void ui_frame_args_set_margin_right(UiFrameArgs *args, int value) { + args->margin_right = value; +} + +void ui_frame_args_set_margin_top(UiFrameArgs *args, int value) { + args->margin_top = value; +} + +void ui_frame_args_set_margin_bottom(UiFrameArgs *args, int value) { + args->margin_bottom = value; +} + + +void ui_frame_args_set_colspan(UiFrameArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_frame_args_set_rowspan(UiFrameArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_frame_args_set_name(UiFrameArgs *args, const char *name) { + args->name = strdup(name); +} + + +void ui_frame_args_set_style_class(UiFrameArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_frame_args_set_subcontainer(UiFrameArgs *args, UiSubContainerType subcontainer) { + args->subcontainer = subcontainer; +} + +void ui_frame_args_set_padding(UiFrameArgs *args, int value) { + args->padding = value; +} + +void ui_frame_args_set_spacing(UiFrameArgs *args, int value) { + args->spacing = value; +} + + +void ui_frame_args_set_columnspacing(UiFrameArgs *args, int value) { + args->columnspacing = value; +} + + +void ui_frame_args_set_rowspacing(UiFrameArgs *args, int value) { + args->rowspacing = value; +} + + +void ui_frame_args_set_expanded(UiFrameArgs *args, UiBool value) { + args->isexpanded = value; +} + + +void ui_frame_args_set_label(UiFrameArgs *args, const char *label) { + args->label = strdup(label); +} + +void ui_frame_args_free(UiFrameArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->label); + free(args); +} + + +/* ---------------------------- UiSidebarArgs -------------------------------*/ + +UiSidebarArgs* ui_sidebar_args_new(void) { + UiSidebarArgs *args = malloc(sizeof(UiSidebarArgs)); + memset(args, 0, sizeof(UiSidebarArgs)); + return args; +} + +void ui_sidebar_args_set_name(UiSidebarArgs *args, const char *name) { + args->name = strdup(name); +} + +void ui_sidebar_args_set_style_class(UiSidebarArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_sidebar_args_set_margin(UiSidebarArgs *args, int value) { + args->margin = value; +} + +void ui_sidebar_args_set_margin_left(UiSidebarArgs *args, int value) { + args->margin_left = value; +} + +void ui_sidebar_args_set_margin_right(UiSidebarArgs *args, int value) { + args->margin_right = value; +} + +void ui_sidebar_args_set_margin_top(UiSidebarArgs *args, int value) { + args->margin_top = value; +} + +void ui_sidebar_args_set_margin_bottom(UiSidebarArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_sidebar_args_set_spacing(UiSidebarArgs *args, int value) { + args->spacing = value; +} + +void ui_sidebar_args_free(UiSidebarArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free(args); +} + + +/* --------------------------- UiSplitPaneArgs ------------------------------*/ + +UiSplitPaneArgs* ui_splitpane_args_new(void) { + UiSplitPaneArgs *args = malloc(sizeof(UiSplitPaneArgs)); + memset(args, 0, sizeof(UiSplitPaneArgs)); + return args; +} + +void ui_splitpane_args_set_fill(UiSplitPaneArgs *args, UiBool fill) { + args->fill = fill; +} + +void ui_splitpane_args_set_hexpand(UiSplitPaneArgs *args, UiBool value) { + args->hexpand = value; +} + +void ui_splitpane_args_set_vexpand(UiSplitPaneArgs *args, UiBool value) { + args->vexpand = value; +} + +void ui_splitpane_args_set_hfill(UiSplitPaneArgs *args, UiBool value) { + args->hfill = value; +} + +void ui_splitpane_args_set_vfill(UiSplitPaneArgs *args, UiBool value) { + args->vfill = value; +} + +void ui_splitpane_args_set_override_defaults(UiSplitPaneArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_splitpane_args_set_colspan(UiSplitPaneArgs *args, int colspan) { + args->colspan = colspan; +} + +void ui_splitpane_args_set_rowspan(UiSplitPaneArgs *args, int rowspan) { + args->rowspan = rowspan; +} + +void ui_splitpane_args_set_name(UiSplitPaneArgs *args, const char *name) { + args->name = strdup(name); +} + +void ui_splitpane_args_set_style_class(UiSplitPaneArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_splitpane_args_set_margin(UiSplitPaneArgs *args, int value) { + args->margin = value; +} + +void ui_splitpane_args_set_margin_left(UiSplitPaneArgs *args, int value) { + args->margin_left = value; +} + +void ui_splitpane_args_set_margin_right(UiSplitPaneArgs *args, int value) { + args->margin_right = value; +} + +void ui_splitpane_args_set_margin_top(UiSplitPaneArgs *args, int value) { + args->margin_top = value; +} + +void ui_splitpane_args_set_margin_bottom(UiSplitPaneArgs *args, int value) { + args->margin_bottom = value; +} + + +void ui_splitpane_args_set_spacing(UiSplitPaneArgs *args, int value) { + args->spacing = value; +} + + +void ui_splitpane_args_set_columnspacing(UiSplitPaneArgs *args, int value) { + args->columnspacing = value; +} + + +void ui_splitpane_args_set_rowspacing(UiSplitPaneArgs *args, int value) { + args->rowspacing = value; +} + + +void ui_splitpane_args_set_initial_position(UiSplitPaneArgs *args, int pos) { + args->initial_position = pos; +} + +void ui_splitpane_args_set_position_property(UiSplitPaneArgs *args, const char *propname) { + args->position_property = strdup(propname); +} + +void ui_splitpane_args_set_varname(UiSplitPaneArgs *args, const char *varname) { + args->varname = strdup(varname); +} + + +void ui_splitpane_args_set_value(UiSplitPaneArgs *args, UiInteger *value) { + args->value = value; +} + +void ui_splitpane_args_set_max_panes(UiSplitPaneArgs *args, int max) { + args->max_panes = max; +} + +void ui_splitpane_args_free(UiSplitPaneArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->varname); + free((void*)args->position_property); + free(args); +} + + +/* ---------------------------- UiTabViewArgs ---------------------------- */ + +UiTabViewArgs* ui_tabview_args_new(void) { + UiTabViewArgs *args = malloc(sizeof(UiTabViewArgs)); + memset(args, 0, sizeof(UiTabViewArgs)); + return args; +} + +void ui_tabview_args_set_fill(UiTabViewArgs *args, UiBool fill) { + args->fill = fill; +} + +void ui_tabview_args_set_hexpand(UiTabViewArgs *args, UiBool value) { + args->hexpand = value; +} + +void ui_tabview_args_set_vexpand(UiTabViewArgs *args, UiBool value) { + args->vexpand = value; +} + +void ui_tabview_args_set_hfill(UiTabViewArgs *args, UiBool value) { + args->hfill = value; +} + +void ui_tabview_args_set_vfill(UiTabViewArgs *args, UiBool value) { + args->vfill = value; +} + +void ui_tabview_args_set_override_defaults(UiTabViewArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_tabview_args_set_colspan(UiTabViewArgs *args, int colspan) { + args->colspan = colspan; +} + +void ui_tabview_args_set_rowspan(UiTabViewArgs *args, int rowspan) { + args->rowspan = rowspan; +} + +void ui_tabview_args_set_name(UiTabViewArgs *args, const char *name) { + args->name = strdup(name); +} + +void ui_tabview_args_set_style_class(UiTabViewArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_tabview_args_set_margin(UiTabViewArgs *args, int value) { + args->margin = value; +} + +void ui_tabview_args_set_margin_left(UiTabViewArgs *args, int value) { + args->margin_left = value; +} + +void ui_tabview_args_set_margin_right(UiTabViewArgs *args, int value) { + args->margin_right = value; +} + +void ui_tabview_args_set_margin_top(UiTabViewArgs *args, int value) { + args->margin_top = value; +} + +void ui_tabview_args_set_margin_bottom(UiTabViewArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_tabview_args_set_padding(UiTabViewArgs *args, int value) { + args->padding = value; +} + +void ui_tabview_args_set_spacing(UiTabViewArgs *args, int value) { + args->spacing = value; +} + +void ui_tabview_args_set_columnspacing(UiTabViewArgs *args, int value) { + args->columnspacing = value; +} + + +void ui_tabview_args_set_rowspacing(UiTabViewArgs *args, int value) { + args->rowspacing = value; +} + +void ui_tabview_args_set_type(UiTabViewArgs *args, UiTabViewType tabview) { + args->tabview = tabview; +} + +void ui_tabview_args_set_onchange(UiTabViewArgs *args, ui_callback cb) { + args->onchange = cb; +} + +void ui_tabview_args_set_onchangedata(UiTabViewArgs *args, void *userdata) { + args->onchangedata = userdata; +} + +void ui_tabview_args_set_varname(UiTabViewArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_tabview_args_set_value(UiTabViewArgs *args, UiInteger *value) { + args->value = value; +} + +void ui_tabview_args_set_subcontainer(UiTabViewArgs *args, UiSubContainerType subcontainer) { + args->subcontainer = subcontainer; +} + +void ui_tabview_args_free(UiTabViewArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->varname); + free(args); +} + + +/* ------------------------- UiWidgetArgs ----------------------------*/ + +UiWidgetArgs* ui_widget_args_new(void) { + UiWidgetArgs *args = malloc(sizeof(UiWidgetArgs)); + memset(args, 0, sizeof(UiWidgetArgs)); + return args; +} + + +void ui_widget_args_set_fill(UiWidgetArgs *args, UiBool fill) { + args->fill = fill; +} + + +void ui_widget_args_set_hexpand(UiWidgetArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_widget_args_set_vexpand(UiWidgetArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_widget_args_set_hfill(UiWidgetArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_widget_args_set_vfill(UiWidgetArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_widget_args_set_override_defaults(UiWidgetArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_widget_args_set_margin(UiWidgetArgs *args, int value) { + args->margin = value; +} + +void ui_widget_args_set_margin_left(UiWidgetArgs *args, int value) { + args->margin_left = value; +} + +void ui_widget_args_set_margin_right(UiWidgetArgs *args, int value) { + args->margin_right = value; +} + +void ui_widget_args_set_margin_top(UiWidgetArgs *args, int value) { + args->margin_top = value; +} + +void ui_widget_args_set_margin_bottom(UiWidgetArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_widget_args_set_colspan(UiWidgetArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_widget_args_set_rowspan(UiWidgetArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_widget_args_set_name(UiWidgetArgs *args, const char *name) { + args->name = strdup(name); +} + + +void ui_widget_args_set_style_class(UiWidgetArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_widget_args_free(UiWidgetArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free(args); +} + + +/* ------------------------- UiLabelArgs ----------------------------*/ + + +UiLabelArgs* ui_label_args_new(void) { + UiLabelArgs *args = malloc(sizeof(UiLabelArgs)); + memset(args, 0, sizeof(UiLabelArgs)); + return args; +} + +void ui_label_args_set_fill(UiLabelArgs *args, UiBool fill) { + args->fill = fill; +} + +void ui_label_args_set_hexpand(UiLabelArgs *args, UiBool value) { + args->hexpand = value; +} + +void ui_label_args_set_vexpand(UiLabelArgs *args, UiBool value) { + args->vexpand = value; +} + +void ui_label_args_set_hfill(UiLabelArgs *args, UiBool value) { + args->hfill = value; +} + +void ui_label_args_set_vfill(UiLabelArgs *args, UiBool value) { + args->vfill = value; +} + +void ui_label_args_set_override_defaults(UiLabelArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_label_args_set_margin(UiLabelArgs *args, int value) { + args->margin = value; +} + +void ui_label_args_set_margin_left(UiLabelArgs *args, int value) { + args->margin_left = value; +} + +void ui_label_args_set_margin_right(UiLabelArgs *args, int value){ + args->margin_right = value; +} + +void ui_label_args_set_margin_top(UiLabelArgs *args, int value) { + args->margin_top = value; +} + +void ui_label_args_set_margin_bottom(UiLabelArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_label_args_set_colspan(UiLabelArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_label_args_set_rowspan(UiLabelArgs *args, int rowspan) { + args->rowspan = rowspan; +} + +void ui_label_args_set_name(UiLabelArgs *args, const char *name) { + args->name = strdup(name); +} + +void ui_label_args_set_style_class(UiLabelArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_label_args_set_label(UiLabelArgs *args, const char *label){ + args->label = strdup(label); +} + +void ui_label_args_set_align(UiLabelArgs *args, UiAlignment align) { + args->align = align; +} + +void ui_label_args_set_style(UiLabelArgs *args, UiLabelStyle style) { + args->style = style; +} + +void ui_label_args_set_varname(UiLabelArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_label_args_set_value(UiLabelArgs *args, UiString *value) { + args->value = value; +} + +void ui_label_args_free(UiLabelArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->label); + free((void*)args->varname); + free(args); +} + + +/* ------------------------- UiProgressbarArgs ----------------------------*/ + + +UiProgressbarArgs* ui_progressbar_args_new(void) { + UiProgressbarArgs *args = malloc(sizeof(UiProgressbarArgs)); + memset(args, 0, sizeof(UiProgressbarArgs)); + return args; +} + + +void ui_progressbar_args_set_fill(UiProgressbarArgs *args, UiBool fill) { + args->fill = fill; +} + + +void ui_progressbar_args_set_hexpand(UiProgressbarArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_progressbar_args_set_vexpand(UiProgressbarArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_progressbar_args_set_hfill(UiProgressbarArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_progressbar_args_set_vfill(UiProgressbarArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_progressbar_args_set_override_defaults(UiProgressbarArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_progressbar_args_set_margin(UiProgressbarArgs *args, int value) { + args->margin = value; +} + +void ui_progressbar_args_set_margin_left(UiProgressbarArgs *args, int value) { + args->margin_left = value; +} + +void ui_progressbar_args_set_margin_right(UiProgressbarArgs *args, int value) { + args->margin_right = value; +} + +void ui_progressbar_args_set_margin_top(UiProgressbarArgs *args, int value) { + args->margin_top = value; +} + +void ui_progressbar_args_set_margin_bottom(UiProgressbarArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_progressbar_args_set_colspan(UiProgressbarArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_progressbar_args_set_rowspan(UiProgressbarArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_progressbar_args_set_name(UiProgressbarArgs *args, const char *name) { + args->name = strdup(name); +} + + +void ui_progressbar_args_set_style_class(UiProgressbarArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_progressbar_args_set_min(UiProgressbarArgs *args, double min) { + args->min = min; +} + +void ui_progressbar_args_set_max(UiProgressbarArgs *args, double max) { + args->max = max; +} + +void ui_progressbar_args_set_varname(UiProgressbarArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_progressbar_args_set_value(UiProgressbarArgs *args, UiDouble *value) { + args->value = value; +} + +void ui_progressbar_args_free(UiProgressbarArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->varname); + free(args); +} + + +/* ------------------------- UiProgressbarSpinnerArgs ----------------------------*/ + +UiProgressbarSpinnerArgs* ui_progress_spinner_args_new(void) { + UiProgressbarSpinnerArgs *args = malloc(sizeof(UiProgressbarSpinnerArgs)); + memset(args, 0, sizeof(UiProgressbarSpinnerArgs)); + return args; +} + +void ui_progress_spinner_args_set_fill(UiProgressbarSpinnerArgs *args, UiBool fill) { + args->fill = fill; +} + +void ui_progress_spinner_args_set_hexpand(UiProgressbarSpinnerArgs *args, UiBool value) { + args->hexpand = value; +} + +void ui_progress_spinner_args_set_vexpand(UiProgressbarSpinnerArgs *args, UiBool value) { + args->vexpand = value; +} + +void ui_progress_spinner_args_set_hfill(UiProgressbarSpinnerArgs *args, UiBool value) { + args->hfill = value; +} + +void ui_progress_spinner_args_set_vfill(UiProgressbarSpinnerArgs *args, UiBool value) { + args->vfill = value; +} + +void ui_progress_spinner_args_set_override_defaults(UiProgressbarSpinnerArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_progress_spinner_args_set_margin(UiProgressbarSpinnerArgs *args, int value) { + args->margin = value; +} + +void ui_progress_spinner_args_set_margin_left(UiProgressbarSpinnerArgs *args, int value) { + args->margin_left = value; +} + +void ui_progress_spinner_args_set_margin_right(UiProgressbarSpinnerArgs *args, int value) { + args->margin_right = value; +} + +void ui_progress_spinner_args_set_margin_top(UiProgressbarSpinnerArgs *args, int value) { + args->margin_top = value; +} + +void ui_progress_spinner_args_set_margin_bottom(UiProgressbarSpinnerArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_progress_spinner_args_set_colspan(UiProgressbarSpinnerArgs *args, int colspan) { + args->colspan = colspan; +} + +void ui_progress_spinner_args_set_rowspan(UiProgressbarSpinnerArgs *args, int rowspan) { + args->rowspan = rowspan; +} + +void ui_progress_spinner_args_set_name(UiProgressbarSpinnerArgs *args, const char *name) { + args->name = strdup(name); +} + +void ui_progress_spinner_args_set_style_class(UiProgressbarSpinnerArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_progress_spinner_args_set_varname(UiProgressbarSpinnerArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_progress_spinner_args_set_value(UiProgressbarSpinnerArgs *args, UiInteger *value) { + args->value = value; +} + +void ui_progress_spinner_args_free(UiProgressbarSpinnerArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->varname); + free(args); +} + + +/* ---------------------------- UiButtonArgs -------------------------------*/ + +UiButtonArgs* ui_button_args_new(void) { + UiButtonArgs *args = malloc(sizeof(UiButtonArgs)); + memset(args, 0, sizeof(UiButtonArgs)); + return args; +} + + +void ui_button_args_set_fill(UiButtonArgs *args, UiBool fill) { + args->fill = fill; +} + + +void ui_button_args_set_hexpand(UiButtonArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_button_args_set_vexpand(UiButtonArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_button_args_set_hfill(UiButtonArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_button_args_set_vfill(UiButtonArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_button_args_set_override_defaults(UiButtonArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_button_args_set_margin(UiButtonArgs *args, int value) { + args->margin = value; +} + +void ui_button_args_set_margin_left(UiButtonArgs *args, int value) { + args->margin_left = value; +} + +void ui_button_args_set_margin_right(UiButtonArgs *args, int value) { + args->margin_right = value; +} + +void ui_button_args_set_margin_top(UiButtonArgs *args, int value) { + args->margin_top = value; +} + +void ui_button_args_set_margin_bottom(UiButtonArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_button_args_set_colspan(UiButtonArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_button_args_set_rowspan(UiButtonArgs *args, int rowspan) { + args->rowspan = rowspan; +} + +void ui_button_args_set_name(UiButtonArgs *args, const char *name) { + args->name = strdup(name); +} + +void ui_button_args_set_style_class(UiButtonArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_button_args_set_label(UiButtonArgs *args, const char *label){ + args->label = strdup(label); +} + +void ui_button_args_set_icon(UiButtonArgs *args, const char *icon){ + args->icon = strdup(icon); +} + +void ui_button_args_set_tooltip(UiButtonArgs *args, const char *tooltip) { + args->tooltip = strdup(tooltip); +} + +void ui_button_args_set_labeltype(UiButtonArgs *args, int labeltype){ + args->labeltype = labeltype; +} + +void ui_button_args_set_onclick(UiButtonArgs *args, ui_callback callback){ + args->onclick = callback; +} + +void ui_button_args_set_onclickdata(UiButtonArgs *args, void *onclickdata){ + args->onclickdata = onclickdata; +} + +void ui_button_args_set_groups(UiButtonArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; +} + +void ui_button_args_free(UiButtonArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->label); + free((void*)args->icon); + free((void*)args->tooltip); + free((void*)args->groups); + free(args); +} + + +/* ------------------------- UiToggleArgs ----------------------------*/ + + +UiToggleArgs* ui_toggle_args_new(void) { + UiToggleArgs *args = malloc(sizeof(UiToggleArgs)); + memset(args, 0, sizeof(UiToggleArgs)); + return args; +} + +void ui_toggle_args_set_fill(UiToggleArgs *args, UiBool fill) { + args->fill = fill; +} + +void ui_toggle_args_set_hexpand(UiToggleArgs *args, UiBool value) { + args->hexpand = value; +} + +void ui_toggle_args_set_vexpand(UiToggleArgs *args, UiBool value) { + args->vexpand = value; +} + +void ui_toggle_args_set_hfill(UiToggleArgs *args, UiBool value) { + args->hfill = value; +} + +void ui_toggle_args_set_vfill(UiToggleArgs *args, UiBool value) { + args->vfill = value; +} + +void ui_toggle_args_set_override_defaults(UiToggleArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_toggle_args_set_margin(UiToggleArgs *args, int value) { + args->margin = value; +} + +void ui_toggle_args_set_margin_left(UiToggleArgs *args, int value) { + args->margin_left = value; +} + +void ui_toggle_args_set_margin_right(UiToggleArgs *args, int value) { + args->margin_right = value; +} + +void ui_toggle_args_set_margin_top(UiToggleArgs *args, int value) { + args->margin_top = value; +} + +void ui_toggle_args_set_margin_bottom(UiToggleArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_toggle_args_set_colspan(UiToggleArgs *args, int colspan) { + args->colspan = colspan; +} + +void ui_toggle_args_set_rowspan(UiToggleArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_toggle_args_set_name(UiToggleArgs *args, const char *name) { + args->name = strdup(name); +} + +void ui_toggle_args_set_style_class(UiToggleArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_toggle_args_set_label(UiToggleArgs *args, const char *label){ + args->label = strdup(label); +} + +void ui_toggle_args_set_icon(UiToggleArgs *args, const char *icon){ + args->icon = strdup(icon); +} + +void ui_toggle_args_set_tooltip(UiToggleArgs *args, const char *tooltip) { + args->tooltip = strdup(tooltip); +} + +void ui_toggle_args_set_labeltype(UiToggleArgs *args, int labeltype){ + args->labeltype = labeltype; +} + +void ui_toggle_args_set_onchange(UiToggleArgs *args, ui_callback callback){ + args->onchange = callback; +} + +void ui_toggle_args_set_onchangedata(UiToggleArgs *args, void *onchangedata){ + args->onchangedata = onchangedata; +} + +void ui_toggle_args_set_varname(UiToggleArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_toggle_args_set_value(UiToggleArgs *args, UiInteger *value) { + args->value = value; +} + +void ui_toggle_args_set_enablegroup(UiToggleArgs *args, int group) { + args->enable_group = group; +} + +void ui_toggle_args_set_groups(UiToggleArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; +} + +void ui_toggle_args_free(UiToggleArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->label); + free((void*)args->icon); + free((void*)args->tooltip); + free((void*)args->varname); + free((void*)args->groups); + free(args); +} + +/* ------------------------- UiLinkButtonArgs ----------------------------*/ + + +UiLinkButtonArgs* ui_linkbutton_args_new(void) { + UiLinkButtonArgs *args = malloc(sizeof(UiLinkButtonArgs)); + memset(args, 0, sizeof(UiLinkButtonArgs)); + return args; +} + + +void ui_linkbutton_args_set_fill(UiLinkButtonArgs *args, UiBool fill) { + args->fill = fill; +} + + +void ui_linkbutton_args_set_hexpand(UiLinkButtonArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_linkbutton_args_set_vexpand(UiLinkButtonArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_linkbutton_args_set_hfill(UiLinkButtonArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_linkbutton_args_set_vfill(UiLinkButtonArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_linkbutton_args_set_override_defaults(UiLinkButtonArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_linkbutton_args_set_margin(UiLinkButtonArgs *args, int value) { + args->margin = value; +} + +void ui_linkbutton_args_set_margin_left(UiLinkButtonArgs *args, int value) { + args->margin_left = value; +} + +void ui_linkbutton_args_set_margin_right(UiLinkButtonArgs *args, int value) { + args->margin_right = value; +} + +void ui_linkbutton_args_set_margin_top(UiLinkButtonArgs *args, int value) { + args->margin_top = value; +} + +void ui_linkbutton_args_set_margin_bottom(UiLinkButtonArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_linkbutton_args_set_colspan(UiLinkButtonArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_linkbutton_args_set_rowspan(UiLinkButtonArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_linkbutton_args_set_name(UiLinkButtonArgs *args, const char *name) { + args->name = strdup(name); +} + + +void ui_linkbutton_args_set_style_class(UiLinkButtonArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_linkbutton_args_set_label(UiLinkButtonArgs *args, const char *label){ + args->label = strdup(label); +} + +void ui_linkbutton_args_set_uri(UiLinkButtonArgs *args, const char *uri) { + args->uri = strdup(uri); +} + +void ui_linkbutton_args_set_onclick(UiLinkButtonArgs *args, ui_callback callback) { + args->onclick = callback; +} + +void ui_linkbutton_args_set_onclickdata(UiLinkButtonArgs *args, void *userdata) { + args->onclickdata = userdata; +} + +void ui_linkbutton_args_set_nofollow(UiLinkButtonArgs *args, UiBool value) { + args->nofollow = value; +} + +void ui_linkbutton_args_set_type(UiLinkButtonArgs *args, UiLinkType type) { + args->type = type; +} + +void ui_linkbutton_args_set_varname(UiLinkButtonArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_linkbutton_args_set_value(UiLinkButtonArgs *args, UiString *value) { + args->value = value; +} + +void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; +} + +void ui_linkbutton_args_free(UiLinkButtonArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->label); + free((void*)args->uri); + free((void*)args->varname); + free((void*)args->groups); + free(args); +} + + +/* ------------------------- UiListArgs ----------------------------*/ + +UiListArgs* ui_list_args_new(void) { + UiListArgs *args = malloc(sizeof(UiListArgs)); + memset(args, 0, sizeof(UiListArgs)); + return args; +} + +void ui_list_args_set_fill(UiListArgs *args, UiBool fill) { + args->fill = fill; +} + +void ui_list_args_set_hexpand(UiListArgs *args, UiBool value) { + args->hexpand = value; +} + +void ui_list_args_set_vexpand(UiListArgs *args, UiBool value) { + args->vexpand = value; +} + +void ui_list_args_set_hfill(UiListArgs *args, UiBool value) { + args->hfill = value; +} + +void ui_list_args_set_vfill(UiListArgs *args, UiBool value) { + args->vfill = value; +} + +void ui_list_args_set_override_defaults(UiListArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_list_args_set_margin(UiListArgs *args, int value) { + args->margin = value; +} + +void ui_list_args_set_margin_left(UiListArgs *args, int value) { + args->margin_left = value; +} + +void ui_list_args_set_margin_right(UiListArgs *args, int value) { + args->margin_right = value; +} + +void ui_list_args_set_margin_top(UiListArgs *args, int value) { + args->margin_top = value; +} + +void ui_list_args_set_margin_bottom(UiListArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_list_args_set_colspan(UiListArgs *args, int colspan) { + args->colspan = colspan; +} + +void ui_list_args_set_rowspan(UiListArgs *args, int rowspan) { + args->rowspan = rowspan; +} + +void ui_list_args_set_name(UiListArgs *args, const char *name) { + args->name = strdup(name); +} + +void ui_list_args_set_style_class(UiListArgs *args, const char *classname) { + args->style_class = classname; +} + +void ui_list_args_set_varname(UiListArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_list_args_set_value(UiListArgs *args, UiList *value) { + args->list = value; +} + +void ui_list_args_set_model(UiListArgs *args, UiModel *model) { + args->model = model; +} + +void ui_list_args_set_static_elements(UiListArgs *args, char **strarray, size_t nelm) { + char **array = calloc(nelm, sizeof(char*)); + for(int i=0;istatic_elements = array; + args->static_nelm = nelm; +} + +void ui_list_args_set_getvalue_func(UiListArgs *args, ui_getvaluefunc getvalue) { + args->getvalue = getvalue; +} + +void ui_list_args_set_getvalue_func2(UiListArgs *args, ui_getvaluefunc2 getvalue) { + args->getvalue2 = getvalue; +} + +void ui_list_args_set_getvalue_data(UiListArgs *args, void *userdata) { + args->getvalue2data = userdata; +} + +void ui_list_args_set_getstyle_func(UiListArgs *args, ui_getstylefunc getstyle) { + args->getstyle = getstyle; +} + +void ui_list_args_set_getstyle_data(UiListArgs *args, void *userdata) { + args->getstyledata = userdata; +} + +void ui_list_args_set_onactivate(UiListArgs *args, ui_callback callback) { + args->onactivate = callback; +} + +void ui_list_args_set_onactivatedata(UiListArgs *args, void *userdata) { + args->onactivatedata = userdata; +} + +void ui_list_args_set_onselection(UiListArgs *args, ui_callback callback) { + args->onselection = callback; +} + +void ui_list_args_set_onselectiondata(UiListArgs *args, void *userdata) { + args->onselectiondata = userdata; +} + +void ui_list_args_set_ondragstart(UiListArgs *args, ui_callback callback) { + args->ondragstart = callback; +} + +void ui_list_args_set_ondragstartdata(UiListArgs *args, void *userdata) { + args->ondragstartdata = userdata; +} + +void ui_list_args_set_ondragcomplete(UiListArgs *args, ui_callback callback) { + args->ondragcomplete = callback; +} + +void ui_list_args_set_ondragcompletedata(UiListArgs *args, void *userdata) { + args->ondragcompletedata = userdata; +} + +void ui_list_args_set_ondrop(UiListArgs *args, ui_callback callback) { + args->ondrop = callback; +} + +void ui_list_args_set_ondropdata(UiListArgs *args, void *userdata) { + args->ondropdata = userdata; +} + +void ui_list_args_set_onsave(UiListArgs *args, ui_list_savefunc onsave) { + args->onsave = onsave; +} + +void ui_list_args_set_onsavedata(UiListArgs *args, void *userdata) { + args->onsavedata = userdata; +} + +void ui_list_args_set_multiselection(UiListArgs *args, UiBool multiselection) { + args->multiselection = multiselection; +} + +void ui_list_args_set_contextmenu(UiListArgs *args, UiMenuBuilder *menubuilder) { + args->contextmenu = menubuilder; +} + +void ui_list_args_set_groups(UiListArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; +} + +void ui_list_args_free(UiListArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->varname); + if(args->static_elements) { + for(int i=0;istatic_nelm;i++) { + free(args->static_elements[i]); + } + free(args->static_elements); + } + free((void*)args->groups); + free(args); +} + + + +/* ---------------------- SurceList ------------------------------------- */ + +UiSourceListArgs* ui_sourcelist_args_new(void) { + UiSourceListArgs *args = malloc(sizeof(UiSourceListArgs)); + memset(args, 0, sizeof(UiSourceListArgs)); + return args; +} + + +void ui_sourcelist_args_set_fill(UiSourceListArgs *args, UiBool fill) { + args->fill = fill; +} + + +void ui_sourcelist_args_set_hexpand(UiSourceListArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_sourcelist_args_set_vexpand(UiSourceListArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_sourcelist_args_set_hfill(UiSourceListArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_sourcelist_args_set_vfill(UiSourceListArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_sourcelist_args_set_override_defaults(UiSourceListArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_sourcelist_args_set_margin(UiSourceListArgs *args, int value) { + args->margin = value; +} + +void ui_sourcelist_args_set_margin_left(UiSourceListArgs *args, int value) { + args->margin_left = value; +} + +void ui_sourcelist_args_set_margin_right(UiSourceListArgs *args, int value) { + args->margin_right = value; +} + +void ui_sourcelist_args_set_margin_top(UiSourceListArgs *args, int value) { + args->margin_top = value; +} + +void ui_sourcelist_args_set_margin_bottom(UiSourceListArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_sourcelist_args_set_colspan(UiSourceListArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_sourcelist_args_set_rowspan(UiSourceListArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_sourcelist_args_set_name(UiSourceListArgs *args, const char *name) { + args->name = strdup(name); +} + + +void ui_sourcelist_args_set_style_class(UiSourceListArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +UIEXPORT void ui_sourcelist_args_set_static_sublists(UiSourceListArgs *args, UiSubList *sublists, size_t numsublists) { + args->sublists = calloc(numsublists, sizeof(UiSubList)); + memcpy(args->sublists, sublists, numsublists * sizeof(UiSubList)); + args->numsublists = numsublists; +} + +void ui_sourcelist_args_set_varname(UiSourceListArgs *args, const char *varname) { + args->varname = strdup(varname); +} + + +void ui_sourcelist_args_set_dynamic_sublists(UiSourceListArgs *args, UiList *value) { + args->dynamic_sublist = value; +} + + +void ui_sourcelist_args_set_getvalue_func(UiSourceListArgs *args, ui_sublist_getvalue_func getvalue) { + args->getvalue = getvalue; +} + +void ui_sourcelist_args_set_getvalue_userdata(UiSourceListArgs *args, void *userdata) { + args->getvaluedata = userdata; +} + +void ui_sourcelist_args_set_onactivate(UiSourceListArgs *args, ui_callback callback) { + args->onactivate = callback; +} + + +void ui_sourcelist_args_set_onactivatedata(UiSourceListArgs *args, void *userdata) { + args->onactivatedata = userdata; +} + + +void ui_sourcelist_args_set_onbuttonclick(UiSourceListArgs *args, ui_callback callback) { + args->onbuttonclick = callback; + +} + + +void ui_sourcelist_args_set_onbuttonclickdata(UiSourceListArgs *args, void *userdata) { + args->onbuttonclickdata = userdata; +} + +void ui_sourcelist_args_set_contextmenu(UiSourceListArgs *args, UiMenuBuilder *menubuilder) { + args->contextmenu = menubuilder; +} + +void ui_sourcelist_args_set_header_is_item(UiSourceListArgs *args, UiBool value) { + args->header_is_item = value; +} + +void ui_sourcelist_args_free(UiSourceListArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->varname); + free((void*)args->sublists); + free((void*)args->groups); + free(args); +} + + +/* ------------------------- UiTextAreaArgs ----------------------------*/ + +UiTextAreaArgs* ui_textarea_args_new(void) { + UiTextAreaArgs *args = malloc(sizeof(UiTextAreaArgs)); + memset(args, 0, sizeof(UiTextAreaArgs)); + return args; +} + + +void ui_textarea_args_set_fill(UiTextAreaArgs *args, UiBool fill) { + args->fill = fill; +} + + +void ui_textarea_args_set_hexpand(UiTextAreaArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_textarea_args_set_vexpand(UiTextAreaArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_textarea_args_set_hfill(UiTextAreaArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_textarea_args_set_vfill(UiTextAreaArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_textarea_args_set_override_defaults(UiTextAreaArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_textarea_args_set_margin(UiTextAreaArgs *args, int value) { + args->margin = value; +} + +void ui_textarea_args_set_margin_left(UiTextAreaArgs *args, int value) { + args->margin_left = value; +} + +void ui_textarea_args_set_margin_right(UiTextAreaArgs *args, int value) { + args->margin_right = value; +} + +void ui_textarea_args_set_margin_top(UiTextAreaArgs *args, int value) { + args->margin_top = value; +} + +void ui_textarea_args_set_margin_bottom(UiTextAreaArgs *args, int value) { + args->margin_bottom = value; +} + + +void ui_textarea_args_set_colspan(UiTextAreaArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_textarea_args_set_rowspan(UiTextAreaArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_textarea_args_set_name(UiTextAreaArgs *args, const char *name) { + args->name = strdup(name); +} + + +void ui_textarea_args_set_style_class(UiTextAreaArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_textarea_args_set_onchange(UiTextAreaArgs *args, ui_callback callback){ + args->onchange = callback; +} + + +void ui_textarea_args_set_onchangedata(UiTextAreaArgs *args, void *onchangedata){ + args->onchangedata = onchangedata; +} + +void ui_textarea_args_set_varname(UiTextAreaArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_textarea_args_set_value(UiTextAreaArgs *args, UiText *value) { + args->value = value; +} + +void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; +} + +void ui_textarea_args_free(UiTextAreaArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->varname); + free((void*)args->groups); + free(args); +} + + + +/* ------------------------- UiTextFieldArgs ----------------------------*/ + +UiTextFieldArgs* ui_textfield_args_new(void) { + UiTextFieldArgs *args = malloc(sizeof(UiTextFieldArgs)); + memset(args, 0, sizeof(UiTextFieldArgs)); + return args; +} + + +void ui_textfield_args_set_fill(UiTextFieldArgs *args, UiBool fill) { + args->fill = fill; +} + + +void ui_textfield_args_set_hexpand(UiTextFieldArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_textfield_args_set_vexpand(UiTextFieldArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_textfield_args_set_hfill(UiTextFieldArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_textfield_args_set_vfill(UiTextFieldArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_textfield_args_set_override_defaults(UiTextFieldArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_textfield_args_set_margin(UiTextFieldArgs *args, int value) { + args->margin = value; +} + +void ui_textfield_args_set_margin_left(UiTextFieldArgs *args, int value) { + args->margin_left = value; +} + +void ui_textfield_args_set_margin_right(UiTextFieldArgs *args, int value) { + args->margin_right = value; +} + +void ui_textfield_args_set_margin_top(UiTextFieldArgs *args, int value) { + args->margin_top = value; +} + +void ui_textfield_args_set_margin_bottom(UiTextFieldArgs *args, int value) { + args->margin_bottom = value; +} + + +void ui_textfield_args_set_colspan(UiTextFieldArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_textfield_args_set_rowspan(UiTextFieldArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_textfield_args_set_name(UiTextFieldArgs *args, const char *name) { + args->name = strdup(name); +} + + +void ui_textfield_args_set_style_class(UiTextFieldArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_textfield_args_set_onchange(UiTextFieldArgs *args, ui_callback callback){ + args->onchange = callback; +} + + +void ui_textfield_args_set_onchangedata(UiTextFieldArgs *args, void *onchangedata){ + args->onchangedata = onchangedata; +} + +void ui_textfield_args_set_onactivate(UiTextFieldArgs *args, ui_callback callback){ + args->onactivate = callback; +} + + +void ui_textfield_args_set_onactivatedata(UiTextFieldArgs *args, void *onactivatedata){ + args->onactivatedata = onactivatedata; +} + +void ui_textfield_args_set_varname(UiTextFieldArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_textfield_args_set_value(UiTextFieldArgs *args, UiString *value) { + args->value = value; +} + +void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; +} + +void ui_textfield_args_free(UiTextFieldArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->varname); + free((void*)args->groups); + free(args); +} + + +/* ------------------------- UiSpinBoxArgs ----------------------------*/ + +UiSpinBoxArgs* ui_spinbox_args_new(void) { + UiSpinBoxArgs *args = malloc(sizeof(UiSpinBoxArgs)); + memset(args, 0, sizeof(UiSpinBoxArgs)); + return args; +} + +void ui_spinbox_args_set_fill(UiSpinBoxArgs *args, UiBool fill) { + args->fill = fill; +} + +void ui_spinbox_args_set_hexpand(UiSpinBoxArgs *args, UiBool value) { + args->hexpand = value; +} + +void ui_spinbox_args_set_vexpand(UiSpinBoxArgs *args, UiBool value) { + args->vexpand = value; +} + +void ui_spinbox_args_set_hfill(UiSpinBoxArgs *args, UiBool value) { + args->hfill = value; +} + +void ui_spinbox_args_set_vfill(UiSpinBoxArgs *args, UiBool value) { + args->vfill = value; +} + +void ui_spinbox_args_set_override_defaults(UiSpinBoxArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_spinbox_args_set_colspan(UiSpinBoxArgs *args, int colspan) { + args->colspan = colspan; +} + +void ui_spinbox_args_set_rowspan(UiSpinBoxArgs *args, int rowspan) { + args->rowspan = rowspan; +} + +void ui_spinbox_args_set_name(UiSpinBoxArgs *args, const char *name) { + args->name = strdup(name); +} + +void ui_spinbox_args_set_style_class(UiSpinBoxArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_spinbox_args_set_onchange(UiSpinBoxArgs *args, ui_callback callback) { + args->onchange = callback; +} + +void ui_spinbox_args_set_onchangedata(UiSpinBoxArgs *args, void *onchangedata) { + args->onchangedata = onchangedata; +} + +void ui_spinbox_args_set_margin(UiSpinBoxArgs *args, int value) { + args->margin = value; +} + +void ui_spinbox_args_set_margin_left(UiSpinBoxArgs *args, int value) { + args->margin_left = value; +} + +void ui_spinbox_args_set_margin_right(UiSpinBoxArgs *args, int value) { + args->margin_right = value; +} + +void ui_spinbox_args_set_margin_top(UiSpinBoxArgs *args, int value) { + args->margin_top = value; +} + +void ui_spinbox_args_set_margin_bottom(UiSpinBoxArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_spinbox_args_set_min(UiSpinBoxArgs *args, double min) { + args->min = min; +} + +void ui_spinbox_args_set_max(UiSpinBoxArgs *args, double max) { + args->max = max; +} + +void ui_spinbox_args_set_step(UiSpinBoxArgs *args, double step) { + args->step = step; +} + +void ui_spinbox_args_set_digits(UiSpinBoxArgs *args, int digits) { + args->digits = digits; +} + +void ui_spinbox_args_set_varname(UiSpinBoxArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_spinbox_args_set_intvalue(UiSpinBoxArgs *args, UiInteger *value) { + args->intvalue = value; +} + +void ui_spinbox_args_set_doublevalue(UiSpinBoxArgs *args, UiDouble *value) { + args->doublevalue = value; +} + +void ui_spinbox_args_set_rangevalue(UiSpinBoxArgs *args, UiRange *value) { + args->rangevalue = value; +} + +void ui_spinbox_args_set_groups(UiSpinBoxArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; +} + +void ui_spinbox_args_free(UiSpinBoxArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->varname); + free((void*)args->groups); + free(args); +} + + +/* ------------------------- UiWebviewArgs ----------------------------*/ + +UiWebviewArgs* ui_webview_args_new(void) { + UiWebviewArgs *args = malloc(sizeof(UiWebviewArgs)); + memset(args, 0, sizeof(UiWebviewArgs)); + return args; +} + + +void ui_webview_args_set_fill(UiWebviewArgs *args, UiBool fill) { + args->fill = fill; +} + + +void ui_webview_args_set_hexpand(UiWebviewArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_webview_args_set_vexpand(UiWebviewArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_webview_args_set_hfill(UiWebviewArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_webview_args_set_vfill(UiWebviewArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_webview_args_set_override_defaults(UiWebviewArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_webview_args_set_margin(UiWebviewArgs *args, int value) { + args->margin = value; +} + +void ui_webview_args_set_margin_left(UiWebviewArgs *args, int value) { + args->margin_left = value; +} + +void ui_webview_args_set_margin_right(UiWebviewArgs *args, int value) { + args->margin_right = value; +} + +void ui_webview_args_set_margin_top(UiWebviewArgs *args, int value) { + args->margin_top = value; +} + +void ui_webview_args_set_margin_bottom(UiWebviewArgs *args, int value) { + args->margin_bottom = value; +} + +void ui_webview_args_set_colspan(UiWebviewArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_webview_args_set_rowspan(UiWebviewArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_webview_args_set_name(UiWebviewArgs *args, const char *name) { + args->name = strdup(name); +} + + +void ui_webview_args_set_style_class(UiWebviewArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_webview_args_set_varname(UiWebviewArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_webview_args_set_value(UiWebviewArgs *args, UiGeneric *value) { + args->value = value; +} + +void ui_webview_args_set_groups(UiWebviewArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; +} + +void ui_webview_args_free(UiWebviewArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->varname); + free((void*)args->groups); + free(args); +} + diff --git a/ui/common/args.h b/ui/common/args.h new file mode 100644 index 0000000..e533193 --- /dev/null +++ b/ui/common/args.h @@ -0,0 +1,581 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_ARGS_H +#define UIC_ARGS_H + +#include "../ui/window.h" +#include "../ui/container.h" +#include "../ui/display.h" +#include "../ui/button.h" +#include "../ui/entry.h" +#include "../ui/menu.h" +#include "../ui/toolbar.h" +#include "../ui/tree.h" +#include "../ui/text.h" +#include "../ui/webview.h" +#include "../ui/widget.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UIEXPORT UiDialogArgs* ui_dialog_args_new(void); +UIEXPORT void ui_dialog_args_set_title(UiDialogArgs *args, const char *title); +UIEXPORT void ui_dialog_args_set_content(UiDialogArgs *args, const char *str); +UIEXPORT void ui_dialog_args_set_button1_label(UiDialogArgs *args, const char *label); +UIEXPORT void ui_dialog_args_set_button2_label(UiDialogArgs *args, const char *label); +UIEXPORT void ui_dialog_args_set_closebutton_label(UiDialogArgs *args, const char *label); +UIEXPORT void ui_dialog_args_set_input_value(UiDialogArgs *args, const char *value); +UIEXPORT void ui_dialog_args_set_input(UiDialogArgs *args, UiBool input); +UIEXPORT void ui_dialog_args_set_password(UiDialogArgs *args, UiBool password); +UIEXPORT void ui_dialog_args_set_result(UiDialogArgs *args, ui_callback cb); +UIEXPORT void ui_dialog_args_set_resultdata(UiDialogArgs *args, void *userdata); +UIEXPORT void ui_dialog_args_free(UiDialogArgs *args); + +UIEXPORT UiDialogWindowArgs* ui_dialogwindow_args_new(void); +UIEXPORT void ui_dialogwindow_args_set_modal(UiDialogWindowArgs *args, UiTri value); +UIEXPORT void ui_dialogwindow_args_set_titlebar_buttons(UiDialogWindowArgs *args, UiTri value); +UIEXPORT void ui_dialogwindow_args_set_show_closebutton(UiDialogWindowArgs *args, UiTri value); +UIEXPORT void ui_dialogwindow_args_set_title(UiDialogWindowArgs *args, const char *title); +UIEXPORT void ui_dialogwindow_args_set_lbutton1(UiDialogWindowArgs *args, const char *label); +UIEXPORT void ui_dialogwindow_args_set_lbutton2(UiDialogWindowArgs *args, const char *label); +UIEXPORT void ui_dialogwindow_args_set_rbutton3(UiDialogWindowArgs *args, const char *label); +UIEXPORT void ui_dialogwindow_args_set_rbutton4(UiDialogWindowArgs *args, const char *label); +UIEXPORT void ui_dialogwindow_args_set_lbutton1_states(UiDialogWindowArgs *args, const int *states); +UIEXPORT void ui_dialogwindow_args_set_lbutton2_states(UiDialogWindowArgs *args, const int *states); +UIEXPORT void ui_dialogwindow_args_set_rbutton3_states(UiDialogWindowArgs *args, const int *states); +UIEXPORT void ui_dialogwindow_args_set_rbutton4_states(UiDialogWindowArgs *args, const int *states); +UIEXPORT void ui_dialogwindow_args_set_default_button(UiDialogWindowArgs *args, int button); +UIEXPORT void ui_dialogwindow_args_set_width(UiDialogWindowArgs *args, int width); +UIEXPORT void ui_dialogwindow_args_set_height(UiDialogWindowArgs *args, int height); +UIEXPORT void ui_dialogwindow_args_set_onclick(UiDialogWindowArgs *args, ui_callback cb); +UIEXPORT void ui_dialogwindow_args_set_onclickdata(UiDialogWindowArgs *args, void *userdata); +UIEXPORT void ui_dialogwindow_args_free(UiDialogWindowArgs *args); + +UIEXPORT UiMenuItemArgs* ui_menuitem_args_new(void); +UIEXPORT void ui_menuitem_args_set_label(UiMenuItemArgs *args, const char *label); +UIEXPORT void ui_menuitem_args_set_icon(UiMenuItemArgs *args, const char *icon); +UIEXPORT void ui_menuitem_args_set_onclick(UiMenuItemArgs *args, ui_callback callback); +UIEXPORT void ui_menuitem_args_set_onclickdata(UiMenuItemArgs *args, void *onclickdata); +UIEXPORT void ui_menuitem_args_free(UiMenuItemArgs *args); + +UIEXPORT UiMenuToggleItemArgs* ui_menutoggleitem_args_new(void); +UIEXPORT void ui_menutoggleitem_args_set_label(UiMenuToggleItemArgs *args, const char *label); +UIEXPORT void ui_menutoggleitem_args_set_icon(UiMenuToggleItemArgs *args, const char *icon); +UIEXPORT void ui_menutoggleitem_args_set_varname(UiMenuToggleItemArgs *args, const char *varname); +UIEXPORT void ui_menutoggleitem_args_set_onchange(UiMenuToggleItemArgs *args, ui_callback callback); +UIEXPORT void ui_menutoggleitem_args_set_onchangedata(UiMenuToggleItemArgs *args, void *onchangedata); +UIEXPORT void ui_menutoggleitem_args_free(UiMenuToggleItemArgs *args); + +UIEXPORT UiMenuItemListArgs* ui_menuitemlist_args_new(void); +UIEXPORT void ui_menuitemlist_args_set_varname(UiMenuItemListArgs *args, const char *varname); +UIEXPORT void ui_menuitemlist_args_set_getvalue(UiMenuItemListArgs *args, ui_getvaluefunc func); +UIEXPORT void ui_menuitemlist_args_set_onselect(UiMenuItemListArgs *args, ui_callback callback); +UIEXPORT void ui_menuitemlist_args_set_onselectdata(UiMenuItemListArgs *args, void *data); +UIEXPORT void ui_menuitemlist_args_set_addseparator(UiMenuItemListArgs *args, UiBool value); +UIEXPORT void ui_menuitemlist_args_free(UiMenuItemListArgs *args); + +UIEXPORT UiToolbarItemArgs* ui_toolbar_item_args_new(void); +UIEXPORT void ui_toolbar_item_args_set_label(UiToolbarItemArgs *args, const char *label); +UIEXPORT void ui_toolbar_item_args_set_icon(UiToolbarItemArgs *args, const char *icon); +UIEXPORT void ui_toolbar_item_args_set_tooltip(UiToolbarItemArgs *args, const char *tooltip); +UIEXPORT void ui_toolbar_item_args_set_onclick(UiToolbarItemArgs *args, ui_callback callback); +UIEXPORT void ui_toolbar_item_args_set_onclickdata(UiToolbarItemArgs *args, void *onclickdata); +UIEXPORT void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates); +UIEXPORT void ui_toolbar_item_args_free(UiToolbarItemArgs *args); + +UIEXPORT UiToolbarToggleItemArgs* ui_toolbar_toggleitem_args_new(void); +UIEXPORT void ui_toolbar_toggleitem_args_set_label(UiToolbarToggleItemArgs *args, const char *label); +UIEXPORT void ui_toolbar_toggleitem_args_set_icon(UiToolbarToggleItemArgs *args, const char *icon); +UIEXPORT void ui_toolbar_toggleitem_args_set_tooltip(UiToolbarToggleItemArgs *args, const char *tooltip); +UIEXPORT void ui_toolbar_toggleitem_args_set_varname(UiToolbarToggleItemArgs *args, const char *varname); +UIEXPORT void ui_toolbar_toggleitem_args_set_onchange(UiToolbarToggleItemArgs *args, ui_callback callback); +UIEXPORT void ui_toolbar_toggleitem_args_set_onchangedata(UiToolbarToggleItemArgs *args, void *onchangedata); +UIEXPORT void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args, int *states, int numstates); +UIEXPORT void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args); + +UIEXPORT UiToolbarMenuArgs* ui_toolbar_menu_args_new(void); +UIEXPORT void ui_toolbar_menu_args_set_label(UiToolbarMenuArgs *args, const char *label); +UIEXPORT void ui_toolbar_menu_args_set_icon(UiToolbarMenuArgs *args, const char *icon); +UIEXPORT void ui_toolbar_menu_args_set_tooltip(UiToolbarMenuArgs *args, const char *tooltip); +UIEXPORT void ui_toolbar_menu_args_free(UiToolbarMenuArgs *args); + +UIEXPORT UiContainerArgs* ui_container_args_new(void); +UIEXPORT void ui_container_args_set_fill(UiContainerArgs *args, UiBool fill); +UIEXPORT void ui_container_args_set_hexpand(UiContainerArgs *args, UiBool value); +UIEXPORT void ui_container_args_set_vexpand(UiContainerArgs *args, UiBool value); +UIEXPORT void ui_container_args_set_hfill(UiContainerArgs *args, UiBool value); +UIEXPORT void ui_container_args_set_vfill(UiContainerArgs *args, UiBool value); +UIEXPORT void ui_container_args_set_override_defaults(UiContainerArgs *args, UiBool value); +UIEXPORT void ui_container_args_set_margin(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_margin_left(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_margin_right(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_margin_top(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_margin_right(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_colspan(UiContainerArgs *args, int colspan); +UIEXPORT void ui_container_args_set_rowspan(UiContainerArgs *args, int rowspan); +UIEXPORT void ui_container_args_set_def_hexpand(UiContainerArgs *args, UiBool value); +UIEXPORT void ui_container_args_set_def_vexpand(UiContainerArgs *args, UiBool value); +UIEXPORT void ui_container_args_set_def_hfill(UiContainerArgs *args, UiBool value); +UIEXPORT void ui_container_args_set_def_vfill(UiContainerArgs *args, UiBool value); +UIEXPORT void ui_container_args_set_name(UiContainerArgs *args, const char *name); +UIEXPORT void ui_container_args_set_style_class(UiContainerArgs *args, const char *classname); +UIEXPORT void ui_container_args_set_spacing(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_columnspacing(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_rowspacing(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_free(UiContainerArgs *args); + +UIEXPORT UiFrameArgs* ui_frame_args_new(void); +UIEXPORT void ui_frame_args_set_fill(UiFrameArgs *args, UiBool fill); +UIEXPORT void ui_frame_args_set_hexpand(UiFrameArgs *args, UiBool value); +UIEXPORT void ui_frame_args_set_vexpand(UiFrameArgs *args, UiBool value); +UIEXPORT void ui_frame_args_set_hfill(UiFrameArgs *args, UiBool value); +UIEXPORT void ui_frame_args_set_vfill(UiFrameArgs *args, UiBool value); +UIEXPORT void ui_frame_args_set_override_defaults(UiFrameArgs *args, UiBool value); +UIEXPORT void ui_frame_args_set_margin(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_margin_left(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_margin_right(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_margin_top(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_margin_bottom(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_colspan(UiFrameArgs *args, int colspan); +UIEXPORT void ui_frame_args_set_rowspan(UiFrameArgs *args, int rowspan); +UIEXPORT void ui_frame_args_set_name(UiFrameArgs *args, const char *name); +UIEXPORT void ui_frame_args_set_style_class(UiFrameArgs *args, const char *classname); +UIEXPORT void ui_frame_args_set_subcontainer(UiFrameArgs *args, UiSubContainerType subcontainer); +UIEXPORT void ui_frame_args_set_padding(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_spacing(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_columnspacing(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_rowspacing(UiFrameArgs *args, int value); +UIEXPORT void ui_frame_args_set_expanded(UiFrameArgs *args, UiBool value); +UIEXPORT void ui_frame_args_set_label(UiFrameArgs *args, const char *label); +UIEXPORT void ui_frame_args_free(UiFrameArgs *args); + +UIEXPORT UiSidebarArgs* ui_sidebar_args_new(void); +UIEXPORT void ui_sidebar_args_set_name(UiSidebarArgs *args, const char *name); +UIEXPORT void ui_sidebar_args_set_style_class(UiSidebarArgs *args, const char *classname); +UIEXPORT void ui_sidebar_args_set_margin(UiSidebarArgs *args, int value); +UIEXPORT void ui_sidebar_args_set_margin_left(UiSidebarArgs *args, int value); +UIEXPORT void ui_sidebar_args_set_margin_right(UiSidebarArgs *args, int value); +UIEXPORT void ui_sidebar_args_set_margin_top(UiSidebarArgs *args, int value); +UIEXPORT void ui_sidebar_args_set_margin_bottom(UiSidebarArgs *args, int value); +UIEXPORT void ui_sidebar_args_set_spacing(UiSidebarArgs *args, int value); +UIEXPORT void ui_sidebar_args_free(UiSidebarArgs *args); + +UIEXPORT UiSplitPaneArgs* ui_splitpane_args_new(void); +UIEXPORT void ui_splitpane_args_set_fill(UiSplitPaneArgs *args, UiBool fill); +UIEXPORT void ui_splitpane_args_set_hexpand(UiSplitPaneArgs *args, UiBool value); +UIEXPORT void ui_splitpane_args_set_vexpand(UiSplitPaneArgs *args, UiBool value); +UIEXPORT void ui_splitpane_args_set_hfill(UiSplitPaneArgs *args, UiBool value); +UIEXPORT void ui_splitpane_args_set_vfill(UiSplitPaneArgs *args, UiBool value); +UIEXPORT void ui_splitpane_args_set_override_defaults(UiSplitPaneArgs *args, UiBool value); +UIEXPORT void ui_splitpane_args_set_margin(UiSplitPaneArgs *args, int value); +UIEXPORT void ui_splitpane_args_set_margin_left(UiSplitPaneArgs *args, int value); +UIEXPORT void ui_splitpane_args_set_margin_right(UiSplitPaneArgs *args, int value); +UIEXPORT void ui_splitpane_args_set_margin_top(UiSplitPaneArgs *args, int value); +UIEXPORT void ui_splitpane_args_set_margin_bottom(UiSplitPaneArgs *args, int value); +UIEXPORT void ui_splitpane_args_set_colspan(UiSplitPaneArgs *args, int colspan); +UIEXPORT void ui_splitpane_args_set_rowspan(UiSplitPaneArgs *args, int rowspan); +UIEXPORT void ui_splitpane_args_set_name(UiSplitPaneArgs *args, const char *name); +UIEXPORT void ui_splitpane_args_set_style_class(UiSplitPaneArgs *args, const char *classname); +UIEXPORT void ui_splitpane_args_set_spacing(UiSplitPaneArgs *args, int value); +UIEXPORT void ui_splitpane_args_set_columnspacing(UiSplitPaneArgs *args, int value); +UIEXPORT void ui_splitpane_args_set_rowspacing(UiSplitPaneArgs *args, int value); +UIEXPORT void ui_splitpane_args_set_initial_position(UiSplitPaneArgs *args, int pos); +UIEXPORT void ui_splitpane_args_set_position_property(UiSplitPaneArgs *args, const char *propname); +UIEXPORT void ui_splitpane_args_set_varname(UiSplitPaneArgs *args, const char *varname); +UIEXPORT void ui_splitpane_args_set_value(UiSplitPaneArgs *args, UiInteger *value); +UIEXPORT void ui_splitpane_args_set_max_panes(UiSplitPaneArgs *args, int max); +UIEXPORT void ui_splitpane_args_free(UiSplitPaneArgs *args); + +UIEXPORT UiTabViewArgs* ui_tabview_args_new(void); +UIEXPORT void ui_tabview_args_set_fill(UiTabViewArgs *args, UiBool fill); +UIEXPORT void ui_tabview_args_set_hexpand(UiTabViewArgs *args, UiBool value); +UIEXPORT void ui_tabview_args_set_vexpand(UiTabViewArgs *args, UiBool value); +UIEXPORT void ui_tabview_args_set_hfill(UiTabViewArgs *args, UiBool value); +UIEXPORT void ui_tabview_args_set_vfill(UiTabViewArgs *args, UiBool value); +UIEXPORT void ui_tabview_args_set_override_defaults(UiTabViewArgs *args, UiBool value); +UIEXPORT void ui_tabview_args_set_margin(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_margin_left(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_margin_right(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_margin_top(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_margin_bottom(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_colspan(UiTabViewArgs *args, int colspan); +UIEXPORT void ui_tabview_args_set_rowspan(UiTabViewArgs *args, int rowspan); +UIEXPORT void ui_tabview_args_set_name(UiTabViewArgs *args, const char *name); +UIEXPORT void ui_tabview_args_set_style_class(UiTabViewArgs *args, const char *classname); +UIEXPORT void ui_tabview_args_set_padding(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_spacing(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_columnspacing(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_rowspacing(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_type(UiTabViewArgs *args, UiTabViewType tabview); +UIEXPORT void ui_tabview_args_set_onchange(UiTabViewArgs *args, ui_callback cb); +UIEXPORT void ui_tabview_args_set_onchangedata(UiTabViewArgs *args, void *userdata); +UIEXPORT void ui_tabview_args_set_varname(UiTabViewArgs *args, const char *varname); +UIEXPORT void ui_tabview_args_set_value(UiTabViewArgs *args, UiInteger *value); +UIEXPORT void ui_tabview_args_set_subcontainer(UiTabViewArgs *args, UiSubContainerType subcontainer); +UIEXPORT void ui_tabview_args_free(UiTabViewArgs *args); + +UIEXPORT UiWidgetArgs* ui_widget_args_new(void); +UIEXPORT void ui_widget_args_set_fill(UiWidgetArgs *args, UiBool fill); +UIEXPORT void ui_widget_args_set_hexpand(UiWidgetArgs *args, UiBool value); +UIEXPORT void ui_widget_args_set_vexpand(UiWidgetArgs *args, UiBool value); +UIEXPORT void ui_widget_args_set_hfill(UiWidgetArgs *args, UiBool value); +UIEXPORT void ui_widget_args_set_vfill(UiWidgetArgs *args, UiBool value); +UIEXPORT void ui_widget_args_set_override_defaults(UiWidgetArgs *args, UiBool value); +UIEXPORT void ui_widget_args_set_margin(UiWidgetArgs *args, int value); +UIEXPORT void ui_widget_args_set_margin_left(UiWidgetArgs *args, int value); +UIEXPORT void ui_widget_args_set_margin_right(UiWidgetArgs *args, int value); +UIEXPORT void ui_widget_args_set_margin_top(UiWidgetArgs *args, int value); +UIEXPORT void ui_widget_args_set_margin_bottom(UiWidgetArgs *args, int value); +UIEXPORT void ui_widget_args_set_colspan(UiWidgetArgs *args, int colspan); +UIEXPORT void ui_widget_args_set_rowspan(UiWidgetArgs *args, int rowspan); +UIEXPORT void ui_widget_args_set_name(UiWidgetArgs *args, const char *name); +UIEXPORT void ui_widget_args_set_style_class(UiWidgetArgs *args, const char *classname); +UIEXPORT void ui_widget_args_free(UiWidgetArgs *args); + +UIEXPORT UiLabelArgs* ui_label_args_new(void); +UIEXPORT void ui_label_args_set_fill(UiLabelArgs *args, UiBool fill); +UIEXPORT void ui_label_args_set_hexpand(UiLabelArgs *args, UiBool value); +UIEXPORT void ui_label_args_set_vexpand(UiLabelArgs *args, UiBool value); +UIEXPORT void ui_label_args_set_hfill(UiLabelArgs *args, UiBool value); +UIEXPORT void ui_label_args_set_vfill(UiLabelArgs *args, UiBool value); +UIEXPORT void ui_label_args_set_override_defaults(UiLabelArgs *args, UiBool value); +UIEXPORT void ui_label_args_set_margin(UiLabelArgs *args, int value); +UIEXPORT void ui_label_args_set_margin_left(UiLabelArgs *args, int value); +UIEXPORT void ui_label_args_set_margin_right(UiLabelArgs *args, int value); +UIEXPORT void ui_label_args_set_margin_top(UiLabelArgs *args, int value); +UIEXPORT void ui_label_args_set_margin_bottom(UiLabelArgs *args, int value); +UIEXPORT void ui_label_args_set_colspan(UiLabelArgs *args, int colspan); +UIEXPORT void ui_label_args_set_rowspan(UiLabelArgs *args, int rowspan); +UIEXPORT void ui_label_args_set_name(UiLabelArgs *args, const char *name); +UIEXPORT void ui_label_args_set_style_class(UiLabelArgs *args, const char *classname); +UIEXPORT void ui_label_args_set_label(UiLabelArgs *args, const char *label); +UIEXPORT void ui_label_args_set_align(UiLabelArgs *args, UiAlignment align); +UIEXPORT void ui_label_args_set_style(UiLabelArgs *args, UiLabelStyle style); +UIEXPORT void ui_label_args_set_value(UiLabelArgs *args, UiString *value); +UIEXPORT void ui_label_args_set_varname(UiLabelArgs *args, const char *varname); +UIEXPORT void ui_label_args_free(UiLabelArgs *args); + +UIEXPORT UiProgressbarArgs* ui_progressbar_args_new(void); +UIEXPORT void ui_progressbar_args_set_fill(UiProgressbarArgs *args, UiBool fill); +UIEXPORT void ui_progressbar_args_set_hexpand(UiProgressbarArgs *args, UiBool value); +UIEXPORT void ui_progressbar_args_set_vexpand(UiProgressbarArgs *args, UiBool value); +UIEXPORT void ui_progressbar_args_set_hfill(UiProgressbarArgs *args, UiBool value); +UIEXPORT void ui_progressbar_args_set_vfill(UiProgressbarArgs *args, UiBool value); +UIEXPORT void ui_progressbar_args_set_override_defaults(UiProgressbarArgs *args, UiBool value); +UIEXPORT void ui_progressbar_args_set_margin(UiProgressbarArgs *args, int value); +UIEXPORT void ui_progressbar_args_set_margin_left(UiProgressbarArgs *args, int value); +UIEXPORT void ui_progressbar_args_set_margin_right(UiProgressbarArgs *args, int value); +UIEXPORT void ui_progressbar_args_set_margin_top(UiProgressbarArgs *args, int value); +UIEXPORT void ui_progressbar_args_set_margin_bottom(UiProgressbarArgs *args, int value); +UIEXPORT void ui_progressbar_args_set_colspan(UiProgressbarArgs *args, int colspan); +UIEXPORT void ui_progressbar_args_set_rowspan(UiProgressbarArgs *args, int rowspan); +UIEXPORT void ui_progressbar_args_set_name(UiProgressbarArgs *args, const char *name); +UIEXPORT void ui_progressbar_args_set_style_class(UiProgressbarArgs *args, const char *classname); +UIEXPORT void ui_progressbar_args_set_min(UiProgressbarArgs *args, double min); +UIEXPORT void ui_progressbar_args_set_max(UiProgressbarArgs *args, double max); +UIEXPORT void ui_progressbar_args_set_value(UiProgressbarArgs *args, UiDouble *value); +UIEXPORT void ui_progressbar_args_set_varname(UiProgressbarArgs *args, const char *varname); +UIEXPORT void ui_progressbar_args_free(UiProgressbarArgs *args); + +UIEXPORT UiProgressbarSpinnerArgs* ui_progress_spinner_args_new(void); +UIEXPORT void ui_progress_spinner_args_set_fill(UiProgressbarSpinnerArgs *args, UiBool fill); +UIEXPORT void ui_progress_spinner_args_set_hexpand(UiProgressbarSpinnerArgs *args, UiBool value); +UIEXPORT void ui_progress_spinner_args_set_vexpand(UiProgressbarSpinnerArgs *args, UiBool value); +UIEXPORT void ui_progress_spinner_args_set_hfill(UiProgressbarSpinnerArgs *args, UiBool value); +UIEXPORT void ui_progress_spinner_args_set_vfill(UiProgressbarSpinnerArgs *args, UiBool value); +UIEXPORT void ui_progress_spinner_args_set_override_defaults(UiProgressbarSpinnerArgs *args, UiBool value); +UIEXPORT void ui_progress_spinner_args_set_margin(UiProgressbarSpinnerArgs *args, int value); +UIEXPORT void ui_progress_spinner_args_set_margin_left(UiProgressbarSpinnerArgs *args, int value); +UIEXPORT void ui_progress_spinner_args_set_margin_right(UiProgressbarSpinnerArgs *args, int value); +UIEXPORT void ui_progress_spinner_args_set_margin_top(UiProgressbarSpinnerArgs *args, int value); +UIEXPORT void ui_progress_spinner_args_set_margin_bottom(UiProgressbarSpinnerArgs *args, int value); +UIEXPORT void ui_progress_spinner_args_set_colspan(UiProgressbarSpinnerArgs *args, int colspan); +UIEXPORT void ui_progress_spinner_args_set_rowspan(UiProgressbarSpinnerArgs *args, int rowspan); +UIEXPORT void ui_progress_spinner_args_set_name(UiProgressbarSpinnerArgs *args, const char *name); +UIEXPORT void ui_progress_spinner_args_set_style_class(UiProgressbarSpinnerArgs *args, const char *classname); +UIEXPORT void ui_progress_spinner_args_set_value(UiProgressbarSpinnerArgs *args, UiInteger *value); +UIEXPORT void ui_progress_spinner_args_set_varname(UiProgressbarSpinnerArgs *args, const char *varname); +UIEXPORT void ui_progress_spinner_args_free(UiProgressbarSpinnerArgs *args); + +UIEXPORT UiButtonArgs* ui_button_args_new(void); +UIEXPORT void ui_button_args_set_fill(UiButtonArgs *args, UiBool fill); +UIEXPORT void ui_button_args_set_hexpand(UiButtonArgs *args, UiBool value); +UIEXPORT void ui_button_args_set_vexpand(UiButtonArgs *args, UiBool value); +UIEXPORT void ui_button_args_set_hfill(UiButtonArgs *args, UiBool value); +UIEXPORT void ui_button_args_set_vfill(UiButtonArgs *args, UiBool value); +UIEXPORT void ui_button_args_set_override_defaults(UiButtonArgs *args, UiBool value); +UIEXPORT void ui_button_args_set_margin(UiButtonArgs *args, int value); +UIEXPORT void ui_button_args_set_margin_left(UiButtonArgs *args, int value); +UIEXPORT void ui_button_args_set_margin_right(UiButtonArgs *args, int value); +UIEXPORT void ui_button_args_set_margin_top(UiButtonArgs *args, int value); +UIEXPORT void ui_button_args_set_margin_bottom(UiButtonArgs *args, int value); +UIEXPORT void ui_button_args_set_colspan(UiButtonArgs *args, int colspan); +UIEXPORT void ui_button_args_set_rowspan(UiButtonArgs *args, int rowspan); +UIEXPORT void ui_button_args_set_name(UiButtonArgs *args, const char *name); +UIEXPORT void ui_button_args_set_style_class(UiButtonArgs *args, const char *classname); +UIEXPORT void ui_button_args_set_label(UiButtonArgs *args, const char *label); +UIEXPORT void ui_button_args_set_icon(UiButtonArgs *args, const char *icon); +UIEXPORT void ui_button_args_set_tooltip(UiButtonArgs *args, const char *tooltip); +UIEXPORT void ui_button_args_set_labeltype(UiButtonArgs *args, int labeltype); +UIEXPORT void ui_button_args_set_onclick(UiButtonArgs *args, ui_callback callback); +UIEXPORT void ui_button_args_set_onclickdata(UiButtonArgs *args, void *onclickdata); +UIEXPORT void ui_button_args_set_groups(UiButtonArgs *args, int *states, int numstates); +UIEXPORT void ui_button_args_free(UiButtonArgs *args); + +UIEXPORT UiToggleArgs* ui_toggle_args_new(void); +UIEXPORT void ui_toggle_args_set_fill(UiToggleArgs *args, UiBool fill); +UIEXPORT void ui_toggle_args_set_hexpand(UiToggleArgs *args, UiBool value); +UIEXPORT void ui_toggle_args_set_vexpand(UiToggleArgs *args, UiBool value); +UIEXPORT void ui_toggle_args_set_hfill(UiToggleArgs *args, UiBool value); +UIEXPORT void ui_toggle_args_set_vfill(UiToggleArgs *args, UiBool value); +UIEXPORT void ui_toggle_args_set_override_defaults(UiToggleArgs *args, UiBool value); +UIEXPORT void ui_toggle_args_set_margin(UiToggleArgs *args, int value); +UIEXPORT void ui_toggle_args_set_margin_left(UiToggleArgs *args, int value); +UIEXPORT void ui_toggle_args_set_margin_right(UiToggleArgs *args, int value); +UIEXPORT void ui_toggle_args_set_margin_top(UiToggleArgs *args, int value); +UIEXPORT void ui_toggle_args_set_margin_bottom(UiToggleArgs *args, int value); +UIEXPORT void ui_toggle_args_set_colspan(UiToggleArgs *args, int colspan); +UIEXPORT void ui_toggle_args_set_rowspan(UiToggleArgs *args, int rowspan); +UIEXPORT void ui_toggle_args_set_name(UiToggleArgs *args, const char *name); +UIEXPORT void ui_toggle_args_set_style_class(UiToggleArgs *args, const char *classname); +UIEXPORT void ui_toggle_args_set_label(UiToggleArgs *args, const char *label); +UIEXPORT void ui_toggle_args_set_icon(UiToggleArgs *args, const char *icon); +UIEXPORT void ui_toggle_args_set_tooltip(UiToggleArgs *args, const char *tooltip); +UIEXPORT void ui_toggle_args_set_labeltype(UiToggleArgs *args, int labeltype); +UIEXPORT void ui_toggle_args_set_onchange(UiToggleArgs *args, ui_callback callback); +UIEXPORT void ui_toggle_args_set_onchangedata(UiToggleArgs *args, void *onchangedata); +UIEXPORT void ui_toggle_args_set_varname(UiToggleArgs *args, const char *varname); +UIEXPORT void ui_toggle_args_set_value(UiToggleArgs *args, UiInteger *value); +UIEXPORT void ui_toggle_args_set_enablegroup(UiToggleArgs *args, int group); +UIEXPORT void ui_toggle_args_set_groups(UiToggleArgs *args, int *states, int numstates); +UIEXPORT void ui_toggle_args_free(UiToggleArgs *args); + +UIEXPORT UiLinkButtonArgs* ui_linkbutton_args_new(void); +UIEXPORT void ui_linkbutton_args_set_fill(UiLinkButtonArgs *args, UiBool fill); +UIEXPORT void ui_linkbutton_args_set_hexpand(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_vexpand(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_hfill(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_vfill(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_override_defaults(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_margin(UiLinkButtonArgs *args, int value); +UIEXPORT void ui_linkbutton_args_set_margin_left(UiLinkButtonArgs *args, int value); +UIEXPORT void ui_linkbutton_args_set_margin_right(UiLinkButtonArgs *args, int value); +UIEXPORT void ui_linkbutton_args_set_margin_top(UiLinkButtonArgs *args, int value); +UIEXPORT void ui_linkbutton_args_set_margin_bottom(UiLinkButtonArgs *args, int value); +UIEXPORT void ui_linkbutton_args_set_colspan(UiLinkButtonArgs *args, int colspan); +UIEXPORT void ui_linkbutton_args_set_rowspan(UiLinkButtonArgs *args, int rowspan); +UIEXPORT void ui_linkbutton_args_set_name(UiLinkButtonArgs *args, const char *name); +UIEXPORT void ui_linkbutton_args_set_style_class(UiLinkButtonArgs *args, const char *classname); +UIEXPORT void ui_linkbutton_args_set_varname(UiLinkButtonArgs *args, const char *varname); +UIEXPORT void ui_linkbutton_args_set_value(UiLinkButtonArgs *args, UiString *value); +UIEXPORT void ui_linkbutton_args_set_label(UiLinkButtonArgs *args, const char *label); +UIEXPORT void ui_linkbutton_args_set_uri(UiLinkButtonArgs *args, const char *uri); +UIEXPORT void ui_linkbutton_args_set_onclick(UiLinkButtonArgs *args, ui_callback callback); +UIEXPORT void ui_linkbutton_args_set_onclickdata(UiLinkButtonArgs *args, void *userdata); +UIEXPORT void ui_linkbutton_args_set_nofollow(UiLinkButtonArgs *args, UiBool value); +UIEXPORT void ui_linkbutton_args_set_type(UiLinkButtonArgs *args, UiLinkType type); +UIEXPORT void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *states, int numstates); +UIEXPORT void ui_linkbutton_args_free(UiLinkButtonArgs *args); + +UIEXPORT UiListArgs* ui_list_args_new(void); +UIEXPORT void ui_list_args_set_fill(UiListArgs *args, UiBool fill); +UIEXPORT void ui_list_args_set_hexpand(UiListArgs *args, UiBool value); +UIEXPORT void ui_list_args_set_vexpand(UiListArgs *args, UiBool value); +UIEXPORT void ui_list_args_set_hfill(UiListArgs *args, UiBool value); +UIEXPORT void ui_list_args_set_vfill(UiListArgs *args, UiBool value); +UIEXPORT void ui_list_args_set_override_defaults(UiListArgs *args, UiBool value); +UIEXPORT void ui_list_args_set_margin(UiListArgs *args, int value); +UIEXPORT void ui_list_args_set_margin_left(UiListArgs *args, int value); +UIEXPORT void ui_list_args_set_margin_right(UiListArgs *args, int value); +UIEXPORT void ui_list_args_set_margin_top(UiListArgs *args, int value); +UIEXPORT void ui_list_args_set_margin_bottom(UiListArgs *args, int value); +UIEXPORT void ui_list_args_set_colspan(UiListArgs *args, int colspan); +UIEXPORT void ui_list_args_set_rowspan(UiListArgs *args, int rowspan); +UIEXPORT void ui_list_args_set_name(UiListArgs *args, const char *name); +UIEXPORT void ui_list_args_set_style_class(UiListArgs *args, const char *classname); +UIEXPORT void ui_list_args_set_varname(UiListArgs *args, const char *varname); +UIEXPORT void ui_list_args_set_value(UiListArgs *args, UiList *value); +UIEXPORT void ui_list_args_set_model(UiListArgs *args, UiModel *model); +UIEXPORT void ui_list_args_set_static_elements(UiListArgs *args, char **strarray, size_t nelm); +UIEXPORT void ui_list_args_set_getvalue_func(UiListArgs *args, ui_getvaluefunc getvalue); +UIEXPORT void ui_list_args_set_getvalue_func2(UiListArgs *args, ui_getvaluefunc2 getvalue); +UIEXPORT void ui_list_args_set_getvalue_data(UiListArgs *args, void *userdata); +UIEXPORT void ui_list_args_set_getstyle_func(UiListArgs *args, ui_getstylefunc getstyle); +UIEXPORT void ui_list_args_set_getstyle_data(UiListArgs *args, void *userdata); +UIEXPORT void ui_list_args_set_onactivate(UiListArgs *args, ui_callback callback); +UIEXPORT void ui_list_args_set_onactivatedata(UiListArgs *args, void *userdata); +UIEXPORT void ui_list_args_set_onselection(UiListArgs *args, ui_callback callback); +UIEXPORT void ui_list_args_set_onselectiondata(UiListArgs *args, void *userdata); +UIEXPORT void ui_list_args_set_ondragstart(UiListArgs *args, ui_callback callback); +UIEXPORT void ui_list_args_set_ondragstartdata(UiListArgs *args, void *userdata); +UIEXPORT void ui_list_args_set_ondragcomplete(UiListArgs *args, ui_callback callback); +UIEXPORT void ui_list_args_set_ondragcompletedata(UiListArgs *args, void *userdata); +UIEXPORT void ui_list_args_set_ondrop(UiListArgs *args, ui_callback callback); +UIEXPORT void ui_list_args_set_ondropdata(UiListArgs *args, void *userdata); +UIEXPORT void ui_list_args_set_onsave(UiListArgs *args, ui_list_savefunc onsave); +UIEXPORT void ui_list_args_set_onsavedata(UiListArgs *args, void *userdata); +UIEXPORT void ui_list_args_set_multiselection(UiListArgs *args, UiBool multiselection); +UIEXPORT void ui_list_args_set_contextmenu(UiListArgs *args, UiMenuBuilder *menubuilder); +UIEXPORT void ui_list_args_set_groups(UiListArgs *args, int *states, int numstates); +UIEXPORT void ui_list_args_free(UiListArgs *args); + +UIEXPORT UiSourceListArgs* ui_sourcelist_args_new(void); +UIEXPORT void ui_sourcelist_args_set_fill(UiSourceListArgs *args, UiBool fill); +UIEXPORT void ui_sourcelist_args_set_hexpand(UiSourceListArgs *args, UiBool value); +UIEXPORT void ui_sourcelist_args_set_vexpand(UiSourceListArgs *args, UiBool value); +UIEXPORT void ui_sourcelist_args_set_hfill(UiSourceListArgs *args, UiBool value); +UIEXPORT void ui_sourcelist_args_set_vfill(UiSourceListArgs *args, UiBool value); +UIEXPORT void ui_sourcelist_args_set_override_defaults(UiSourceListArgs *args, UiBool value); +UIEXPORT void ui_sourcelist_args_set_margin(UiSourceListArgs *args, int value); +UIEXPORT void ui_sourcelist_args_set_margin_left(UiSourceListArgs *args, int value); +UIEXPORT void ui_sourcelist_args_set_margin_right(UiSourceListArgs *args, int value); +UIEXPORT void ui_sourcelist_args_set_margin_top(UiSourceListArgs *args, int value); +UIEXPORT void ui_sourcelist_args_set_margin_bottom(UiSourceListArgs *args, int value); +UIEXPORT void ui_sourcelist_args_set_colspan(UiSourceListArgs *args, int colspan); +UIEXPORT void ui_sourcelist_args_set_rowspan(UiSourceListArgs *args, int rowspan); +UIEXPORT void ui_sourcelist_args_set_name(UiSourceListArgs *args, const char *name); +UIEXPORT void ui_sourcelist_args_set_style_class(UiSourceListArgs *args, const char *classname); +UIEXPORT void ui_sourcelist_args_set_static_sublists(UiSourceListArgs *args, UiSubList *sublists, size_t numsublists); +UIEXPORT void ui_sourcelist_args_set_varname(UiSourceListArgs *args, const char *varname); +UIEXPORT void ui_sourcelist_args_set_dynamic_sublists(UiSourceListArgs *args, UiList *value); +UIEXPORT void ui_sourcelist_args_set_getvalue_func(UiSourceListArgs *args, ui_sublist_getvalue_func getvalue); +UIEXPORT void ui_sourcelist_args_set_getvalue_userdata(UiSourceListArgs *args, void *userdata); +UIEXPORT void ui_sourcelist_args_set_onactivate(UiSourceListArgs *args, ui_callback callback); +UIEXPORT void ui_sourcelist_args_set_onactivatedata(UiSourceListArgs *args, void *userdata); +UIEXPORT void ui_sourcelist_args_set_onbuttonclick(UiSourceListArgs *args, ui_callback callback); +UIEXPORT void ui_sourcelist_args_set_onbuttonclickdata(UiSourceListArgs *args, void *userdata); +UIEXPORT void ui_sourcelist_args_set_contextmenu(UiSourceListArgs *args, UiMenuBuilder *menubuilder); +UIEXPORT void ui_sourcelist_args_set_header_is_item(UiSourceListArgs *args, UiBool value); +UIEXPORT void ui_sourcelist_args_free(UiSourceListArgs *args); + +UIEXPORT UiTextAreaArgs* ui_textarea_args_new(void); +UIEXPORT void ui_textarea_args_set_fill(UiTextAreaArgs *args, UiBool fill); +UIEXPORT void ui_textarea_args_set_hexpand(UiTextAreaArgs *args, UiBool value); +UIEXPORT void ui_textarea_args_set_vexpand(UiTextAreaArgs *args, UiBool value); +UIEXPORT void ui_textarea_args_set_hfill(UiTextAreaArgs *args, UiBool value); +UIEXPORT void ui_textarea_args_set_vfill(UiTextAreaArgs *args, UiBool value); +UIEXPORT void ui_textarea_args_set_override_defaults(UiTextAreaArgs *args, UiBool value); +UIEXPORT void ui_textarea_args_set_margin(UiTextAreaArgs *args, int value); +UIEXPORT void ui_textarea_args_set_margin_left(UiTextAreaArgs *args, int value); +UIEXPORT void ui_textarea_args_set_margin_right(UiTextAreaArgs *args, int value); +UIEXPORT void ui_textarea_args_set_margin_top(UiTextAreaArgs *args, int value); +UIEXPORT void ui_textarea_args_set_margin_bottom(UiTextAreaArgs *args, int value); +UIEXPORT void ui_textarea_args_set_colspan(UiTextAreaArgs *args, int colspan); +UIEXPORT void ui_textarea_args_set_rowspan(UiTextAreaArgs *args, int rowspan); +UIEXPORT void ui_textarea_args_set_name(UiTextAreaArgs *args, const char *name); +UIEXPORT void ui_textarea_args_set_style_class(UiTextAreaArgs *args, const char *classname); +UIEXPORT void ui_textarea_args_set_onchange(UiTextAreaArgs *args, ui_callback callback); +UIEXPORT void ui_textarea_args_set_onchangedata(UiTextAreaArgs *args, void *onchangedata); +UIEXPORT void ui_textarea_args_set_varname(UiTextAreaArgs *args, const char *varname); +UIEXPORT void ui_textarea_args_set_value(UiTextAreaArgs *args, UiText *value); +UIEXPORT void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *states, int numstates); +UIEXPORT void ui_textarea_args_free(UiTextAreaArgs *args); + +UIEXPORT UiTextFieldArgs* ui_textfield_args_new(void); +UIEXPORT void ui_textfield_args_set_fill(UiTextFieldArgs *args, UiBool fill); +UIEXPORT void ui_textfield_args_set_hexpand(UiTextFieldArgs *args, UiBool value); +UIEXPORT void ui_textfield_args_set_vexpand(UiTextFieldArgs *args, UiBool value); +UIEXPORT void ui_textfield_args_set_hfill(UiTextFieldArgs *args, UiBool value); +UIEXPORT void ui_textfield_args_set_vfill(UiTextFieldArgs *args, UiBool value); +UIEXPORT void ui_textfield_args_set_override_defaults(UiTextFieldArgs *args, UiBool value); +UIEXPORT void ui_textfield_args_set_margin(UiTextFieldArgs *args, int value); +UIEXPORT void ui_textfield_args_set_margin_left(UiTextFieldArgs *args, int value); +UIEXPORT void ui_textfield_args_set_margin_right(UiTextFieldArgs *args, int value); +UIEXPORT void ui_textfield_args_set_margin_top(UiTextFieldArgs *args, int value); +UIEXPORT void ui_textfield_args_set_margin_bottom(UiTextFieldArgs *args, int value); +UIEXPORT void ui_textfield_args_set_colspan(UiTextFieldArgs *args, int colspan); +UIEXPORT void ui_textfield_args_set_rowspan(UiTextFieldArgs *args, int rowspan); +UIEXPORT void ui_textfield_args_set_name(UiTextFieldArgs *args, const char *name); +UIEXPORT void ui_textfield_args_set_style_class(UiTextFieldArgs *args, const char *classname); +UIEXPORT void ui_textfield_args_set_onchange(UiTextFieldArgs *args, ui_callback callback); +UIEXPORT void ui_textfield_args_set_onchangedata(UiTextFieldArgs *args, void *onchangedata); +UIEXPORT void ui_textfield_args_set_onactivate(UiTextFieldArgs *args, ui_callback callback); +UIEXPORT void ui_textfield_args_set_onactivatedata(UiTextFieldArgs *args, void *onactivatedata); +UIEXPORT void ui_textfield_args_set_varname(UiTextFieldArgs *args, const char *varname); +UIEXPORT void ui_textfield_args_set_value(UiTextFieldArgs *args, UiString *value); +UIEXPORT void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *states, int numstates); +UIEXPORT void ui_textfield_args_free(UiTextFieldArgs *args); + +UIEXPORT UiSpinBoxArgs* ui_spinbox_args_new(void); +UIEXPORT void ui_spinbox_args_set_fill(UiSpinBoxArgs *args, UiBool fill); +UIEXPORT void ui_spinbox_args_set_hexpand(UiSpinBoxArgs *args, UiBool value); +UIEXPORT void ui_spinbox_args_set_vexpand(UiSpinBoxArgs *args, UiBool value); +UIEXPORT void ui_spinbox_args_set_hfill(UiSpinBoxArgs *args, UiBool value); +UIEXPORT void ui_spinbox_args_set_vfill(UiSpinBoxArgs *args, UiBool value); +UIEXPORT void ui_spinbox_args_set_override_defaults(UiSpinBoxArgs *args, UiBool value); +UIEXPORT void ui_spinbox_args_set_margin(UiSpinBoxArgs *args, int value); +UIEXPORT void ui_spinbox_args_set_margin_left(UiSpinBoxArgs *args, int value); +UIEXPORT void ui_spinbox_args_set_margin_right(UiSpinBoxArgs *args, int value); +UIEXPORT void ui_spinbox_args_set_margin_top(UiSpinBoxArgs *args, int value); +UIEXPORT void ui_spinbox_args_set_margin_bottom(UiSpinBoxArgs *args, int value); +UIEXPORT void ui_spinbox_args_set_colspan(UiSpinBoxArgs *args, int colspan); +UIEXPORT void ui_spinbox_args_set_rowspan(UiSpinBoxArgs *args, int rowspan); +UIEXPORT void ui_spinbox_args_set_name(UiSpinBoxArgs *args, const char *name); +UIEXPORT void ui_spinbox_args_set_style_class(UiSpinBoxArgs *args, const char *classname); +UIEXPORT void ui_spinbox_args_set_onchange(UiSpinBoxArgs *args, ui_callback callback); +UIEXPORT void ui_spinbox_args_set_onchangedata(UiSpinBoxArgs *args, void *onchangedata); +UIEXPORT void ui_spinbox_args_set_min(UiSpinBoxArgs *args, double min); +UIEXPORT void ui_spinbox_args_set_max(UiSpinBoxArgs *args, double max); +UIEXPORT void ui_spinbox_args_set_step(UiSpinBoxArgs *args, double step); +UIEXPORT void ui_spinbox_args_set_digits(UiSpinBoxArgs *args, int digits); +UIEXPORT void ui_spinbox_args_set_varname(UiSpinBoxArgs *args, const char *varname); +UIEXPORT void ui_spinbox_args_set_intvalue(UiSpinBoxArgs *args, UiInteger *value); +UIEXPORT void ui_spinbox_args_set_doublevalue(UiSpinBoxArgs *args, UiDouble *value); +UIEXPORT void ui_spinbox_args_set_rangevalue(UiSpinBoxArgs *args, UiRange *value); +UIEXPORT void ui_spinbox_args_set_groups(UiSpinBoxArgs *args, int *states, int numstates); +UIEXPORT void ui_spinbox_args_free(UiSpinBoxArgs *args); + +UIEXPORT UiWebviewArgs* ui_webview_args_new(void); +UIEXPORT void ui_webview_args_set_fill(UiWebviewArgs *args, UiBool fill); +UIEXPORT void ui_webview_args_set_hexpand(UiWebviewArgs *args, UiBool value); +UIEXPORT void ui_webview_args_set_vexpand(UiWebviewArgs *args, UiBool value); +UIEXPORT void ui_webview_args_set_hfill(UiWebviewArgs *args, UiBool value); +UIEXPORT void ui_webview_args_set_vfill(UiWebviewArgs *args, UiBool value); +UIEXPORT void ui_webview_args_set_override_defaults(UiWebviewArgs *args, UiBool value); +UIEXPORT void ui_webview_args_set_margin(UiWebviewArgs *args, int value); +UIEXPORT void ui_webview_args_set_margin_left(UiWebviewArgs *args, int value); +UIEXPORT void ui_webview_args_set_margin_right(UiWebviewArgs *args, int value); +UIEXPORT void ui_webview_args_set_margin_top(UiWebviewArgs *args, int value); +UIEXPORT void ui_webview_args_set_margin_bottom(UiWebviewArgs *args, int value); +UIEXPORT void ui_webview_args_set_colspan(UiWebviewArgs *args, int colspan); +UIEXPORT void ui_webview_args_set_rowspan(UiWebviewArgs *args, int rowspan); +UIEXPORT void ui_webview_args_set_name(UiWebviewArgs *args, const char *name); +UIEXPORT void ui_webview_args_set_style_class(UiWebviewArgs *args, const char *classname); +UIEXPORT void ui_webview_args_set_varname(UiWebviewArgs *args, const char *varname); +UIEXPORT void ui_webview_args_set_value(UiWebviewArgs *args, UiGeneric *value); +UIEXPORT void ui_webview_args_set_groups(UiWebviewArgs *args, int *states, int numstates); +UIEXPORT void ui_webview_args_free(UiWebviewArgs *args); + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_ARGS_H */ + diff --git a/ui/common/condvar.c b/ui/common/condvar.c new file mode 100644 index 0000000..16f4954 --- /dev/null +++ b/ui/common/condvar.c @@ -0,0 +1,74 @@ +/* + * 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 _WIN32 + +#include "condvar.h" + +#include + +UiCondVar* ui_condvar_create(void) { + UiPosixCondVar *var = malloc(sizeof(UiPosixCondVar)); + var->var.data = NULL; + var->var.intdata = 0; + var->set = 0; + pthread_mutex_init(&var->lock, NULL); + pthread_cond_init(&var->cond, NULL); + return (UiCondVar*)var; +} + +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); + +} + +#endif \ No newline at end of file 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/container.c b/ui/common/container.c new file mode 100644 index 0000000..6f0dde9 --- /dev/null +++ b/ui/common/container.c @@ -0,0 +1,87 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "container.h" +#include "object.h" + +void ui_end_new(UiObject *obj) { + if(!obj->container_end) { + return; + } + UiContainerX *rm = obj->container_end; + uic_object_pop_container(obj); + ui_free(obj->ctx, rm); +} + +void ui_newline(UiObject *obj) { + UiContainerX *container = obj->container_end; + if(container) { + container->newline = TRUE; + } +} + +void uic_layout_setup_expand_fill( + UiLayout *layout, + UiBool def_hexpand, + UiBool def_vexpand, + UiBool def_hfill, + UiBool def_vfill) +{ + if(layout->fill) { + layout->hfill = TRUE; + layout->vfill = TRUE; + layout->hexpand = TRUE; + layout->vexpand = TRUE; + return; + } + + if(!layout->override_defaults) { + if(def_hexpand) { + layout->hexpand = TRUE; + } + if(def_hfill) { + layout->hfill = TRUE; + } + if(def_vexpand) { + layout->vexpand = TRUE; + } + if(def_vfill) { + layout->vfill = TRUE; + } + } +} + +void uic_layout_setup_margin(UiLayout *layout) { + int margin = layout->margin; + if(margin > 0) { + layout->margin_left = margin; + layout->margin_right = margin; + layout->margin_top = margin; + layout->margin_bottom = margin; + } +} diff --git a/ui/common/container.h b/ui/common/container.h new file mode 100644 index 0000000..6f102ed --- /dev/null +++ b/ui/common/container.h @@ -0,0 +1,59 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_CONTAINER_H +#define UIC_CONTAINER_H + +#include "../ui/container.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * prepares the layout horizontal and vertical fill/expand settings + * based on fill and defaults + */ +void uic_layout_setup_expand_fill( + UiLayout *layout, + UiBool def_hexpand, + UiBool def_vexpand, + UiBool def_hfill, + UiBool def_vfill); + +/* + * adjusts margin_* if margin > 0 + */ +void uic_layout_setup_margin(UiLayout *layout); + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_CONTAINER_H */ + diff --git a/ui/common/context.c b/ui/common/context.c new file mode 100644 index 0000000..6c3d1c6 --- /dev/null +++ b/ui/common/context.c @@ -0,0 +1,640 @@ +/* + * 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 "../ui/widget.h" +#include "document.h" +#include "types.h" + + +static UiContext* global_context; + +void uic_init_global_context(void) { + CxMempool *mp = cxMempoolCreateSimple(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->destroy_handler = cxArrayListCreate(ctx->allocator, NULL, sizeof(UiDestroyHandler), 16); + ctx->obj = toplevel; + ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16); + + ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, 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_document; + +#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_add_destructor(UiContext *ctx, cx_destructor_func func, void *data) { + UiDestroyHandler handler; + handler.destructor = func; + handler.data = data; + cxListAdd(ctx->destroy_handler, &handler); +} + +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); + doc_ctx->parent = ctx; + + // if a document variable has the same name as a parent variable, + // move the bindings to the document + UiContext *var_ctx = ctx; + while(var_ctx) { + CxMapIterator i = cxMapIterator(var_ctx->vars); + cx_foreach(CxMapEntry*, entry, i) { + printf("attach %.*s\n", (int)entry->key->len, (char*)entry->key->data); + UiVar *var = entry->value; + UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key); + if(docvar) { + // bind var to document var + uic_copy_binding(var, docvar, TRUE); + cxIteratorFlagRemoval(i); + } + } + + var_ctx = var_ctx->parent; + } +} + +static void uic_context_unbind_vars(UiContext *ctx) { + CxMapIterator mi = cxMapIterator(ctx->vars); + cx_foreach(CxMapEntry*, entry, mi) { + UiVar *var = entry->value; + // var->from && var->from_ctx && var->from_ctx != ctx + uic_save_var(var); + if(var->from) { + uic_copy_binding(var, var->from, FALSE); + cxMapPut(var->from->from_ctx->vars, *entry->key, var->from); + var->from = NULL; + } + } + + if(ctx->documents) { + CxIterator i = cxListIterator(ctx->documents); + cx_foreach(void *, doc, i) { + UiContext *subctx = ui_document_context(doc); + uic_context_unbind_vars(subctx); + } + } +} + +void uic_context_detach_document(UiContext *ctx, void *document) { + // find the document in the documents list + size_t docIndex = cxListFind(ctx->documents, document); + if(!cxListIndexValid(ctx->documents, docIndex)) { + 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 + docctx->parent = NULL; +} + +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); + } + + cxListFree(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->original_value = NULL; + var->from = NULL; + var->from_ctx = ctx; + + cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var); + + cxMapPut(ctx->vars, 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->original_value = NULL; + 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; + void *tovalue = to->value; + // update var + if(copytodoc) { + to->from = from; // from which UiVar are the bindings copied + from->original_value = fromvalue; // save original value otherwise it would be lost + // widgets store a reference to the UiVar with their value + // the UiVar object must be updated to contain the current value object + from->value = tovalue; + } else { + if(to->original_value) { + to->value = to->original_value; + tovalue = to->value; + } + } + + ui_setop_enable(TRUE); + + // 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 = tovalue; + if(!f->obj) break; + uic_int_copy(f, t); + t->set(t, t->value); + break; + } + case UI_VAR_DOUBLE: { + UiDouble *f = fromvalue; + UiDouble *t = tovalue; + if(!f->obj) break; + uic_double_copy(f, t); + t->set(t, t->value); + break; + } + case UI_VAR_STRING: { + UiString *f = fromvalue; + UiString *t = tovalue; + if(!f->obj) break; + uic_string_copy(f, t); + char *tvalue = t->value.ptr ? t->value.ptr : ""; + char *fvalue = f->value.ptr ? f->value.ptr : ""; + t->set(t, tvalue); + break; + } + case UI_VAR_TEXT: { + UiText *f = fromvalue; + UiText *t = tovalue; + if(!f->obj) break; + uic_text_copy(f, t); + t->restore(t); + break; + } + case UI_VAR_LIST: { + UiList *f = fromvalue; + UiList *t = tovalue; + uic_list_copy(f, t); + ui_list_update(t); + break; + } + case UI_VAR_RANGE: { + UiRange *f = fromvalue; + UiRange *t = tovalue; + 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 = tovalue; + if(!f->obj) break; + uic_generic_copy(f, t); + t->set(t, t->value, t->type); + break; + } + } + + ui_setop_enable(FALSE); +} + +void uic_save_var(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, const 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_document(UiContext *ctx, void *document) { + uic_context_detach_document(ctx, document); +} + +void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata) { + ctx->close_callback = fnc; + ctx->close_data = udata; +} + +void ui_context_destroy(UiContext *ctx) { + CxIterator i = cxListIterator(ctx->destroy_handler); + cx_foreach(UiDestroyHandler *, h, i) { + h->destructor(h->data); + } + cxMempoolFree(ctx->mp); +} + +UiContext* ui_context_parent(UiContext *ctx) { + return ctx->parent; +} + + +void ui_set_group(UiContext *ctx, int group) { + if(!cxListIndexValid(ctx->groups, cxListFind(ctx->groups, &group))) { + 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, ...) { + if(enable == NULL) { + enable = (ui_enablefunc)ui_set_enabled; + } + // 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); + + cxListFree(groups); +} + +void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, int *groups, int ngroups) { + if(enable == NULL) { + enable = (ui_enablefunc)ui_set_enabled; + } + CxList *ls = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), ngroups); + for(int i=0;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; +} + +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; +} + +void ui_reg_destructor(UiContext *ctx, void *data, ui_destructor_func destr) { + cxMempoolRegister(ctx->mp, data, (cx_destructor_func)destr); +} + +void ui_set_destructor(void *mem, ui_destructor_func destr) { + cxMempoolSetDestructor(mem, (cx_destructor_func)destr); +} diff --git a/ui/common/context.h b/ui/common/context.h new file mode 100644 index 0000000..f517090 --- /dev/null +++ b/ui/common/context.h @@ -0,0 +1,155 @@ +/* + * 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 struct UiDestroyHandler UiDestroyHandler; + +typedef 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 +} UiVarType; + +struct UiContext { + UiContext *parent; + UiObject *obj; + CxMempool *mp; + const CxAllocator *allocator; + CxList *destroy_handler; + + void *document; + CxList *documents; + + CxMap *vars; + + 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; +}; + +struct UiVar { + void *value; + void *original_value; + UiVarType type; + UiVar *from; + UiContext *from_ctx; +}; + +struct UiGroupWidget { + void *widget; + ui_enablefunc enable; + int *groups; + int numgroups; +}; + +struct UiDestroyHandler { + cx_destructor_func destructor; + void *data; +}; + + +void uic_init_global_context(void); + +UiContext* uic_context(UiObject *toplevel, CxMempool *mp); +UiContext* uic_root_context(UiContext *ctx); +void uic_context_add_destructor(UiContext *ctx, cx_destructor_func func, void *data); + +void uic_context_prepare_close(UiContext *ctx); + +void uic_context_attach_document(UiContext *ctx, void *document); +void uic_context_detach_document(UiContext *ctx, void *document); +void uic_context_attach_context(UiContext *ctx, UiContext *doc_ctx); // TODO +void uic_context_detach_context(UiContext *ctx, UiContext *doc_ctx); // TODO +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_var(UiVar *var); +void uic_unbind_var(UiVar *var); + +void uic_reg_var(UiContext *ctx, const 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..3eb5874 --- /dev/null +++ b/ui/common/document.c @@ -0,0 +1,77 @@ +/* + * 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 + + + + +void* ui_document_new(size_t size) { + CxMempool *mp = cxMempoolCreateSimple(256); + const CxAllocator *a = mp->allocator; + UiContext *ctx = uic_context(NULL, mp); + + UiDoc *document = cxCalloc(a, sizeof(UiDoc) + size, 1); + document->ctx = ctx; + return &document->doc; +} + +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.eventdatatype = 0; + ev.intval = 0; + + if(ctx->close_callback) { + ctx->close_callback(&ev, ctx->close_data); + } + cxMempoolFree(ctx->mp); + } +} + +UiContext* ui_document_context(void *doc) { + if(doc) { + char *docPtr = doc; + UiDoc *document = (UiDoc*)(docPtr - sizeof(void*)); + return document->ctx; + } else { + return NULL; + } +} diff --git a/ui/common/document.h b/ui/common/document.h new file mode 100644 index 0000000..b98c810 --- /dev/null +++ b/ui/common/document.h @@ -0,0 +1,52 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_DOCUMENT_H +#define UIC_DOCUMENT_H + +#include "../ui/toolkit.h" +#include "context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiDoc { + UiContext *ctx; + char doc[]; +} UiDoc; + +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..cdf3a8c --- /dev/null +++ b/ui/common/menu.c @@ -0,0 +1,337 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "menu.h" + +#include +#include +#include + +#include +#include + + +static UiMenuBuilder *current_builder; +static UiMenuBuilder global_builder; + +static int menu_item_counter = 0; + +void uic_menu_init(void) { + global_builder.current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); + current_builder = &global_builder; +} + +static void add_menu(UiMenu *menu) { + cx_linked_list_add( + (void**)¤t_builder->menus_begin, + (void**)¤t_builder->menus_end, + offsetof(UiMenu, item.prev), + offsetof(UiMenu, item.next), + menu); +} + +static void add_item(UiMenuItemI *item) { + UiMenu *menu = cxListAt(current_builder->current, 0); + cx_linked_list_add( + (void**)&menu->items_begin, + (void**)&menu->items_end, + offsetof(UiMenu, item.prev), + offsetof(UiMenu, item.next), + item); +} + +static void mitem_set_id(UiMenuItemI *item) { + snprintf(item->id, 8, "%x", menu_item_counter++); +} + +static char* nl_strdup(const char* s) { + return s ? strdup(s) : NULL; +} + +int* uic_copy_groups(const int* groups, size_t *ngroups) { + *ngroups = 0; + if (!groups) { + return NULL; + } + + size_t n; + for (n = 0; groups[n] > -1; n++) { } + + if (ngroups > 0) { + int* newarray = calloc(n+1, sizeof(int)); + memcpy(newarray, groups, n * sizeof(int)); + newarray[n] = -1; + *ngroups = n; + return newarray; + } + return NULL; +} + +void ui_menu_create(const char *label) { + // create menu + UiMenu *menu = malloc(sizeof(UiMenu)); + mitem_set_id(&menu->item); + menu->item.prev = NULL; + menu->item.next = NULL; + menu->item.type = UI_MENU; + + menu->label = nl_strdup(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->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->icon = nl_strdup(args->icon); + item->varname = nl_strdup(args->varname); + item->userdata = args->onchangedata; + item->callback = args->onchange; + item->groups = uic_copy_groups(args->groups, &item->ngroups); + + add_item((UiMenuItemI*)item); +} + +void ui_menu_radioitem_create(UiMenuToggleItemArgs *args) { + UiMenuCheckItem* item = malloc(sizeof(UiMenuCheckItem)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_RADIO_ITEM; + + item->label = nl_strdup(args->label); + item->icon = nl_strdup(args->icon); + item->varname = nl_strdup(args->varname); + item->userdata = args->onchangedata; + item->callback = args->onchange; + item->groups = uic_copy_groups(args->groups, &item->ngroups); + + add_item((UiMenuItemI*)item); +} + +void ui_menu_itemlist_create(UiMenuItemListArgs *args) { + UiMenuItemList*item = malloc(sizeof(UiMenuItemList)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_ITEM_LIST; + item->getvalue = args->getvalue; + item->callback = args->onselect; + item->userdata = args->onselectdata; + item->varname = nl_strdup(args->varname); + item->addseparator = args->addseparator; + + add_item((UiMenuItemI*)item); +} + +void ui_menu_checkitemlist_create(UiMenuItemListArgs *args) { + UiMenuItemList* item = malloc(sizeof(UiMenuItemList)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_CHECKITEM_LIST; + item->callback = args->onselect; + item->userdata = args->onselectdata; + item->varname = nl_strdup(args->varname); + + add_item((UiMenuItemI*)item); +} + +void ui_menu_radioitemlist_create(UiMenuItemListArgs *args) { + UiMenuItemList* item = malloc(sizeof(UiMenuItemList)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_RADIOITEM_LIST; + item->callback = args->onselect; + item->userdata = args->onselectdata; + item->varname = nl_strdup(args->varname); + + add_item((UiMenuItemI*)item); +} + + +void uic_add_menu_to_stack(UiMenu* menu) { + cxListInsert(current_builder->current, 0, menu); +} + +UiMenu* uic_get_menu_list(void) { + return current_builder->menus_begin; +} + +UIEXPORT void ui_menu_close(void) { + UiMenu* menu = cxListAt(current_builder->current, 0); + menu->end = 1; +} + +UIEXPORT int ui_menu_is_open(void) { + UiMenu* menu = cxListAt(current_builder->current, 0); + if (menu->end) { + ui_menu_end(); + return 0; + } + return 1; +} + + +void ui_contextmenu_builder(UiMenuBuilder **out_builder) { + UiMenuBuilder *builder = malloc(sizeof(UiMenuBuilder)); + builder->menus_begin = NULL; + builder->menus_end = NULL; + builder->current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); + builder->ref = 1; + 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; + free(menu->label); + 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->icon); + break; + } + case UI_MENU_CHECK_ITEM: { + UiMenuCheckItem *i = (UiMenuCheckItem*)item; + free(i->groups); + free(i->label); + free(i->icon); + free(i->varname); + break; + } + case UI_MENU_RADIO_ITEM: { + UiMenuRadioItem *i = (UiMenuRadioItem*)item; + free(i->groups); + free(i->label); + free(i->icon); + free(i->varname); + break; + } + case UI_MENU_ITEM_LIST: { + break; + } + case UI_MENU_CHECKITEM_LIST: { + break; + } + case UI_MENU_RADIOITEM_LIST: { + break; + } + } + + free(item); +} + +void ui_menubuilder_free(UiMenuBuilder *builder) { + UiMenuItemI *m = &builder->menus_begin->item; + while(m) { + UiMenuItemI *next = m->next; + free_menuitem(m); + m = next; + } + cxListFree(builder->current); + free(builder); +} + +void ui_menubuilder_ref(UiMenuBuilder *builder) { + builder->ref++; +} + +void ui_menubuilder_unref(UiMenuBuilder *builder) { + if(--builder->ref <= 0) { + ui_menubuilder_free(builder); + } +} diff --git a/ui/common/menu.h b/ui/common/menu.h new file mode 100644 index 0000000..e9dce38 --- /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; + char *label; + UiMenuItemI *items_begin; + UiMenuItemI *items_end; + UiMenu *parent; + int end; +}; + +struct UiMenuItem { + UiMenuItemI item; + ui_callback callback; + char *label; + char *icon; + void *userdata; + int *groups; + size_t ngroups; +}; + +struct UiMenuCheckItem { + UiMenuItemI item; + char *label; + char *icon; + char *varname; + ui_callback callback; + void *userdata; + int *groups; + size_t ngroups; +}; + +struct UiMenuRadioItem { + UiMenuItemI item; + char *label; + char *icon; + char *varname; + ui_callback callback; + void *userdata; + int *groups; + size_t ngroups; +}; + +struct UiMenuItemList { + UiMenuItemI item; + ui_getvaluefunc getvalue; + ui_callback callback; + void *userdata; + char *varname; + UiBool addseparator; +}; + + + +struct UiMenuBuilder { + UiMenu *menus_begin; + UiMenu *menus_end; + CxList *current; + int ref; +}; + +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..4c41e28 --- /dev/null +++ b/ui/common/object.c @@ -0,0 +1,147 @@ +/* + * 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" + +#include + +#include "../ui/container.h" + +static CxList *creation_callbacks; +static CxList *destruction_callbacks; + +typedef struct objcallback { + ui_object_callback func; + void *userdata; +} objcallback; + +void ui_register_object_creation_callback(ui_object_callback func, void *userdata) { + if(!creation_callbacks) { + creation_callbacks = cxLinkedListCreateSimple(sizeof(objcallback)); + } + objcallback cb = { func, userdata }; + cxListAdd(creation_callbacks, &cb); +} + +void ui_register_object_destruction_callback(ui_object_callback func, void *userdata) { + if(!destruction_callbacks) { + destruction_callbacks = cxLinkedListCreateSimple(sizeof(objcallback)); + } + objcallback cb = { func, userdata }; + cxListAdd(destruction_callbacks, &cb); +} + +void uic_object_created(UiObject *obj) { + CxIterator i = cxListIterator(creation_callbacks); + cx_foreach(objcallback *, cb, i) { + cb->func(obj, cb->userdata); + } +} + +void uic_object_destroyed(UiObject *obj) { + CxIterator i = cxListIterator(destruction_callbacks); + cx_foreach(objcallback *, cb, i) { + cb->func(obj, cb->userdata); + } +} + +void ui_object_ref(UiObject *obj) { + obj->ref++; +} + +int ui_object_unref(UiObject *obj) { + // it is possible to have 0 references, in case + // a window was created but ui_show was never called + if(obj->ref == 0 || --obj->ref == 0) { + if(obj->destroy) { + obj->destroy(obj); + } + uic_object_destroy(obj); + return 0; + } + return 1; +} + +void uic_object_destroy(UiObject *obj) { + if(obj->ctx->close_callback) { + UiEvent ev; + ev.window = obj->window; + ev.document = obj->ctx->document; + ev.obj = obj; + ev.eventdata = NULL; + ev.eventdatatype = 0; + ev.intval = 0; + obj->ctx->close_callback(&ev, obj->ctx->close_data); + } + uic_object_destroyed(obj); + cxMempoolFree(obj->ctx->mp); +} + +UiObject* uic_object_new_toplevel(void) { + fflush(stdout); + CxMempool *mp = cxMempoolCreateSimple(256); + UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); + fflush(stdout); + obj->ctx = uic_context(obj, mp); + obj->ctx->parent = ui_global_context(); + fflush(stdout); + uic_object_created(obj); + fflush(stdout); + return obj; +} + +UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget) { + UiObject *newobj = cxCalloc(ctx->allocator, 1, sizeof(UiObject)); + newobj->ctx = ctx; + newobj->widget = widget; + uic_object_created(newobj); + return newobj; +} + +void uic_object_push_container(UiObject *toplevel, UiContainerX *newcontainer) { + newcontainer->prev = toplevel->container_end; + if(toplevel->container_end) { + toplevel->container_end->next = newcontainer; + toplevel->container_end = newcontainer; + } else { + toplevel->container_begin = newcontainer; + toplevel->container_end = newcontainer; + } +} + +void uic_object_pop_container(UiObject *toplevel) { + toplevel->container_end = toplevel->container_end->prev; + if(toplevel->container_end) { + toplevel->container_end->next = NULL; + } else { + toplevel->container_begin = NULL; + } +} diff --git a/ui/common/object.h b/ui/common/object.h new file mode 100644 index 0000000..a899a83 --- /dev/null +++ b/ui/common/object.h @@ -0,0 +1,62 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UIC_OBJECT_H +#define UIC_OBJECT_H + +#include "../ui/toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*ui_object_callback)(UiObject *obj, void *userdata); + +void ui_register_object_creation_callback(ui_object_callback func, void *userdata); +void ui_register_object_destruction_callback(ui_object_callback func, void *userdata); + +void uic_object_created(UiObject *obj); +void uic_object_destroyed(UiObject *obj); + +void uic_object_destroy(UiObject *obj); + +UiObject* uic_object_new_toplevel(void); +UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget); +UiObject* uic_ctx_object_new(UiContext *ctx, UIWIDGET widget); + +void uic_object_push_container(UiObject *toplevel, UiContainerX *newcontainer); +void uic_object_pop_container(UiObject *toplevel); + + + +#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..4d401b5 --- /dev/null +++ b/ui/common/objs.mk @@ -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. +# + +COMMON_SRC_DIR = ui/common/ +COMMON_OBJPRE = $(OBJ_DIR)$(COMMON_SRC_DIR) + +COMMON_OBJ = context$(OBJ_EXT) +COMMON_OBJ += document$(OBJ_EXT) +COMMON_OBJ += object$(OBJ_EXT) +COMMON_OBJ += container$(OBJ_EXT) +COMMON_OBJ += types$(OBJ_EXT) +COMMON_OBJ += properties$(OBJ_EXT) +COMMON_OBJ += menu$(OBJ_EXT) +COMMON_OBJ += toolbar$(OBJ_EXT) +COMMON_OBJ += ucx_properties$(OBJ_EXT) +COMMON_OBJ += threadpool$(OBJ_EXT) +COMMON_OBJ += condvar$(OBJ_EXT) +COMMON_OBJ += args$(OBJ_EXT) +COMMON_OBJ += wrapper$(OBJ_EXT) + +TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)uic_%) +TOOLKITSOURCE += $(COMMON_OBJ:%$(OBJ_EXT)=common/%.c) + diff --git a/ui/common/properties.c b/ui/common/properties.c new file mode 100644 index 0000000..28676fb --- /dev/null +++ b/ui/common/properties.c @@ -0,0 +1,339 @@ +/* + * 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 + +#ifdef _WIN32 +#include +#endif + +#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(const 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); + application_properties->collection.simple_destructor = free; + + 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; +} + +// public +int ui_app_save_settings(void) { + return uic_store_app_properties(); +} + + +const char* ui_get_property(const char *name) { + return cxMapGet(application_properties, name); +} + +void ui_set_property(const char *name, const char *value) { + if(value) { + cxMapPut(application_properties, name, strdup(value)); + } else { + cxMapRemove(application_properties, name); + } +} + +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(const char *path) { + free(locales_dir); + locales_dir = path ? strdup(path) : NULL; +} + +void ui_pixmaps_dir(const char *path) { + free(pixmaps_dir); + pixmaps_dir = path ? strdup(path) : NULL; +} + +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(const 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(const char *name) { + char *value = uistr_n(name); + return value ? value : "missing string"; +} + +char* uistr_n(const 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..7988eab --- /dev/null +++ b/ui/common/threadpool.c @@ -0,0 +1,196 @@ +/* + * 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 _WIN32 + +#include "threadpool.h" +#include "context.h" + +#include +#include +#include +#include + +static threadpool_job kill_job; + +UiThreadpool* threadpool_new(int min, int max) { + UiThreadpool *pool = malloc(sizeof(UiThreadpool)); + pool->queue = NULL; + pool->queue_len = 0; + pool->num_idle = 0; + pool->min_threads = min; + pool->max_threads = max; + + pthread_mutex_init(&pool->queue_lock, NULL); + pthread_mutex_init(&pool->avlbl_lock, NULL); + pthread_cond_init(&pool->available, NULL); + + return pool; +} + +int threadpool_start(UiThreadpool *pool) { + /* create pool threads */ + for(int i=0;imin_threads;i++) { + pthread_t t; + if (pthread_create(&t, NULL, threadpool_func, pool) != 0) { + fprintf(stderr, "uic: threadpool_start: pthread_create failed: %s", strerror(errno)); + return 1; + } + } + return 0; +} + +void* threadpool_func(void *data) { + UiThreadpool *pool = (UiThreadpool*)data; + + for(;;) { + threadpool_job *job = threadpool_get_job(pool); + if(job == &kill_job) { + break; + } + + job->callback(job->data); + + free(job); + } + return NULL; +} + +threadpool_job* threadpool_get_job(UiThreadpool *pool) { + pthread_mutex_lock(&pool->queue_lock); + + threadpool_job *job = NULL; + pool->num_idle++; + while(job == NULL) { + if(pool->queue_len == 0) { + pthread_cond_wait(&pool->available, &pool->queue_lock); + continue; + } else { + pool_queue_t *q = pool->queue; + job = q->job; + pool->queue = q->next; + pool->queue_len--; + free(q); + } + } + pool->num_idle--; + + pthread_mutex_unlock(&pool->queue_lock); + return job; +} + +void threadpool_run(UiThreadpool *pool, job_callback_f func, void *data) { + // TODO: handle errors + + threadpool_job *job = malloc(sizeof(threadpool_job)); + job->callback = func; + job->data = data; + + pthread_mutex_lock(&pool->queue_lock); + threadpool_enqueue_job(pool, job); + + int create_thread = 0; + int destroy_thread = 0; + int diff = pool->queue_len - pool->num_idle; + + //if(pool->queue_len == 1) { + pthread_cond_signal(&pool->available); + //} + + pthread_mutex_unlock(&pool->queue_lock); +} + +void threadpool_enqueue_job(UiThreadpool *pool, threadpool_job *job) { + pool_queue_t *q = malloc(sizeof(pool_queue_t)); + q->job = job; + q->next = NULL; + + if(pool->queue == NULL) { + pool->queue = q; + } else { + pool_queue_t *last_elem = pool->queue; + while(last_elem->next != NULL) { + last_elem = last_elem->next; + } + last_elem->next = q; + } + pool->queue_len++; +} + + + + + + +UiThreadpool* ui_threadpool_create(int nthreads) { + UiThreadpool *pool = threadpool_new(nthreads, nthreads); + threadpool_start(pool); // TODO: check return value + return pool; +} + +void ui_threadpool_destroy(UiThreadpool* pool) { + +} + +static int ui_threadpool_job_finish(void *data) { + UiJob *job = data; + UiEvent event; + event.obj = job->obj; + event.window = job->obj->window; + event.document = job->obj->ctx->document; + event.intval = 0; + event.eventdata = NULL; + event.eventdatatype = 0; + 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..d167062 --- /dev/null +++ b/ui/common/threadpool.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 UIC_THREADPOOL_H +#define UIC_THREADPOOL_H + +#include "../ui/toolkit.h" + +#ifndef _WIN32 +#include +#endif + +#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..f237290 --- /dev/null +++ b/ui/common/toolbar.c @@ -0,0 +1,156 @@ +/* + * 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 +#include + +static CxMap* toolbar_items; + +static CxList* toolbar_defaults[8]; // 0: left 1: center 2: right + +static UiToolbarMenuItem* ui_appmenu; + + +void uic_toolbar_init(void) { + toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + for(int i=0;i<8;i++) { + toolbar_defaults[i] = 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, size_t *nvstates) { + UiToolbarItemArgs newargs; + newargs.label = nl_strdup(args->label); + newargs.icon = nl_strdup(args->icon); + newargs.tooltip = nl_strdup(args->tooltip); + newargs.onclick = args->onclick; + newargs.onclickdata = args->onclickdata; + newargs.groups = uic_copy_groups(args->groups, ngroups); + newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates); + 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, &item->nvstates); + cxMapPut(toolbar_items, name, item); +} + + +static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs *args, size_t *ngroups, size_t *nvstates) { + UiToolbarToggleItemArgs newargs; + newargs.label = nl_strdup(args->label); + newargs.icon = nl_strdup(args->icon); + newargs.tooltip = nl_strdup(args->tooltip); + newargs.varname = nl_strdup(args->varname); + newargs.onchange = args->onchange; + newargs.onchangedata = args->onchangedata; + newargs.groups = uic_copy_groups(args->groups, ngroups); + newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates); + 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, &item->nvstates); + cxMapPut(toolbar_items, name, item); +} + +static UiToolbarMenuArgs menuargs_copy(UiToolbarMenuArgs *args, size_t *nvstates) { + UiToolbarMenuArgs newargs; + newargs.label = nl_strdup(args->label); + newargs.icon = nl_strdup(args->icon); + newargs.tooltip = nl_strdup(args->tooltip); + newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates); + 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->nvstates); + + 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 < 8) { + 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 < 8) { + cxListAdd(toolbar_defaults[pos], cp); + } else { + fprintf(stderr, "Error: illegal toolbar position: %d\n", (int)pos); + } +} + +UiBool uic_toolbar_isenabled(void) { + size_t size = 0; + for(int i=0;i<8;i++) { + size += cxListSize(toolbar_defaults[i]); + } + return size > 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..1ac1d55 --- /dev/null +++ b/ui/common/toolbar.h @@ -0,0 +1,101 @@ +/* + * 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; + size_t nvstates; +}; + +struct UiToolbarToggleItem { + UiToolbarItemI item; + UiToolbarToggleItemArgs args; + size_t ngroups; + size_t nvstates; +}; + +struct UiToolbarMenuItem { + UiToolbarItemI item; + UiMenu menu; + UiToolbarMenuArgs args; + size_t nvstates; + 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..57ad68e --- /dev/null +++ b/ui/common/types.c @@ -0,0 +1,828 @@ +/* + * 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" +#include "../ui/image.h" + +static ui_list_init_func default_list_init; +static void *default_list_init_userdata; + +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.eventdatatype = UI_EVENT_DATA_POINTER; + 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 --------------------------- */ + +void uic_ucx_list_init(UiContext *ctx, UiList *list, void *unused) { + list->data = cxArrayListCreate(ctx->mp->allocator, NULL, CX_STORE_POINTERS, 32); + list->first = ui_list_first; + list->next = ui_list_next; + list->get = ui_list_get; + list->count = ui_list_count; +} + +UiList* ui_list_new(UiContext *ctx, const char *name) { + return ui_list_new2(ctx, name, default_list_init ? default_list_init : uic_ucx_list_init, default_list_init_userdata); +} + +UiList* ui_list_new2(UiContext *ctx, const char *name, ui_list_init_func listinit, void *userdata) { + UiList *list = cxMalloc(ctx->mp->allocator, sizeof(UiList)); + memset(list, 0, sizeof(UiList)); + listinit(ctx, list, userdata); + + if(name) { + uic_reg_var(ctx, name, UI_VAR_LIST, list); + } + + return list; +} + +void ui_list_free(UiList *list) { + cxListFree(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); +} + +void ui_list_update(UiList *list) { + if(list->update) { + list->update(list, -1); + } +} + +void ui_list_update_row(UiList *list, int row) { + if(list->update) { + list->update(list, row); + } +} + +UiListSelection ui_list_get_selection(UiList *list) { + if(list->getselection) { + return list->getselection(list); + } else { + return (UiListSelection){0, NULL}; + } +} + +void ui_list_addobsv(UiList *list, ui_callback f, void *data) { + list->observers = ui_add_observer(list->observers, f, data); +} + +void ui_list_notify(UiList *list) { + ui_notify(list->observers, list); +} + + +typedef struct { + int type; + char *name; +} UiColumn; + +UiModel* ui_model(UiContext *ctx, ...) { + UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel)); + + va_list ap; + va_start(ap, ctx); + + 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->alloc = len; + 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] = ui_strdup(ctx, c->name); + i++; + } + cxListFree(cols); + + return info; +} + +#define UI_MODEL_DEFAULT_ALLOC_SIZE 16 + +UiModel* ui_model_new(UiContext *ctx) { + UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel)); + info->alloc = UI_MODEL_DEFAULT_ALLOC_SIZE; + info->types = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(UiModelType)); + info->titles = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(char*)); + info->columnsize = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(int)); + return info; +} + +void ui_model_add_column(UiContext *ctx, UiModel *model, UiModelType type, const char *title, int width) { + if(model->columns >= model->alloc) { + model->alloc += UI_MODEL_DEFAULT_ALLOC_SIZE; + model->types = ui_realloc(ctx, model->types, model->alloc * sizeof(UiModelType)); + model->titles = ui_realloc(ctx, model->titles, model->alloc * sizeof(char*)); + model->columnsize = ui_realloc(ctx, model->columnsize, model->alloc * sizeof(int)); + } + model->types[model->columns] = type; + model->titles[model->columns] = ui_strdup(ctx, title); + model->columnsize[model->columns] = width; + model->columns++; +} + +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; + } + newmodel->columnsize = cxCalloc(a, model->columns, sizeof(int)); + memcpy(newmodel->columnsize, model->columnsize, model->columns*sizeof(int)); + + return newmodel; +} + +void ui_model_free(UiContext *ctx, UiModel *mi) { + const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator; + for(int i=0;icolumns;i++) { + ui_free(ctx, mi->titles[i]); + } + cxFree(a, mi->types); + cxFree(a, mi->titles); + cxFree(a, mi->columnsize); + cxFree(a, mi); +} + +// types + +// public functions +UiInteger* ui_int_new(UiContext *ctx, const 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, const 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; +} + +static void string_destroy(UiString *s) { + if(s->value.free && s->value.ptr) { + s->value.free(s->value.ptr); + } +} + +UiString* ui_string_new(UiContext *ctx, const char *name) { + UiString *s = ui_malloc(ctx, sizeof(UiString)); + memset(s, 0, sizeof(UiString)); + ui_set_destructor(s, (ui_destructor_func)string_destroy); + if(name) { + uic_reg_var(ctx, name, UI_VAR_STRING, s); + } + return s; +} + +static void text_destroy(UiText *t) { + if(t->destroy) { + t->destroy(t); + } +} + +UiText* ui_text_new(UiContext *ctx, const char *name) { + UiText *t = ui_malloc(ctx, sizeof(UiText)); + memset(t, 0, sizeof(UiText)); + ui_set_destructor(t, (ui_destructor_func)text_destroy); + if(name) { + uic_reg_var(ctx, name, UI_VAR_TEXT, t); + } + return t; +} + +UiRange* ui_range_new(UiContext *ctx, const 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; +} + +static void generic_destroy(UiGeneric *g) { + if(g->destroy) { + g->destroy(g); + } +} + +UiGeneric* ui_generic_new(UiContext *ctx, const char *name) { + UiGeneric *g = ui_malloc(ctx, sizeof(UiGeneric)); + memset(g, 0, sizeof(UiGeneric)); + ui_set_destructor(g, (ui_destructor_func)generic_destroy); + if(name) { + uic_reg_var(ctx, name, UI_VAR_GENERIC, g); + } + return g; +} + + +void ui_int_set(UiInteger* i, int64_t value) { + if (i) { + if (i->set) { + ui_setop_enable(TRUE); + i->set(i, value); + ui_setop_enable(FALSE); + } else { + i->value = 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) { + if (d->set) { + ui_setop_enable(TRUE); + d->set(d, value); + ui_setop_enable(FALSE); + } else { + d->value = 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) { + if (s->set) { + ui_setop_enable(TRUE); + s->set(s, value); + ui_setop_enable(FALSE); + } else { + if(s->value.free) { + s->value.free(s->value.ptr); + } + if (value) { + s->value.ptr = strdup(value); + s->value.free = free; + } else { + s->value.ptr = NULL; + s->value.free = NULL; + } + } + } +} + +char* ui_string_get(UiString* s) { + if (s) { + return s->get ? s->get(s) : s->value.ptr; + } + else { + return NULL; + } +} + +void ui_text_set(UiText* s, const char* value) { + if (s) { + if (s->set) { + ui_setop_enable(TRUE); + s->set(s, value); + ui_setop_enable(FALSE); + } else { + if(s->value.free) { + s->value.free(s->value.ptr); + } + if (value) { + s->value.ptr = strdup(value); + s->value.free = free; + } else { + s->value.ptr = NULL; + s->value.free = NULL; + } + } + } +} + +char* ui_text_get(UiText* s) { + if (s) { + return s->get ? s->get(s) : s->value.ptr; + } + else { + return NULL; + } +} + +void ui_range_set(UiRange *r, double value) { + if (r) { + if (r->set) { + r->set(r, value); + } else { + r->value = value; + } + } +} + +void ui_range_set_range(UiRange *r, double min, double max) { + if (r) { + if (r->setrange) { + r->setrange(r, min, max); + } else { + r->min = min; + r->max = max; + } + } +} + +void ui_range_set_extent(UiRange *r, double extent) { + if (r) { + if (r->setextent) { + r->setextent(r, extent); + } else { + r->extent = extent; + } + } +} + +double ui_range_get(UiRange *r) { + if (r) { + return r->get ? r->get(r) : r->value; + } else { + return 0; + } +} + +double ui_range_get_min(UiRange *r) { + if (r) { + return r->min; + } else { + return 0; + } +} + +double ui_range_get_max(UiRange *r) { + if (r) { + return r->max; + } else { + return 0; + } +} + +double ui_range_get_extent(UiRange *r) { + if (r) { + return r->extent; + } else { + return 0; + } +} + +void ui_generic_set_image(UiGeneric *g, void *img) { + if(g->set) { + g->set(g, img, UI_IMAGE_OBJECT_TYPE); + } else { + if(g->value) { + ui_image_unref(g->value); + } + ui_image_ref(img); + g->value = img; + g->type = UI_IMAGE_OBJECT_TYPE; + } +} + +void* ui_generic_get_image(UiGeneric *g) { + if(g->type) { + if(!strcmp(g->type, UI_IMAGE_OBJECT_TYPE)) { + return g->value; + } else { + return NULL; + } + } + return g->value; +} + + +// 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->restore = from->restore; + to->save = from->save; + to->destroy = from->destroy; + + to->obj = from->obj; + // do not copy the undo manager, data1, data2 +} + +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->getselection = from->getselection; + to->setselection = from->setselection; + 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->save(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; +} + +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->getselection = NULL; + l->setselection = 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) { + UiListSelection sel = { 0, NULL }; + if(index >= 0) { + 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); +} + +static int ui_set_op = 0; + +void ui_setop_enable(int set) { + ui_set_op = set; +} + +int ui_get_setop(void) { + return ui_set_op; +} + +/* ---------------- List initializers and wrapper functions ---------------- */ + +void ui_global_list_initializer(ui_list_init_func func, void *userdata) { + default_list_init = func; + default_list_init_userdata = userdata; +} + +void ui_list_class_set_first(UiList *list, void*(*first)(UiList *list)) { + list->first = first; +} + +void ui_list_class_set_next(UiList *list, void*(*next)(UiList *list)) { + list->next = next; +} + +void ui_list_class_set_get(UiList *list, void*(*get)(UiList *list, int i)) { + list->get = get; +} + +void ui_list_class_set_count(UiList *list, int(*count)(UiList *list)) { + list->count = count; +} + +void ui_list_class_set_data(UiList *list, void *data) { + list->data = data; +} + +void ui_list_class_set_iter(UiList *list, void *iter) { + list->iter = iter; +} diff --git a/ui/common/types.h b/ui/common/types.h new file mode 100644 index 0000000..46d6c2e --- /dev/null +++ b/ui/common/types.h @@ -0,0 +1,70 @@ +/* + * 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_ucx_list_init(UiContext *ctx, UiList *list, void *unused); + +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..21e9341 --- /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) { + CxMapIterator 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/common/wrapper.c b/ui/common/wrapper.c new file mode 100644 index 0000000..50ecf40 --- /dev/null +++ b/ui/common/wrapper.c @@ -0,0 +1,335 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "wrapper.h" +#include "types.h" +#include +#include + +/* ---------------------------- UiObject ---------------------------- */ + +UiContext* ui_object_get_context(UiObject *obj) { + return obj->ctx; +} + +void* ui_object_get_windowdata(UiObject *obj) { + return obj->window; +} + +void ui_object_set_windowdata(UiObject *obj, void *windowdata) { + obj->window = windowdata; +} + + +/* ---------------------------- UiList ---------------------------- */ + +void* ui_list_get_data(UiList *list) { + return list->data; +} + +void ui_list_set_data(UiList *list, void *data) { + list->data = data; +} + +void* ui_list_get_iter(UiList *list) { + return list->iter; +} + +void ui_list_set_iter(UiList *list, void *iter) { + list->iter = iter; +} + + +/* ------------------------------ UiSublist ------------------------------ */ + +UiSubList* ui_sublist_new(void) { + UiSubList *sublist = malloc(sizeof(UiSubList)); + memset(sublist, 0, sizeof(UiSubList)); + return sublist; +} + +void ui_sublist_set_value(UiSubList *sublist, UiList *value) { + sublist->value = value; +} + +void ui_sublist_set_varname(UiSubList *sublist, const char *varname) { + free((void*)sublist->varname); + sublist->varname = strdup(varname); +} + +void ui_sublist_set_header(UiSubList *sublist, const char *header) { + free((void*)sublist->header); + sublist->header = strdup(header); +} + +void ui_sublist_set_separator(UiSubList *sublist, UiBool separator) { + sublist->separator = separator; +} + +void ui_sublist_set_userdata(UiSubList *sublist, void *userdata) { + sublist->userdata = userdata; +} + +void ui_sublist_free(UiSubList *sublist) { + free((void*)sublist->varname); + free((void*)sublist->header); + free(sublist); +} + + +/* -------------------- Source list (UiList) -------------------- */ + +UiList* ui_srclist_new(UiContext *ctx, const char *name) { + UiList *list = ui_list_new2(ctx, name, uic_ucx_list_init, NULL); + CxList *cxlist = list->data; + cxlist->collection.simple_destructor = (cx_destructor_func)ui_sublist_free; + return list; +} + +void ui_srclist_add(UiList *list, UiSubList *item) { + ui_list_append(list, item); +} + +void ui_srclist_insert(UiList *list, int index, UiSubList *item) { + CxList *cxlist = list->data; + cxListInsert(cxlist, index, item); +} + +void ui_srclist_remove(UiList *list, int index) { + CxList *cxlist = list->data; + cxListRemove(cxlist, index); +} + +void ui_srclist_clear(UiList *list) { + CxList *cxlist = list->data; + cxListClear(cxlist); +} + +int ui_srclist_size(UiList *list) { + return ui_list_count(list); +} + +/* + * numerates all sublists and sets the sublist index as userdata + */ +void ui_srclist_generate_sublist_num_data(UiList *list) { + CxList *cxlist = list->data; + CxIterator i = cxListIterator(cxlist); + cx_foreach(UiSubList *, sublist, i) { + sublist->userdata = (void*)i.index; + } +} + + +/* ---------------------------- UiSubListEventData ---------------------------- */ + +UiList* ui_sublist_event_get_list(UiSubListEventData *event) { + return event->list; +} + +int ui_sublist_event_get_sublist_index(UiSubListEventData *event) { + return event->sublist_index; +} + +int ui_sublist_event_get_row_index(UiSubListEventData *event) { + return event->row_index; +} + +void* ui_sublist_event_get_row_data(UiSubListEventData *event) { + return event->row_data; +} + +void* ui_sublist_event_get_sublist_userdata(UiSubListEventData *event) { + return event->sublist_userdata; +} + +void* ui_sublist_event_get_event_data(UiSubListEventData *event) { + return event->event_data; +} + + +/* ---------------------------- UiEvent ---------------------------- */ + +UiObject* ui_event_get_obj(UiEvent *event) { + return event->obj; +} + +void* ui_event_get_document(UiEvent *event) { + return event->document; +} + +void* ui_event_get_windowdata(UiEvent *event) { + return event->window; +} + +void* ui_event_get_eventdata(UiEvent *event) { + return event->eventdata; +} + +int ui_event_get_eventdatatype(UiEvent *event) { + return event->eventdatatype; +} + +int ui_event_get_int(UiEvent *event) { + return event->intval; +} + +int ui_event_get_set(UiEvent *event) { + return event->set; +} + + +/* ------------------------- SubListItem (public) ------------------------- */ + +void ui_sublist_item_set_icon(UiSubListItem *item, const char *icon) { + item->icon = icon ? strdup(icon) : NULL; +} + +void ui_sublist_item_set_label(UiSubListItem *item, const char *label) { + item->label = label ? strdup(label) : NULL; +} + +void ui_sublist_item_set_button_icon(UiSubListItem *item, const char *button_icon) { + item->button_icon = button_icon ? strdup(button_icon) : NULL; +} + +void ui_sublist_item_set_button_label(UiSubListItem *item, const char *button_label) { + item->button_label = button_label ? strdup(button_label) : NULL; +} + +void ui_sublist_item_set_button_menu(UiSubListItem *item, UiMenuBuilder *menu) { + item->button_menu = menu; +} + +void ui_sublist_item_set_badge(UiSubListItem *item, const char *badge) { + item->badge = badge ? strdup(badge) : NULL; +} + +void ui_sublist_item_set_eventdata(UiSubListItem *item, void *eventdata) { + item->eventdata = NULL; +} + +/* ---------------------------- UiListSelection ---------------------------- */ + +UiListSelection* ui_list_get_selection_allocated(UiList *list) { + UiListSelection *sel = malloc(sizeof(UiListSelection)); + *sel = ui_list_get_selection(list); + return sel; + +} + +int ui_list_selection_get_count(UiListSelection *sel) { + return sel->count; +} + +int* ui_list_selection_get_rows(UiListSelection *sel) { + return sel->rows; +} + +UIEXPORT void ui_list_set_selected_indices(UiList *list, int *indices, int num) { + UiListSelection sel; + sel.rows = indices; + sel.count = num; + if(list->setselection) { + list->setselection(list, sel); + } +} + +void ui_list_selection_free(UiListSelection *sel) { + ui_listselection_free(*sel); + free(sel); +} + +/* ---------------------------- UiFileList ---------------------------- */ + +int ui_filelist_count(UiFileList *flist) { + return flist->nfiles; +} + +char* ui_filelist_get(UiFileList *flist, int index) { + if(index >= 0 && index < flist->nfiles) { + return flist->files[index]; + } + return NULL; +} + +/* ---------------------------- UiTextStyle ---------------------------- */ + +void ui_textstyle_set_bold(UiTextStyle *style, UiBool set) { + if(set) { + style->text_style |= UI_TEXT_STYLE_BOLD; + } else { + style->text_style &= ~UI_TEXT_STYLE_BOLD; + } +} + +void ui_textstyle_set_underline(UiTextStyle *style, UiBool set) { + if(set) { + style->text_style |= UI_TEXT_STYLE_UNDERLINE; + } else { + style->text_style &= ~UI_TEXT_STYLE_UNDERLINE; + } +} + +void ui_textstyle_set_italic(UiTextStyle *style, UiBool set) { + if(set) { + style->text_style |= UI_TEXT_STYLE_ITALIC; + } else { + style->text_style &= ~UI_TEXT_STYLE_ITALIC; + } +} + +void ui_textstyle_set_color(UiTextStyle *style, int r, int g, int b) { + style->fg_set = TRUE; + style->fg.red = r; + style->fg.green = g; + style->fg.blue = b; +} + +void ui_textstyle_enable_color(UiTextStyle *style, UiBool enable) { + style->fg_set = enable; +} + + +/* ---------------------------- UiCellValue ---------------------------- */ + +UiBool ui_cell_value_is_string(UiCellValue *value) { + return value->type == UI_STRING_EDITABLE; +} + +UiBool ui_cell_value_is_int(UiCellValue *value) { + return FALSE; // TODO +} + +const char* ui_cell_value_get_string(UiCellValue *value) { + return value->string; +} + +int64_t ui_cell_value_get_int(UiCellValue *value) { + return value->i; +} diff --git a/ui/common/wrapper.h b/ui/common/wrapper.h new file mode 100644 index 0000000..88fd8c8 --- /dev/null +++ b/ui/common/wrapper.h @@ -0,0 +1,104 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_WRAPPER_H +#define UIC_WRAPPER_H + +#include "../ui/toolkit.h" +#include "../ui/tree.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UIEXPORT UiContext* ui_object_get_context(UiObject *obj); +UIEXPORT void* ui_object_get_windowdata(UiObject *obj); +UIEXPORT void ui_object_set_windowdata(UiObject *obj, void *windowdata); + +UIEXPORT void* ui_list_get_data(UiList *list); +UIEXPORT void* ui_list_get_iter(UiList *list); +UIEXPORT void ui_list_set_iter(UiList *list, void *iter); + +UIEXPORT UiSubList* ui_sublist_new(void); +UIEXPORT void ui_sublist_set_value(UiSubList *sublist, UiList *value); +UIEXPORT void ui_sublist_set_varname(UiSubList *sublist, const char *varname); +UIEXPORT void ui_sublist_set_header(UiSubList *sublist, const char *header); +UIEXPORT void ui_sublist_set_separator(UiSubList *sublist, UiBool separator); +UIEXPORT void ui_sublist_set_userdata(UiSubList *sublist, void *userdata); +UIEXPORT void ui_sublist_free(UiSubList *sublist); + +UIEXPORT UiList* ui_srclist_new(UiContext *ctx, const char *name); +UIEXPORT void ui_srclist_add(UiList *list, UiSubList *item); +UIEXPORT void ui_srclist_insert(UiList *list, int index, UiSubList *item); +UIEXPORT void ui_srclist_remove(UiList *list, int index); +UIEXPORT void ui_srclist_clear(UiList *list); +UIEXPORT int ui_srclist_size(UiList *list); +UIEXPORT void ui_srclist_generate_sublist_num_data(UiList *list); + +UIEXPORT UiList* ui_sublist_event_get_list(UiSubListEventData *event); +UIEXPORT int ui_sublist_event_get_sublist_index(UiSubListEventData *event); +UIEXPORT int ui_sublist_event_get_row_index(UiSubListEventData *event); +UIEXPORT void* ui_sublist_event_get_row_data(UiSubListEventData *event); +UIEXPORT void* ui_sublist_event_get_sublist_userdata(UiSubListEventData *event); +UIEXPORT void* ui_sublist_event_get_event_data(UiSubListEventData *event); + +UIEXPORT UiObject* ui_event_get_obj(UiEvent *event); +UIEXPORT void* ui_event_get_document(UiEvent *event); +UIEXPORT void* ui_event_get_windowdata(UiEvent *event); +UIEXPORT void* ui_event_get_eventdata(UiEvent *event); +UIEXPORT int ui_event_get_eventdatatype(UiEvent *event); +UIEXPORT int ui_event_get_int(UiEvent *event); +UIEXPORT int ui_event_get_set(UiEvent *event); + +UIEXPORT UiListSelection* ui_list_get_selection_allocated(UiList *list); +UIEXPORT int ui_list_selection_get_count(UiListSelection *sel); +UIEXPORT int* ui_list_selection_get_rows(UiListSelection *sel); +UIEXPORT void ui_list_set_selected_indices(UiList *list, int *indices, int num); +UIEXPORT void ui_list_selection_free(UiListSelection *sel); + +UIEXPORT int ui_filelist_count(UiFileList *flist); +UIEXPORT char* ui_filelist_get(UiFileList *flist, int index); + +UIEXPORT void ui_textstyle_set_bold(UiTextStyle *style, UiBool set); +UIEXPORT void ui_textstyle_set_underline(UiTextStyle *style, UiBool set); +UIEXPORT void ui_textstyle_set_italic(UiTextStyle *style, UiBool set); +UIEXPORT void ui_textstyle_set_color(UiTextStyle *style, int r, int g, int b); +UIEXPORT void ui_textstyle_enable_color(UiTextStyle *style, UiBool enable); + +UIEXPORT UiBool ui_cell_value_is_string(UiCellValue *value); +UIEXPORT UiBool ui_cell_value_is_int(UiCellValue *value); +UIEXPORT const char* ui_cell_value_get_string(UiCellValue *value); +UIEXPORT int64_t ui_cell_value_get_int(UiCellValue *value); + + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_WRAPPER_H */ + diff --git a/ui/gtk/Makefile b/ui/gtk/Makefile new file mode 100644 index 0000000..d1979fb --- /dev/null +++ b/ui/gtk/Makefile @@ -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. +# + +$(GTK_OBJPRE)%.o: gtk/%.c + $(CC) -o $@ -c -I../ucx $(CFLAGS) $(SHLIB_CFLAGS) $(TK_CFLAGS) $< + +$(UI_LIB): $(OBJ) + $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ) + +$(UI_SHLIB): $(OBJ) + $(CC) -o $(UI_SHLIB) $(LDFLAGS) $(SHLIB_LDFLAGS) $(TK_LDFLAGS) $(OBJ) -L../build/lib -lucx diff --git a/ui/gtk/button.c b/ui/gtk/button.c new file mode 100644 index 0000000..6db5141 --- /dev/null +++ b/ui/gtk/button.c @@ -0,0 +1,1000 @@ +/* + * 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 +#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, + const char *tooltip, + 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(tooltip) { + gtk_widget_set_tooltip_text(button, tooltip); + } + + if(onclick) { + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = userdata; + event->callback = onclick; + event->value = event_value; + event->customdata = NULL; + event->customint = 0; + + 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) { + GtkWidget *button = ui_create_button(obj, args->label, args->icon, args->tooltip, 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); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, button, &layout); + 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.eventdatatype = 0; + e.intval = event->value; + e.set = ui_get_setop(); + event->callback(&e, event->userdata); +} + +void ui_button_set_label(UIWIDGET button, const char *label) { + gtk_button_set_label(GTK_BUTTON(button), label); +} + +void ui_button_set_icon(UIWIDGET button, const char *icon) { + ui_button_set_icon_name(button, icon); +} + +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.eventdatatype = UI_EVENT_DATA_INTEGER_VALUE; + e.intval = i->get(i); + e.set = ui_get_setop(); + + 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.eventdatatype = 0; + e.intval = gtk_toggle_button_get_active(widget); + e.set = ui_get_setop(); + 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 *tooltip, + 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); + if(tooltip) { + gtk_widget_set_tooltip_text(togglebutton, tooltip); + } + + 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) +{ + UiVar* var = uic_widget_var(obj->ctx, obj->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; + event->customint = 0; + + 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; + event->customint = 0; + + 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) { + ui_setup_togglebutton( + obj, + widget, + args->label, + args->icon, + args->tooltip, + 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); + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); + + 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.eventdatatype = 0; + e.intval = gtk_check_button_get_active(widget); + e.set = ui_get_setop(); + 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) { + 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); + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); + + return widget; +} + +#else +UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args) { + return togglebutton_create(obj, gtk_check_button_new(), args); +} +#endif + + +#if GTK_MAJOR_VERSION >= 3 + +static void switch_changed( + GObject *gobject, + GParamSpec *pspec, + UiVarEventData *event) +{ + GtkSwitch *sw = GTK_SWITCH (gobject); + gboolean active = gtk_switch_get_active (sw); + + UiEvent e; + e.obj = event->obj; + e.document = e.obj->ctx->document; + e.window = e.obj->window; + e.eventdata = NULL; + e.eventdatatype = 0; + e.set = ui_get_setop(); + + if(event->callback) { + event->callback(&e, event->userdata); + } + if(event->var) { + UiInteger *i = event->var->value; + ui_notify_evt(i->observers, &e); + } +} + +UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { + GtkWidget *widget = gtk_switch_new(); + ui_set_name_and_style(widget, args->name, args->style_class); + ui_set_widget_groups(obj->ctx, widget, args->groups); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + if(var) { + UiInteger *value = var->value; + value->obj = widget; + value->get = ui_switch_get; + value->set = ui_switch_set; + + if(value->value) { + gtk_switch_set_active(GTK_SWITCH(widget), TRUE); + } + + + } + + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->callback = args->onchange; + event->userdata = args->onchangedata; + event->var = var; + event->observers = NULL; + + g_signal_connect( + widget, + "notify::active", + G_CALLBACK(switch_changed), + event); + + g_signal_connect( + widget, + "destroy", + G_CALLBACK(ui_destroy_vardata), + event); + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); + + return widget; +} + +int64_t ui_switch_get(UiInteger *value) { + GtkSwitch *sw = GTK_SWITCH((GtkWidget*)value->obj); + value->value = gtk_switch_get_active(sw); + return value->value; +} + +void ui_switch_set(UiInteger *value, int64_t i) { + GtkSwitch *sw = GTK_SWITCH((GtkWidget*)value->obj); + value->value = i; + gtk_switch_set_active(sw, i); +} + +#else + +UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { + 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.eventdatatype = 0; + e.intval = RADIOBUTTON_GET_ACTIVE(widget); + e.set = ui_get_setop(); + event->callback(&e, event->userdata); +} + +typedef struct UiRadioButtonData { + UiInteger *value; + UiVarEventData *eventdata; + UiBool first; +} UiRadioButtonData; + +static void destroy_radiobutton(GtkWidget *w, UiRadioButtonData *data) { + if(data->first) { + ui_destroy_vardata(w, data->eventdata); + g_slist_free(data->value->obj); + data->value->obj = NULL; + data->value->get = NULL; + data->value->set = NULL; + } else { + free(data->eventdata); + } + free(data); +} + +UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args) { + GSList *rg = NULL; + UiInteger *rgroup; + + UiVar* var = uic_widget_var(obj->ctx, obj->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; + event->customint = 0; + + g_signal_connect( + rbutton, + "toggled", + G_CALLBACK(radiobutton_toggled), + event); + g_signal_connect( + rbutton, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, rbutton, &layout); + + 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.eventdatatype = 0; + 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 + + +static void ui_destroy_linkbutton(GtkWidget *widget, UiLinkButton *data) { + free(data->link); + free(data); +} + +static const char* linkbutton_get_uri(UiLinkButton *link) { + if(link->type == UI_LINK_BUTTON) { + return link->link; + } else { + return gtk_link_button_get_uri(GTK_LINK_BUTTON(link->widget)); + } +} + +static void linkbutton_set_uri(UiLinkButton *link, const char *uri) { + if(link->type == UI_LINK_BUTTON) { + free(link->link); + link->link = uri ? strdup(uri) : NULL; + } else { + gtk_link_button_set_uri(GTK_LINK_BUTTON(link->widget), uri); + } +} + +static gboolean linkbutton_get_visited(UiLinkButton *link) { + if(link->type == UI_LINK_BUTTON) { + return FALSE; + } else { + gtk_link_button_get_visited(GTK_LINK_BUTTON(link->widget)); + } +} + +static void linkbutton_set_visited(UiLinkButton *link, gboolean visited) { + if(link->type != UI_LINK_BUTTON) { + gtk_link_button_set_visited(GTK_LINK_BUTTON(link->widget), visited); + } +} + +/* + * Apply linkbutton settings from json. Expects jsonvalue to be a valid + * json object. + * + * { + * "label": "label text", + * "uri": "http://example.com", + * "visited": true + * } + * + */ +static void linkbutton_apply_value(UiLinkButton *link, const char *jsonvalue) { + CxJson json; + cxJsonInit(&json, NULL); + cxJsonFill(&json, jsonvalue); + + CxJsonValue *value; + if(cxJsonNext(&json, &value) == CX_JSON_NO_ERROR) { + if(cxJsonIsObject(value)) { + CxJsonValue *label = cxJsonObjGet(value, "label"); + CxJsonValue *uri = cxJsonObjGet(value, "uri"); + CxJsonValue *visited = cxJsonObjGet(value, "visited"); + if(label) { + gtk_button_set_label(GTK_BUTTON(link->widget), cxJsonIsString(label) ? cxJsonAsString(label) : NULL); + } + if(uri) { + linkbutton_set_uri(link, cxJsonIsString(uri) ? cxJsonAsString(uri) : NULL); + + } + if(visited) { + linkbutton_set_visited(link, cxJsonIsBool(visited) ? cxJsonAsBool(visited) : FALSE); + } + } + cxJsonValueFree(value); + } + cxJsonDestroy(&json); +} + +static char* create_linkbutton_jsonvalue(const char *label, const char *uri, gboolean include_null, gboolean visited, gboolean set_visited) { + CxJsonValue *obj = cxJsonCreateObj(NULL); + if(label) { + cxJsonObjPutString(obj, CX_STR("label"), label); + } else if(include_null) { + cxJsonObjPutLiteral(obj, CX_STR("label"), CX_JSON_NULL); + } + + if(uri) { + cxJsonObjPutString(obj, CX_STR("uri"), uri); + } else if(include_null) { + cxJsonObjPutLiteral(obj, CX_STR("uri"), CX_JSON_NULL); + } + + if(set_visited) { + cxJsonObjPutLiteral(obj, CX_STR("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE); + } + + CxJsonWriter writer = cxJsonWriterCompact(); + CxBuffer buf; + cxBufferInit(&buf, NULL, 128, NULL, CX_BUFFER_AUTO_EXTEND); + cxJsonWrite(&buf, obj, (cx_write_func)cxBufferWrite, &writer); + cxJsonValueFree(obj); + cxBufferTerminate(&buf); + + return buf.space; +} + +static char* linkbutton_get_value(UiLinkButton *link) { + const char *label = gtk_button_get_label(GTK_BUTTON(link->widget)); + const char *uri = linkbutton_get_uri(link); + gboolean visited = linkbutton_get_visited(link); + return create_linkbutton_jsonvalue(label, uri, TRUE, visited, TRUE); +} + +static void linkbutton_callback(GtkWidget *widget, UiLinkButton *data) { + if(data->onclick) { + UiEvent e; + e.obj = data->obj; + e.document = e.obj->ctx->document; + e.window = e.obj->window; + e.eventdata = (char*)linkbutton_get_uri(data); + e.eventdatatype = UI_EVENT_DATA_STRING; + e.intval = 0; + e.set = ui_get_setop(); + data->onclick(&e, data->onclickdata); + } +} + +static void linkbutton_clicked(GtkWidget *widget, UiLinkButton *data) { + linkbutton_callback(widget, data); + if(data->link) { +#if GTK_CHECK_VERSION(4, 0, 0) + GtkUriLauncher *launcher = gtk_uri_launcher_new (data->link); + gtk_uri_launcher_launch (launcher, NULL, NULL, NULL, NULL); + g_object_unref (launcher); +#elif GTK_CHECK_VERSION(3, 22, 0) + GError *error = NULL; + gtk_show_uri_on_window(NULL, data->link, GDK_CURRENT_TIME, &error); +#elif + // TODO: call xdg-open +#endif + } +} + +static gboolean linkbutton_activate_link(GtkLinkButton *self, UiLinkButton *data) { + linkbutton_callback(data->widget, data); + return data->nofollow; +} + +UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args) { + UiLinkButton *data = malloc(sizeof(UiLinkButton)); + memset(data, 0, sizeof(UiLinkButton)); + data->obj = obj; + data->type = args->type; + data->nofollow = args->nofollow; + data->onclick = args->onclick; + data->onclickdata = args->onclickdata; + + GtkWidget *button; + if(args->type == UI_LINK_BUTTON) { + button = gtk_button_new(); + g_signal_connect( + button, + "clicked", + G_CALLBACK(linkbutton_clicked), + data); + } else { + button = gtk_link_button_new("file:///"); + g_signal_connect( + button, + "activate-link", + G_CALLBACK(linkbutton_activate_link), + data); + } + gtk_button_set_label(GTK_BUTTON(button), args->label); +#if GTK_CHECK_VERSION(4, 0, 0) + gtk_button_set_can_shrink(GTK_BUTTON(button), TRUE); +#elif GTK_MAJOR_VERSION == 3 + GtkWidget *child = gtk_bin_get_child(GTK_BIN(button)); + gtk_label_set_ellipsize(GTK_LABEL(child), PANGO_ELLIPSIZE_END); +#endif + g_object_set_data(G_OBJECT(button), "ui_linkbutton", data); + g_signal_connect( + button, + "destroy", + G_CALLBACK(ui_destroy_linkbutton), + data); + + data->widget = button; + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); + if(var) { + UiString *str = var->value; + char *current_value = ui_get(str); + if(current_value) { + linkbutton_apply_value(data, current_value); + } + + str->obj = data; + str->get = ui_linkbutton_get; + str->set = ui_linkbutton_set; + } + + ui_set_name_and_style(button, args->name, args->style_class); + ui_set_widget_groups(obj->ctx, button, args->groups); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, button, &layout); + + return button; +} + +char* ui_linkbutton_get(UiString *s) { + UiLinkButton *link = s->obj; + if(s->value.free) { + s->value.free(s->value.ptr); + } + s->value.ptr = linkbutton_get_value(link); + s->value.free = free; + return s->value.ptr; +} + +void ui_linkbutton_set(UiString *s, const char *str) { + linkbutton_apply_value(s->obj, str); + if(s->value.free) { + s->value.free(s->value.ptr); + } +#if GTK_MAJOR_VERSION == 3 + UiLinkButton *data = s->obj; + GtkWidget *child = gtk_bin_get_child(GTK_BIN(data->widget)); + gtk_label_set_ellipsize(GTK_LABEL(child), PANGO_ELLIPSIZE_END); +#endif +} + + +void ui_linkbutton_value_set(UiString *str, const char *label, const char *uri) { + char *value = create_linkbutton_jsonvalue(label, uri, TRUE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_label(UiString *str, const char *label) { + char *value = create_linkbutton_jsonvalue(label, NULL, FALSE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_uri(UiString *str, const char *uri) { + char *value = create_linkbutton_jsonvalue(NULL, uri, FALSE, FALSE, TRUE); + ui_set(str, value); + free(value); +} + +void ui_linkbutton_value_set_visited(UiString *str, UiBool visited) { + char *value = create_linkbutton_jsonvalue(NULL, NULL, FALSE, visited, TRUE); + ui_set(str, value); + free(value); +} + + +void ui_linkbutton_set_label(UIWIDGET button, const char *label) { + gtk_button_set_label(GTK_BUTTON(button), label); +} + +void ui_linkbutton_set_uri(UIWIDGET button, const char *label) { + UiLinkButton *link = g_object_get_data(G_OBJECT(button), "ui_linkbutton"); + if(link) { + linkbutton_set_uri(link, label); + } else { + fprintf(stderr, "Error: ui_linkbutton_set_label: widget is not a linkbutton\n"); + } +} + +void ui_linkbutton_set_visited(UIWIDGET button, UiBool visited) { + UiLinkButton *link = g_object_get_data(G_OBJECT(button), "ui_linkbutton"); + if(link) { + linkbutton_set_visited(link, visited); + } else { + fprintf(stderr, "Error: ui_linkbutton_set_label: widget is not a linkbutton\n"); + } +} + +char* ui_linkbutton_get_label(UIWIDGET button) { + const char *label = gtk_button_get_label(GTK_BUTTON(button)); + return label ? strdup(label) : NULL; +} + +char* ui_linkbutton_get_uri(UIWIDGET button) { + UiLinkButton *link = g_object_get_data(G_OBJECT(button), "ui_linkbutton"); + if(link) { + const char *uri = linkbutton_get_uri(link); + return uri ? strdup(uri) : NULL; + } else { + fprintf(stderr, "Error: ui_linkbutton_set_label: widget is not a linkbutton\n"); + } + return NULL; +} + +UiBool ui_linkbutton_get_visited(UIWIDGET button) { + UiLinkButton *link = g_object_get_data(G_OBJECT(button), "ui_linkbutton"); + if(link) { + return linkbutton_get_visited(link); + } else { + fprintf(stderr, "Error: ui_linkbutton_set_label: widget is not a linkbutton\n"); + } + return FALSE; +} diff --git a/ui/gtk/button.h b/ui/gtk/button.h new file mode 100644 index 0000000..ce94b90 --- /dev/null +++ b/ui/gtk/button.h @@ -0,0 +1,115 @@ +/* + * 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 + +typedef struct UiLinkButton { + UiObject *obj; + GtkWidget *widget; + UiLinkType type; + UiBool nofollow; + char *link; + ui_callback onclick; + void *onclickdata; +} UiLinkButton; + +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, + const char *tooltip, + 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 *tooltip, + 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_switch_get(UiInteger *value); +void ui_switch_set(UiInteger *value, int64_t i); + +int64_t ui_radiobutton_get(UiInteger *value); +void ui_radiobutton_set(UiInteger *value, int64_t i); + +char* ui_linkbutton_get(UiString *s); +void ui_linkbutton_set(UiString *s, const char *str); + +#ifdef __cplusplus +} +#endif + +#endif /* BUTTON_H */ + diff --git a/ui/gtk/container.c b/ui/gtk/container.c new file mode 100644 index 0000000..9ca07f9 --- /dev/null +++ b/ui/gtk/container.c @@ -0,0 +1,1341 @@ +/* + * 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 "container.h" +#include "toolkit.h" +#include "headerbar.h" + +#include "../common/context.h" +#include "../common/object.h" +#include "../common/container.h" + +#include "../ui/properties.h" + + +void ui_container_begin_close(UiObject *obj) { + UiContainerX *ct = obj->container_end; + ct->close = 1; +} + +int ui_container_finish(UiObject *obj) { + UiContainerX *ct = obj->container_end; + if(ct->close) { + ui_end_new(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 *obj, + int spacing, + int columnspacing, + int rowspacing, + int margin) +{ + GtkWidget *sub = NULL; + GtkWidget *add = NULL; + UiContainerX *container = NULL; + switch(type) { + default: { + sub = ui_gtk_vbox_new(spacing); + add = ui_gtk_set_margin(sub, margin, 0, 0, 0, 0); + container = ui_box_container(obj, sub, type); + break; + } + case UI_CONTAINER_HBOX: { + sub = ui_gtk_hbox_new(spacing); + add = ui_gtk_set_margin(sub, margin, 0, 0, 0, 0); + container = ui_box_container(obj, sub, type); + break; + } + case UI_CONTAINER_GRID: { + sub = ui_create_grid_widget(columnspacing, rowspacing); + add = ui_gtk_set_margin(sub, margin, 0, 0, 0, 0); + container = ui_grid_container(obj, sub, FALSE, FALSE, FALSE, FALSE); + break; + } + case UI_CONTAINER_NO_SUB: { + break; + } + } + if(container) { + uic_object_push_container(obj, container); + } + return add; +} + + +/* -------------------- Box Container -------------------- */ +UiContainerX* 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 (UiContainerX*)ct; +} + +void ui_box_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + UiBoxContainer *bc = (UiBoxContainer*)ct; + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); + + UiBool fill = 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 + + ct->current = widget; +} + +UiContainerX* ui_grid_container( + UiObject *obj, + GtkWidget *grid, + UiBool def_hexpand, + UiBool def_vexpand, + UiBool def_hfill, + UiBool def_vfill) +{ + UiGridContainer *ct = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiGridContainer)); + ct->def_hexpand = def_hexpand; + ct->def_vexpand = def_vexpand; + ct->def_hfill = def_hfill; + ct->def_vfill = def_vfill; + ct->container.widget = grid; + ct->container.add = ui_grid_container_add; + UI_GTK_V2(ct->width = 0); + UI_GTK_V2(ct->height = 1); + return (UiContainerX*)ct; +} + + +#if GTK_MAJOR_VERSION >= 3 +void ui_grid_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + UiGridContainer *grid = (UiGridContainer*)ct; + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); + + if(ct->container.newline) { + grid->x = 0; + grid->y++; + ct->container.newline = FALSE; + } + + uic_layout_setup_expand_fill(layout, grid->def_hexpand, grid->def_vexpand, grid->def_hfill, grid->def_vfill); + + if(!layout->hfill) { + gtk_widget_set_halign(widget, GTK_ALIGN_START); + } + if(!layout->vfill) { + gtk_widget_set_valign(widget, GTK_ALIGN_START); + } + + gtk_widget_set_hexpand(widget, layout->hexpand); + gtk_widget_set_vexpand(widget, layout->vexpand); + + int colspan = layout->colspan > 0 ? layout->colspan : 1; + int rowspan = layout->rowspan > 0 ? layout->rowspan : 1; + + gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, colspan, rowspan); + grid->x += colspan; + + grid->container.current = widget; +} +#endif +#ifdef UI_GTK2 +void ui_grid_container_add(UiContainerPrivate *ct, GtkWidget *widget) { + UiGridContainer *grid = (UiGridContainer*)ct; + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); + + if(ct->container.newline) { + grid->x = 0; + grid->y++; + ct->container.newline = FALSE; + } + + uic_layout_setup_expand_fill(layout, grid->def_hexpand, grid->def_vexpand, grid->def_hfill, grid->def_vfill); + + GtkAttachOptions xoptions = 0; + GtkAttachOptions yoptions = 0; + if(layout->hexpand) { + xoptions = GTK_EXPAND; + } + if(layout->hfill) { + xoptions |= GTK_FILL; + } + if(layout->vexpand) { + yoptions = GTK_EXPAND; + } + if(layout->vfill) { + yoptions |= GTK_FILL; + } + + int colspan = layout->colspan > 0 ? layout->colspan : 1; + int rowspan = layout->rowspan > 0 ? 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); + } + + ct->current = widget; +} +#endif + +UiContainerX* ui_frame_container(UiObject *obj, GtkWidget *frame) { + UiContainerPrivate *ct = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiContainerPrivate)); + ct->widget = frame; + ct->add = ui_frame_container_add; + return (UiContainerX*)ct; +} + +void ui_frame_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); + FRAME_SET_CHILD(ct->widget, widget); + ct->current = widget; +} + +UiContainerX* ui_expander_container(UiObject *obj, GtkWidget *expander) { + UiContainerPrivate *ct = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiContainerPrivate)); + ct->widget = expander; + ct->add = ui_expander_container_add; + return (UiContainerX*)ct; +} + +void ui_expander_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); + EXPANDER_SET_CHILD(ct->widget, widget); + ct->current = widget; +} + +void ui_scrolledwindow_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); + // TODO: check if the widget implements GtkScrollable + SCROLLEDWINDOW_SET_CHILD(ct->widget, widget); + ct->current = widget; +} + +UiContainerX* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) { + UiContainerPrivate *ct = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiContainerPrivate)); + ct->widget = scrolledwindow; + ct->add = ui_scrolledwindow_container_add; + return (UiContainerX*)ct; +} + +UiContainerX* 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 (UiContainerX*)ct; +} + +void ui_tabview_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + UiGtkTabView *data = ui_widget_get_tabview_data(ct->widget); + if(!data) { + fprintf(stderr, "UI Error: widget is not a tabview"); + return; + } + widget = ui_gtk_set_margin(widget, layout->margin, layout->margin_left, layout->margin_right, layout->margin_top, layout->margin_bottom); + data->add_tab(ct->widget, -1, layout->label, widget); + + ct->current = widget; +} + +#ifdef UI_GTK2 + +static void alignment_child_visibility_changed(GtkWidget *widget, gpointer user_data) { + gtk_widget_set_visible(gtk_widget_get_parent(widget), gtk_widget_get_visible(widget)); +} + +#endif + +GtkWidget* ui_gtk_set_margin(GtkWidget *widget, int margin, int margin_left, int margin_right, int margin_top, int margin_bottom) { + if(margin > 0) { + margin_left = margin; + margin_right = margin; + margin_top = margin; + margin_bottom = margin; + } + GtkWidget *ret = widget; +#if GTK_MAJOR_VERSION >= 3 +#if GTK_CHECK_VERSION(3, 12, 0) + gtk_widget_set_margin_start(widget, margin_left); + gtk_widget_set_margin_end(widget, margin_right); +#else + gtk_widget_set_margin_left(widget, margin_left); + gtk_widget_set_margin_right(widget, margin_right); +#endif + gtk_widget_set_margin_top(widget, margin_top); + gtk_widget_set_margin_bottom(widget, margin_bottom); +#elif defined(UI_GTK2) + GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin_top, margin_bottom, margin_left, margin_right); + gtk_container_add(GTK_CONTAINER(a), widget); + g_signal_connect( + widget, + "show", + G_CALLBACK(alignment_child_visibility_changed), + NULL); + g_signal_connect( + widget, + "hide", + G_CALLBACK(alignment_child_visibility_changed), + NULL); + ret = a; +#endif + return ret; +} + +UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs *args, UiSubContainerType type) { + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(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); + ct->add(ct, box, &layout); + + UiContainerX *container = ui_box_container(obj, box, type); + uic_object_push_container(obj, container); + + return box; +} + +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) { + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + GtkWidget *widget; + + GtkWidget *grid = ui_create_grid_widget(args->columnspacing, args->rowspacing); + ui_set_name_and_style(grid, args->name, args->style_class); + ct->add(ct, grid, &layout); + + UiContainerX *container = ui_grid_container(obj, grid, args->def_hexpand, args->def_vexpand, args->def_hfill, args->def_vfill); + uic_object_push_container(obj, container); + + return grid; +} + +static void frame_create_subcontainer(UiObject *obj, UiFrameArgs *args) { + switch(args->subcontainer) { + default: + case UI_CONTAINER_VBOX: { + UiContainerArgs sub_args = { .spacing = args->spacing, .margin = args->padding }; + ui_vbox_create(obj, &sub_args); + break; + } + case UI_CONTAINER_HBOX: { + UiContainerArgs sub_args = { .spacing = args->spacing, .margin = args->padding }; + ui_hbox_create(obj, &sub_args); + break; + } + case UI_CONTAINER_GRID: { + UiContainerArgs sub_args = { .columnspacing = args->columnspacing, .rowspacing = args->rowspacing, .margin = args->padding }; + ui_grid_create(obj, &sub_args); + break; + } + case UI_CONTAINER_NO_SUB: { + break; // NOOP + } + } +} + +UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) { + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + + GtkWidget *frame = gtk_frame_new(args->label); + ct->add(ct, frame, &layout); + + GtkWidget *sub = ui_subcontainer_create( + args->subcontainer, + obj, args->spacing, + args->columnspacing, + args->rowspacing, + args->padding); + if(sub) { + FRAME_SET_CHILD(frame, sub); + } else { + UiContainerX *container = ui_frame_container(obj, frame); + uic_object_push_container(obj, container); + } + + return frame; +} + +UIEXPORT UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs *args) { + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + + GtkWidget *expander = gtk_expander_new(args->label); + gtk_expander_set_expanded(GTK_EXPANDER(expander), args->isexpanded); + ct->add(ct, expander, &layout); + + GtkWidget *sub = ui_subcontainer_create( + args->subcontainer, + obj, args->spacing, + args->columnspacing, + args->rowspacing, + args->padding); + if(sub) { + EXPANDER_SET_CHILD(expander, sub); + } else { + UiContainerX *container = ui_expander_container(obj, expander); + uic_object_push_container(obj, container); + } + + return expander; +} + + +UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs *args) { + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + + GtkWidget *sw = SCROLLEDWINDOW_NEW(); + ui_set_name_and_style(sw, args->name, args->style_class); + ct->add(ct, sw, &layout); + + GtkWidget *sub = ui_subcontainer_create( + args->subcontainer, + obj, args->spacing, + args->columnspacing, + args->rowspacing, + args->padding); + if(sub) { + SCROLLEDWINDOW_SET_CHILD(sw, sub); + } else { + UiContainerX *container = ui_scrolledwindow_container(obj, sw); + uic_object_push_container(obj, container); + } + + 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"); +} + +static void tabview_switch_page( + GtkNotebook *self, + GtkWidget *page, + guint page_num, + gpointer userdata) +{ + UiGtkTabView *tabview = userdata; + if(!tabview->onchange) { + return; + } + + UiEvent event; + event.obj = tabview->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.set = ui_get_setop(); + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = page_num; + + tabview->onchange(&event, tabview->onchange); +} + +#if GTK_CHECK_VERSION(3, 10, 0) + +static void tabview_stack_changed( + GObject *object, + GParamSpec *pspec, + UiGtkTabView *tabview) +{ + if(!tabview->onchange) { + return; + } + + UiEvent event; + event.obj = tabview->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.set = ui_get_setop(); + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = 0; + + tabview->onchange(&event, tabview->onchange); +} + +#endif + +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)); + memset(data, 0, sizeof(UiGtkTabView)); + data->padding = args->padding; + 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(); + g_signal_connect(stack, "notify::visible-child", G_CALLBACK(tabview_stack_changed), data); + 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(); + g_signal_connect( + widget, + "switch-page", + G_CALLBACK(tabview_switch_page), + data); + 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; + } + } + + if(args->value || args->varname) { + UiVar *var = uic_widget_var(obj->ctx, obj->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; + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); + + UiContainerX *container = ui_tabview_container(obj, widget); + uic_object_push_container(obj, container); + + return widget; +} + +static GtkWidget* create_tab(UiObject *obj, UiGtkTabView *tabview, const char *title, int tab) { + UiContainerX *container; + GtkWidget *sub; + switch(tabview->subcontainer) { + default: { + sub = ui_gtk_vbox_new(tabview->spacing); + container = ui_box_container(obj, sub, tabview->subcontainer); + break; + } + case UI_CONTAINER_HBOX: { + sub = ui_gtk_hbox_new(tabview->spacing); + container = ui_box_container(obj, sub, tabview->subcontainer); + break; + } + case UI_CONTAINER_GRID: { + sub = ui_create_grid_widget(tabview->columnspacing, tabview->rowspacing); + container = ui_grid_container(obj, sub, FALSE, FALSE, FALSE, FALSE); + break; + } + } + + uic_object_push_container(obj, container); + + GtkWidget *widget = ui_gtk_set_margin(sub, tabview->padding, 0, 0, 0, 0); + tabview->add_tab(tabview->widget, tab, title, widget); + + return sub; +} + +void ui_tab_create(UiObject* obj, const char* title) { + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + GtkWidget *tabview = ct->widget; + UiGtkTabView *data = ui_widget_get_tabview_data(tabview); + if(!data) { + fprintf(stderr, "UI Error: widget is not a tabview\n"); + return; + } + + create_tab(obj, data, title, -1); +} + + + +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 = uic_object_new_toplevel(); + newobj->widget = create_tab(newobj, data, name, tab_index); + + return newobj; +} + + +/* -------------------- Headerbar -------------------- */ + +static void hb_set_part(UiObject *obj, int part) { + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + GtkWidget *headerbar = ct->widget; + + UiHeaderbarContainer *hb = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiHeaderbarContainer)); + memcpy(hb, ct, sizeof(UiHeaderbarContainer)); + hb->part = part; + uic_object_push_container(obj, (UiContainerX*)hb); +} + +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) { + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(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, &layout); + + UiContainerX *container = ui_headerbar_fallback_container(obj, box); + uic_object_push_container(obj, container); + + return box; +} + +static void hb_fallback_set_part(UiObject *obj, int part) { + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + GtkWidget *headerbar = ct->widget; + + UiContainerX *container = ui_headerbar_container(obj, headerbar); + uic_object_push_container(obj, container); + + UiHeaderbarContainer *hb = (UiHeaderbarContainer*)container; + hb->part = part; +} + +UiContainerX* 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 (UiContainerX*)ct; +} + +void ui_headerbar_fallback_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + 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); + } + + UiContainerX *container = ui_headerbar_container(obj, headerbar); + uic_object_push_container(obj, container); + + return headerbar; +} + +UiContainerX* 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 (UiContainerX*)ct; +} + +void ui_headerbar_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + 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 + +/* -------------------- Sidebar -------------------- */ + +#ifdef UI_LIBADWAITA +UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) { + GtkWidget *sidebar_toolbar_view = g_object_get_data(G_OBJECT(obj->widget), "ui_sidebar"); + if(!sidebar_toolbar_view) { + fprintf(stderr, "Error: window is not configured for sidebar\n"); + return NULL; + } + + GtkWidget *box = ui_gtk_vbox_new(args->spacing); + ui_gtk_set_margin(box, args->margin, args->margin_left, args->margin_right, args->margin_top, args->margin_bottom); + adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), box); + + UiContainerX *container = ui_box_container(obj, box, UI_CONTAINER_VBOX); + uic_object_push_container(obj, container); + + return box; +} +#else +UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) { + GtkWidget *sidebar_vbox = g_object_get_data(G_OBJECT(obj->widget), "ui_sidebar"); + + GtkWidget *box = ui_gtk_vbox_new(args->spacing); + ui_gtk_set_margin(box, args->margin, args->margin_left, args->margin_right, args->margin_top, args->margin_bottom); + BOX_ADD_EXPAND(sidebar_vbox, box); + + UiContainerX *container = ui_box_container(obj, box, UI_CONTAINER_VBOX); + uic_object_push_container(obj, container); + + return box; +} +#endif + +/* ------------------------ Split Window Panels ------------------------ */ + +static UIWIDGET splitwindow_panel(UiObject *obj, GtkWidget *pbox, UiSidebarArgs *args) { + if(!pbox) { + fprintf(stderr, "Error: window is not a splitview window\n"); + return NULL; + } + + GtkWidget *box = ui_gtk_vbox_new(args->spacing); + ui_set_name_and_style(box, args->name, args->style_class); + ui_gtk_set_margin(box, args->margin, args->margin_left, args->margin_right, args->margin_top, args->margin_bottom); + BOX_ADD_EXPAND(pbox, box); + + UiContainerX *container = ui_box_container(obj, box, UI_CONTAINER_VBOX); + uic_object_push_container(obj, container); + + return box; +} + +UIWIDGET ui_left_panel_create(UiObject *obj, UiSidebarArgs *args) { + return splitwindow_panel(obj, g_object_get_data(G_OBJECT(obj->widget), "ui_left_panel"), args); +} + +UIWIDGET ui_right_panel_create(UiObject *obj, UiSidebarArgs *args) { + return splitwindow_panel(obj, g_object_get_data(G_OBJECT(obj->widget), "ui_right_panel"), args); +} + + +/* -------------------- 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; +} + +static void save_pane_pos(GtkWidget *widget, char *property_name) { + int pos = gtk_paned_get_position(GTK_PANED(widget)); + char buf[32]; + snprintf(buf, 32, "%d", pos); + ui_set_property(property_name, buf); + free(property_name); +} + +static UIWIDGET splitpane_create(UiObject *obj, UiOrientation orientation, UiSplitPaneArgs *args) { + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + + GtkWidget *pane0 = create_paned(orientation); + ct->add(ct, pane0, &layout); + + int max = args->max_panes == 0 ? 2 : args->max_panes; + + if(args->position_property) { + const char *pos_str = ui_get_property(args->position_property); + if(pos_str) { + char *end; + long pos = strtol(pos_str, &end, 10); + if(*end == '\0') { + args->initial_position = (int)pos; + } + } + + g_signal_connect( + pane0, + "destroy", + G_CALLBACK(save_pane_pos), + strdup(args->position_property)); + } + + UiSplitPane *splitpane = ui_create_splitpane_data(pane0, orientation, max, args->initial_position); + UiContainerX *container = ui_splitpane_container(obj, pane0, splitpane); + uic_object_push_container(obj, container); + + g_object_set_data(G_OBJECT(pane0), "ui_splitpane", splitpane); + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + if(var) { + UiInteger *i = var->value; + splitpane->initial_position = i->value; + + i->obj = splitpane; + i->get = ui_splitpane_get; + i->set = ui_splitpane_set; + } + + return pane0; +} + +UIWIDGET ui_hsplitpane_create(UiObject *obj, UiSplitPaneArgs *args) { + return splitpane_create(obj, UI_HORIZONTAL, args); +} + +UIWIDGET ui_vsplitpane_create(UiObject *obj, UiSplitPaneArgs *args) { + return splitpane_create(obj, UI_VERTICAL, args); +} + +UiSplitPane* ui_create_splitpane_data(GtkWidget *pane, UiOrientation orientation, int max, int init) { + UiSplitPane *ct = malloc(sizeof(UiSplitPane)); + ct->current_pane = pane; + ct->orientation = orientation; + ct->max = max; + ct->initial_position = init; + ct->children = cxArrayListCreateSimple(CX_STORE_POINTERS, 4); + return ct; +} + +UiContainerX* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiSplitPane *data) { + UiSplitPaneContainer *ct = ui_calloc(obj->ctx, 1, sizeof(UiSplitPaneContainer)); + ct->container.widget = pane; + ct->container.add = ui_splitpane_container_add; + ct->splitpane = data; + return (UiContainerX*)ct; +} + +void ui_splitpane_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + UiSplitPaneContainer *sct = (UiSplitPaneContainer*)ct; + UiSplitPane *s = sct->splitpane; + + if(s->nchildren >= s->max) { + fprintf(stderr, "splitpane: maximum number of children reached\n"); + return; + } + + cxListAdd(s->children, widget); + + if(s->pos == 0) { + PANED_SET_CHILD1(s->current_pane, widget); + if(s->initial_position > 0) { + gtk_paned_set_position(GTK_PANED(s->current_pane), s->initial_position); + } + s->pos++; + s->nchildren++; + } else { + if(s->nchildren+1 == s->max) { + PANED_SET_CHILD2(s->current_pane, widget); + } else { + GtkWidget *pane = create_paned(s->orientation); + PANED_SET_CHILD1(pane, widget); + PANED_SET_CHILD2(s->current_pane, pane); + s->current_pane = pane; + } + + s->pos = 0; + s->nchildren++; + } +} + +int64_t ui_splitpane_get(UiInteger *i) { + UiSplitPane *s = i->obj; + i->value = gtk_paned_get_position(GTK_PANED(s->current_pane)); + return i->value; +} + +void ui_splitpane_set(UiInteger *i, int64_t value) { + UiSplitPane *s = i->obj; + i->value = value; + gtk_paned_set_position(GTK_PANED(s->current_pane), (int)value); +} + +UIEXPORT void ui_splitpane_set_visible(UIWIDGET splitpane, int child_index, UiBool visible) { + UiSplitPane *s = g_object_get_data(G_OBJECT(splitpane), "ui_splitpane"); + if(!s) { + fprintf(stderr, "UI Error: not a splitpane\n"); + return; + } + + GtkWidget *w = cxListAt(s->children, child_index); + if(w) { + gtk_widget_set_visible(w, visible); + } +} + +/* -------------------- ItemList Container -------------------- */ + +static void remove_item(void *data, void *item) { + UiGtkItemListContainer *ct = data; + UiObject *obj = item; + if(ct->remove_items) { + BOX_REMOVE(ct->widget, obj->widget); + } + uic_object_destroy(obj); +} + +static void update_itemlist(UiList *list, int c) { + UiGtkItemListContainer *ct = list->obj; + + CxMap *new_items = cxHashMapCreateSimple(CX_STORE_POINTERS); + new_items->collection.advanced_destructor = remove_item; + new_items->collection.destructor_data = ct; + + // only create new widgets for new elements, so at first we have + // to find which elements are new + // check which elements in the list are already in the container + void *elm = list->first(list); + int j = 0; + while(elm) { + CxHashKey key = cx_hash_key(&elm, sizeof(void*)); + UiObject *item_obj = NULL; + cxMapRemoveAndGet(ct->current_items, key, &item_obj); + if(item_obj) { + g_object_ref(G_OBJECT(item_obj->widget)); + BOX_REMOVE(ct->widget, item_obj->widget); + cxMapPut(new_items, key, item_obj); + } + elm = list->next(list); + j++; + } + + // ct->current_items only contains elements, that are not in the list + cxMapFree(ct->current_items); // calls destructor remove_item + ct->current_items = new_items; + + // add all items + int index = 0; + elm = list->first(list); + while(elm) { + CxHashKey key = cx_hash_key(&elm, sizeof(void*)); + UiObject *item_obj = cxMapGet(ct->current_items, key); + if(item_obj) { + // re-add previously created widget + UiLayout layout = {0}; + ui_box_container_add(ct->container, item_obj->widget, &layout); + } else { + // create new widget and object for this list element + UiObject *obj = uic_object_new_toplevel(); + obj->ctx->parent = ct->parent->ctx; + obj->window = NULL; + obj->widget = ui_subcontainer_create( + ct->subcontainer, + obj, + ct->spacing, + ct->columnspacing, + ct->rowspacing, + ct->margin); + UiLayout layout = {0}; + ui_box_container_add(ct->container, obj->widget, &layout); + if(ct->create_ui) { + ct->create_ui(obj, index, elm, ct->userdata); + } + cxMapPut(new_items, key, obj); + } + elm = list->next(list); + index++; + } + +#if GTK_MAJOR_VERSION < 4 + gtk_widget_show_all(ct->widget); +#endif +} + +static void destroy_itemlist_container(GtkWidget *w, UiGtkItemListContainer *container) { + container->remove_items = FALSE; + cxMapFree(container->current_items); + free(container); +} + +UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs *args) { + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + + GtkWidget *box = args->container == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args->spacing) : ui_gtk_hbox_new(args->spacing); + ui_set_name_and_style(box, args->name, args->style_class); + ct->add(ct, box, &layout); + + UiGtkItemListContainer *container = malloc(sizeof(UiGtkItemListContainer)); + container->parent = obj; + container->widget = box; + container->container = (UiContainerPrivate*)ui_box_container(obj, box, args->container); + container->create_ui = args->create_ui; + container->userdata = args->userdata; + container->subcontainer = args->subcontainer; + container->current_items = cxHashMapCreateSimple(CX_STORE_POINTERS); + container->current_items->collection.advanced_destructor = remove_item; + container->current_items->collection.destructor_data = container; + container->margin = args->sub_margin; + container->spacing = args->sub_spacing; + container->columnspacing = args->sub_columnspacing; + container->rowspacing = args->sub_rowspacing; + container->remove_items = TRUE; + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_LIST); + if(var) { + UiList *list = var->value; + list->obj = container; + list->update = update_itemlist; + update_itemlist(list, 0); + } + g_signal_connect( + box, + "destroy", + G_CALLBACK(destroy_itemlist_container), + container); + + return box; +} + + diff --git a/ui/gtk/container.h b/ui/gtk/container.h new file mode 100644 index 0000000..2f2510c --- /dev/null +++ b/ui/gtk/container.h @@ -0,0 +1,206 @@ +/* + * 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 "toolkit.h" +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ui_reset_layout(layout) memset(&(layout), 0, sizeof(UiLayout)) + + +typedef struct UiDocumentView UiDocumentView; + +typedef struct UiContainerPrivate UiContainerPrivate; +struct UiContainerPrivate { + UiContainerX container; + GtkWidget *widget; + UIMENU menu; + GtkWidget *current; // TODO: remove + + void (*add)(UiContainerPrivate*, GtkWidget*, UiLayout *layout); + UiLayout layout; + + int close; +}; + +typedef struct UiBoxContainer { + UiContainerPrivate container; + UiSubContainerType type; + UiBool has_fill; +} UiBoxContainer; + +typedef struct UiGridContainer { + UiContainerPrivate container; + UiBool def_hexpand; + UiBool def_vexpand; + UiBool def_hfill; + UiBool def_vfill; + int x; + int y; +#ifdef UI_GTK2 + int width; + int height; +#endif +} UiGridContainer; + +typedef struct UiTabViewContainer { + UiContainerPrivate 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 padding; + int spacing; + int columnspacing; + int rowspacing; + ui_callback onchange; + void *onchangedata; +} UiGtkTabView; + +typedef struct UiSplitPane { + GtkWidget *current_pane; + CxList *children; + UiOrientation orientation; + int pos; + int max; + int nchildren; + int initial_position; +} UiSplitPane; + +typedef struct UiSplitPaneContainer { + UiContainerPrivate container; + UiSplitPane *splitpane; +} UiSplitPaneContainer; + +typedef struct UiHeaderbarContainer { + UiContainerPrivate container; + GtkWidget *centerbox; + int part; + UiHeaderbarAlternative alternative; /* only used by fallback headerbar */ +} UiHeaderbarContainer; + +typedef struct UiGtkItemListContainer { + UiObject *parent; + GtkWidget *widget; + UiContainerPrivate *container; + void (*create_ui)(UiObject *, int, void *, void *); + void *userdata; + UiSubContainerType subcontainer; + CxMap *current_items; + int margin; + int spacing; + int columnspacing; + int rowspacing; + bool remove_items; +} UiGtkItemListContainer; + +GtkWidget* ui_gtk_vbox_new(int spacing); +GtkWidget* ui_gtk_hbox_new(int spacing); + +GtkWidget* ui_subcontainer_create( + UiSubContainerType type, + UiObject *newobj, + int spacing, + int columnspacing, + int rowspacing, + int margin); + +GtkWidget* ui_gtk_set_margin(GtkWidget *widget, int margin, int margin_left, int margin_right, int margin_top, int margin_bottom); +UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs *args, UiSubContainerType type); + +UiContainerX* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type); +void ui_box_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); + +GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing); +UiContainerX* ui_grid_container( + UiObject *obj, + GtkWidget *grid, + UiBool def_hexpand, + UiBool def_vexpand, + UiBool def_hfill, + UiBool def_vfill); +void ui_grid_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); + +UiContainerX* ui_frame_container(UiObject *obj, GtkWidget *frame); +void ui_frame_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); + +UiContainerX* ui_expander_container(UiObject *obj, GtkWidget *expander); +void ui_expander_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); + +UiContainerX* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow); +void ui_scrolledwindow_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); + +UiContainerX* ui_tabview_container(UiObject *obj, GtkWidget *tabview); +void ui_tabview_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); + +UiSplitPane* ui_create_splitpane_data(GtkWidget *pane, UiOrientation orientation, int max, int init); +UiContainerX* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiSplitPane *data); +void ui_splitpane_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); + +int64_t ui_splitpane_get(UiInteger *i); +void ui_splitpane_set(UiInteger *i, int64_t value); + +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) +UiContainerX* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar); +void ui_headerbar_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); +#endif + +UiContainerX* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar); +void ui_headerbar_fallback_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout); + +#ifdef __cplusplus +} +#endif + +#endif /* CONTAINER_H */ + diff --git a/ui/gtk/display.c b/ui/gtk/display.c new file mode 100644 index 0000000..1f8c39e --- /dev/null +++ b/ui/gtk/display.c @@ -0,0 +1,266 @@ +/* + * 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) { + 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: set_alignment(widget, .5, .5); break; + } + + + UiVar* var = uic_widget_var(obj->ctx, obj->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; + } + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); + + 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(""); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); + + 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 + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); + + return widget; +} +*/ + +/* ------------------------- progress bar ------------------------- */ + +typedef struct UiProgressBarRange { + double min; + double max; +} UiProgressBarRange; + +UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs *args) { + 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, obj->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); + } + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, progressbar, &layout); + + 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) { + GtkWidget *spinner = gtk_spinner_new(); + + UiVar* var = uic_widget_var(obj->ctx, obj->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); + } + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, spinner, &layout); + + 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..8cb03b0 --- /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) { + cxListFree(dnd->providers); + free(dnd); +} + +UiDnDAction ui_dnd_result(UiDnD *dnd) { + switch(dnd->selected_action) { + case 0: return UI_DND_ACTION_NONE; + case GDK_ACTION_COPY: return UI_DND_ACTION_COPY; + case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE; + case GDK_ACTION_LINK: return UI_DND_ACTION_LINK; + default: break; + } + return UI_DND_ACTION_CUSTOM; +} + +#else + +void ui_selection_settext(UiDnD *sel, char *str, int len) { + gtk_selection_data_set_text(sel->data, str, len); +} + +void ui_selection_seturis(UiDnD *sel, char **uris, int nelm) { + char **uriarray = calloc(nelm+1, sizeof(char*)); + for(int i=0;idata, uriarray); + free(uriarray); +} + +char* ui_selection_gettext(UiDnD *sel) { + if(!sel->data) { + return NULL; + } + + guchar *text = gtk_selection_data_get_text(sel->data); + if(text) { + char *textcp = strdup((char*)text); + g_free(text); + return textcp; + } + return NULL; +} + +UiFileList ui_selection_geturis(UiDnD *sel) { + if(!sel->data) { + return (UiFileList){NULL,0}; + } + + gchar **uris = gtk_selection_data_get_uris(sel->data); + if(uris) { + size_t al = 32; + char **array = malloc(al * sizeof(char*)); + size_t i = 0; + while(uris[i] != NULL) { + if(i >= al) { + al *= 2; + array = realloc(array, al * sizeof(char*)); + } + array[i] = strdup((char*)uris[i]); + i++; + } + g_strfreev(uris); + return (UiFileList){array,i}; + } + + return (UiFileList){NULL,0}; +} + +UiDnDAction ui_dnd_result(UiDnD *dnd) { + switch(dnd->selected_action) { + case 0: return UI_DND_ACTION_NONE; + case GDK_ACTION_COPY: return UI_DND_ACTION_COPY; + case GDK_ACTION_MOVE: return UI_DND_ACTION_MOVE; + case GDK_ACTION_LINK: return UI_DND_ACTION_LINK; + default: break; + } + return UI_DND_ACTION_CUSTOM; +} + + +UiDnD* ui_create_dnd(void) { + UiDnD *dnd = malloc(sizeof(UiDnD)); + memset(dnd, 0, sizeof(UiDnD)); + return dnd; +} + +void ui_dnd_free(UiDnD *dnd) { + free(dnd); +} + +#endif + +UiBool ui_dnd_need_delete(UiDnD *dnd) { + return dnd->delete; +} + +void ui_dnd_accept(UiDnD *dnd, UiBool accept) { + dnd->accept = accept; +} + diff --git a/ui/gtk/dnd.h b/ui/gtk/dnd.h 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..c067bc0 --- /dev/null +++ b/ui/gtk/draw_cairo.c @@ -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. + */ + +#include +#include + +#include "container.h" + +#include "draw_cairo.h" + + +#if GTK_MAJOR_VERSION >= 3 +void ui_drawingarea_draw(UiDrawingArea *drawingarea, cairo_t *cr, int width, int height) { + UiCairoGraphics g; + g.g.width = width; + g.g.height = height; + g.widget = drawingarea->widget;; + g.cr = cr; + + UiEvent event; + event.obj = drawingarea->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = 0; + event.set = 0; + + if(drawingarea->draw) { + drawingarea->draw(&event, &g.g, drawingarea->drawdata); + } +} +#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, UiBool 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..a1ae8d7 --- /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; + +void ui_drawingarea_draw(UiDrawingArea *drawingarea, cairo_t *cr, int width, int height); + +#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..b411191 --- /dev/null +++ b/ui/gtk/entry.c @@ -0,0 +1,220 @@ +/* + * 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_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { + double min = args->min; + double max = args->max != 0 ? args->max : 1000; + + UiVar *var = NULL; + UiVarType vartype = 0; + if(args->varname) { + var = uic_get_var(obj->ctx, args->varname); + if(var) { + vartype = var->type; + } else { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, args->varname, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(!var) { + if(args->intvalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER); + vartype = UI_VAR_INTEGER; + } else if(args->doublevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE); + vartype = UI_VAR_DOUBLE; + } else if(args->rangevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(vartype == 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); + + if(args->width > 0) { + gtk_widget_set_size_request(spin, args->width, -1); + } + + gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), args->digits); + UiObserver **obs = NULL; + if(var) { + double value = 0; + switch(vartype) { + 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); + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, spin, &layout); + + 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 = NULL; + e.eventdatatype = 0; + 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..33fd186 --- /dev/null +++ b/ui/gtk/graphics.c @@ -0,0 +1,237 @@ +/* + * 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" + +#if GTK_CHECK_VERSION(3, 0, 0) +#include "draw_cairo.h" +#endif + +static void destroy_drawingarea(GtkWidget *widget, UiDrawingArea *drawingarea) { + free(drawingarea); +} + +static void drawingarea_draw(UiDrawingArea *drawingarea, cairo_t *cr, int width, int height) { + UiEvent event; + event.obj = drawingarea->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = 0; + event.set = 0; + + +} + +#if GTK_CHECK_VERSION(4, 0, 0) +static void drawfunc( + GtkDrawingArea *area, + cairo_t *cr, + int width, + int height, + gpointer userdata) +{ + ui_drawingarea_draw(userdata, cr, width, height); +} +#elif GTK_CHECK_VERSION(3, 0, 0) +gboolean draw_callback(GtkWidget *widget, cairo_t *cr, UiDrawingArea *drawingarea) { + int width = gtk_widget_get_allocated_width(widget); + int height = gtk_widget_get_allocated_height(widget); + ui_drawingarea_draw(drawingarea, cr, width, height); + return FALSE; +} +#endif + +UIWIDGET ui_drawingarea_create(UiObject *obj, UiDrawingAreaArgs *args) { + GtkWidget *widget = gtk_drawing_area_new(); + ui_set_name_and_style(widget, args->name, args->style_class); + +#if GTK_CHECK_VERSION(4, 0, 0) + gtk_drawing_area_set_content_width(GTK_DRAWING_AREA(widget), args->width > 0 ? args->width : 100); + gtk_drawing_area_set_content_height(GTK_DRAWING_AREA(widget), args->height > 0 ? args->height : 100); +#else + int w = args->width > 0 ? args->width : 100; + int h = args->height > 0 ? args->height : 100; + gtk_widget_set_size_request(widget, w, h); +#endif + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); + + UiDrawingArea *drawingarea = malloc(sizeof(UiDrawingArea)); + drawingarea->obj = obj; + drawingarea->widget = widget; + drawingarea->draw = args->draw; + drawingarea->drawdata = args->drawdata; + drawingarea->onclick = args->onclick; + drawingarea->onclickdata = args->onclickdata; + drawingarea->onmotion = args->onmotion; + drawingarea->onmotiondata = args->onmotiondata; + +#if GTK_CHECK_VERSION(4, 0, 0) + gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(widget), drawfunc, drawingarea, NULL); +#elif GTK_CHECK_VERSION(3, 0, 0) + g_signal_connect( + widget, + "draw", + G_CALLBACK(draw_callback), + NULL); +#endif + + g_signal_connect( + widget, + "destroy", + G_CALLBACK(destroy_drawingarea), + drawingarea); + + 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, const 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..fb89539 --- /dev/null +++ b/ui/gtk/graphics.h @@ -0,0 +1,64 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DRAWINGAREA_H +#define DRAWINGAREA_H + +#include "../ui/graphics.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct UiDrawingArea { + UiObject *obj; + GtkWidget *widget; + ui_drawfunc draw; + void *drawdata; + ui_callback onclick; + void *onclickdata; + ui_callback onmotion; + void *onmotiondata; +} UiDrawingArea; + +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..0a9bd3e --- /dev/null +++ b/ui/gtk/headerbar.c @@ -0,0 +1,227 @@ +/* + * 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" + +#include "../ui/properties.h" + +#if GTK_CHECK_VERSION(3, 10, 0) + +void ui_fill_headerbar(UiObject *obj, GtkWidget *sidebar_headerbar, GtkWidget *main_headerbar, GtkWidget *right_headerbar) { + CxList *sidebar_left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_SIDEBAR_LEFT); + CxList *sidebar_right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_SIDEBAR_RIGHT); + + 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); + + CxList *rightpanel_left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHTPANEL_LEFT); + CxList *rightpanel_center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHTPANEL_CENTER); + CxList *rightpanel_right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHTPANEL_RIGHT); + + UiToolbarMenuItem *appmenu = uic_get_appmenu(); + const char *appmenu_pos_str = ui_get_property("ui.gtk.window.appmenu.position"); + int appmenu_pos = UI_TOOLBAR_RIGHT; + if(sidebar_headerbar) { + appmenu_pos = UI_TOOLBAR_SIDEBAR_RIGHT; + } else if(right_headerbar) { + appmenu_pos = UI_TOOLBAR_RIGHTPANEL_RIGHT; + } + if(appmenu_pos_str) { + if(!strcmp(appmenu_pos_str, "sidebar") && sidebar_headerbar) { + appmenu_pos = UI_TOOLBAR_SIDEBAR_RIGHT; + } else if(!strcmp(appmenu_pos_str, "main")) { + appmenu_pos = UI_TOOLBAR_RIGHT; + } else if(!strcmp(appmenu_pos_str, "rightpanel") && right_headerbar) { + appmenu_pos = UI_TOOLBAR_RIGHTPANEL_RIGHT; + } + } + + // main toolbar + ui_headerbar_add_items(obj, main_headerbar, left_defaults, UI_TOOLBAR_LEFT); + ui_headerbar_add_items(obj, main_headerbar, center_defaults, UI_TOOLBAR_CENTER); + + if(appmenu && appmenu_pos == UI_TOOLBAR_RIGHT) { + ui_add_headerbar_menu(main_headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT); + } + ui_headerbar_add_items(obj, main_headerbar, right_defaults, UI_TOOLBAR_RIGHT); + + // sidebar + if(sidebar_headerbar) { + // ui_headerbar_add_items pos parameter uses only UI_TOOLBAR_LEFT, UI_TOOLBAR_CENTER, UI_TOOLBAR_RIGHT + ui_headerbar_add_items(obj, sidebar_headerbar, sidebar_left_defaults, UI_TOOLBAR_LEFT); + + if(appmenu && appmenu_pos == UI_TOOLBAR_SIDEBAR_RIGHT) { + ui_add_headerbar_menu(sidebar_headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT); + } + ui_headerbar_add_items(obj, sidebar_headerbar, sidebar_right_defaults, UI_TOOLBAR_RIGHT); + } + + // right panel + if(right_headerbar) { + ui_headerbar_add_items(obj, right_headerbar, rightpanel_left_defaults, UI_TOOLBAR_LEFT); + ui_headerbar_add_items(obj, right_headerbar, rightpanel_center_defaults, UI_TOOLBAR_CENTER); + + if(appmenu && appmenu_pos == UI_TOOLBAR_RIGHTPANEL_RIGHT) { + ui_add_headerbar_menu(right_headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT); + } + ui_headerbar_add_items(obj, right_headerbar, rightpanel_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.tooltip, item->args.onclick, item->args.onclickdata, 0, FALSE); + ui_set_widget_groups(obj->ctx, button, item->args.groups); + ui_set_widget_visibility_states(obj->ctx, button, item->args.visibility_states); + 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); + ui_set_widget_visibility_states(obj->ctx, button, item->args.visibility_states); + WIDGET_ADD_CSS_CLASS(button, "flat"); + ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.tooltip, 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(); + ui_set_widget_visibility_states(obj->ctx, menubutton, item->args.visibility_states); + 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(); + GtkWidget *menu = gtk_menu_new(); + ui_add_menu_items(menu, 0, &item->menu, obj); + gtk_widget_show_all(menu); + gtk_menu_button_set_popup(GTK_MENU_BUTTON(menubutton), menu); + +#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..93028ea --- /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 *sidebar_headerbar, GtkWidget *main_headerbar, GtkWidget *right_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..e60dbdd --- /dev/null +++ b/ui/gtk/image.c @@ -0,0 +1,501 @@ +/* + * 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" + +static void imageviewer_destroy(UiImageViewer *iv) { + if(iv->pixbuf) { + g_object_unref(iv->pixbuf); + } + free(iv); +} + +#if GTK_MAJOR_VERSION >= 4 + +static void imageviewer_draw( + GtkDrawingArea *drawingarea, + cairo_t *cr, + int width, + int height, + gpointer userdata) +{ + ui_cairo_draw_image(userdata, cr, width, height); +} + +#else + +static gboolean imageviewer_draw(GtkWidget *widget, cairo_t *cr, gpointer userdata) { + int width = gtk_widget_get_allocated_width(widget); + int height = gtk_widget_get_allocated_height(widget); + ui_cairo_draw_image(userdata, cr, width, height); + return FALSE; +} + +#endif + +UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs *args) { + GtkWidget *drawingarea = gtk_drawing_area_new(); + GtkWidget *toplevel; + GtkWidget *widget = drawingarea; + + gtk_widget_set_size_request(drawingarea, 100, 100); + +#if GTK_MAJOR_VERSION < 4 + GtkWidget *eventbox = gtk_event_box_new(); + gtk_container_add(GTK_CONTAINER(eventbox), drawingarea); + widget = eventbox; +#endif + + if(args->scrollarea) { + toplevel = SCROLLEDWINDOW_NEW(); + SCROLLEDWINDOW_SET_CHILD(toplevel, widget); + args->adjustwidgetsize = TRUE; + } else { + toplevel = widget; + } + + UiImageViewer *imgviewer = malloc(sizeof(UiImageViewer)); + memset(imgviewer, 0, sizeof(UiImageViewer)); + imgviewer->obj = obj; + imgviewer->onbuttonpress = args->onbuttonpress; + imgviewer->onbuttonpressdata = args->onbuttonpressdata; + imgviewer->onbuttonrelease = args->onbuttonrelease; + imgviewer->onbuttonreleasedata = args->onbuttonreleasedata; + if(args->image_padding > 0) { + imgviewer->padding_left = args->image_padding; + imgviewer->padding_right = args->image_padding; + imgviewer->padding_top = args->image_padding; + imgviewer->padding_bottom = args->image_padding; + } else { + imgviewer->padding_left = args->image_padding_left; + imgviewer->padding_right = args->image_padding_right; + imgviewer->padding_top = args->image_padding_top; + imgviewer->padding_bottom = args->image_padding_bottom; + } + imgviewer->adjustwidgetsize = args->adjustwidgetsize; + imgviewer->autoscale = args->autoscale; + imgviewer->useradjustable = args->useradjustable; + imgviewer->zoom_scale = 20; + + g_object_set_data_full(G_OBJECT(drawingarea), "uiimageviewer", imgviewer, (GDestroyNotify)imageviewer_destroy); + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_GENERIC); + imgviewer->var = var; + imgviewer->widget = drawingarea; + + 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 = imgviewer; + 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); + g_object_unref(pixbuf); + } + } + +#if GTK_MAJOR_VERSION >= 4 + gtk_drawing_area_set_draw_func( + GTK_DRAWING_AREA(drawingarea), + imageviewer_draw, + imgviewer, + NULL); + + if(args->useradjustable) { + gtk_widget_set_focusable(drawingarea, TRUE); + } + + GtkEventController *scrollcontroller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_VERTICAL); + g_signal_connect(scrollcontroller, "scroll", G_CALLBACK(ui_imageviewer_scroll), imgviewer); + gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(scrollcontroller)); + + GtkGesture *drag = gtk_gesture_drag_new(); + g_signal_connect(drag, "drag-begin", G_CALLBACK(ui_imageviewer_drag_begin_cb), imgviewer); + g_signal_connect(drag, "drag-end", G_CALLBACK(ui_imageviewer_drag_end_cb), imgviewer); + g_signal_connect(drag, "drag-update", G_CALLBACK(ui_imageviewer_drag_update_cb), imgviewer); + gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(drag)); + + GtkGesture *click = gtk_gesture_click_new(); + g_signal_connect(click, "pressed", G_CALLBACK(ui_imageviewer_pressed_cb), imgviewer); + g_signal_connect(click, "released", G_CALLBACK(ui_imageviewer_released_cb), imgviewer); + gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(click)); + +#elif GTK_MAJOR_VERSION == 3 + g_signal_connect( + drawingarea, + "draw", + G_CALLBACK(imageviewer_draw), + imgviewer); + + gtk_widget_add_events(eventbox, GDK_SCROLL_MASK); + + g_signal_connect( + eventbox, + "scroll-event", + G_CALLBACK(ui_imageviewer_scroll_event), + imgviewer); + g_signal_connect( + eventbox, + "button-press-event", + G_CALLBACK(ui_imageviewer_button_press_event), + imgviewer); + g_signal_connect( + eventbox, + "button-release-event", + G_CALLBACK(ui_imageviewer_button_release_event), + imgviewer); + +#endif + + if(args->contextmenu) { + UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, widget); + ui_widget_set_contextmenu(widget, menu); + } + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, toplevel, &layout); + + return toplevel; +} + +static void imageviewer_reset(UiImageViewer *imgviewer) { + imgviewer->isautoscaled = FALSE; + imgviewer->transx = 0; + imgviewer->transy; + imgviewer->begin_transx = 0; + imgviewer->begin_transy = 0; + imgviewer->scale = 1; + imgviewer->user_scale = 1; +} + +UIWIDGET ui_imageviewer_reset(UIWIDGET w) { + UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer"); + if(imgviewer) { + imageviewer_reset(imgviewer); + gtk_widget_queue_draw(w); + } +} + +UIWIDGET ui_imageviewer_set_autoscale(UIWIDGET w, UiBool set) { + UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer"); + if(imgviewer) { + imgviewer->autoscale = set; + } +} + +UIWIDGET ui_imageviewer_set_adjustwidgetsize(UIWIDGET w, UiBool set) { + UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer"); + if(imgviewer) { + imgviewer->adjustwidgetsize = set; + } +} + +UIWIDGET ui_imageviewer_set_useradjustable(UIWIDGET w, UiBool set) { + UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer"); + if(imgviewer) { + imgviewer->useradjustable = set; + } +} + +void ui_cairo_draw_image(UiImageViewer *imgviewer, cairo_t *cr, int width, int height) { + if(!imgviewer->pixbuf) { + return; + } + + GdkPixbuf *pixbuf = imgviewer->pixbuf; + double dpixwidth = (double)gdk_pixbuf_get_width(pixbuf); + double dpixheight = (double)gdk_pixbuf_get_height(pixbuf); + + double dwidth = width; + double dheight = height; + double scale = 1; + // if autoscale is enabled, scale the image to fill available space + // if useradjustable is also enabled, the autoscaling is only done once + if(imgviewer->autoscale && imgviewer->scale != 0) { + if(!imgviewer->isautoscaled) { + scale = dwidth / dpixwidth; + if(dpixheight * scale > dheight) { + scale = dheight / dpixheight; + } + + if(imgviewer->useradjustable) { + imgviewer->isautoscaled = TRUE; + } + + imgviewer->scale = scale; + } else { + scale = imgviewer->scale; + } + + imgviewer->user_scale = scale; + } else { + // user-adjusted scaling + //scale = 1 + ((double)imgviewer->zoom / (double)imgviewer->zoom_scale); + scale = imgviewer->user_scale; + } + + dpixwidth *= scale; + dpixheight *= scale; + double x = (dwidth - dpixwidth) / 2; + double y = (dheight - dpixheight) / 2; + + x += imgviewer->transx; + y += imgviewer->transy; + + cairo_translate(cr, x, y); + cairo_scale(cr, scale, scale); + + gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0); + cairo_paint(cr); +} + +void* ui_imageviewer_get(UiGeneric *g) { + UiImageViewer *imgviewer = g->obj; + g->value = imgviewer->pixbuf; + return g->value; +} + +const char* ui_imageviewer_get_type(UiGeneric *g) { + return UI_IMAGE_OBJECT_TYPE; +} + +int ui_imageviewer_set(UiGeneric *g, void *value, const char *type) { + if(!type || strcmp(type, UI_IMAGE_OBJECT_TYPE)) { + return 1; + } + + GdkPixbuf *pixbuf = value; + g_object_ref(pixbuf); + + UiImageViewer *imgviewer = g->obj; + g->value = pixbuf; + + imageviewer_reset(imgviewer); + + if(imgviewer->pixbuf) { + g_object_unref(imgviewer->pixbuf); + } + imgviewer->pixbuf = pixbuf; + + if(imgviewer->adjustwidgetsize && !imgviewer->autoscale) { + int width = gdk_pixbuf_get_width(pixbuf); + int height = gdk_pixbuf_get_height(pixbuf); + gtk_widget_set_size_request(imgviewer->widget, width, height); + } + gtk_widget_queue_draw(imgviewer->widget); + + 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); + g_object_unref(pixbuf); + } else { + obj->value = pixbuf; + obj->type = UI_IMAGE_OBJECT_TYPE; + } + + return 0; +} + +UIEXPORT int ui_image_load_data(UiGeneric *obj, const void *imgdata, size_t size) { + GBytes *bytes = g_bytes_new_static(imgdata, size); + GInputStream *in = g_memory_input_stream_new_from_bytes(bytes); + GError *error = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_stream(in, NULL, &error); + g_object_unref(in); + if(!pixbuf) { + return 1; + } + + if(obj->set) { + obj->set(obj, pixbuf, UI_IMAGE_OBJECT_TYPE); + g_object_unref(pixbuf); + } else { + obj->value = pixbuf; + obj->type = UI_IMAGE_OBJECT_TYPE; + } + + return 0; +} + +void ui_image_ref(UIIMAGE img) { + g_object_ref(img); +} + +void ui_image_unref(UIIMAGE img) { + g_object_unref(img); +} + +#if GTK_MAJOR_VERSION >= 4 + +gboolean ui_imageviewer_scroll( + GtkEventControllerScroll *widget, + gdouble dx, + gdouble dy, + gpointer userdata) +{ + UiImageViewer *imgviewer = userdata; + if(imgviewer->useradjustable) { + double step = dy / imgviewer->zoom_scale; + if(imgviewer->user_scale - step > 0) { + imgviewer->user_scale -= step; + } + + imgviewer->scale = 0; // disable autoscale + gtk_widget_queue_draw(imgviewer->widget); + return TRUE; + } + return FALSE; +} + +void ui_imageviewer_drag_begin_cb( + GtkGestureDrag *self, + gdouble start_x, + gdouble start_y, + gpointer userdata) +{ + UiImageViewer *imgviewer = userdata; + imgviewer->begin_transx = imgviewer->transx; + imgviewer->begin_transy = imgviewer->transy; +} + +void ui_imageviewer_drag_end_cb( + GtkGestureDrag* self, + gdouble x, + gdouble y, + gpointer userdata) +{ + +} + +void ui_imageviewer_drag_update_cb( + GtkGestureDrag *self, + gdouble x, + gdouble y, + gpointer userdata) +{ + UiImageViewer *imgviewer = userdata; + if(imgviewer->useradjustable) { + imgviewer->transx = imgviewer->begin_transx + x; + imgviewer->transy = imgviewer->begin_transy + y; + gtk_widget_queue_draw(imgviewer->widget); + } +} + +static void imgviewer_button_event( + GtkGestureClick *gesture, + UiImageViewer *imgviewer, + ui_callback callback, + void *userdata) +{ + UiEvent event; + event.obj = imgviewer->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture)); + event.set = 0; + callback(&event, userdata); +} + +void ui_imageviewer_pressed_cb( + GtkGestureClick *self, + gint n_press, + gdouble x, + gdouble y, + gpointer userdata) +{ + UiImageViewer *imgviewer = userdata; + if(imgviewer->onbuttonpress) { + imgviewer_button_event(self, imgviewer, imgviewer->onbuttonpress, imgviewer->onbuttonpressdata); + } +} + +void ui_imageviewer_released_cb( + GtkGestureClick *self, + gint n_press, + gdouble x, + gdouble y, + gpointer userdata) +{ + UiImageViewer *imgviewer = userdata; + if(imgviewer->onbuttonrelease) { + imgviewer_button_event(self, imgviewer, imgviewer->onbuttonrelease, imgviewer->onbuttonreleasedata); + } +} + +#else + +gboolean ui_imageviewer_scroll_event( + GtkWidget *widget, + GdkEventScroll event, + gpointer userdata) +{ + // TODO + return FALSE; +} + +gboolean ui_imageviewer_button_press_event( + GtkWidget *widget, + GdkEventButton event, + gpointer userdata) +{ + // TODO + return FALSE; +} + +gboolean ui_imageviewer_button_release_event( + GtkWidget *widget, + GdkEventButton event, + gpointer userdata) +{ + // TODO + return FALSE; +} + +#endif diff --git a/ui/gtk/image.h b/ui/gtk/image.h new file mode 100644 index 0000000..987c73b --- /dev/null +++ b/ui/gtk/image.h @@ -0,0 +1,137 @@ +/* + * 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 + +typedef struct UiImageViewer { + UiObject *obj; + GtkWidget *widget; + UiVar *var; + int padding_left; + int padding_right; + int padding_top; + int padding_bottom; + UiBool autoscale; + UiBool adjustwidgetsize; + UiBool useradjustable; + GdkPixbuf *pixbuf; + + double zoom_scale; + int transx; + int transy; + int begin_transx; + int begin_transy; + UiBool isautoscaled; + double user_scale; + double scale; + + ui_callback onbuttonpress; + void *onbuttonpressdata; + ui_callback onbuttonrelease; + void *onbuttonreleasedata; +} UiImageViewer; + +void ui_cairo_draw_image(UiImageViewer *imgviewer, cairo_t *cr, int width, int height); + +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); + +#if GTK_MAJOR_VERSION >= 4 + +gboolean ui_imageviewer_scroll( + GtkEventControllerScroll *widget, + gdouble dx, + gdouble dy, + gpointer userdata); + +void ui_imageviewer_drag_begin_cb( + GtkGestureDrag* self, + gdouble start_x, + gdouble start_y, + gpointer userdata); + +void ui_imageviewer_drag_end_cb( + GtkGestureDrag* self, + gdouble x, + gdouble y, + gpointer userdata); + +void ui_imageviewer_drag_update_cb( + GtkGestureDrag* self, + gdouble x, + gdouble y, + gpointer userdata); + +void ui_imageviewer_pressed_cb( + GtkGestureClick *self, + gint n_press, + gdouble x, + gdouble y, + gpointer userdata); + +void ui_imageviewer_released_cb( + GtkGestureClick *self, + gint n_press, + gdouble x, + gdouble y, + gpointer userdata); + +#else + +gboolean ui_imageviewer_scroll_event( + GtkWidget *widget, + GdkEventScroll event, + gpointer userdata); + +gboolean ui_imageviewer_button_press_event( + GtkWidget *widget, + GdkEventButton event, + gpointer userdata); + +gboolean ui_imageviewer_button_release_event( + GtkWidget *widget, + GdkEventButton event, + gpointer userdata); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* IMAGE_H */ + diff --git a/ui/gtk/list.c b/ui/gtk/list.c new file mode 100644 index 0000000..e4612bf --- /dev/null +++ b/ui/gtk/list.c @@ -0,0 +1,2675 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "../common/context.h" +#include "../common/object.h" +#include "container.h" + +#include +#include + +#include "list.h" +#include "button.h" +#include "icon.h" +#include "menu.h" +#include "dnd.h" + + +static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata; + return getvalue(elm, col); +} + +static void* str_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + return elm; +} + +static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + return NULL; +} + +/* +static GtkTargetEntry targetentries[] = + { + { "STRING", 0, 0 }, + { "text/plain", 0, 1 }, + { "text/uri-list", 0, 2 }, + }; +*/ + +static void listview_copy_static_elements(UiListView *listview, char **elm, size_t nelm) { + listview->elements = calloc(nelm, sizeof(char*)); + listview->nelm = nelm; + for(int i=0;ielements[i] = strdup(elm[i]); + } +} + +static UiListView* create_listview(UiObject *obj, UiListArgs *args) { + UiListView *tableview = malloc(sizeof(UiListView)); + memset(tableview, 0, sizeof(UiListView)); + tableview->obj = obj; + tableview->model = args->model; + tableview->onactivate = args->onactivate; + tableview->onactivatedata = args->onactivatedata; + tableview->onselection = args->onselection; + tableview->onselectiondata = args->onselectiondata; + tableview->ondragstart = args->ondragstart; + tableview->ondragstartdata = args->ondragstartdata; + tableview->ondragcomplete = args->ondragcomplete; + tableview->ondragcompletedata = args->ondragcompletedata; + tableview->ondrop = args->ondrop; + tableview->ondropdata = args->ondropdata; + tableview->selection.count = 0; + tableview->selection.rows = NULL; + tableview->current_row = -1; + tableview->getstyle = args->getstyle; + tableview->getstyledata = args->getstyledata; + tableview->onsave = args->onsave; + tableview->onsavedata = args->onsavedata; + + if(args->getvalue2) { + tableview->getvalue = args->getvalue2; + tableview->getvaluedata = args->getvalue2data; + } else if(args->getvalue) { + tableview->getvalue = getvalue_wrapper; + tableview->getvaluedata = (void*)args->getvalue; + } else { + tableview->getvalue = null_getvalue; + } + + return tableview; +} + +#if GTK_CHECK_VERSION(4, 10, 0) + + +/* BEGIN GObject wrapper for generic pointers */ + +typedef struct _ObjWrapper { + GObject parent_instance; + void *data; + int i; +} ObjWrapper; + +typedef struct _ObjWrapperClass { + GObjectClass parent_class; +} ObjWrapperClass; + +G_DEFINE_TYPE(ObjWrapper, obj_wrapper, G_TYPE_OBJECT) + +static void obj_wrapper_class_init(ObjWrapperClass *klass) { + +} + +static void obj_wrapper_init(ObjWrapper *self) { + self->data = NULL; +} + +ObjWrapper* obj_wrapper_new(void* data, int i) { + ObjWrapper *obj = g_object_new(obj_wrapper_get_type(), NULL); + obj->data = data; + obj->i = i; + return obj; +} + +/* END GObject wrapper for generic pointers */ + +typedef struct UiCellEntry { + GtkEntry *entry; + UiListView *listview; + char *previous_value; + int row; + int col; +} UiCellEntry; + +static void cell_save_value(UiCellEntry *data, int restore) { + if(data->listview && data->listview->onsave) { + UiVar *var = data->listview->var; + UiList *list = var ? var->value : NULL; + const char *str = ENTRY_GET_TEXT(data->entry); + UiCellValue value; + value.string = str; + value.type = UI_STRING_EDITABLE; + if(data->listview->onsave(list, data->row, data->col, &value, data->listview->onsavedata)) { + free(data->previous_value); + data->previous_value = strdup(str); + } else if(restore) { + ENTRY_SET_TEXT(data->entry, data->previous_value); + } + } +} + +static void cell_entry_leave_focus( + GtkEventControllerFocus *self, + UiCellEntry *data) +{ + // TODO: use a different singal to track focus + // we only want to call cell_save_value, when another entry is selected, + // not when the window loses focus or something like that + cell_save_value(data, TRUE); +} + +static void cell_entry_destroy(GtkWidget *object, UiCellEntry *data) { + free(data->previous_value); + free(data); +} + +static void cell_entry_unmap(GtkWidget *w, UiCellEntry *data) { + const char *text = ENTRY_GET_TEXT(w); + cell_save_value(data, FALSE); +} + +static void cell_entry_activate( + GtkEntry *self, + UiCellEntry *data) +{ + cell_save_value(data, TRUE); +} + +static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { + UiColData *col = userdata; + UiModel *model = col->listview->model; + UiModelType type = model->types[col->model_column]; + if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + GtkWidget *image = gtk_image_new(); + GtkWidget *label = gtk_label_new(NULL); + BOX_ADD(hbox, image); + BOX_ADD(hbox, label); + gtk_list_item_set_child(item, hbox); + g_object_set_data(G_OBJECT(hbox), "image", image); + g_object_set_data(G_OBJECT(hbox), "label", label); + } else if(type == UI_ICON) { + GtkWidget *image = gtk_image_new(); + gtk_list_item_set_child(item, image); + } else if(type == UI_STRING_EDITABLE) { + GtkWidget *textfield = gtk_entry_new(); + gtk_widget_add_css_class(textfield, "ui-table-entry"); + gtk_list_item_set_child(item, textfield); + + UiCellEntry *entry_data = malloc(sizeof(UiCellEntry)); + entry_data->entry = GTK_ENTRY(textfield); + entry_data->listview = NULL; + entry_data->previous_value = NULL; + entry_data->col = 0; + entry_data->row = 0; + g_object_set_data(G_OBJECT(textfield), "ui_entry_data", entry_data); + + g_signal_connect( + textfield, + "destroy", + G_CALLBACK(cell_entry_destroy), + entry_data); + g_signal_connect( + textfield, + "activate", + G_CALLBACK(cell_entry_activate), + entry_data); + g_signal_connect( + textfield, + "unmap", + G_CALLBACK(cell_entry_unmap), + entry_data); + + GtkEventController *focus_controller = gtk_event_controller_focus_new(); + g_signal_connect(focus_controller, "leave", G_CALLBACK(cell_entry_leave_focus), entry_data); + gtk_widget_add_controller(textfield, focus_controller); + } else if(type == UI_BOOL_EDITABLE) { + GtkWidget *checkbox = gtk_check_button_new(); + gtk_list_item_set_child(item, checkbox); + }else { + GtkWidget *label = gtk_label_new(NULL); + gtk_label_set_xalign(GTK_LABEL(label), 0); + gtk_list_item_set_child(item, label); + } +} + +PangoAttrList* textstyle2pangoattributes(UiTextStyle style) { + PangoAttrList *attr = pango_attr_list_new(); + + if(style.text_style & UI_TEXT_STYLE_BOLD) { + pango_attr_list_insert(attr, pango_attr_weight_new(PANGO_WEIGHT_BOLD)); + } + if(style.text_style & UI_TEXT_STYLE_ITALIC) { + pango_attr_list_insert(attr, pango_attr_style_new(PANGO_STYLE_ITALIC)); + } + if(style.text_style & UI_TEXT_STYLE_UNDERLINE) { + pango_attr_list_insert(attr, pango_attr_underline_new(PANGO_UNDERLINE_SINGLE)); + } + + // foreground color, convert from 8bit to 16bit + guint16 r = (guint16)style.fg.red * 257; + guint16 g = (guint16)style.fg.green * 257; + guint16 b = (guint16)style.fg.blue * 257; + pango_attr_list_insert(attr, pango_attr_foreground_new(r, g, b)); + + return attr; +} + +static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, gpointer userdata) { + UiColData *col = userdata; + UiList *list = col->listview->var ? col->listview->var->value : NULL; + UiListView *listview = col->listview; + + ObjWrapper *obj = gtk_list_item_get_item(item); + UiModel *model = col->listview->model; + UiModelType type = model->types[col->model_column]; + + // cache the GtkListItem + CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int)); + UiRowItems *row = cxMapGet(listview->bound_rows, row_key); + if(row) { + if(row->items[col->model_column] == NULL) { + row->bound++; + } + } else { + row = calloc(1, sizeof(UiRowItems) + listview->numcolumns * sizeof(GtkListItem*)); + cxMapPut(listview->bound_rows, row_key, row); + row->bound = 1; + } + row->items[col->model_column] = item; + + UiBool freevalue = FALSE; + void *data = listview->getvalue(list, obj->data, obj->i, col->data_column, listview->getvaluedata, &freevalue); + GtkWidget *child = gtk_list_item_get_child(item); + + PangoAttrList *attributes = NULL; + UiTextStyle style = { 0, 0 }; + if(listview->getstyle) { + // query current row style, if it wasn't already queried + if(obj->i != listview->current_row) { + listview->current_row = obj->i; + listview->row_style = (UiTextStyle){ 0, 0 }; + listview->apply_row_style = listview->getstyle(list, obj->data, obj->i, -1, listview->getstyledata, &listview->row_style); + style = listview->row_style; + if(listview->apply_row_style) { + pango_attr_list_unref(listview->current_row_attributes); + listview->current_row_attributes = textstyle2pangoattributes(style); + } + } + + int style_col = col->data_column; + if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { + style_col++; // col->data_column is the icon, we need the next col for the label + } + + // get the column style + if(listview->getstyle(list, obj->data, obj->i, style_col, listview->getstyledata, &style)) { + attributes = textstyle2pangoattributes(style); + } else if(listview->apply_row_style) { + attributes = listview->current_row_attributes; + } + } + + switch(type) { + case UI_STRING_FREE: { + freevalue = TRUE; + } + case UI_STRING: { + gtk_label_set_label(GTK_LABEL(child), data); + if(freevalue) { + free(data); + } + gtk_label_set_attributes(GTK_LABEL(child), attributes); + break; + } + case UI_INTEGER: { + intptr_t intvalue = (intptr_t)data; + char buf[32]; + snprintf(buf, 32, "%d", (int)intvalue); + gtk_label_set_label(GTK_LABEL(child), buf); + gtk_label_set_attributes(GTK_LABEL(child), attributes); + break; + } + case UI_ICON: { + UiIcon *icon = data; + if(icon) { + gtk_image_set_from_paintable(GTK_IMAGE(child), GDK_PAINTABLE(icon->info)); + } + break; + } + case UI_ICON_TEXT: { + + } + case UI_ICON_TEXT_FREE: { + void *data2 = listview->getvalue(list, obj->data, obj->i, col->data_column+1, listview->getvaluedata, &freevalue); + if(type == UI_ICON_TEXT_FREE) { + freevalue = TRUE; + } + GtkWidget *image = g_object_get_data(G_OBJECT(child), "image"); + GtkWidget *label = g_object_get_data(G_OBJECT(child), "label"); + if(data && image) { + UiIcon *icon = data; + gtk_image_set_from_paintable(GTK_IMAGE(image), GDK_PAINTABLE(icon->info)); + } + if(data2 && label) { + gtk_label_set_label(GTK_LABEL(label), data2); + gtk_label_set_attributes(GTK_LABEL(label), attributes); + } + if(freevalue) { + free(data2); + } + break; + } + case UI_STRING_EDITABLE: { + UiCellEntry *entry = g_object_get_data(G_OBJECT(child), "ui_entry_data"); + if(entry) { + entry->listview = col->listview; + entry->row = obj->i; + entry->col = col->data_column; + entry->previous_value = strdup(data); + } + ENTRY_SET_TEXT(child, data); + break; + } + case UI_BOOL_EDITABLE: { + intptr_t i = (intptr_t)data; + gtk_check_button_set_active(GTK_CHECK_BUTTON(child), (gboolean)i); + break; + } + } + + if(attributes != listview->current_row_attributes) { + pango_attr_list_unref(attributes); + } +} + +static void column_factory_unbind(GtkSignalListItemFactory *self, GtkListItem *item, UiColData *col) { + ObjWrapper *obj = gtk_list_item_get_item(item); + UiListView *listview = col->listview; + CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int)); + UiRowItems *row = cxMapGet(listview->bound_rows, row_key); + if(row) { + row->items[col->model_column] = NULL; + row->bound--; + if(row->bound == 0) { + cxMapRemove(listview->bound_rows, row_key); + } + } // else: should not happen + + GtkWidget *child = gtk_list_item_get_child(item); + UiCellEntry *entry = g_object_get_data(G_OBJECT(child), "ui_entry_data"); + if(entry) { + cell_save_value(entry, FALSE); + entry->listview = NULL; + free(entry->previous_value); + entry->previous_value = NULL; + } else if(GTK_IS_CHECK_BUTTON(child)) { + + } +} + + +static GtkSelectionModel* create_selection_model(UiListView *listview, GListStore *liststore, bool multiselection) { + GtkSelectionModel *selection_model; + if(multiselection) { + selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(liststore))); + } else { + selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(liststore))); + gtk_single_selection_set_can_unselect(GTK_SINGLE_SELECTION(selection_model), TRUE); + gtk_single_selection_set_autoselect(GTK_SINGLE_SELECTION(selection_model), FALSE); + } + g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), listview); + return selection_model; +} + +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { + // to simplify things and share code with ui_table_create, we also + // use a UiModel for the listview + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + args->model = model; + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + UiListView *listview = create_listview(obj, args); + if(!args->getvalue && !args->getvalue2) { + listview->getvalue = str_getvalue; + } + + listview->numcolumns = 1; + listview->columns = malloc(sizeof(UiColData)); + listview->columns->listview = listview; + listview->columns->data_column = 0; + listview->columns->model_column = 0; + + listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); + listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + + GtkSelectionModel *selection_model = create_selection_model(listview, ls, args->multiselection); + GtkWidget *view = gtk_list_view_new(GTK_SELECTION_MODEL(selection_model), factory); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + // init listview + listview->widget = view; + listview->var = var; + listview->liststore = ls; + listview->selectionmodel = selection_model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + // bind listview to list + if(var && var->value) { + UiList *list = var->value; + + list->obj = listview; + list->update = ui_listview_update2; + list->getselection = ui_listview_getselection2; + list->setselection = ui_listview_setselection2; + + ui_update_liststore(ls, list); + } else if (args->static_elements && args->static_nelm > 0) { + listview_copy_static_elements(listview, args->static_elements, args->static_nelm); + listview->getvalue = str_getvalue; // force string values + ui_update_liststore_static(ls, listview->elements, listview->nelm); + } + + // event handling + if(args->onactivate) { + // columnview and listview can use the same callback function, because + // the first parameter (which is technically a different pointer type) + // is ignored + g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview); + } + if(args->contextmenu) { + UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, view); + ui_widget_set_contextmenu(view, menu); + } + + // add widget to parent + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + if(args->width > 0 || args->height > 0) { + int width = args->width; + int height = args->height; + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(scroll_area, width, height); + } + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, scroll_area, &layout); + + return scroll_area; +} + +UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { + // to simplify things and share code with ui_tableview_create, we also + // use a UiModel for the listview + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + args->model = model; + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + UiListView *listview = create_listview(obj, args); + + if(!args->getvalue && !args->getvalue2) { + listview->getvalue = str_getvalue; + } + + listview->numcolumns = 1; + listview->columns = malloc(sizeof(UiColData)); + listview->columns->listview = listview; + listview->columns->data_column = 0; + listview->columns->model_column = 0; + + listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); + listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + + GtkWidget *view = gtk_drop_down_new(G_LIST_MODEL(ls), NULL); + gtk_drop_down_set_factory(GTK_DROP_DOWN(view), factory); + if(args->width > 0) { + gtk_widget_set_size_request(view, args->width, -1); + } + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + // init listview + listview->widget = view; + listview->var = var; + listview->liststore = ls; + listview->selectionmodel = NULL; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + // bind listview to list + if(var && var->value) { + UiList *list = var->value; + + list->obj = listview; + list->update = ui_listview_update2; + list->getselection = ui_combobox_getselection; + list->setselection = ui_combobox_setselection; + + ui_update_liststore(ls, list); + } else if (args->static_elements && args->static_nelm > 0) { + listview_copy_static_elements(listview, args->static_elements, args->static_nelm); + listview->getvalue = str_getvalue; // force string values + ui_update_liststore_static(ls, listview->elements, listview->nelm); + } + + // event handling + if(args->onactivate) { + g_signal_connect(view, "notify::selected", G_CALLBACK(ui_dropdown_notify), listview); + } + + // add widget to parent + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, view, &layout); + + return view; +} + +void ui_listview_select(UIWIDGET listview, int index) { + GtkSelectionModel *model = gtk_list_view_get_model(GTK_LIST_VIEW(listview)); + gtk_selection_model_select_item(model, index, TRUE); +} + +void ui_combobox_select(UIWIDGET dropdown, int index) { + gtk_drop_down_set_selected(GTK_DROP_DOWN(dropdown), index); +} + +UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + //g_list_store_append(ls, v1); + + // create obj to store all relevant data we need for handling events + // and list updates + UiListView *tableview = create_listview(obj, args); + + GtkSelectionModel *selection_model = create_selection_model(tableview, ls, args->multiselection); + GtkWidget *view = gtk_column_view_new(GTK_SELECTION_MODEL(selection_model)); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + // init tableview + tableview->widget = view; + tableview->var = var; + tableview->liststore = ls; + tableview->selectionmodel = selection_model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + tableview); + + + // create columns from UiModel + UiModel *model = args->model; + int columns = model ? model->columns : 0; + + tableview->columns = calloc(columns, sizeof(UiColData)); + tableview->numcolumns = columns; + + tableview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); + tableview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; + + int addi = 0; + for(int i=0;icolumns[i].listview = tableview; + tableview->columns[i].model_column = i; + tableview->columns[i].data_column = i+addi; + + if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { + // icon+text has 2 data columns + addi++; + } + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + UiColData *col = &tableview->columns[i]; + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); + + GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[i], factory); + gtk_column_view_column_set_resizable(column, true); + gtk_column_view_append_column(GTK_COLUMN_VIEW(view), column); + + int size = model->columnsize[i]; + if(size > 0) { + gtk_column_view_column_set_fixed_width(column, size); + } else if(size < 0) { + gtk_column_view_column_set_expand(column, TRUE); + } + } + + // bind listview to list + if(var && var->value) { + UiList *list = var->value; + + list->obj = tableview; + list->update = ui_listview_update2; + list->getselection = ui_listview_getselection2; + list->setselection = ui_listview_setselection2; + + ui_update_liststore(ls, list); + } + + // event handling + if(args->onactivate) { + g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), tableview); + } + if(args->contextmenu) { + UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, view); + ui_widget_set_contextmenu(view, menu); + } + + // add widget to parent + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + if(args->width > 0 || args->height > 0) { + int width = args->width; + int height = args->height; + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(scroll_area, width, height); + } + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, scroll_area, &layout); + + return scroll_area; +} + +static UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) { + UiListSelection sel = { 0, NULL }; + GtkBitset *bitset = gtk_selection_model_get_selection(model); + int n = gtk_bitset_get_size(bitset); + printf("bitset %d\n", n); + + gtk_bitset_unref(bitset); + return sel; +} + +static void listview_event(ui_callback cb, void *cbdata, UiListView *view) { + UiEvent event; + event.obj = view->obj; + event.document = event.obj->ctx->document; + event.window = event.obj->window; + event.intval = view->selection.count; + event.eventdata = &view->selection; + event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; + event.set = ui_get_setop(); + if(cb) { + cb(&event, cbdata); + } +} + +static void listview_update_selection(UiListView *view) { + free(view->selection.rows); + view->selection.count = 0; + view->selection.rows = NULL; + + CX_ARRAY_DECLARE(int, newselection); + cx_array_initialize(newselection, 8); + + size_t nitems = g_list_model_get_n_items(G_LIST_MODEL(view->liststore)); + + for(size_t i=0;iselectionmodel, i)) { + int s = (int)i; + cx_array_simple_add(newselection, s); + } + } + + if(newselection_size > 0) { + view->selection.count = newselection_size; + view->selection.rows = newselection; + } else { + free(newselection); + } +} + +void ui_dropdown_notify(GtkWidget *dropdown, GObject *pspec, gpointer userdata) { + UiListView *view = userdata; + guint index = gtk_drop_down_get_selected(GTK_DROP_DOWN(dropdown)); + GObject *item = gtk_drop_down_get_selected_item(GTK_DROP_DOWN(dropdown)); + if(item && view->onactivate) { + ObjWrapper *eventdata = (ObjWrapper*)item; + UiEvent event; + event.obj = view->obj; + event.document = event.obj->ctx->document; + event.window = event.obj->window; + event.intval = index; + event.eventdata = eventdata->data; + event.eventdatatype = UI_EVENT_DATA_LIST_ELM; + event.set = ui_get_setop(); + view->onactivate(&event, view->onactivatedata); + } +} + + +void ui_columnview_activate(void *ignore, guint position, gpointer userdata) { + UiListView *view = userdata; + if(view->selection.count == 0) { + listview_update_selection(view); + } + listview_event(view->onactivate, view->onactivatedata, view); +} + +void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) { + UiListView *view = userdata; + listview_update_selection(view); + listview_event(view->onselection, view->onselectiondata, view); +} + +void ui_dropdown_activate(GtkDropDown* self, gpointer userdata) { + UiListView *view = userdata; + guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); + UiListSelection sel = { 0, NULL }; + int sel2 = (int)selection; + if(selection != GTK_INVALID_LIST_POSITION) { + sel.count = 1; + sel.rows = &sel2; + } + + if(view->onactivate) { + UiEvent event; + event.obj = view->obj; + event.document = event.obj->ctx->document; + event.window = event.obj->window; + event.intval = view->selection.count; + event.eventdata = &view->selection; + event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; + event.set = ui_get_setop(); + view->onactivate(&event, view->onactivatedata); + } +} + +void ui_update_liststore(GListStore *liststore, UiList *list) { + g_list_store_remove_all(liststore); + int i = 0; + void *elm = list->first(list); + while(elm) { + ObjWrapper *obj = obj_wrapper_new(elm, i++); + g_list_store_append(liststore, obj); + elm = list->next(list); + } +} + +void ui_update_liststore_static(GListStore *liststore, char **elm, size_t nelm) { + g_list_store_remove_all(liststore); + for(int i=0;iobj; + if(i < 0) { + ui_update_liststore(view->liststore, list); + } else { + void *value = list->get(list, i); + if(value) { + ObjWrapper *obj = g_list_model_get_item(G_LIST_MODEL(view->liststore), i); + if(obj) { + obj->data = value; + } + + CxHashKey row_key = cx_hash_key(&i, sizeof(int)); + UiRowItems *row = cxMapGet(view->bound_rows, row_key); + if(row) { + for(int c=0;cnumcolumns;c++) { + if(row->items[c] != NULL) { + column_factory_bind(NULL, row->items[c], &view->columns[c]); + } + } + } + } + } +} + +UiListSelection ui_listview_getselection2(UiList *list) { + UiListView *view = list->obj; + UiListSelection selection; + selection.count = view->selection.count; + selection.rows = calloc(selection.count, sizeof(int)); + memcpy(selection.rows, view->selection.rows, selection.count*sizeof(int)); + return selection; +} + +void ui_listview_setselection2(UiList *list, UiListSelection selection) { + ui_setop_enable(TRUE); + UiListView *view = list->obj; + UiListSelection newselection; + newselection.count = view->selection.count; + if(selection.count > 0) { + newselection.rows = calloc(newselection.count, sizeof(int)); + memcpy(newselection.rows, selection.rows, selection.count*sizeof(int)); + } else { + newselection.rows = NULL; + } + free(view->selection.rows); + view->selection = newselection; + + gtk_selection_model_unselect_all(view->selectionmodel); + if(selection.count > 0) { + for(int i=0;iselectionmodel, selection.rows[i], FALSE); + } + } + ui_setop_enable(FALSE); +} + +UiListSelection ui_combobox_getselection(UiList *list) { + UiListView *view = list->obj; + guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); + UiListSelection sel = { 0, NULL }; + if(selection != GTK_INVALID_LIST_POSITION) { + sel.count = 1; + sel.rows = malloc(sizeof(int)); + sel.rows[0] = (int)selection; + } + return sel; +} + +void ui_combobox_setselection(UiList *list, UiListSelection selection) { + ui_setop_enable(TRUE); + UiListView *view = list->obj; + if(selection.count > 0) { + gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), selection.rows[0]); + } else { + gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), GTK_INVALID_LIST_POSITION); + } + ui_setop_enable(FALSE); +} + +#else + +static void update_list_row(UiListView *listview, GtkListStore *store, GtkTreeIter *iter, UiList *list, void *elm, int row) { + UiModel *model = listview->model; + ui_getstylefunc getstyle = listview->getstyle; + + // get the row style + UiBool style_set = FALSE; + UiTextStyle style = { 0, 0 }; + if(getstyle) { + style_set = getstyle(list, elm, row, -1, listview->getstyledata, &style); + } + + // set column values + int c = 0; + for(int i=0;icolumns;i++,c++) { + UiBool freevalue = FALSE; + void *data = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue); + + UiModelType type = model->types[i]; + + if(getstyle) { + // in case the column is icon+text, only get a style for the text column + int style_col = c; + if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { + style_col++; + } + + // Get the individual column style + // The column style overrides the row style, however if no column style + // is provided, we stick with the row style + if(getstyle(list, elm, row, style_col, listview->getstyledata, &style)) { + style_set = TRUE; + } + } + + GValue value = G_VALUE_INIT; + switch(type) { + case UI_STRING_FREE: { + freevalue = TRUE; + } + case UI_STRING: { + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, data); + if(freevalue) { + free(data); + } + break; + } + case UI_INTEGER: { + g_value_init(&value, G_TYPE_INT); + intptr_t intptr = (intptr_t)data; + g_value_set_int(&value, (int)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++; + + freevalue = FALSE; + char *str = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue); + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, str); + if(model->types[i] == UI_ICON_TEXT_FREE || freevalue) { + free(str); + } + break; + } + } + + gtk_list_store_set_value(store, iter, c, &value); + + if(style_set) { + int soff = listview->style_offset + i*6; + + GValue style_set_value = G_VALUE_INIT; + g_value_init(&style_set_value, G_TYPE_BOOLEAN); + g_value_set_boolean(&style_set_value, TRUE); + gtk_list_store_set_value(store, iter, soff, &style_set_value); + + GValue style_weight_value = G_VALUE_INIT; + g_value_init(&style_weight_value, G_TYPE_INT); + if(style.text_style & UI_TEXT_STYLE_BOLD) { + g_value_set_int(&style_weight_value, 600); + } else { + g_value_set_int(&style_weight_value, 400); + } + gtk_list_store_set_value(store, iter, soff + 1, &style_weight_value); + + GValue style_underline_value = G_VALUE_INIT; + g_value_init(&style_underline_value, G_TYPE_INT); + if(style.text_style & UI_TEXT_STYLE_UNDERLINE) { + g_value_set_int(&style_underline_value, PANGO_UNDERLINE_SINGLE); + } else { + g_value_set_int(&style_underline_value, PANGO_UNDERLINE_NONE); + } + gtk_list_store_set_value(store, iter, soff + 2, &style_underline_value); + + GValue style_italic_value = G_VALUE_INIT; + g_value_init(&style_italic_value, G_TYPE_INT); + if(style.text_style & UI_TEXT_STYLE_ITALIC) { + g_value_set_int(&style_italic_value, PANGO_STYLE_ITALIC); + } else { + g_value_set_int(&style_italic_value, PANGO_STYLE_NORMAL); + } + gtk_list_store_set_value(store, iter, soff + 3, &style_italic_value); + + GValue style_fgset_value = G_VALUE_INIT; + g_value_init(&style_fgset_value, G_TYPE_BOOLEAN); + g_value_set_boolean(&style_fgset_value, style.fg_set); + gtk_list_store_set_value(store, iter, soff + 4, &style_fgset_value); + + if(style.fg_set) { + char buf[8]; + snprintf(buf, 8, "#%02X%02X%02X", (int)style.fg.red, (int)style.fg.green, (int)style.fg.blue); + + GValue style_fg_value = G_VALUE_INIT; + g_value_init(&style_fg_value, G_TYPE_STRING); + g_value_set_string(&style_fg_value, buf); + gtk_list_store_set_value(store, iter, soff + 5, &style_fg_value); + } + } + } +} + +static GtkListStore* create_list_store(UiListView *listview, UiList *list) { + UiModel *model = listview->model; + int columns = model->columns; + GType *types = calloc(columns*8, sizeof(GType)); + 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; + } + } + } + int s = 0; + for(int i=0;istyle_offset+s] = G_TYPE_BOOLEAN; s++; // *-set + types[listview->style_offset+s] = G_TYPE_INT; s++; // weight + types[listview->style_offset+s] = G_TYPE_INT; s++; // underline + types[listview->style_offset+s] = G_TYPE_INT; s++; // style + types[listview->style_offset+s] = G_TYPE_BOOLEAN; s++; // foreground-set + types[listview->style_offset+s] = G_TYPE_STRING; s++; // foreground + } + + GtkListStore *store = gtk_list_store_newv(c+s, types); + free(types); + + if(list) { + void *elm = list->first(list); + int i = 0; + while(elm) { + // insert new row + GtkTreeIter iter; + gtk_list_store_insert (store, &iter, -1); + + update_list_row(listview, store, &iter, list, elm, i++); + + // next row + elm = list->next(list); + } + } + + return store; +} + + +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { + // 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); + + UiListView *listview = create_listview(obj, args); + listview->style_offset = 1; + if(!args->getvalue && !args->getvalue2) { + listview->getvalue = str_getvalue; + } + listview->model = model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + // init listview + listview->widget = view; + listview->var = var; + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(listview, list); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); + g_object_unref(listmodel); + + // 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); + + if(args->width > 0 || args->height > 0) { + int width = args->width; + int height = args->height; + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(scroll_area, width, height); + } + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, scroll_area, &layout); + + return scroll_area; +} + +void ui_listview_select(UIWIDGET listview, int index) { + GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(listview)); + GtkTreePath *path = gtk_tree_path_new_from_indicesv(&index, 1); + gtk_tree_selection_select_path(sel, path); + //g_object_unref(path); +} + +void ui_combobox_select(UIWIDGET dropdown, int index) { + gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), index); +} + +UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { + // create treeview + GtkWidget *view = gtk_tree_view_new(); + + UiModel *model = args->model; + int columns = model ? model->columns : 0; + + // find the last data column index + int addi = 0; + int style_offset = 0; + int i = 0; + for(;itypes[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { + addi++; + } + } + style_offset = i+addi; + + // create columns and init cell renderers + addi = 0; + for(i=0;itypes[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { + 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", addi + i); + gtk_tree_view_column_add_attribute(column, textrenderer, "text", addi + i+1); + + if(args->getstyle) { + int soff = style_offset + i*6; + gtk_tree_view_column_add_attribute(column, textrenderer, "weight-set", soff); + gtk_tree_view_column_add_attribute(column, textrenderer, "underline-set", soff); + gtk_tree_view_column_add_attribute(column, textrenderer, "style-set", soff); + + gtk_tree_view_column_add_attribute(column, textrenderer, "weight", soff + 1); + gtk_tree_view_column_add_attribute(column, textrenderer, "underline", soff + 2); + gtk_tree_view_column_add_attribute(column, textrenderer, "style", soff + 3); + gtk_tree_view_column_add_attribute(column, textrenderer, "foreground-set", soff + 4); + gtk_tree_view_column_add_attribute(column, textrenderer, "foreground", soff + 5); + } + + 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 *textrenderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + model->titles[i], + textrenderer, + "text", + i + addi, + NULL); + + if(args->getstyle) { + int soff = style_offset + i*6; + gtk_tree_view_column_add_attribute(column, textrenderer, "weight-set", soff); + gtk_tree_view_column_add_attribute(column, textrenderer, "underline-set", soff); + gtk_tree_view_column_add_attribute(column, textrenderer, "style-set", soff); + + gtk_tree_view_column_add_attribute(column, textrenderer, "weight", soff + 1); + gtk_tree_view_column_add_attribute(column, textrenderer, "underline", soff + 2); + gtk_tree_view_column_add_attribute(column, textrenderer, "style", soff + 3); + gtk_tree_view_column_add_attribute(column, textrenderer, "foreground-set", soff + 4); + gtk_tree_view_column_add_attribute(column, textrenderer, "foreground", soff + 5); + } + } + + 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, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + //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 = create_listview(obj, args); + tableview->widget = view; + tableview->style_offset = style_offset; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + tableview); + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(tableview, list); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); + g_object_unref(listmodel); + + // 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->width > 0 || args->height > 0) { + int width = args->width; + int height = args->height; + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(scroll_area, width, height); + } + + 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 + } + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, scroll_area, &layout); + + return scroll_area; +} + + + +void ui_listview_update(UiList *list, int i) { + UiListView *view = list->obj; + if(i < 0) { + GtkListStore *store = create_list_store(view, list); + gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); + g_object_unref(G_OBJECT(store)); + } else { + void *elm = list->get(list, i); + GtkTreeModel *store = gtk_tree_view_get_model(GTK_TREE_VIEW(view->widget)); + GtkTreeIter iter; + if(gtk_tree_model_iter_nth_child(store, &iter, NULL, i)) { + update_list_row(view, GTK_LIST_STORE(store), &iter, list, elm, i); + } + } +} + +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) { + ui_setop_enable(TRUE); + 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); + ui_setop_enable(FALSE); +} + + + +/* --------------------------- ComboBox --------------------------- */ + +UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { + GtkWidget *combobox = gtk_combo_box_new(); + if(args->width > 0) { + gtk_widget_set_size_request(combobox, args->width, -1); + } + + ui_set_name_and_style(combobox, args->name, args->style_class); + ui_set_widget_groups(obj->ctx, combobox, args->groups); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, combobox, &layout); + + UiListView *listview = create_listview(obj, args); + listview->widget = combobox; + listview->style_offset = 1; + listview->model = ui_model(obj->ctx, UI_STRING, "", -1); + g_signal_connect( + combobox, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(listview, list); + if(var) { + listview->var = var; + list->update = ui_combobox_modelupdate; + list->getselection = ui_combobox_getselection; + list->setselection = ui_combobox_setselection; + list->obj = listview; + list->update(list, -1); + } else if(args->static_nelm > 0) { + listview_copy_static_elements(listview, args->static_elements, args->static_nelm); + for(int i=0;istatic_nelm;i++) { + GtkTreeIter iter; + GValue value = G_VALUE_INIT; + gtk_list_store_insert(listmodel, &iter, -1); + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, listview->elements[i]); + gtk_list_store_set_value(listmodel, &iter, 0, &value); + } + } + + if(listmodel) { + gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); + g_object_unref(listmodel); + } + + 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(args->onactivate) { + UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); + event->obj = obj; + event->userdata = args->onactivatedata; + event->callback = args->onactivate; + event->value = 0; + event->customdata = listview; + + g_signal_connect( + combobox, + "changed", + G_CALLBACK(ui_combobox_change_event), + event); + } + + return combobox; +} + +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { + int index = gtk_combo_box_get_active(widget); + UiListView *listview = e->customdata; + void *eventdata = NULL; + if(listview->var && listview->var->value) { + UiList *list = listview->var->value; + eventdata = ui_list_get(list, index); + } else if(listview->elements && listview->nelm > index) { + eventdata = listview->elements[index]; + } + + UiEvent event; + event.obj = e->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = eventdata; + event.intval = index; + event.set = ui_get_setop(); + e->callback(&event, e->userdata); +} + +void ui_combobox_modelupdate(UiList *list, int i) { + UiListView *view = list->obj; + GtkListStore *store = create_list_store(view, list); + 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) { + ui_setop_enable(TRUE); + UiListView *combobox = list->obj; + if(selection.count > 0) { + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]); + } + ui_setop_enable(FALSE); +} + + + + +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; + e.set = ui_get_setop(); + 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; + e.set = ui_get_setop(); + event->selection(&e, event->selectiondata); + + if(selection.count > 0) { + free(selection.rows); + } +} + +UiListSelection ui_listview_selection( + GtkTreeSelection *selection, + UiTreeEventData *event) +{ + GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); + + UiListSelection ls; + ls.count = g_list_length(rows); + ls.rows = calloc(ls.count, sizeof(int)); + GList *r = rows; + int i = 0; + while(r) { + GtkTreePath *path = r->data; + ls.rows[i] = ui_tree_path_list_index(path); + r = r->next; + i++; + } + return ls; +} + +int ui_tree_path_list_index(GtkTreePath *path) { + int depth = gtk_tree_path_get_depth(path); + if(depth == 0) { + fprintf(stderr, "UiError: treeview selection: depth == 0\n"); + return -1; + } + int *indices = gtk_tree_path_get_indices(path); + return indices[depth - 1]; +} + + +#endif + + +#if GTK_MAJOR_VERSION >= 4 + +static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) { + //printf("drag prepare\n"); + UiListView *listview = data; + + UiDnD *dnd = ui_create_dnd(); + GdkContentProvider *provider = NULL; + + + if(listview->ondragstart) { + UiEvent event; + event.obj = listview->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = dnd; + event.eventdatatype = UI_EVENT_DATA_DND; + event.intval = 0; + event.set = ui_get_setop(); + 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.eventdatatype = UI_EVENT_DATA_DND; + event.intval = 0; + event.set = ui_get_setop(); + 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.eventdatatype = UI_EVENT_DATA_DND; + event.intval = 0; + event.set = ui_get_setop(); + 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; + event.set = ui_get_setop(); + 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; + event.set = ui_get_setop(); + 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; + event.set = ui_get_setop(); + 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;ivar) { + ui_destroy_boundvar(v->obj->ctx, v->var); + } + if(v->elements) { + for(int i=0;inelm;i++) { + free(v->elements[i]); + } + free(v->elements); + } +#if GTK_CHECK_VERSION(4, 10, 0) + free(v->columns); + pango_attr_list_unref(v->current_row_attributes); + cxMapFree(v->bound_rows); +#endif + free(v->selection.rows); + free(v); +} + + +/* ------------------------------ Source List ------------------------------ */ + +static ui_sourcelist_update_func sourcelist_update_finished_callback; + +void ui_sourcelist_set_update_callback(ui_sourcelist_update_func cb) { + sourcelist_update_finished_callback = cb; +} + +static void ui_sourcelist_update_finished(void) { + if(sourcelist_update_finished_callback) { + sourcelist_update_finished_callback(); + } +} + +static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) { + cxListFree(v->sublists); + free(v); +} + +static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) { + free(sublist->header); + ui_destroy_boundvar(obj->ctx, sublist->var); + cxListFree(sublist->widgets); +} + +static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) { + // first rows in sublists have the ui_listbox property + UiListBox *listbox = g_object_get_data(G_OBJECT(row), "ui_listbox"); + if(!listbox) { + return; + } + + UiListBoxSubList *sublist = g_object_get_data(G_OBJECT(row), "ui_listbox_sublist"); + if(!sublist) { + return; + } + + if(sublist->separator) { + GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + gtk_list_box_row_set_header(row, separator); + } else if(sublist->header && !listbox->header_is_item) { + GtkWidget *header = gtk_label_new(sublist->header); + gtk_widget_set_halign(header, GTK_ALIGN_START); + if(row == listbox->first_row) { + WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header-first"); + } else { + WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header"); + } + gtk_list_box_row_set_header(row, header); + } +} + +#ifdef UI_GTK3 +typedef struct _UiSidebarListBoxClass { + GtkListBoxClass parent_class; +} UiSidebarListBoxClass; + +typedef struct _UiSidebarListBox { + GtkListBox parent_instance; +} UiSidebarListBox; + +G_DEFINE_TYPE(UiSidebarListBox, ui_sidebar_list_box, GTK_TYPE_LIST_BOX) + +/* Initialize the instance */ +static void ui_sidebar_list_box_class_init(UiSidebarListBoxClass *klass) { + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + gtk_widget_class_set_css_name (widget_class, "placessidebar"); +} + +static void ui_sidebar_list_box_init(UiSidebarListBox *self) { + +} +#endif + + +static void add_sublist(UiListBox *uilistbox, CxList *sublists, UiSubList *sublist) { + UiListBoxSubList uisublist; + uisublist.var = uic_widget_var( + uilistbox->obj->ctx, + uilistbox->obj->ctx, + sublist->value, + sublist->varname, + UI_VAR_LIST); + uisublist.numitems = 0; + uisublist.header = sublist->header ? strdup(sublist->header) : NULL; + uisublist.separator = sublist->separator; + uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS); + uisublist.listbox = uilistbox; + uisublist.userdata = sublist->userdata; + uisublist.index = cxListSize(sublists); + uisublist.startpos = 0; + cxListAdd(sublists, &uisublist); + + // bind UiList + UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(sublists)-1); + if(uisublist.var && uisublist.var->value) { + UiList *list = uisublist.var->value; + list->obj = sublist_ptr; + list->update = ui_listbox_list_update; + } +} + +UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) { +#ifdef UI_GTK3 + GtkWidget *listbox = g_object_new(ui_sidebar_list_box_get_type(), NULL); +#else + GtkWidget *listbox = gtk_list_box_new(); +#endif + if(!args->style_class) { +#if GTK_MAJOR_VERSION >= 4 + WIDGET_ADD_CSS_CLASS(listbox, "navigation-sidebar"); +#else + WIDGET_ADD_CSS_CLASS(listbox, "sidebar"); +#endif + } + gtk_list_box_set_header_func(GTK_LIST_BOX(listbox), listbox_create_header, NULL, NULL); + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + SCROLLEDWINDOW_SET_CHILD(scroll_area, listbox); + + ui_set_name_and_style(listbox, args->name, args->style_class); + ui_set_widget_groups(obj->ctx, listbox, args->groups); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, scroll_area, &layout); + + UiListBox *uilistbox = malloc(sizeof(UiListBox)); + uilistbox->obj = obj; + uilistbox->listbox = GTK_LIST_BOX(listbox); + uilistbox->header_is_item = args->header_is_item; + uilistbox->getvalue = args->getvalue; + uilistbox->getvaluedata = args->getvaluedata; + uilistbox->onactivate = args->onactivate; + uilistbox->onactivatedata = args->onactivatedata; + uilistbox->onbuttonclick = args->onbuttonclick; + uilistbox->onbuttonclickdata = args->onbuttonclickdata; + uilistbox->sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), 4); + uilistbox->sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_destroy; + uilistbox->sublists->collection.destructor_data = obj; + uilistbox->first_row = NULL; + + if(args->sublists) { + // static sublist initalization + if(args->numsublists == 0 && args->sublists) { + args->numsublists = INT_MAX; + } + for(int i=0;inumsublists;i++) { + UiSubList sublist = args->sublists[i]; + if(!sublist.varname && !sublist.value) { + break; + } + + add_sublist(uilistbox, uilistbox->sublists, &sublist); + } + + // fill items + ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); + } else { + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->dynamic_sublist, args->varname, UI_VAR_LIST); + if(var) { + UiList *list = var->value; + list->obj = uilistbox; + list->update = ui_listbox_dynamic_update; + + ui_listbox_dynamic_update(list, -1); + } + } + + // register uilistbox for both widgets, so it doesn't matter which + // widget is used later + g_object_set_data(G_OBJECT(scroll_area), "ui_listbox", uilistbox); + g_object_set_data(G_OBJECT(listbox), "ui_listbox", uilistbox); + + if(args->contextmenu) { + UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, listbox); + ui_widget_set_contextmenu(listbox, menu); + } + + // signals + g_signal_connect( + listbox, + "destroy", + G_CALLBACK(ui_destroy_sourcelist), + uilistbox); + + if(args->onactivate) { + g_signal_connect( + listbox, + "row-activated", + G_CALLBACK(ui_listbox_row_activate), + NULL); + } + + return scroll_area; +} + +void ui_listbox_dynamic_update(UiList *list, int x) { + UiListBox *uilistbox = list->obj; + + // unbind/free previous list vars + CxIterator i = cxListIterator(uilistbox->sublists); + cx_foreach(UiListBoxSubList *, s, i) { + // TODO: "unbind/free previous list vars" will also remove + // the widget list. This makes the widget optimization + // in ui_listbox_update_sublist pointless + // Is it actually possible to not recreate the whole list? + CxIterator r = cxListIterator(s->widgets); + cx_foreach(GtkWidget*, widget, r) { + LISTBOX_REMOVE(uilistbox->listbox, widget); + } + + if(s->var) { + UiList *sl = s->var->value; + sl->obj = NULL; + sl->update = NULL; + if(s->var->type == UI_VAR_SPECIAL) { + ui_free(s->var->from_ctx, s->var); + } + } + } + + cxListFree(uilistbox->sublists); + CxList *new_sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), list->count(list)); + uilistbox->sublists = new_sublists; + + UiSubList *sublist = list->first(list); + while(sublist) { + add_sublist(uilistbox, new_sublists, sublist); + sublist = list->next(list); + } + + ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); +} + +void ui_listbox_update(UiListBox *listbox, int from, int to) { + CxIterator i = cxListIterator(listbox->sublists); + size_t pos = 0; + cx_foreach(UiListBoxSubList *, sublist, i) { + if(i.index < from) { + pos += sublist->numitems; + continue; + } + if(i.index > to) { + break; + } + + // reload sublist + sublist->startpos = pos; + ui_listbox_update_sublist(listbox, sublist, pos); + pos += sublist->numitems; + } + + ui_sourcelist_update_finished(); +} + +static void listbox_button_clicked(GtkWidget *button, UiEventDataExt *data) { + UiListBoxSubList *sublist = data->customdata0; + + UiSubListEventData eventdata; + eventdata.list = sublist->var->value; + eventdata.sublist_index = sublist->index; + eventdata.row_index = data->value0; + eventdata.sublist_userdata = sublist->userdata; + eventdata.row_data = eventdata.list->get(eventdata.list, eventdata.row_index); + eventdata.event_data = data->customdata2; + + UiEvent event; + event.obj = data->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &eventdata; + event.eventdatatype = UI_EVENT_DATA_SUBLIST; + event.intval = data->value0; + event.set = ui_get_setop(); + + if(data->callback2) { + data->callback2(&event, data->userdata2); + } + + if(data->customdata3) { + UIMENU menu = data->customdata3; + g_object_set_data(G_OBJECT(button), "ui-button-popup", menu); + gtk_popover_popup(GTK_POPOVER(menu)); + } +} + +#if GTK_CHECK_VERSION(3, 0, 0) +static void button_popover_closed(GtkPopover *popover, GtkWidget *button) { + g_object_set_data(G_OBJECT(button), "ui-button-popup", NULL); + if(g_object_get_data(G_OBJECT(button), "ui-button-invisible")) { + g_object_set_data(G_OBJECT(button), "ui-button-invisible", NULL); + gtk_widget_set_visible(button, FALSE); + } +} +#endif + +static void listbox_fill_row(UiListBox *listbox, GtkWidget *row, UiListBoxSubList *sublist, UiSubListItem *item, int index) { + UiBool is_header = index < 0; + + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); + if(item->icon) { + GtkWidget *icon = ICON_IMAGE(item->icon); + BOX_ADD(hbox, icon); + } + GtkWidget *label = gtk_label_new(item->label); + if(is_header) { + WIDGET_ADD_CSS_CLASS(label, "ui-listbox-header-row"); + } + gtk_widget_set_halign(label, GTK_ALIGN_START); + BOX_ADD_EXPAND(hbox, label); + if(item->badge) { + + } + LISTBOX_ROW_SET_CHILD(row, hbox); + + // signals + UiEventDataExt *event = malloc(sizeof(UiEventDataExt)); + memset(event, 0, sizeof(UiEventDataExt)); + event->obj = listbox->obj; + event->customdata0 = sublist; + event->customdata1 = sublist->var; + event->customdata2 = item->eventdata; + event->callback = listbox->onactivate; + event->userdata = listbox->onactivatedata; + event->callback2 = listbox->onbuttonclick; + event->userdata2 = listbox->onbuttonclickdata; + event->value0 = index; + + // TODO: semi-memory leak when listbox_fill_row is called again for the same row + // each row update will create a new UiEventDataExt object and a separate destroy handler + + g_signal_connect( + row, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + + g_object_set_data(G_OBJECT(row), "ui-listbox-row-eventdata", event); + + // badge + if(item->badge) { + GtkWidget *badge = gtk_label_new(item->badge); + WIDGET_ADD_CSS_CLASS(badge, "ui-badge"); +#if GTK_CHECK_VERSION(3, 14, 0) + gtk_widget_set_valign(badge, GTK_ALIGN_CENTER); + BOX_ADD(hbox, badge); +#else + GtkWidget *align = gtk_alignment_new(0.5, 0.5, 0, 0); + gtk_container_add(GTK_CONTAINER(align), badge); + BOX_ADD(hbox, align); +#endif + } + // button + if(item->button_icon || item->button_label) { + GtkWidget *button = gtk_button_new(); + gtk_button_set_label(GTK_BUTTON(button), item->button_label); + ui_button_set_icon_name(button, item->button_icon); + WIDGET_ADD_CSS_CLASS(button, "flat"); + BOX_ADD(hbox, button); + g_signal_connect( + button, + "clicked", + G_CALLBACK(listbox_button_clicked), + event + ); + gtk_widget_set_visible(button, FALSE); + + g_object_set_data(G_OBJECT(row), "ui-listbox-row-button", button); + + // menu + if(item->button_menu) { + UIMENU menu = ui_contextmenu_create(item->button_menu, listbox->obj, button); + event->customdata3 = menu; + g_signal_connect(menu, "closed", G_CALLBACK(button_popover_closed), button); + ui_menubuilder_unref(item->button_menu); + } + } +} + +static void update_sublist_item(UiListBox *listbox, UiListBoxSubList *sublist, int index) { + int header_row = listbox->header_is_item && sublist->header ? 1 : 0; + GtkListBoxRow *row = gtk_list_box_get_row_at_index(listbox->listbox, sublist->startpos + index + header_row); + if(!row) { + return; + } + UiList *list = sublist->var->value; + if(!list) { + return; + } + + void *elm = list->get(list, index); + UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; + if(listbox->getvalue) { + listbox->getvalue(list, sublist->userdata, elm, index, &item, listbox->getvaluedata); + } else { + item.label = strdup(elm); + } + + LISTBOX_ROW_REMOVE_CHILD(row); + + listbox_fill_row(listbox, GTK_WIDGET(row), sublist, &item, index); + + // cleanup + free(item.label); + free(item.icon); + free(item.button_label); + free(item.button_icon); + free(item.badge); +} + +static void listbox_row_on_enter(GtkWidget *row) { + GtkWidget *button = g_object_get_data(G_OBJECT(row), "ui-listbox-row-button"); + if(button) { + gtk_widget_set_visible(button, TRUE); + } +} + +static void listbox_row_on_leave(GtkWidget *row) { + GtkWidget *button = g_object_get_data(G_OBJECT(row), "ui-listbox-row-button"); + if(button) { + if(!g_object_get_data(G_OBJECT(button), "ui-button-popup")) { + gtk_widget_set_visible(button, FALSE); + } else { + g_object_set_data(G_OBJECT(button), "ui-button-invisible", (void*)1); + } + } +} + +#if GTK_CHECK_VERSION(4, 0, 0) +static void listbox_row_enter( + GtkEventControllerMotion* self, + gdouble x, + gdouble y, + GtkWidget *row) +{ + listbox_row_on_enter(row); +} + +static void listbox_row_leave( + GtkEventControllerMotion* self, + GtkWidget *row) +{ + listbox_row_on_leave(row); +} +#else +static gboolean listbox_row_enter( + GtkWidget *row, + GdkEventCrossing event, + gpointer user_data) +{ + listbox_row_on_enter(row); + return FALSE; +} + + +static gboolean listbox_row_leave( + GtkWidget *row, + GdkEventCrossing *event, + gpointer user_data) +{ + listbox_row_on_leave(row); + return FALSE; +} + +#endif + +void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) { + // clear sublist + CxIterator r = cxListIterator(sublist->widgets); + cx_foreach(GtkWidget*, widget, r) { + LISTBOX_REMOVE(listbox->listbox, widget); + } + cxListClear(sublist->widgets); + + sublist->numitems = 0; + + // create items for each UiList element + if(!sublist->var) { + return; + } + UiList *list = sublist->var->value; + if(!list) { + return; + } + + int index = 0; + void *elm = list->first(list); + void *first = elm; + + if(sublist->header && !listbox->header_is_item && !elm) { + // empty row for header + GtkWidget *row = gtk_list_box_row_new(); + cxListAdd(sublist->widgets, row); + g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); + g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); + //intptr_t rowindex = listbox_insert_index + index; + //g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); + gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index); + sublist->numitems = 1; + return; + } + + int first_index = 0; + int header_row = 0; + if(listbox->header_is_item && sublist->header) { + index = -1; + first_index = -1; + header_row = 1; + elm = sublist->header; + } + + while(elm) { + UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; + if(listbox->getvalue) { + listbox->getvalue(list, sublist->userdata, elm, index, &item, listbox->getvaluedata); + } else { + item.label = strdup(elm); + } + + if(item.label == NULL && index == -1 && sublist->header) { + item.label = strdup(sublist->header); + } + + // create listbox item + GtkWidget *row = gtk_list_box_row_new(); +#if GTK_CHECK_VERSION(4, 0, 0) + GtkEventController *motion_controller = gtk_event_controller_motion_new(); + gtk_widget_add_controller(GTK_WIDGET(row), motion_controller); + g_signal_connect(motion_controller, "enter", G_CALLBACK(listbox_row_enter), row); + g_signal_connect(motion_controller, "leave", G_CALLBACK(listbox_row_leave), row); +#else + gtk_widget_set_events(GTK_WIDGET(row), GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); + g_signal_connect(row, "enter-notify-event", G_CALLBACK(listbox_row_enter), NULL); + g_signal_connect(row, "leave-notify-event", G_CALLBACK(listbox_row_leave), NULL); +#endif + + listbox_fill_row(listbox, row, sublist, &item, index); + if(index == first_index) { + // first row in the sublist, set ui_listbox data to the row + // which is then used by the headerfunc + g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); + g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); + + if(listbox_insert_index == 0) { + // first row in the GtkListBox + listbox->first_row = GTK_LIST_BOX_ROW(row); + } + } + //intptr_t rowindex = listbox_insert_index + index; + //g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); + gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index + header_row); + cxListAdd(sublist->widgets, row); + + // cleanup + free(item.label); + free(item.icon); + free(item.button_label); + free(item.button_icon); + free(item.badge); + + // next row + elm = index >= 0 ? list->next(list) : first; + index++; + } + + sublist->numitems = cxListSize(sublist->widgets); +} + +void ui_listbox_list_update(UiList *list, int i) { + UiListBoxSubList *sublist = list->obj; + if(i < 0) { + ui_listbox_update_sublist(sublist->listbox, sublist, sublist->startpos); + size_t pos = 0; + CxIterator it = cxListIterator(sublist->listbox->sublists); + cx_foreach(UiListBoxSubList *, ls, it) { + ls->startpos = pos; + pos += ls->numitems; + } + } else { + update_sublist_item(sublist->listbox, sublist, i); + } + + ui_sourcelist_update_finished(); +} + +void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { + UiEventDataExt *data = g_object_get_data(G_OBJECT(row), "ui-listbox-row-eventdata"); + if(!data) { + return; + } + UiListBoxSubList *sublist = data->customdata0; + + UiSubListEventData eventdata; + eventdata.list = sublist->var->value; + eventdata.sublist_index = sublist->index; + eventdata.row_index = data->value0; + eventdata.sublist_userdata = sublist->userdata; + eventdata.row_data = eventdata.row_index >= 0 ? eventdata.list->get(eventdata.list, eventdata.row_index) : NULL; + eventdata.event_data = data->customdata2; + + UiEvent event; + event.obj = data->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = &eventdata; + event.eventdatatype = UI_EVENT_DATA_SUBLIST; + event.intval = data->value0; + event.set = ui_get_setop(); + + if(data->callback) { + data->callback(&event, data->userdata); + } +} diff --git a/ui/gtk/list.h b/ui/gtk/list.h new file mode 100644 index 0000000..06f7fac --- /dev/null +++ b/ui/gtk/list.h @@ -0,0 +1,203 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TREE_H +#define TREE_H + +#include "../ui/tree.h" +#include "toolkit.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiColData UiColData; + +#if GTK_CHECK_VERSION(4, 10, 0) +typedef struct UiRowItems { + int bound; + GtkListItem *items[]; +} UiRowItems; +#endif + +typedef struct UiListView { + UiObject *obj; + GtkWidget *widget; + UiVar *var; + UiModel *model; + ui_getvaluefunc2 getvalue; + void *getvaluedata; + ui_getstylefunc getstyle; + void *getstyledata; + char **elements; + size_t nelm; + int current_row; + UiTextStyle row_style; + UiBool apply_row_style; +#if GTK_CHECK_VERSION(4, 10, 0) + CxMap *bound_rows; + GListStore *liststore; + GtkSelectionModel *selectionmodel; + UiColData *columns; + int numcolumns; + PangoAttrList *current_row_attributes; +#else + int style_offset; +#endif + ui_callback onactivate; + void *onactivatedata; + ui_callback onselection; + void *onselectiondata; + ui_callback ondragstart; + void *ondragstartdata; + ui_callback ondragcomplete; + void *ondragcompletedata; + ui_callback ondrop; + void *ondropdata; + ui_list_savefunc onsave; + void *onsavedata; + UiListSelection selection; + +} UiListView; + +struct UiColData { + UiListView *listview; + int model_column; + int data_column; +}; + +typedef struct UiTreeEventData { + UiObject *obj; + ui_callback activate; + ui_callback selection; + void *activatedata; + void *selectiondata; +} UiTreeEventData; + +typedef struct UiListBox UiListBox; + +typedef struct UiListBoxSubList { + UiVar *var; + size_t numitems; + char *header; + UiBool separator; + CxList *widgets; + UiListBox *listbox; + void *userdata; + size_t index; + size_t startpos; +} UiListBoxSubList; + +struct UiListBox { + UiObject *obj; + GtkListBox *listbox; + CxList *sublists; // contains UiListBoxSubList elements + ui_sublist_getvalue_func getvalue; + void *getvaluedata; + ui_callback onactivate; + void *onactivatedata; + ui_callback onbuttonclick; + void *onbuttonclickdata; + GtkListBoxRow *first_row; + UiBool header_is_item; +}; + + +#if GTK_CHECK_VERSION(4, 10, 0) + +void ui_update_liststore(GListStore *liststore, UiList *list); +void ui_update_liststore_static(GListStore *liststore, char **elm, size_t nelm); + +void ui_listview_update2(UiList *list, int i); +UiListSelection ui_listview_getselection2(UiList *list); +void ui_listview_setselection2(UiList *list, UiListSelection selection); + +void ui_dropdown_notify(GtkWidget *dropdown, GObject *pspec, gpointer userdata); +void ui_columnview_activate(void *ignore, guint position, gpointer userdata); +void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer user_data); + +void ui_dropdown_activate(GtkDropDown* self, gpointer userdata); + +#endif + +void* ui_strmodel_getvalue(void *elm, int column); + +UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); +UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb); + +GtkWidget* ui_get_tree_widget(UIWIDGET widget); + +void ui_listview_update(UiList *list, int i); +UiListSelection ui_listview_getselection(UiList *list); +void ui_listview_setselection(UiList *list, UiListSelection selection); + +void ui_combobox_destroy(GtkWidget *w, UiListView *v); +void ui_listview_destroy(GtkWidget *w, UiListView *v); + +#if GTK_CHECK_VERSION(4, 10, 0) + +#else +void ui_listview_activate_event( + GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + UiTreeEventData *event); +void ui_listview_selection_event( + GtkTreeSelection *treeselection, + UiTreeEventData *event); +UiListSelection ui_listview_selection( + GtkTreeSelection *selection, + UiTreeEventData *event); +int ui_tree_path_list_index(GtkTreePath *path); +#endif + +void ui_listview_add_dnd(UiListView *listview, UiListArgs *args); +void ui_listview_enable_drop(UiListView *listview, UiListArgs *args); + +UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); +GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, char **elm, size_t nelm, ui_callback f, void *udata); +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e); +void ui_combobox_modelupdate(UiList *list, int i); +UiListSelection ui_combobox_getselection(UiList *list); +void ui_combobox_setselection(UiList *list, UiListSelection selection); + +void ui_listbox_dynamic_update(UiList *list, int i); +void ui_listbox_update(UiListBox *listbox, int from, int to); +void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index); +void ui_listbox_list_update(UiList *list, int i); + +void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data); + +#ifdef __cplusplus +} +#endif + +#endif /* TREE_H */ + diff --git a/ui/gtk/menu.c b/ui/gtk/menu.c new file mode 100644 index 0000000..c399d85 --- /dev/null +++ b/ui/gtk/menu.c @@ -0,0 +1,851 @@ +/* + * 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 "widget.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 +#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); + cxListFree(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 +} + +static void menuitem_list_remove_binding(void *obj) { + UiActiveMenuItemList *ls = obj; + UiList *list = ls->var->value; + CxList *bindings = list->obj; + if(bindings) { + (void)cxListFindRemove(bindings, obj); + if(cxListSize(bindings) == 0) { + cxListFree(bindings); + list->obj = NULL; + list->update = NULL; + } + } +} + +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); + //UiVar* var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST); + ls->var = var; + if(var) { + UiList *list = var->value; + list->update = ui_menulist_update; + list->getselection = NULL; + list->setselection = NULL; + + // It is possible, that the UiVar is from a global shared context, + // used by multiple windows. To support this usecase, the list->obj + // binding object is a list of all connected UiActiveMenuItemList. + CxList *bindings = list->obj; + if(!bindings) { + bindings = cxLinkedListCreate(var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS); + list->obj = bindings; + } + cxListAdd(bindings, ls); + + // The destruction of the toplevel obj must remove the menulist binding + uic_context_add_destructor(obj->ctx, menuitem_list_remove_binding, ls); + + ui_update_menuitem_list(ls); + } + + ls->callback = il->callback; + ls->userdata = il->userdata; +} + +void ui_menulist_update(UiList *list, int ignored) { + CxList *bindings = list->obj; + CxIterator i = cxListIterator(bindings); + cx_foreach(UiActiveMenuItemList *, ls, i) { + ui_update_menuitem_list(ls); + } +} + + +void ui_update_menuitem_list(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++; + } + } + + UiList *ls = list->var->value; + + void* elm = ui_list_first(ls); + 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(ls); + 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 + +GtkWidget *ui_create_menubar(UiObject *obj) { + UiMenu *menus_begin = uic_get_menu_list(); + if(menus_begin == NULL) { + return NULL; + } + + GMenu *menu = g_menu_new(); + UiMenu *ls = menus_begin; + while(ls) { + GMenu *sub_menu = g_menu_new(); + ui_gmenu_add_menu_items(sub_menu, 0, ls, obj); + g_menu_append_submenu(menu, ls->label, G_MENU_MODEL(sub_menu)); + ls = (UiMenu*)ls->item.next; + } + + + // Create a menubar from the menu model + return gtk_popover_menu_bar_new_from_model(G_MENU_MODEL(menu)); +} + +static ui_gmenu_add_f createMenuItem[] = { + /* UI_MENU */ ui_gmenu_add_menu, + /* UI_MENU_ITEM */ ui_gmenu_add_menuitem, + /* UI_MENU_CHECK_ITEM */ ui_gmenu_add_checkitem, + /* UI_MENU_RADIO_ITEM */ ui_gmenu_add_radioitem, + /* UI_MENU_ITEM_LIST */ ui_gmenu_add_menuitem_list, + /* UI_MENU_CHECKITEM_LIST */ ui_gmenu_add_menuitem_list, + /* UI_MENU_RADIOITEM_LIST */ ui_gmenu_add_menuitem_list, + /* UI_MENU_SEPARATOR */ ui_gmenu_add_menuseparator +}; + +void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj) { + UiMenuItemI *it = menu->items_begin; + int index = 0; + int index_section = 0; + GMenu *section = NULL; + while(it) { + if(it->type == UI_MENU_SEPARATOR) { + if(section) { + g_object_unref(section); + } + 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; + } + if(section) { + g_object_unref(section); + } +} + +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)); + g_object_unref(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)); + g_object_unref(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); + cxListFree(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; + event->customint = 0; + + 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 +} + + + +typedef struct UiCallbackData { + ui_callback callback; + void *userdata; +} UiCallbackData; + +static void radiogroup_remove_binding(void *obj) { + UiMenuRadioGroup *group = obj; + if(group->var) { + UiInteger *i = group->var->value; + CxList *bindings = i->obj; + if(bindings) { + (void)cxListFindRemove(bindings, obj); + } + } +} + +static void stateful_action_notify_group(UiMenuRadioGroup *group, UiInteger *i) { + UiEvent event; + event.obj = group->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = (int)i->value; + event.set = ui_get_setop(); + + CxIterator iter = cxListIterator(group->callbacks); + cx_foreach(UiCallbackData *, cb, iter) { + if(cb->callback) { + cb->callback(&event, cb->userdata); + } + } + + UiObserver *obs = i->observers; + while(obs) { + if(obs->callback) { + obs->callback(&event, obs->data); + } + obs = obs->next; + } +} + +static void ui_action_set_state(UiInteger *i, int64_t value) { + i->value = value; + CxList *bindings = i->obj; + CxIterator iter = cxListIterator(bindings); + cx_foreach(UiMenuRadioGroup *, group, iter) { + char buf[32]; + snprintf(buf, 32, "%d", (int)value); + GVariant *state = g_variant_new_string(buf); + g_action_change_state(G_ACTION(group->action), state); + stateful_action_notify_group(group, i); + } +} + +static int64_t ui_action_get_state(UiInteger *i) { + return i->value; +} + +static UiMenuRadioGroup* create_radio_group(UiObject *obj, UiMenuRadioItem *item, GSimpleAction *action) { + UiMenuRadioGroup *group = cxZalloc(obj->ctx->allocator, sizeof(UiMenuRadioGroup)); + group->callbacks = cxArrayListCreate(obj->ctx->allocator, NULL, sizeof(UiCallbackData), 8); + group->var = uic_create_var(ui_global_context(), item->varname, UI_VAR_INTEGER); + group->obj = obj; + group->action = action; + if(group->var) { + UiInteger *i = group->var->value; + CxList *bindings = i->obj; + if(!bindings) { + bindings = cxLinkedListCreate(group->var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS); + i->obj = bindings; + i->set = ui_action_set_state; + i->get = ui_action_get_state; + } + cxListAdd(bindings, group); + // the destruction of the toplevel obj must remove the binding + uic_context_add_destructor(obj->ctx, radiogroup_remove_binding, group); + } + return group; +} + +static void stateful_action_activate( + GSimpleAction *action, + GVariant *state, + UiMenuRadioGroup *group) +{ + g_simple_action_set_state(action, state); + + UiVar *var = group->var; + if(!var) { + return; + } + UiInteger *i = var->value; + + gsize len; + const char *s = g_variant_get_string(state, &len); + cxstring value = cx_strn(s, len); + int v; + if(!cx_strtoi(value, &v, 10)) { + i->value = v; + } + + CxList *bindings = i->obj; + CxIterator iter = cxListIterator(bindings); + cx_foreach(UiMenuRadioGroup *, gr, iter) { + stateful_action_notify_group(gr, i); + } +} + + +void ui_gmenu_add_radioitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { + UiMenuRadioItem *i = (UiMenuRadioItem*)item; + + if(!i->varname) { + return; + } + + // All radio buttons with the name varname use the same GAction + UiMenuRadioGroup *group = NULL; + GAction *action = g_action_map_lookup_action(obj->ctx->action_map, i->varname); + if(!action) { + GVariant *state = g_variant_new_string("0"); + GSimpleAction *newAction = g_simple_action_new_stateful(i->varname, G_VARIANT_TYPE_STRING, state); + g_action_map_add_action(obj->ctx->action_map, G_ACTION(newAction)); + g_object_unref(newAction); + + group = create_radio_group(obj, i, newAction); + g_object_set_data(G_OBJECT(newAction), "ui_radiogroup", group); + + g_signal_connect( + newAction, + "change-state", + G_CALLBACK(stateful_action_activate), + group); + } else { + group = g_object_get_data(G_OBJECT(action), "ui_radiogroup"); + if(!group) { + fprintf(stderr, "Error: missing ui_radiogroup property for action %s\n", i->varname); + return; // error, should not happen + } + } + + size_t item_index = cxListSize(group->callbacks); + + UiCallbackData cb; + cb.callback = i->callback; + cb.userdata = i->userdata; + cxListAdd(group->callbacks, &cb); + + + cxmutstr action_name = cx_asprintf("win.%s::%d", i->varname, (int)item_index); + g_menu_append(parent, i->label, action_name.ptr); + free(action_name.ptr); +} + +static void menuitem_list_remove_binding(void *obj) { + UiActiveGMenuItemList *ls = obj; + UiList *list = ls->var->value; + CxList *bindings = list->obj; + if(bindings) { + (void)cxListFindRemove(bindings, obj); + if(cxListSize(bindings) == 0) { + cxListFree(bindings); + list->obj = NULL; + list->update = NULL; + } + } +} + +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; + + 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)); + g_object_unref(action); + snprintf(ls->action, 32, "win.%s", item->id); + + UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); + //UiVar* var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST); + ls->var = var; + if(var) { + UiList *list = var->value; + list->update = ui_menulist_update; + list->getselection = NULL; + list->setselection = NULL; + + // It is possible, that the UiVar is from a global shared context, + // used by multiple windows. To support this usecase, the list->obj + // binding object is a list of all connected UiActiveMenuItemList. + CxList *bindings = list->obj; + if(!bindings) { + bindings = cxLinkedListCreate(var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS); + list->obj = bindings; + } + cxListAdd(bindings, ls); + + // The destruction of the toplevel obj must remove the menulist binding + uic_context_add_destructor(obj->ctx, menuitem_list_remove_binding, ls); + + ui_update_gmenu_item_list(ls); + } + + ls->callback = il->callback; + ls->userdata = il->userdata; + + + UiEventData *event = malloc(sizeof(UiEventData)); + event->obj = obj; + event->userdata = il->userdata; + event->callback = il->callback; + event->customdata = var; + event->customint = 0; + 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); +} + +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.eventdatatype = event->customint; + 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; + + if(!event->callback) { + return; + } + + 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.eventdatatype = UI_EVENT_DATA_LIST_ELM; + evt.intval = index; + event->callback(&evt, event->userdata); + +} + +void ui_menulist_update(UiList *list, int ignored) { + CxList *bindings = list->obj; + CxIterator i = cxListIterator(bindings); + cx_foreach(UiActiveGMenuItemList *, ls, i) { + ui_update_gmenu_item_list(ls); + } +} + +void ui_update_gmenu_item_list(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); + g_object_unref(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)); + g_object_unref(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..a7f65e7 --- /dev/null +++ b/ui/gtk/menu.h @@ -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. + */ + +#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); + +GtkWidget *ui_create_menubar(UiObject *obj); + +#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; + UiVar *var; + ui_getvaluefunc getvalue; + ui_callback callback; + void *userdata; +}; + +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_menulist_update(UiList *list, int ignored); +void ui_update_menuitem_list(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; +}; + +typedef struct UiMenuRadioGroup { + UiObject *obj; + CxList *callbacks; + UiVar *var; + GSimpleAction *action; +} UiMenuRadioGroup; + + +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_menulist_update(UiList *list, int ignored); +void ui_update_gmenu_item_list(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..abad5a0 --- /dev/null +++ b/ui/gtk/objs.mk @@ -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. +# + +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 +GTKOBJ += webview.o +GTKOBJ += widget.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..e33b24a --- /dev/null +++ b/ui/gtk/range.c @@ -0,0 +1,135 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#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; + event->customint = 0; + + g_signal_connect( + G_OBJECT(scrollbar), + "value-changed", + G_CALLBACK(ui_scrollbar_value_changed), + event); + g_signal_connect( + scrollbar, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + } + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + //UiLayout layout = UI_ARGS2LAYOUT(args); + UiLayout layout = {0}; + ct->add(ct, scrollbar, &layout); + + 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.eventdatatype = 0; + 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_CHECK_VERSION(3, 18, 0) + 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..0859642 --- /dev/null +++ b/ui/gtk/text.c @@ -0,0 +1,1217 @@ +/* + * 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; + } +} + +static void textarea_set_text_funcs(UiText *value) { + +} + +#if GTK_MAJOR_VERSION == 2 +static void textarea_set_undomgr(GtkWidget *text_area, UiText *value) { + GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area)); + + if(!value->data2) { + value->data2 = ui_create_undomgr(); + } + + // 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); +} +#endif + +static GtkTextBuffer* create_textbuffer(UiTextArea *textarea) { + GtkTextBuffer *buf = gtk_text_buffer_new(NULL); + if(textarea) { + g_signal_connect( + buf, + "changed", + G_CALLBACK(ui_textbuf_changed), + textarea); + } else { + fprintf(stderr, "Error: create_textbuffer: textarea == NULL\n"); + } + return buf; +} + +UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs *args) { + UiVar* var = uic_widget_var(obj->ctx, obj->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_object_set_data(G_OBJECT(text_area), "ui_textarea", uitext); + + 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); + + if(args->width > 0 || args->height > 0) { + int width = args->width; + int height = args->height; + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(scroll_area, width, height); + } + + // 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 + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, scroll_area, &layout); + + // bind value + if(var) { + UiText *value = var->value; + GtkTextBuffer *buf; + if(value->data1 && value->datatype == UI_TEXT_TYPE_BUFFER) { + buf = value->data1; + } else { + buf = create_textbuffer(uitext); + if(value->value.ptr) { + gtk_text_buffer_set_text(buf, value->value.ptr, -1); + value->value.free(value->value.ptr); + } + } + gtk_text_view_set_buffer(GTK_TEXT_VIEW(text_area), buf); + value->obj = text_area; + value->save = ui_textarea_save; + value->restore = ui_textarea_restore; + value->destroy = ui_textarea_text_destroy; + 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->data1 = buf; + value->data2 = NULL; + value->datatype == UI_TEXT_TYPE_BUFFER; + value->value.ptr = NULL; + value->value.free = NULL; + +#if GTK_MAJOR_VERSION == 2 + textarea_set_undomgr(text_area, value); +#endif + } + + 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); +} + +void ui_textarea_save(UiText *text) { + // NOOP +} + +void ui_textarea_restore(UiText *text) { + GtkWidget *textarea = text->obj; + if(!text->data1) { + text->data1 = create_textbuffer(g_object_get_data(G_OBJECT(textarea), "ui_textarea")); + text->datatype = UI_TEXT_TYPE_BUFFER; + } + gtk_text_view_set_buffer(GTK_TEXT_VIEW(textarea), text->data1); +} + +void ui_textarea_text_destroy(UiText *text) { + GtkTextBuffer *buf = text->data1; + g_object_unref(buf); +} + +char* ui_textarea_get(UiText *text) { + if(text->value.ptr) { + text->value.free(text->value.ptr); + } + GtkTextBuffer *buf = text->data1; + 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->data1, 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->data1; + GtkTextIter ib; + GtkTextIter ie; + gtk_text_buffer_get_iter_at_offset(text->data1, &ib, begin); + gtk_text_buffer_get_iter_at_offset(text->data1, &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->data1, &offset, pos); + gtk_text_buffer_insert(text->data1, &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->data1, &iter, pos); + gtk_text_buffer_place_cursor(text->data1, &iter); +} + +int ui_textarea_position(UiText *text) { + GtkTextIter begin; + GtkTextIter end; + gtk_text_buffer_get_selection_bounds(text->data1, &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->data1, &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->data1; + 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->data1; + 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.eventdatatype = UI_EVENT_DATA_TEXT_VALUE; + e.intval = 0; + e.set = ui_get_setop(); + + 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->data2) { + value->data2 = ui_create_undomgr(); + } + UiUndoMgr *mgr = value->data2; + 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->data2) { + value->data2 = ui_create_undomgr(); + } + UiUndoMgr *mgr = value->data2; + 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->data2; + + 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->data2; + + 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); + + UiVar* var = uic_widget_var(obj->ctx, obj->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; + uitext->onactivate = args->onactivate; + uitext->onactivatedata = args->onactivatedata; + + g_signal_connect( + textfield, + "destroy", + G_CALLBACK(ui_textfield_destroy), + uitext); + + if(args->width > 0) { + // An early implementation used gtk_entry_set_width_chars, + // but that is not available on gtk4 and other toolkits + // also don't have this. Setting the width in pixels can + // be implemented on all platforms + gtk_widget_set_size_request(textfield, args->width, -1); + } + if(frameless) { + // TODO: gtk2legacy workaroud + gtk_entry_set_has_frame(GTK_ENTRY(textfield), FALSE); + } + if(password) { + gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE); + } + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, textfield, &layout); + + 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); + } + + if(args->onactivate) { + g_signal_connect( + textfield, + "activate", + G_CALLBACK(ui_textfield_activate), + 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.eventdatatype = UI_EVENT_DATA_TEXT_VALUE; + e.intval = 0; + e.set = ui_get_setop(); + + if(textfield->onchange) { + textfield->onchange(&e, textfield->onchangedata); + } + + if(textfield->var) { + ui_notify_evt(value->observers, &e); + } +} + +void ui_textfield_activate(GtkEntry* self, UiTextField *textfield) { + if(textfield->onactivate) { + UiEvent e; + e.obj = textfield->obj; + e.window = e.obj->window; + e.document = textfield->obj->ctx->document; + e.eventdata = NULL; + e.eventdatatype = 0; + e.intval = 0; + e.set = ui_get_setop(); + textfield->onactivate(&e, textfield->onactivatedata); + } +} + +char* ui_textfield_get(UiString *str) { + if(str->value.ptr) { + str->value.free(str->value.ptr); + } + str->value.ptr = g_strdup(ENTRY_GET_TEXT(str->obj)); + str->value.free = (ui_freefunc)g_free; + return str->value.ptr; +} + +void ui_textfield_set(UiString *str, const char *value) { + ENTRY_SET_TEXT(str->obj, value); + if(str->value.ptr) { + str->value.free(str->value.ptr); + str->value.ptr = NULL; + str->value.free = NULL; + } +} + +// ----------------------- path textfield ----------------------- + +// TODO: move to common +static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) { + cxstring *pathelms; + size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms); + + if (nelm == 0) { + *ret_nelm = 0; + return NULL; + } + + UiPathElm* elms = (UiPathElm*)calloc(nelm, sizeof(UiPathElm)); + size_t n = nelm; + int j = 0; + for (int i = 0; i < nelm; i++) { + cxstring c = pathelms[i]; + if (c.length == 0) { + if (i == 0) { + c.length = 1; + } + else { + n--; + continue; + } + } + + cxmutstr m = cx_strdup(c); + elms[j].name = m.ptr; + elms[j].name_len = m.length; + + size_t elm_path_len = c.ptr + c.length - full_path; + cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len)); + elms[j].path = elm_path.ptr; + elms[j].path_len = elm_path.length; + + j++; + } + *ret_nelm = n; + + return elms; +} + +static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { + for(int i=0;ientry); + free(pathtf); +} + +void ui_path_button_clicked(GtkWidget *widget, UiEventDataExt *event) { + UiPathTextField *pathtf = event->customdata1; + for(int i=0;ivalue1;i++) { + if(i <= event->value0) { + WIDGET_REMOVE_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive"); + } else { + WIDGET_ADD_CSS_CLASS(pathtf->current_path_buttons[i], "pathbar-button-inactive"); + } + } + + UiPathElm *elm = event->customdata0; + cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len)); + UiEvent evt; + evt.obj = event->obj; + evt.window = evt.obj->window; + evt.document = evt.obj->ctx->document; + evt.eventdata = elm->path; + evt.eventdatatype = UI_EVENT_DATA_STRING; + evt.intval = event->value0; + evt.set = ui_get_setop(); + 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.eventdatatype = UI_EVENT_DATA_STRING; + 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) { + 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"); + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, pathtf->stack, &layout); + + 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, obj->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) { + 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); + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, eventbox, &layout); + + // 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, obj->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..b7fbda4 --- /dev/null +++ b/ui/gtk/text.h @@ -0,0 +1,163 @@ +/* + * 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; + ui_callback onactivate; + void *onactivatedata; +} UiTextField; + +typedef struct UiPathTextField { + UiObject *obj; + + GtkWidget *stack; + GtkWidget *hbox; + GtkWidget *entry_box; + GtkWidget *entry; +#if GTK_MAJOR_VERSION == 3 + GtkWidget *buttonbox; +#endif + + char *current_path; + UiPathElm *current_pathelms; + GtkWidget **current_path_buttons; + size_t current_nelm; + + ui_pathelm_func getpathelm; + void* getpathelmdata; + + ui_callback onactivate; + void* onactivatedata; + + ui_callback ondragstart; + void* ondragstartdata; + ui_callback ondragcomplete; + void* ondragcompletedata; + ui_callback ondrop; + void* ondropdata; +} UiPathTextField; + +UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var); +void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea); + +void ui_textarea_save(UiText *text); +void ui_textarea_restore(UiText *text); +void ui_textarea_text_destroy(UiText *text); +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); +void ui_textfield_activate(GtkEntry* self, 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..64b221a --- /dev/null +++ b/ui/gtk/toolbar.c @@ -0,0 +1,413 @@ +/* + * 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; + button = gtk_tool_button_new(NULL, item->args.label); + if(item->args.tooltip) { + gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), item->args.tooltip); + } + + 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->customint = 0; + 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; + 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); + } + if(item->args.tooltip) { + gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), item->args.tooltip); + } + 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.eventdatatype = 0; + 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; + 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); + } + if(item->args.tooltip) { + gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), item->args.tooltip); + } + 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..87d1ef1 --- /dev/null +++ b/ui/gtk/toolkit.c @@ -0,0 +1,561 @@ +/* + * 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 + +#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; + +static UiBool exit_on_shutdown; + +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_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; +} + +void ui_app_exit_on_shutdown(UiBool exitapp) { + exit_on_shutdown = exitapp; +} + +#ifdef UI_APPLICATION +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"); +} + +static void app_shutdown(GtkApplication *app, gpointer userdata) { + if(exit_func) { + exit_func(NULL, exit_data); + } + ui_app_save_settings(); +} + +#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, "shutdown", G_CALLBACK (app_shutdown), 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(); + if(exit_func) { + exit_func(NULL, exit_data); + } + ui_app_save_settings(); +#endif + if(exit_on_shutdown) { + exit(0); + } +} + +#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; + event.eventdatatype = 0; + + 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) { +#if GTK_MAJOR_VERSION >= 4 + gtk_widget_set_visible(widget, visible); +#else + 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_widget_var(GtkWidget *object, UiVar *var) { + ui_destroy_boundvar(NULL, var); +} + +// TODO: move to common +void ui_destroy_boundvar(UiContext *ctx, UiVar *var) { + uic_save_var(var); + uic_unbind_var(var); + + // UI_VAR_SPECIAL: anonymous value variable, that is not registered + // in ctx->vars + if(var->type == UI_VAR_SPECIAL) { + ui_free(var->from_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 + +#if GTK_MAJOR_VERSION == 4 +static const char *ui_gtk_css = +"#path-textfield-box {\n" +" background-color: alpha(currentColor, 0.1);" +" border-radius: 6px;" +" padding: 0px;" +"}\n" +".pathbar-extra-button {\n" +" border-top-right-radius: 6px;" +" border-bottom-right-radius: 6px;" +" border-top-left-radius: 0px;" +" border-bottom-left-radius: 0px;" +"}\n" +"#pathbar button {\n" +" margin: 3px;" +" border-radius: 4px;" +" padding-top: 0px;" +" padding-bottom: 0px;" +" padding-left: 8px;" +" padding-right: 8px;" +"}\n" +"#path-textfield-box entry {\n" +" background-color: #00000000;" +" border-top-left-radius: 6px;" +" border-bottom-left-radius: 6px;" +" border-top-right-radius: 0px;" +" border-bottom-right-radius: 0px;" +"}\n" +".pathbar-button-inactive {\n" +" color: alpha(currentColor, 0.5);" +"}\n" +".ui_test {\n" +" background-color: red;\n" +"}\n" +".ui_label_title {\n" +" font-weight: bold;\n" +"}\n" +".ui-listbox-header {\n" +" font-weight: bold;\n" +" margin-left: 10px;\n" +" margin-top: 12px;\n" +" margin-bottom: 10px;\n" +"}\n" +".ui-listbox-header-first {\n" +" font-weight: bold;\n" +" margin-left: 10px;\n" +" margin-top: 4px;\n" +" margin-bottom: 10px;\n" +"}\n" +".ui-listbox-header-row {\n" +" font-weight: bold;\n" +"}\n" +".ui-badge {\n" +" background-color: #e53935;\n" +" color: white;\n" +" border-radius: 9999px;\n" +" padding: 0px 10px 0px 10px;\n" +" font-weight: bold;\n" +" margin-left: 4px;" +" margin-right: 4px;" +"}\n" +".ui-nopadding {" +" padding: 0;" +"}\n" +".ui-table-entry {" +" border: none;" +" box-shadow: none;" +" background: transparent;" +"}\n" +; + +#elif GTK_MAJOR_VERSION == 3 +static const char *ui_gtk_css = +"#path-textfield-box {\n" +" background-color: @theme_base_color;\n" +" border-radius: 5px;\n" +" padding: 0px;\n" +"}\n" +".pathbar-button-inactive {\n" +" color: alpha(currentColor, 0.5);" +"}\n" +".ui_test {\n" +" background-color: red;\n" +"}\n" +".ui_label_title {\n" +" font-weight: bold;\n" +"}\n" +"placessidebar row {\n" +" padding-left: 10px;\n" +"}\n" +".ui-listbox-header {\n" +" font-weight: bold;\n" +" margin-left: 10px;\n" +" margin-top: 12px;\n" +" margin-bottom: 10px;\n" +"}\n" +".ui-listbox-header-first {\n" +" font-weight: bold;\n" +" margin-left: 10px;\n" +" margin-top: 4px;\n" +" margin-bottom: 10px;\n" +"}\n" +".ui-listbox-header-row {\n" +" font-weight: bold;\n" +"}\n" +".ui-badge {\n" +" background-color: #e53935;\n" +" color: white;\n" +" border-radius: 9999px;\n" +" padding: 0px 10px 0px 10px;\n" +" font-weight: bold;\n" +" margin-left: 4px;" +" margin-right: 4px;" +"}\n" +".ui-nopadding {" +" padding: 0;" +"}\n" +; +#endif + +void ui_css_init(void) { + ui_add_styledata(ui_gtk_css, -1); +} + +void ui_add_styledata(const char *styledata, int len) { + GtkCssProvider *css = gtk_css_provider_new(); + +#ifdef UI_GTK3 + gtk_css_provider_load_from_data(css, styledata, len, NULL); + + GdkScreen *screen = gdk_screen_get_default(); + gtk_style_context_add_provider_for_screen( + screen, + GTK_STYLE_PROVIDER(css), + GTK_STYLE_PROVIDER_PRIORITY_USER); +#endif /* UI_GTK3 */ + +#ifdef UI_GTK4 + + +#if GTK_MINOR_VERSION < 12 + gtk_css_provider_load_from_data(css, styledata, len); +#else + if(len < 0) { + gtk_css_provider_load_from_string(css, ui_gtk_css); + } else { + GBytes *style_data = g_bytes_new(styledata, len); + gtk_css_provider_load_from_bytes(css, style_data); + g_bytes_unref(style_data); + + } +#endif /* GTK_MINOR_VERSION < 12 */ + + GdkDisplay *display = gdk_display_get_default(); + gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(css), 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); + } +} + +void ui_set_widget_visibility_states(UiContext *ctx, GtkWidget *widget, const int *states) { + if(!states) { + return; + } + size_t nstates = uic_group_array_size(states); + ui_set_widget_nvisibility_states(ctx, widget, states, nstates); +} + + +void ui_set_widget_nvisibility_states(UiContext *ctx, GtkWidget *widget, const int *states, size_t ngroups) { + if(ngroups > 0) { + uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_visible, states, ngroups); + ui_set_visible(widget, FALSE); + } +} diff --git a/ui/gtk/toolkit.h b/ui/gtk/toolkit.h new file mode 100644 index 0000000..cfa25bb --- /dev/null +++ b/ui/gtk/toolkit.h @@ -0,0 +1,203 @@ +/* + * 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 > 2074 +#define UI_G_APPLICATION_FLAGS G_APPLICATION_DEFAULT_FLAGS +#else +#define UI_G_APPLICATION_FLAGS G_APPLICATION_FLAGS_NONE +#endif + +#ifdef UI_LIBADWAITA +#define UI_APPLICATION AdwApplication* +#define UI_APPLICATION_NEW(id) adw_application_new(id, UI_G_APPLICATION_FLAGS) +#elif GTK_MAJOR_VERSION >= 3 +#define UI_APPLICATION GtkApplication* +#define UI_APPLICATION_NEW(id) gtk_application_new(id, UI_G_APPLICATION_FLAGS) +#endif + +#if GTK_MAJOR_VERSION >= 4 +#define WINDOW_SHOW(window) gtk_window_present(GTK_WINDOW(window)) +#define WINDOW_DESTROY(window) gtk_window_destroy(GTK_WINDOW(window)) +#define WINDOW_SET_CONTENT(window, child) gtk_window_set_child(GTK_WINDOW(window), child) +#define BOX_ADD(box, child) gtk_box_append(GTK_BOX(box), child) +#define BOX_ADD_EXPAND(box, child) gtk_widget_set_hexpand(child, TRUE); gtk_widget_set_vexpand(child, TRUE); gtk_box_append(GTK_BOX(box), child) +#define BOX_ADD_NO_EXPAND(box, child) gtk_box_append(GTK_BOX(box), child) +#define BOX_REMOVE(box, child) gtk_box_remove(GTK_BOX(box), child) +#define ENTRY_SET_TEXT(entry, text) gtk_editable_set_text(GTK_EDITABLE(entry), text) +#define ENTRY_GET_TEXT(entry) gtk_editable_get_text(GTK_EDITABLE(entry)) +#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new() +#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw), child) +#define SCROLLEDWINDOW_GET_CHILD(sw) gtk_scrolled_window_get_child(GTK_SCROLLED_WINDOW(sw)) +#define FRAME_SET_CHILD(frame, child) gtk_frame_set_child(GTK_FRAME(frame), child) +#define EXPANDER_SET_CHILD(expander, child) gtk_expander_set_child(GTK_EXPANDER(expander), child) +#define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_widget_add_css_class(w, cssclass) +#define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_widget_remove_css_class(w, cssclass) +#define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon) +#define LISTBOX_REMOVE(listbox, row) gtk_list_box_remove(GTK_LIST_BOX(listbox), row) +#define LISTBOX_ROW_SET_CHILD(row, child) gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), child) +#define LISTBOX_ROW_REMOVE_CHILD(row) gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), NULL) +#define PANED_SET_CHILD1(paned, child) gtk_paned_set_start_child(GTK_PANED(paned), child) +#define PANED_SET_CHILD2(paned, child) gtk_paned_set_end_child(GTK_PANED(paned), child) +#else +#define WINDOW_SHOW(window) gtk_widget_show_all(window) +#define WINDOW_DESTROY(window) gtk_widget_destroy(window) +#define WINDOW_SET_CONTENT(window, child) gtk_container_add(GTK_CONTAINER(window), child) +#define BOX_ADD(box, child) gtk_container_add(GTK_CONTAINER(box), child) +#define BOX_ADD_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, TRUE, 0) +#define BOX_ADD_NO_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, FALSE, 0) +#define BOX_REMOVE(box, child) gtk_container_remove(GTK_CONTAINER(box), child) +#define ENTRY_SET_TEXT(entry, text) gtk_entry_set_text(GTK_ENTRY(entry), text) +#define ENTRY_GET_TEXT(entry) gtk_entry_get_text(GTK_ENTRY(entry)) +#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new(NULL, NULL) +#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_container_add(GTK_CONTAINER(sw), child) +#define SCROLLEDWINDOW_GET_CHILD(sw) gtk_bin_get_child(GTK_BIN(sw)) +#define FRAME_SET_CHILD(frame, child) gtk_container_add(GTK_CONTAINER(frame), child) +#define EXPANDER_SET_CHILD(expander, child) gtk_container_add(GTK_CONTAINER(expander), child) +#define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_style_context_add_class(gtk_widget_get_style_context(w), cssclass) +#define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_style_context_remove_class(gtk_widget_get_style_context(w), cssclass) +#define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON) +#define LISTBOX_REMOVE(listbox, row) gtk_container_remove(GTK_CONTAINER(listbox), row) +#define LISTBOX_ROW_SET_CHILD(row, child) gtk_container_add(GTK_CONTAINER(row), child) +#define LISTBOX_ROW_REMOVE_CHILD(row) gtk_container_remove(GTK_CONTAINER(row), gtk_bin_get_child(GTK_BIN(row))) +#define PANED_SET_CHILD1(paned, child) gtk_paned_pack1(GTK_PANED(paned), child, TRUE, TRUE) +#define PANED_SET_CHILD2(paned, child) gtk_paned_pack2(GTK_PANED(paned), child, TRUE, TRUE) +#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; + int customint; + 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; + +typedef enum UiOrientation UiOrientation; +enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL }; + +#ifndef UI_GTK4 +struct UiSelection { + GtkSelectionData *data; +}; +#endif + +#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_set_widget_visibility_states(UiContext *ctx, GtkWidget *widget, const int *states); +void ui_set_widget_nvisibility_states(UiContext *ctx, GtkWidget *widget, const int *states, size_t ngroups); + +void ui_destroy_userdata(GtkWidget *object, void *userdata); +void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data); +void ui_destroy_widget_var(GtkWidget *object, UiVar *var); +void ui_destroy_boundvar(UiContext *ctx, UiVar *var); + +void ui_set_active_window(UiObject *obj); +UiObject *ui_get_active_window(); + +#if GTK_MAJOR_VERSION >= 3 +void ui_css_init(void); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* TOOLKIT_H */ + diff --git a/ui/gtk/webview.c b/ui/gtk/webview.c new file mode 100644 index 0000000..f8f5718 --- /dev/null +++ b/ui/gtk/webview.c @@ -0,0 +1,185 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "toolkit.h" +#include "container.h" + +#include "webview.h" + +#ifdef UI_WEBVIEW + +UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args) { + GtkWidget *webview = webkit_web_view_new(); + + ui_set_name_and_style(webview, args->name, args->style_class); + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_GENERIC); + if(var) { + WebViewData *data = malloc(sizeof(WebViewData)); + memset(data, 0, sizeof(WebViewData)); + data->webview = WEBKIT_WEB_VIEW(webview); + WebKitSettings *settings = webkit_web_view_get_settings(data->webview); + data->javascript = webkit_settings_get_enable_javascript(settings); + data->zoom = webkit_web_view_get_zoom_level(data->webview); + + UiGeneric *value = var->value; + value->get = ui_webview_get; + value->get_type = ui_webview_get_type; + value->set = ui_webview_set; + value->obj = data; + if(value->value && value->type && !strcmp(value->type, UI_WEBVIEW_OBJECT_TYPE)) { + ui_webview_set(value, value->value, UI_WEBVIEW_OBJECT_TYPE); + } + } + + ui_set_widget_groups(obj->ctx, webview, args->groups); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, webview, &layout); + + return webview; +} + +void* ui_webview_get(UiGeneric *g) { + return g->value; +} + +const char* ui_webview_get_type(UiGeneric *g) { + return UI_WEBVIEW_OBJECT_TYPE; +} + +int ui_webview_set(UiGeneric *g, void *value, const char *type) { + if(!type || strcmp(type, UI_WEBVIEW_OBJECT_TYPE)) { + return 1; + } + + WebViewData *obj = g->obj; + if(!obj->webview) { + return 1; + } + + WebViewData *data = value; + if(data->type == WEBVIEW_CONTENT_URL) { + webkit_web_view_load_uri(obj->webview, data->uri); + } else { + if(!data->content) { + return 1; + } + + GBytes *bytes = g_bytes_new(data->content, data->contentlength); + webkit_web_view_load_bytes(obj->webview, bytes, data->mimetype, data->encoding, data->uri); + } + + ui_webview_enable_javascript(g, data->javascript); + ui_webview_set_zoom(g, data->zoom); + + return 0; +} + +void ui_webview_load_url(UiGeneric *g, const char *url) { + WebViewData data = { .uri = (char*)url, .type = WEBVIEW_CONTENT_URL, .javascript = TRUE, .zoom = 1 }; + g->set(g, &data, UI_WEBVIEW_OBJECT_TYPE); +} + +void ui_webview_load_content( + UiGeneric *g, + const char *uri, + const char *content, + size_t contentlength, + const char *mimetype, + const char *encoding) +{ + WebViewData *data0 = g->obj; + if(!data0) { + return; + } + + WebViewData data; + memset(&data, 0, sizeof(WebViewData)); + data.webview = data0->webview; + data.uri = (char*)uri; + data.content = (char*)content; + data.contentlength = contentlength; + data.mimetype = (char*)mimetype; + data.encoding = (char*)encoding; + data.type = WEBVIEW_CONTENT_CONTENT; + data.javascript = FALSE; + data.zoom = 1; + g->set(g, &data, UI_WEBVIEW_OBJECT_TYPE); +} + +void ui_webview_reload(UiGeneric *g) { + WebViewData *webview = g->obj; + webkit_web_view_reload(webview->webview); +} + +UiBool ui_webview_can_go_back(UiGeneric *g) { + WebViewData *webview = g->obj; + return webkit_web_view_can_go_back(webview->webview); +} + +UiBool ui_webview_can_go_forward(UiGeneric *g) { + WebViewData *webview = g->obj; + return webkit_web_view_can_go_forward(webview->webview); +} + +void ui_webview_go_back(UiGeneric *g) { + WebViewData *webview = g->obj; + webkit_web_view_go_back(webview->webview); +} + +void ui_webview_go_forward(UiGeneric *g) { + WebViewData *webview = g->obj; + webkit_web_view_go_forward(webview->webview); +} + +const char* ui_webview_get_uri(UiGeneric *g) { + WebViewData *webview = g->obj; + return webkit_web_view_get_uri(webview->webview); +} + +void ui_webview_enable_javascript(UiGeneric *g, UiBool enable) { + WebViewData *webview = g->obj; + WebKitSettings *settings = webkit_web_view_get_settings(webview->webview); + webkit_settings_set_enable_javascript(settings, enable); +} + +void ui_webview_set_zoom(UiGeneric *g, double zoom) { + WebViewData *webview = g->obj; + webkit_web_view_set_zoom_level(webview->webview, zoom); + webview->zoom = zoom; +} + +double ui_webview_get_zoom(UiGeneric *g) { + WebViewData *webview = g->obj; + webview->zoom = webkit_web_view_get_zoom_level(webview->webview); + return webview->zoom; +} + + +#endif diff --git a/ui/gtk/webview.h b/ui/gtk/webview.h new file mode 100644 index 0000000..e350858 --- /dev/null +++ b/ui/gtk/webview.h @@ -0,0 +1,75 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef WEBVIEW_H +#define WEBVIEW_H + +#ifdef UI_WEBVIEW + +#include "../ui/webview.h" + +#if GTK_MAJOR_VERSION >= 4 +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum WebViewDataType { + WEBVIEW_CONTENT_URL, + WEBVIEW_CONTENT_CONTENT +}; + +typedef struct WebViewData { + WebKitWebView *webview; + char *uri; + char *mimetype; + char *encoding; + char *content; + size_t contentlength; + enum WebViewDataType type; + + double zoom; + UiBool javascript; +} WebViewData; + +void* ui_webview_get(UiGeneric *g); +const char* ui_webview_get_type(UiGeneric *g); +int ui_webview_set(UiGeneric *g, void *value, const char *type); + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_WEBVIEW */ + +#endif /* WEBVIEW_H */ + diff --git a/ui/gtk/widget.c b/ui/gtk/widget.c new file mode 100644 index 0000000..06ef1b2 --- /dev/null +++ b/ui/gtk/widget.c @@ -0,0 +1,59 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "widget.h" +#include "container.h" + +#include "../common/object.h" + +UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) { + UIWIDGET widget = create_widget(obj, args, userdata); + + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); + + return widget; +} + +UIWIDGET ui_separator_create(UiObject *obj, UiWidgetArgs *args) { + GtkWidget *widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + ui_set_name_and_style(widget, args->name, args->style_class); + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; + UiLayout layout = UI_ARGS2LAYOUT(args); + ct->add(ct, widget, &layout); + return widget; +} + +void ui_widget_set_size(UIWIDGET w, int width, int height) { + gtk_widget_set_size_request(w, width, height); +} + +void ui_widget_redraw(UIWIDGET w) { + gtk_widget_queue_draw(w); +} diff --git a/ui/gtk/widget.h b/ui/gtk/widget.h new file mode 100644 index 0000000..57c7314 --- /dev/null +++ b/ui/gtk/widget.h @@ -0,0 +1,46 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WIDGET_H +#define WIDGET_H + +#include "../ui/widget.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + + +#ifdef __cplusplus +} +#endif + +#endif /* WIDGET_H */ + diff --git a/ui/gtk/window.c b/ui/gtk/window.c new file mode 100644 index 0000000..f90997f --- /dev/null +++ b/ui/gtk/window.c @@ -0,0 +1,1053 @@ +/* + * 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 int splitview_window_default_pos = -1; +static UiBool splitview_window_use_prop = TRUE; + +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) { + if(obj->widget) { + void *appwindow = g_object_get_data(G_OBJECT(obj->widget), "ui.appwindow"); + if(appwindow) { + int width = 0; + int height = 0; +#if GTK_CHECK_VERSION(4, 10, 0) + graphene_rect_t bounds; + if(gtk_widget_compute_bounds(obj->widget, obj->widget, &bounds)) { + width = bounds.size.width; + height = bounds.size.height; + } +#elif GTK_CHECK_VERSION(4, 0, 0) + GtkAllocation alloc; + gtk_widget_get_allocation(GTK_WIDGET(obj->widget), &alloc); + width = alloc.width; + height = alloc.height; +#else + gtk_window_get_size(GTK_WINDOW(obj->widget), &width, &height); +#endif + if(width > 0 && height > 0) { + char width_str[32]; + char height_str[32]; + snprintf(width_str, 32, "%d", width); + snprintf(height_str, 32, "%d", height); + ui_set_property("ui.window.width", width_str); + ui_set_property("ui.window.height", height_str); + } + } + } + + 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 void save_window_splitview_pos(GtkWidget *widget, void *unused) { + int pos = gtk_paned_get_position(GTK_PANED(widget)); + char buf[32]; + snprintf(buf, 32, "%d", pos); + ui_set_property("ui.window.splitview.pos", buf); +} + +static UiObject* create_window(const char *title, void *window_data, UiBool sidebar, UiBool splitview, UiBool simple) { + UiObject *obj = uic_object_new_toplevel(); + +#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->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); + } + + if(!simple) { + g_object_set_data(G_OBJECT(obj->widget), "ui.appwindow", obj); + } + + int window_width = window_default_width; + int window_height = window_default_height; + if(!simple) { + const char *width = ui_get_property("ui.window.width"); + const char *height = ui_get_property("ui.window.height"); + if(width && height) { + int w = atoi(width); + int h = atoi(height); + if(w > 0 && h > 0) { + window_width = w; + window_height = h; + } + } + } + gtk_window_set_default_size( + GTK_WINDOW(obj->widget), + window_width, + window_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_toolbar_view_set_content(ADW_TOOLBAR_VIEW(toolbar_view), vbox); + + GtkWidget *headerbar_sidebar = NULL; + GtkWidget *headerbar_main = adw_header_bar_new(); + GtkWidget *headerbar_right = NULL; + + GtkWidget *content = toolbar_view; + if(splitview) { + content = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + g_signal_connect( + content, + "destroy", + G_CALLBACK(save_window_splitview_pos), + NULL); + + const char *splitview_pos_str = ui_get_property("ui.window.splitview.pos"); + int pos = splitview_window_default_pos; + if(pos < 0) { + pos = window_width / 2; + } + if(splitview_pos_str && splitview_window_use_prop) { + int splitview_pos = atoi(splitview_pos_str); + if(splitview_pos > 0) { + pos = splitview_pos; + } + } + gtk_paned_set_position(GTK_PANED(content), pos); + + GtkWidget *right_panel = adw_toolbar_view_new(); + GtkWidget *right_vbox = ui_gtk_vbox_new(0); + adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(right_panel), right_vbox); + + headerbar_right = adw_header_bar_new(); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_right), FALSE); + adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(right_panel), headerbar_right); + + adw_header_bar_set_show_end_title_buttons(ADW_HEADER_BAR(headerbar_main), FALSE); + + gtk_paned_set_start_child(GTK_PANED(content), toolbar_view); + gtk_paned_set_end_child(GTK_PANED(content), right_panel); + + g_object_set_data(G_OBJECT(obj->widget), "ui_window_splitview", content); + g_object_set_data(G_OBJECT(obj->widget), "ui_left_panel", vbox); + g_object_set_data(G_OBJECT(obj->widget), "ui_right_panel", right_vbox); + } + + GtkWidget *content_box = vbox; + + if(sidebar) { + GtkWidget *splitview = adw_overlay_split_view_new(); + adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), splitview); + + GtkWidget *sidebar_toolbar_view = adw_toolbar_view_new(); + adw_overlay_split_view_set_sidebar(ADW_OVERLAY_SPLIT_VIEW(splitview), sidebar_toolbar_view); + headerbar_sidebar = adw_header_bar_new(); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), FALSE); + adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), headerbar_sidebar); + + adw_overlay_split_view_set_content(ADW_OVERLAY_SPLIT_VIEW(splitview), content); + + g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_toolbar_view); + } else { + adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), content); + } + + const char *show_title = ui_get_property("ui.gtk.window.showtitle"); + if(show_title) { + if(!strcmp(show_title, "main") && sidebar) { + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), FALSE); + } else if(!strcmp(show_title, "sidebar")) { + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_main), FALSE); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), TRUE); + } else if(!strcmp(show_title, "false")) { + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), FALSE); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_main), FALSE); + } else { + fprintf(stderr, "Unknown value '%s' for property ui.gtk.window.showtitle\n", show_title); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), FALSE); + } + } else { + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_main), FALSE); + if(sidebar) { + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), TRUE); + } + } + + adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar_main); + g_object_set_data(G_OBJECT(obj->widget), "ui_headerbar", headerbar_main); + + if(!simple) { + ui_fill_headerbar(obj, headerbar_sidebar, headerbar_main, headerbar_right); + } +#elif GTK_MAJOR_VERSION >= 4 + GtkWidget *content_box = ui_gtk_vbox_new(0); + WINDOW_SET_CONTENT(obj->widget, vbox); + if(!simple) { + if(uic_get_menu_list()) { + GtkWidget *mb = ui_create_menubar(obj); + if(mb) { + BOX_ADD(vbox, mb); + } + } + } + if(sidebar) { + GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0); + gtk_paned_set_start_child(GTK_PANED(paned), sidebar_vbox); + gtk_paned_set_end_child(GTK_PANED(paned), content_box); + BOX_ADD_EXPAND(GTK_BOX(vbox), paned); + g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_vbox); + } else { + BOX_ADD_EXPAND(GTK_BOX(vbox), content_box); + } +#else + if(!simple) { + // menu + 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); + } + + GtkWidget *content_box = ui_gtk_vbox_new(0); + WINDOW_SET_CONTENT(obj->widget, vbox); + if(sidebar) { + GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + GtkWidget *sidebar_vbox = ui_gtk_vbox_new(0); + gtk_paned_add1(GTK_PANED(paned), sidebar_vbox); + gtk_paned_add2(GTK_PANED(paned), content_box); + BOX_ADD_EXPAND(GTK_BOX(vbox), paned); + g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_vbox); + gtk_paned_set_position (GTK_PANED(paned), 200); + } else { + BOX_ADD_EXPAND(GTK_BOX(vbox), content_box); + } + +#endif + + // window content + // the content has a (TODO: not yet) configurable frame + // TODO: really? why + /* + GtkWidget *frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE); + gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); + + // 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); + */ + UiContainerX *container = ui_box_container(obj, content_box, UI_CONTAINER_VBOX); + uic_object_push_container(obj, container); + + nwindows++; + return obj; +} + + +UiObject* ui_window(const char *title, void *window_data) { + return create_window(title, window_data, FALSE, FALSE, FALSE); +} + +UiObject *ui_sidebar_window(const char *title, void *window_data) { + return create_window(title, window_data, TRUE, FALSE, FALSE); +} + +UIEXPORT UiObject *ui_splitview_window(const char *title, UiBool sidebar) { + return create_window(title, NULL, sidebar, TRUE, FALSE); +} + +UiObject* ui_simple_window(const char *title, void *window_data) { + return create_window(title, window_data, FALSE, FALSE, TRUE); +} + +void ui_window_size(UiObject *obj, int width, int height) { + gtk_window_set_default_size( + GTK_WINDOW(obj->widget), + width, + height); +} + +void ui_window_default_size(int width, int height) { + window_default_width = width; + window_default_height = height; +} + +void ui_splitview_window_set_pos(UiObject *obj, int pos) { + GtkWidget *splitview = g_object_get_data(G_OBJECT(obj->widget), "ui_window_splitview"); + if(splitview) { + gtk_paned_set_position(GTK_PANED(splitview), pos); + } else { + fprintf(stderr, "Error: window has no splitview\n"); + } +} + +int ui_splitview_window_get_pos(UiObject *obj) { + GtkWidget *splitview = g_object_get_data(G_OBJECT(obj->widget), "ui_window_splitview"); + if(splitview) { + return gtk_paned_get_position(GTK_PANED(splitview)); + } else { + fprintf(stderr, "Error: window has no splitview\n"); + } + return 0; +} + +void ui_splitview_window_set_default_pos(int pos) { + splitview_window_default_pos = pos; +} + +void ui_splitview_window_use_property(UiBool enable) { + splitview_window_use_prop = enable; +} + +UIEXPORT void ui_splitview_window_set_visible(UiObject *obj, int pane, UiBool visible) { + GtkWidget *panel = NULL; + if(pane == 0) { + panel = g_object_get_data(G_OBJECT(obj->widget), "ui_left_panel"); + } else if(pane == 1) { + panel = g_object_get_data(G_OBJECT(obj->widget), "ui_right_panel"); + } + + if(panel == NULL) { + fprintf(stderr, "Error: obj is not a splitview window or invalid pane %d specified\n", pane); + return; + } + + gtk_widget_set_visible(panel, visible); +} + +#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.eventdatatype = 0; + 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)); + evt.eventdatatype = UI_EVENT_DATA_STRING; + } + + 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->customint = 0; + 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; + event->customint = 0; + } + + 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; + evt.eventdatatype = UI_EVENT_DATA_FILE_LIST; + + 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->customint = 0; + 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); + + 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); + } + + UiObject *obj = uic_object_new_toplevel(); + 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); + UiContainerX *container = ui_box_container(obj, content_vbox, UI_CONTAINER_VBOX); + uic_object_push_container(obj, container); + 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, NULL/*tooltip*/, 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, NULL/*tooltip*/, 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, NULL/*tooltip*/, 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, NULL/*tooltip*/, 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_gtk_set_margin(grid, 16, 0, 0, 0, 0); + gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE); + + if(args->lbutton1) { + GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, NULL/*tooltip*/, 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, NULL/*tooltip*/, 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, NULL/*tooltip*/, 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, NULL/*tooltip*/, 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/Grid.c b/ui/motif/Grid.c new file mode 100644 index 0000000..a286f8a --- /dev/null +++ b/ui/motif/Grid.c @@ -0,0 +1,651 @@ +/* + * 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. + */ + +/* + * + */ + +#define _GNU_SOURCE +#include +#include +#include + +#include "Grid.h" + +#include + + + +static XtActionsRec actionslist[] = { + {"getfocus",grid_getfocus}, + {"loosefocus",grid_loosefocus}, + {"NULL",NULL} +}; + +//static char defaultTranslations[] = ": mousedown()\n"; +static char defaultTranslations[] = "\ +: getfocus()\n\ +: loosefocus()\n"; + +static XtResource resources[] = +{ + { + gridColumnSpacing, + gridColumnSpacing, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridRec, + mywidget.columnspacing), + XmRImmediate, + (XtPointer) 0 + }, + { + gridRowSpacing, + gridRowSpacing, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridRec, + mywidget.rowspacing), + XmRImmediate, + (XtPointer) 0 + }, + { + gridMargin, + gridMargin, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridRec, + mywidget.margin), + XmRImmediate, + (XtPointer) 0 + } +}; + +///* +static XtResource constraints[] = +{ + { + gridColumn, + gridColumn, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.x), + XmRImmediate, + (XtPointer) 0 + }, + { + gridRow, + gridRow, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.y), + XmRImmediate, + (XtPointer) 0 + }, + { + gridColspan, + gridColspan, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.colspan), + XmRImmediate, + (XtPointer) 0 + }, + { + gridRowspan, + gridRowspan, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.rowspan), + XmRImmediate, + (XtPointer) 0 + }, + { + gridMarginLeft, + gridMarginLeft, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.margin_left), + XmRImmediate, + (XtPointer) 0 + }, + { + gridMarginRight, + gridMarginRight, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.margin_right), + XmRImmediate, + (XtPointer) 0 + }, + { + gridMarginTop, + gridMarginTop, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.margin_top), + XmRImmediate, + (XtPointer) 0 + }, + { + gridMarginBottom, + gridMarginBottom, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.margin_bottom), + XmRImmediate, + (XtPointer) 0 + }, + { + gridHExpand, + gridHExpand, + XmRBoolean, + sizeof (Boolean), + XtOffsetOf( GridConstraintRec, + grid.hexpand), + XmRImmediate, + (XtPointer) 0 + }, + { + gridVExpand, + gridVExpand, + XmRBoolean, + sizeof (Boolean), + XtOffsetOf( GridConstraintRec, + grid.vexpand), + XmRImmediate, + (XtPointer) 0 + }, + { + gridHFill, + gridHFill, + XmRBoolean, + sizeof (Boolean), + XtOffsetOf( GridConstraintRec, + grid.hfill), + XmRImmediate, + (XtPointer) 0 + }, + { + gridVFill, + gridVFill, + XmRBoolean, + sizeof (Boolean), + XtOffsetOf( GridConstraintRec, + grid.vfill), + XmRImmediate, + (XtPointer) 0 + }, + { + gridMinWidth, + gridMinWidth, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.min_width), + XmRImmediate, + (XtPointer) 0 + } +}; +//*/ + +GridClassRec gridClassRec = { + // Core Class + { + //(WidgetClass)&constraintClassRec, // superclass + (WidgetClass)&xmManagerClassRec, + "Grid", // class_name + sizeof(GridRec), // widget_size + grid_class_initialize, // class_initialize + NULL, // class_part_initialize + FALSE, // class_inited + (XtInitProc)grid_initialize, // initialize + NULL, // initialize_hook + grid_realize, // realize + actionslist, // actions + XtNumber(actionslist), // num_actions + resources, // resources + XtNumber(resources), // num_resources + NULLQUARK, // xrm_class + True, // compress_motion + True, // compress_exposure + True, // compress_enterleave + False, // visible_interest + (XtWidgetProc)grid_destroy, // destroy + (XtWidgetProc)grid_resize, // resize + (XtExposeProc)grid_expose, // expose + grid_set_values, // set_values + NULL, // set_values_hook + XtInheritSetValuesAlmost, // set_values_almost + NULL, // get_values_hook + (XtAcceptFocusProc)grid_acceptfocus, // accept_focus + XtVersion, // version + NULL, // callback_offsets + //NULL, // tm_table + defaultTranslations, + XtInheritQueryGeometry, // query_geometry + NULL, // display_accelerator + NULL, // extension + }, + // Composite Class + { + GridGeometryManager, /* geometry_manager */ + GridChangeManaged, /* change_managed */ + XtInheritInsertChild, /* insert_child */ + XtInheritDeleteChild, /* delete_child */ + NULL, /* extension */ + }, + // Constraint Class + { + constraints, /* resources */ + XtNumber(constraints), /* num_resources */ + sizeof(GridConstraintRec), /* constraint_size */ + grid_constraint_init, /* initialize */ + NULL, /* destroy */ + ConstraintSetValues, /* set_values */ + NULL, /* extension */ + }, + // XmManager Class + ///* + { + XtInheritTranslations, + NULL, + 0, + NULL, + 0, + XmInheritParentProcess, + NULL + }, + //*/ + // MyWidget Class + { + 0 + } +}; + +WidgetClass gridClass = (WidgetClass)&gridClassRec; + + +void grid_class_initialize(void) { + +} +void grid_initialize(Widget request, Widget new, ArgList args, Cardinal num_args) { + Grid mn = (Grid)new; + + mn->mywidget.max_col = 0; + mn->mywidget.max_row = 0; + +} +void grid_realize(Widget w,XtValueMask *valueMask,XSetWindowAttributes *attributes) { + Grid grid = (Grid)w; + XtMakeResizeRequest(w, 400, 400, NULL, NULL); + (coreClassRec.core_class.realize)((Widget)w, valueMask, attributes); + grid_place_children(grid); +} + + +void grid_destroy(Grid widget) { + +} +void grid_resize(Grid widget) { + grid_place_children(widget); +} + +void grid_expose(Grid widget, XEvent *event, Region region) { + +} + + +Boolean grid_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) { + return False; +} + +Boolean grid_acceptfocus(Widget w, Time *t) { + +} + +void grid_getfocus(Widget myw, XEvent *event, String *params, Cardinal *nparam) { + +} + +void grid_loosefocus(Widget myw, XEvent *event, String *params, Cardinal *nparam) { + +} + + + +XtGeometryResult GridGeometryManager(Widget widget, XtWidgetGeometry *request, XtWidgetGeometry *reply) { + GridRec *grid = (GridRec*)XtParent(widget); + GridConstraintRec *constraints = widget->core.constraints; + //XtVaSetValues(widget, XmNwidth, request->width, XmNheight, request->height, NULL); + if((request->request_mode & CWWidth) == CWWidth) { + widget->core.width = request->width; + constraints->grid.pref_width = request->width; + } + if((request->request_mode & CWHeight) == CWHeight) { + widget->core.height = request->height; + constraints->grid.pref_height = request->height; + } + grid_place_children((Grid)XtParent(widget)); + return XtGeometryYes; +} + +void GridChangeManaged(Widget widget) { + +} + +Boolean ConstraintSetValues(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) { + GridConstraintRec *constraints = neww->core.constraints; + Grid grid = (Grid)XtParent(neww); + if(constraints->grid.x > grid->mywidget.max_col) { + grid->mywidget.max_col = constraints->grid.x; + } + if(constraints->grid.y > grid->mywidget.max_row) { + grid->mywidget.max_row = constraints->grid.y; + } +} + + +void grid_constraint_init( + Widget request, + Widget neww, + ArgList args, + Cardinal* num_args +) +{ + GridConstraintRec *constraints = neww->core.constraints; + + Grid grid = (Grid)XtParent(neww); + if(constraints->grid.x > grid->mywidget.max_col) { + grid->mywidget.max_col = constraints->grid.x; + } + if(constraints->grid.y > grid->mywidget.max_row) { + grid->mywidget.max_row = constraints->grid.y; + } + constraints->grid.pref_width = neww->core.width; + constraints->grid.pref_height = neww->core.height; +} + +void grid_place_children(Grid w) { + int ncols = w->mywidget.max_col+1; + int nrows = w->mywidget.max_row+1; + GridDef *cols = calloc(ncols, sizeof(GridDef)); + GridDef *rows = calloc(nrows, sizeof(GridDef)); + int num_cols_expanding = 0; + int num_rows_expanding = 0; + int req_width = 0; + int req_height = 0; + + //printf("container width: %d\n", (int)w->core.width); + + // calculate the minimum size requirements for all columns and rows + // we need to run this 2 times: for widgets without colspan/rowspan first + // and then again for colspan/rowspan > 1 + int span_max = 1; + for(int r=0;r<2;r++) { + for(int i=0;icomposite.num_children;i++) { + Widget child = w->composite.children[i]; + GridConstraintRec *constraints = child->core.constraints; + if(constraints->grid.pref_width == 0) { + constraints->grid.pref_width = child->core.width; + } + if(constraints->grid.pref_height == 0) { + constraints->grid.pref_height = child->core.height; + } + if(constraints->grid.pref_width < constraints->grid.min_width) { + constraints->grid.pref_width = constraints->grid.min_width; + } + + if(constraints->grid.colspan > span_max || constraints->grid.rowspan > span_max) { + continue; + } + + int x = constraints->grid.x; + int y = constraints->grid.y; + // make sure ncols/nrows is correct + // errors shouldn't happen, unless someone messes up the grid internals + if(x >= ncols) { + fprintf(stderr, "Error: widget x out of bounds\n"); + continue; + } + if(y >= nrows) { + fprintf(stderr, "Error: widget y out of bounds\n"); + continue; + } + GridDef *col = &cols[x]; + GridDef *row = &rows[y]; + + if(constraints->grid.hexpand) { + if(constraints->grid.colspan > 1) { + // check if any column in the span is expanding + // if not, make the last column expanding + GridDef *last_col = col; + for(int c=x;cexpand) { + break; + } + } + last_col->expand = TRUE; + } else { + col->expand = TRUE; + } + } + if(constraints->grid.vexpand) { + if(constraints->grid.rowspan > 1) { + GridDef *last_row = row; + for(int c=x;cexpand) { + break; + } + } + last_row->expand = TRUE; + } else { + row->expand = TRUE; + } + } + + // column size + if(constraints->grid.colspan > 1) { + // check size of all columns in span + Dimension span_width = col->size; + GridDef *last_col = col; + for(int s=x+1;ssize; + + } + int diff = constraints->grid.pref_width - span_width; + if(diff > 0) { + last_col->size += diff; + } + } else if(constraints->grid.pref_width > col->size) { + col->size = constraints->grid.pref_width; + } + // row size + if(constraints->grid.rowspan > 1) { + Dimension span_height = row->size; + GridDef *last_row = row; + for(int s=x+1;ssize; + + } + int diff = constraints->grid.pref_height - span_height; + if(diff > 0) { + last_row->size += diff; + } + } else if(constraints->grid.pref_height > row->size) { + row->size = constraints->grid.pref_height; + } + } + span_max = 50000; // not sure if this is unreasonable low or high + } + + // calc required size + for(int i=0;i 0 && req_height > 0) { + // add col/row spacing + req_width += (ncols-1)*w->mywidget.columnspacing; + req_height += (nrows-1)*w->mywidget.rowspacing; + + Widget parent = w->core.parent; + Dimension rwidth = req_width; + Dimension rheight = req_height; + if(rwidth < w->core.width) { + //rwidth = w->core.width; + } + if(rheight < w->core.height) { + //rheight = w->core.height; + } + + if(!w->mywidget.sizerequest) { + Dimension actual_width, actual_height; + w->mywidget.sizerequest = TRUE; + + //printf("sizerequest: %d x %d\n", (int)req_width, (int)req_height); + + //XtWidgetGeometry request; + //request.width = req_width; + //request.request_mode = CWWidth; + //XtWidgetGeometry reply; + //XtGeometryResult result = XtMakeGeometryRequest((Widget)w, &request, &reply); + + XtMakeResizeRequest((Widget)w, req_width, req_height, &actual_width, &actual_height); + w->mywidget.sizerequest = FALSE; + //printf("size request: %d %d\n", (int)actual_width, (int)actual_height); + } + + + + } + + // how much space can we add to each expanding col/row + int hexpand = 0; + int width_diff = (int)w->core.width - req_width; + int hexpand2 = 0; + if(width_diff > 0 && num_cols_expanding > 0) { + hexpand = width_diff / num_cols_expanding; + hexpand2 = width_diff-hexpand*num_cols_expanding; + } + int x = 0; + for(int i=0;imywidget.columnspacing; + + hexpand2 = 0; + } + + int vexpand = 0; + int height_diff = (int)w->core.height - req_height; + int vexpand2 = 0; + if(height_diff > 0 && num_rows_expanding > 0) { + vexpand = height_diff / num_rows_expanding; + vexpand2 = height_diff-vexpand*num_rows_expanding; + } + int y = 0; + for(int i=0;imywidget.rowspacing; + + vexpand2 = 0; + } + + for(int i=0;icomposite.num_children;i++) { + Widget child = w->composite.children[i]; + GridConstraintRec *constraints = child->core.constraints; + GridDef c = cols[constraints->grid.x]; + GridDef r = rows[constraints->grid.y]; + int x = c.pos; + int y = r.pos; + int width = constraints->grid.pref_width; + int height = constraints->grid.pref_height; + if(constraints->grid.hfill) { + if(constraints->grid.colspan > 1) { + Dimension cwidth = 0; + for(int j=0;jgrid.colspan;j++) { + if(constraints->grid.x+j < ncols) { + cwidth += cols[constraints->grid.x+j].size + (j > 0 ? w->mywidget.columnspacing : 0); + } + } + width = cwidth; + } else { + width = c.size - w->mywidget.columnspacing; + } + } + if(constraints->grid.vfill) { + if(constraints->grid.rowspan > 1) { + Dimension cheight = 0; + for(int j=0;jgrid.rowspan;j++) { + if(constraints->grid.y+j < nrows) { + cheight += rows[constraints->grid.y+j].size + (j > 0 ? w->mywidget.rowspacing : 0); + } + } + height = cheight; + } else { + height = r.size - w->mywidget.rowspacing; + } + } + + if(width > 0 && height > 0) { + XtConfigureWidget(child, x, y, width, height, child->core.border_width); + } + //printf("child %d %d - %d %d\n", (int)child->core.x, (int)child->core.y, (int)child->core.width, (int)child->core.height); + } + + free(cols); + free(rows); +} + diff --git a/ui/motif/Grid.h b/ui/motif/Grid.h new file mode 100644 index 0000000..f135cbd --- /dev/null +++ b/ui/motif/Grid.h @@ -0,0 +1,163 @@ +/* + * 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 GRID_H +#define GRID_H + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// resources +#define gridColumnSpacing "gridColumnSpacing" +#define gridRowSpacing "gridRowSpacing" +#define gridMargin "gridMargin" + +// constraints +#define gridColumn "gridColumn" +#define gridRow "gridRow" +#define gridColspan "gridColspan" +#define gridRowspan "gridRowspan" +#define gridHExpand "gridHExpand" +#define gridVExpand "gridVExpand" +#define gridHFill "gridHFill" +#define gridVFill "gridVFill" +#define gridMarginLeft "gridMarginLeft" +#define gridMarginRight "gridMarginRight" +#define gridMarginTop "gridMarginTop" +#define gridMarginBottom "gridMarginBottom" +#define gridMinWidth "gridMinWidth" + + +typedef struct GridDef { + Dimension size; + Dimension pos; + Boolean expand; +} GridDef; + +typedef struct GridClassPart { + int test; +} GridClassPart; + +typedef struct GridClassRec { + CoreClassPart core_class; + CompositeClassPart composite_class; + ConstraintClassPart constraint_class; + XmManagerClassPart manager_class; + GridClassPart mywidgetclass; +} GridClassRec; + + +typedef struct GridPart { + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int max_col; + int max_row; + Dimension columnspacing; + Dimension rowspacing; + Dimension margin; + + Boolean sizerequest; +} GridPart; + +typedef struct GridRec { + CorePart core; + CompositePart composite; + ConstraintPart constraint; + XmManagerPart manager; + GridPart mywidget; +} GridRec; + +typedef struct GridContraintPart { + Dimension x; + Dimension y; + Dimension margin_left; + Dimension margin_right; + Dimension margin_top; + Dimension margin_bottom; + Boolean hexpand; + Boolean vexpand; + Boolean hfill; + Boolean vfill; + Dimension colspan; + Dimension rowspan; + Dimension pref_width; + Dimension pref_height; + Dimension min_width; +} GridContraintPart; + +typedef struct GridConstraintRec { + XmManagerConstraintPart manager; + GridContraintPart grid; +} GridConstraintRec; + +typedef GridRec* Grid; + +extern WidgetClass gridClass; + +void grid_class_initialize(void); +void grid_initialize(Widget request, Widget new, ArgList args, Cardinal num_args); +void grid_realize(Widget w,XtValueMask *valueMask,XSetWindowAttributes *attributes); +void grid_destroy(Grid widget); +void grid_resize(Grid widget); +void grid_expose(Grid widget, XEvent *event, Region region); +Boolean grid_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args); +Boolean grid_acceptfocus(Widget , Time*); + +void grid_place_children(Grid w); + +void grid_getfocus(Widget myw, XEvent *event, String *params, Cardinal *nparam); +void grid_loosefocus(Widget myw, XEvent *event, String *params, Cardinal *nparam); + +void grid_constraint_init( + Widget request, + Widget neww, + ArgList args, + Cardinal* num_args +); + +XtGeometryResult GridGeometryManager(Widget widget, XtWidgetGeometry *request, XtWidgetGeometry *reply); +void GridChangeManaged(Widget widget); +Boolean ConstraintSetValues(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args); + + +#ifdef __cplusplus +} +#endif + +#endif /* GRID_H */ + diff --git a/ui/motif/Makefile b/ui/motif/Makefile new file mode 100644 index 0000000..7f7b420 --- /dev/null +++ b/ui/motif/Makefile @@ -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. +# + +$(MOTIF_OBJPRE)%.o: motif/%.c + $(CC) -o $@ -c -I../ucx $(CFLAGS) $(SHLIB_CFLAGS) $(TK_CFLAGS) $< + +$(UI_LIB): $(OBJ) + $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ) + +$(UI_SHLIB): $(OBJ) + $(CC) -o $(UI_SHLIB) $(LDFLAGS) $(SHLIB_LDFLAGS) $(TK_LDFLAGS) $(OBJ) -L../build/lib -lucx diff --git a/ui/motif/button.c b/ui/motif/button.c new file mode 100644 index 0000000..d2528c4 --- /dev/null +++ b/ui/motif/button.c @@ -0,0 +1,443 @@ +/* + * 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 + +#include "button.h" +#include "container.h" +#include "../common/context.h" +#include + +#include +#include +#include + +#include + + +UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs *args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + + XmString label = NULL; + if(args->label) { + label = XmStringCreateLocalized((char*)args->label); + XtSetArg(xargs[n], XmNlabelString, label); n++; + } + + char *name = args->name ? (char*)args->name : "button"; + Widget button = XmCreatePushButton(parent, name, xargs, n); + XtManageChild(button); + ui_container_add(ctn, button); + + ui_set_widget_groups(obj->ctx, button, args->groups); + + if(args->onclick) { + UiEventData *eventdata = malloc(sizeof(UiEventData)); + eventdata->callback = args->onclick; + eventdata->userdata = args->onclickdata; + eventdata->obj = obj; + eventdata->value = 0; + XtAddCallback( + button, + XmNactivateCallback, + (XtCallbackProc)ui_push_button_callback, + eventdata); + XtAddCallback( + button, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + eventdata); + } + + + XmStringFree(label); + return button; +} + +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; + e.set = 0; + event->callback(&e, event->userdata); +} + +UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs *args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + XtSetArg(xargs[n], XmNfillOnSelect, True); n++; + XtSetArg(xargs[n], XmNindicatorOn, False); n++; + + XmString label = NULL; + if(args->label) { + label = XmStringCreateLocalized((char*)args->label); + XtSetArg(xargs[n], XmNlabelString, label); n++; + } + + char *name = args->name ? (char*)args->name : "togglebutton"; + Widget button = XmCreateToggleButton(parent, name, xargs, n); + XtManageChild(button); + ui_container_add(ctn, button); + + ui_set_widget_groups(obj->ctx, button, args->groups); + + ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_group); + + XmStringFree(label); + return button; +} + +UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + + XmString label = NULL; + if(args->label) { + label = XmStringCreateLocalized((char*)args->label); + XtSetArg(xargs[n], XmNlabelString, label); n++; + } + + char *name = args->name ? (char*)args->name : "button"; + Widget button = XmCreateToggleButton(parent, name, xargs, n); + XtManageChild(button); + ui_container_add(ctn, button); + + ui_set_widget_groups(obj->ctx, button, args->groups); + + ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_group); + + XmStringFree(label); + return button; +} + +UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { + return ui_checkbox_create(obj, args); +} + +static void togglebutton_changed(Widget w, UiVarEventData *event, XmToggleButtonCallbackStruct *tb) { + if(event->value > 0) { + // button in configured to enable/disable states + if(tb->set) { + ui_set_group(event->obj->ctx, event->value); + } else { + ui_unset_group(event->obj->ctx, event->value); + } + } + + UiEvent e; + e.obj = event->obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = NULL; + e.eventdatatype = 0; + e.intval = XmToggleButtonGetState(w); + e.set = ui_get_setop(); + + if(event->callback) { + event->callback(&e, event->userdata); + } + + if(event->var && event->var->value) { + UiInteger *v = event->var->value; + v->value = e.intval; + ui_notify_evt(v->observers, &e); + } +} + +void ui_bind_togglebutton( + UiObject *obj, + Widget widget, + const char *varname, + UiInteger *value, + ui_callback onchange, + void *onchangedata, + int enable_state) +{ + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, value, varname, UI_VAR_INTEGER); + if(var) { + value = (UiInteger*)var->value; + value->obj = widget; + value->get = ui_togglebutton_get; + value->set = ui_togglebutton_set; + + if(value->value) { + XmToggleButtonSetState(widget, True, False); + } + } + + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->callback = onchange; + event->userdata = onchangedata; + event->var = var; + event->observers = NULL; + event->value = enable_state; + XtAddCallback( + widget, + XmNvalueChangedCallback, + (XtCallbackProc)togglebutton_changed, + event); + XtAddCallback( + widget, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + event); +} + +int64_t ui_togglebutton_get(UiInteger *i) { + Widget togglebutton = i->obj; + Boolean state = XmToggleButtonGetState(togglebutton); + i->value = state; + return state; +} + +void ui_togglebutton_set(UiInteger *i, int64_t value) { + Widget togglebutton = i->obj; + i->value = value; + XmToggleButtonSetState(togglebutton, (Boolean)value, False); +} + +static void destroy_list(Widget w, CxList *list, XtPointer d) { + cxListFree(list); +} + +static void radiobutton_changed(Widget w, UiVarEventData *event, XmToggleButtonCallbackStruct *tb) { + if(event->value > 0) { + // button in configured to enable/disable states + if(tb->set) { + ui_set_group(event->obj->ctx, event->value); + } else { + ui_unset_group(event->obj->ctx, event->value); + } + } + + if(!tb->set) { + return; // only handle set-events + } + + UiInteger *value = NULL; + int64_t v = 0; + if(event->var) { + value = event->var->value; + // find widget index and update all radiobuttons + // the UiInteger value must always be up-to-date + CxList *list = value->obj; + CxIterator i = cxListIterator(list); + cx_foreach(Widget, button, i) { + Boolean state = False; + if(button == w) { + value->value = i.index+1; // update value + state = True; + } + XmToggleButtonSetState(button, state, False); + } + v = value->value; + } + + UiEvent e; + e.obj = event->obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = value; + e.eventdatatype = UI_EVENT_DATA_INTEGER_VALUE; + e.intval = v; + e.set = ui_get_setop(); + + if(event->callback) { + event->callback(&e, event->userdata); + } + + if(value) { + ui_notify_evt(value->observers, &e); + } +} + +void ui_bind_radiobutton(UiObject *obj, Widget rbutton, UiInteger *value, const char *varname, ui_callback onchange, void *onchangedata, int enable_group) { + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, value, varname, UI_VAR_INTEGER); + if(var) { + UiInteger *value = var->value; + CxList *rb = value->obj; + if(!rb) { + // first button in the radiobutton group + // create a list for all buttons and use the list as value obj + rb = cxArrayListCreateSimple(CX_STORE_POINTERS, 4); + value->obj = rb; + value->get = ui_radiobutton_get; + value->set = ui_radiobutton_set; + + // the first radio button is also responsible for cleanup + XtAddCallback( + rbutton, + XmNdestroyCallback, + (XtCallbackProc)destroy_list, + rb); + } + cxListAdd(rb, rbutton); + + // set the radiobutton state, if the value is already set + if(cxListSize(rb) == value->value) { + XmToggleButtonSetState(rbutton, True, False); + } + } + + // the radio button needs to handle change events to update all + // other buttons in the radio button group + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->callback = onchange; + event->userdata = onchangedata; + event->observers = NULL; + event->var = var; + event->value = enable_group; + XtAddCallback( + rbutton, + XmNvalueChangedCallback, + (XtCallbackProc)radiobutton_changed, + event); + XtAddCallback( + rbutton, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + event); +} + +UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs *args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + XtSetArg(xargs[n], XmNindicatorType, XmONE_OF_MANY_ROUND); n++; + XmString label = NULL; + if(args->label) { + label = XmStringCreateLocalized((char*)args->label); + XtSetArg(xargs[n], XmNlabelString, label); n++; + } + + char *name = args->name ? (char*)args->name : "button"; + Widget button = XmCreateToggleButton(parent, name, xargs, n); + XtManageChild(button); + ui_container_add(ctn, button); + + ui_set_widget_groups(obj->ctx, button, args->groups); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + if(var) { + UiInteger *value = var->value; + CxList *rb = value->obj; + if(!rb) { + // first button in the radiobutton group + // create a list for all buttons and use the list as value obj + rb = cxArrayListCreateSimple(CX_STORE_POINTERS, 4); + value->obj = rb; + value->get = ui_radiobutton_get; + value->set = ui_radiobutton_set; + + // the first radio button is also responsible for cleanup + XtAddCallback( + button, + XmNdestroyCallback, + (XtCallbackProc)destroy_list, + rb); + } + cxListAdd(rb, button); + + // set the radiobutton state, if the value is already set + if(cxListSize(rb) == value->value) { + XmToggleButtonSetState(button, True, False); + } + } + + // the radio button needs to handle change events to update all + // other buttons in the radio button group + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->callback = args->onchange; + event->userdata = args->onchangedata; + event->observers = NULL; + event->var = var; + event->value = args->enable_group; + XtAddCallback( + button, + XmNvalueChangedCallback, + (XtCallbackProc)radiobutton_changed, + event); + XtAddCallback( + button, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + event); + + XmStringFree(label); + return button; + + +} + +int64_t ui_radiobutton_get(UiInteger *i) { + // the UiInteger should be updated automatically by change events + return i->value; +} + +void ui_radiobutton_set(UiInteger *i, int64_t value) { + CxList *list = i->obj; + if(i->value > 0) { + Widget current = cxListAt(list, i->value-1); + if(current) { + XmToggleButtonSetState(current, False, False); + } + } + if(value > 0 && value <= cxListSize(list)) { + Widget button = cxListAt(list, value-1); + if(button) { + XmToggleButtonSetState(button, True, False); + i->value = value; + } + } +} diff --git a/ui/motif/button.h b/ui/motif/button.h new file mode 100644 index 0000000..b6dd711 --- /dev/null +++ b/ui/motif/button.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. + */ + +#ifndef BUTTON_H +#define BUTTON_H + +#include "../ui/button.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void ui_push_button_callback(Widget widget, UiEventData *event, XtPointer d); + +void ui_bind_togglebutton( + UiObject *obj, + Widget widget, + const char *varname, + UiInteger *value, + ui_callback onchange, + void *onchangedata, + int enable_state); + +int64_t ui_togglebutton_get(UiInteger *i); +void ui_togglebutton_set(UiInteger *i, int64_t value); + +void ui_bind_radiobutton(UiObject *obj, Widget rbutton, UiInteger *value, const char *varname, ui_callback onchange, void *onchangedata, int enable_group); + +int64_t ui_radiobutton_get(UiInteger *i); +void ui_radiobutton_set(UiInteger *i, int64_t value); + +#ifdef __cplusplus +} +#endif + +#endif /* BUTTON_H */ + diff --git a/ui/motif/container.c b/ui/motif/container.c new file mode 100644 index 0000000..444bedb --- /dev/null +++ b/ui/motif/container.c @@ -0,0 +1,621 @@ +/* + * 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 +#include + +#include "container.h" +#include "../common/context.h" +#include "../common/object.h" +#include "../common/container.h" + +#include + +#include "Grid.h" + + + +Widget ui_container_prepare(UiContainerPrivate *container, UiLayout *layout, Arg *args, int *n) { + if(layout->margin != 0) { + layout->margin_left = layout->margin; + layout->margin_right = layout->margin; + layout->margin_top = layout->margin; + layout->margin_bottom = layout->margin; + } + return container->prepare(container, layout, args, n); +} + +void ui_container_add(UiContainerPrivate *container, Widget widget) { + container->add(container, widget); +} + + +/* ---------------------------- Box Container ---------------------------- */ + +static UIWIDGET box_create(UiObject *obj, UiContainerArgs *args, UiBoxOrientation orientation) { + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Arg xargs[16]; + int n = 0; + + if(orientation == UI_BOX_VERTICAL) { + //XtSetArg(xargs[n], gridRowSpacing, args->spacing); n++; + } else { + //XtSetArg(xargs[n], gridColumnSpacing, args->spacing); n++; + } + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + Widget grid = XtCreateManagedWidget(args->name ? args->name : "boxcontainer", gridClass, parent, xargs, n); + ctn->add(ctn, grid); + + UiContainerX *container = ui_box_container(obj, grid, orientation); + uic_object_push_container(obj, container); + + return grid; +} + +// public +UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs *args) { + return box_create(obj, args, UI_BOX_VERTICAL); +} + +// public +UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs *args) { + return box_create(obj, args, UI_BOX_HORIZONTAL); +} + +UiContainerX* ui_box_container(UiObject *obj, Widget grid, UiBoxOrientation orientation) { + UiBoxContainer *ctn = ui_malloc(obj->ctx, sizeof(UiBoxContainer)); + memset(ctn, 0, sizeof(UiBoxContainer)); + ctn->container.prepare = orientation == UI_BOX_VERTICAL ? ui_vbox_prepare : ui_hbox_prepare; + ctn->container.add = ui_box_container_add; + ctn->container.widget = grid; + ctn->n = 0; + return (UiContainerX*)ctn; +} + +static Widget ui_box_container_prepare(UiBoxContainer *box, UiLayout *layout, Arg *args, int *n) { + int a = *n; + box->n++; + return box->container.widget; +} + +Widget ui_vbox_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n) { + UiBoxContainer *box = (UiBoxContainer*)ctn; + int a = *n; + XtSetArg(args[a], gridRow, box->n); a++; + if(layout->fill) { + XtSetArg(args[a], gridVExpand, TRUE); a++; + XtSetArg(args[a], gridVFill, TRUE); a++; + } + XtSetArg(args[a], gridHExpand, TRUE); a++; + XtSetArg(args[a], gridHFill, TRUE); a++; + *n = a; + return ui_box_container_prepare(box, layout, args, n); +} + +Widget ui_hbox_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n) { + UiBoxContainer *box = (UiBoxContainer*)ctn; + int a = *n; + XtSetArg(args[a], gridColumn, box->n); a++; + if(layout->fill) { + XtSetArg(args[a], gridHExpand, TRUE); a++; + XtSetArg(args[a], gridHFill, TRUE); a++; + } + XtSetArg(args[a], gridVExpand, TRUE); a++; + XtSetArg(args[a], gridVFill, TRUE); a++; + *n = a; + return ui_box_container_prepare(box, layout, args, n); +} + +void ui_box_container_add(UiContainerPrivate *ctn, Widget widget) { + +} + + +/* ---------------------------- Grid Container ---------------------------- */ + +// public +UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + XtSetArg(xargs[n], gridMargin, args->margin); n++; + XtSetArg(xargs[n], gridColumnSpacing, args->columnspacing); n++; + XtSetArg(xargs[n], gridRowSpacing, args->rowspacing); n++; + Widget grid = XtCreateManagedWidget(args->name ? args->name : "gridcontainer", gridClass, parent, xargs, n); + ui_container_add(ctn, grid); + + UiContainerX *container = ui_grid_container(obj, grid, args->def_hexpand, args->def_vexpand, args->def_hfill, args->def_vfill); + uic_object_push_container(obj, container); + + return grid; +} + +UiContainerX* ui_grid_container( + UiObject *obj, + Widget grid, + UiBool def_hexpand, + UiBool def_vexpand, + UiBool def_hfill, + UiBool def_vfill) +{ + UiGridContainer *ctn = ui_malloc(obj->ctx, sizeof(UiGridContainer)); + memset(ctn, 0, sizeof(UiBoxContainer)); + ctn->container.prepare = ui_grid_container_prepare; + ctn->container.add = ui_grid_container_add; + ctn->container.widget = grid; + ctn->x = 0; + ctn->y = 0; + ctn->def_hexpand = def_hexpand; + ctn->def_vexpand = def_vexpand; + ctn->def_hfill = def_hfill; + ctn->def_vfill = def_vfill; + return (UiContainerX*)ctn; +} + +Widget ui_grid_container_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n) { + UiGridContainer *grid = (UiGridContainer*)ctn; + if(ctn->container.newline) { + grid->y++; + grid->x = 0; + } + + int a = *n; + XtSetArg(args[a], gridColumn, grid->x); a++; + XtSetArg(args[a], gridRow, grid->y); a++; + if(layout->colspan > 0) { + XtSetArg(args[a], gridColspan, layout->colspan); a++; + } + if(layout->rowspan > 0) { + XtSetArg(args[a], gridRowspan, layout->rowspan); a++; + } + + uic_layout_setup_expand_fill(layout, grid->def_hexpand, grid->def_vexpand, grid->def_hfill, grid->def_vfill); + + if(layout->hfill) { + XtSetArg(args[a], gridHFill, TRUE); a++; + } + if(layout->vfill) { + XtSetArg(args[a], gridVFill, TRUE); a++; + } + if(layout->hexpand) { + XtSetArg(args[a], gridHExpand, TRUE); a++; + } + if(layout->vexpand) { + XtSetArg(args[a], gridVExpand, TRUE); a++; + } + + *n = a; + return ctn->widget; +} + +void ui_grid_container_add(UiContainerPrivate *ctn, Widget widget) { + UiGridContainer *grid = (UiGridContainer*)ctn; + grid->x++; + grid->container.container.newline = FALSE; +} + + +/* -------------------------- TabView Container -------------------------- */ + +static void ui_tabbar_resize(Widget widget, XtPointer udata, XtPointer cdata) { + UiMotifTabView *tabview = udata; + + if(tabview->tabview == UI_TABVIEW_INVISIBLE) { + return; + } + + int width = 0; + int height = 0; + XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL); + int numbuttons = cxListSize(tabview->tabs); + if(numbuttons == 0) { + return; + } + int button_width = width / numbuttons; + int x = 0; + + CxIterator i = cxListIterator(tabview->tabs); + cx_foreach(UiTab *, tab, i) { + if(i.index + 1 == numbuttons) { + button_width = width - x; + } + XtVaSetValues( + tab->tab_button, + XmNx, x, + XmNy, 0, + XmNwidth, + button_width, + + NULL); + x += button_width; + } + + if(height <= tabview->height) { + XtVaSetValues(widget, XmNheight, tabview->height + 4, NULL); + } +} + +static void ui_tabbar_expose(Widget widget, XtPointer udata, XtPointer cdata) { + UiMotifTabView *tabview = udata; + XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)cdata; + XEvent *event = cbs->event; + Display *dpy = XtDisplay(widget); + + if(!tabview->gc_initialized) { + XGCValues gcvals; + gcvals.foreground = tabview->fg1; + tabview->gc = XCreateGC(XtDisplay(tabview->tabbar), XtWindow(tabview->tabbar), (GCForeground), &gcvals); + } + + if(tabview->current_tab) { + Widget tab = tabview->current_tab->tab_button; + XFillRectangle(dpy, XtWindow(widget), tabview->gc, tab->core.x, tab->core.height, tab->core.width, 4); + } +} + +UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs *args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + // create widgets + // form + // - tabbar (Drawing Area) + // - content (Frame) + UiMotifTabView *tabview = malloc(sizeof(UiMotifTabView)); + memset(tabview, 0, sizeof(UiMotifTabView)); + char *name = args->name ? (char*)args->name : "tabview"; + XtSetArg(xargs[n], XmNuserData, tabview); n++; + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + Widget form = XmCreateForm(parent, name, xargs, n); + XtManageChild(form); + ui_container_add(ctn, parent); + + n = 0; + XtSetArg(xargs[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(xargs[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(xargs[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(xargs[n], XmNorientation, XmHORIZONTAL); n++; + XtSetArg(xargs[n], XmNpacking, XmPACK_TIGHT); n++; + XtSetArg(xargs[n], XmNspacing, 1); n++; + XtSetArg(xargs[n], XmNmarginWidth, 0); n++; + XtSetArg(xargs[n], XmNmarginHeight, 0); n++; + Widget tabbar = XmCreateDrawingArea(form, "ui_test", xargs, n); + XtManageChild(tabbar); + XtAddCallback(tabbar, XmNresizeCallback , ui_tabbar_resize, tabview); + XtAddCallback(tabbar, XmNexposeCallback, ui_tabbar_expose, tabview); + + n = 0; + XtSetArg(xargs[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(xargs[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(xargs[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(xargs[n], XmNtopAttachment, XmATTACH_WIDGET); n++; + XtSetArg(xargs[n], XmNtopWidget, tabbar); n++; + Widget content = XmCreateFrame(form, "tabviewcontent", xargs, n); + + // setup tabview object, that holds all relevant objects + tabview->obj = obj; + tabview->form = form; + tabview->tabbar = tabbar; + tabview->content = content; + tabview->tabview = args->tabview; + tabview->subcontainer = args->subcontainer; + tabview->select = ui_motif_tabview_select; + tabview->add = ui_motif_tabview_add_tab; + tabview->remove = ui_motif_tabview_remove; + tabview->tabs = cxArrayListCreate(obj->ctx->allocator, cx_cmp_ptr, sizeof(UiTab), 8); + tabview->current_index = -1; + + UiTabViewContainer *ct = ui_malloc(obj->ctx, sizeof(UiTabViewContainer)); + ct->container.widget = form; + ct->container.type = UI_CONTAINER_TABVIEW; + ct->container.prepare = ui_tabview_container_prepare; + ct->container.add = ui_tabview_container_add; + ct->tabview = tabview; + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + if(var) { + UiInteger *i = var->value; + i->obj = tabview; + i->get = ui_tabview_get; + i->set = ui_tabview_set; + } + + uic_object_push_container(obj, (UiContainerX*)ct); + + return form; +} + +int64_t ui_tabview_get(UiInteger *i) { + UiMotifTabView *tabview = i->obj; + i->value = tabview->current_index; + return i->value; +} + +void ui_tabview_set(UiInteger *i, int64_t value) { + UiMotifTabView *tabview = i->obj; + if(value < cxListSize(tabview->tabs)) { + ui_motif_tabview_select(tabview, value); + i->value = value; + } +} + +void ui_tab_create(UiObject *obj, const char* title) { + UiContainerPrivate *ctn = ui_obj_container(obj); + if(ctn->type != UI_CONTAINER_TABVIEW) { + fprintf(stderr, "UI Error: container is not a tabview\n"); + return; + } + + UiMotifTabView *tabview = NULL; + XtVaGetValues(ctn->widget, XmNuserData, &tabview, NULL); + if(!tabview) { + fprintf(stderr, "UI Error: no tabview\n"); + return; + } + + + Widget child = ui_vbox_create(obj, &(UiContainerArgs) { 0 }); + if(tabview->current) { + XtUnmanageChild(child); + } else { + tabview->current = child; + } + + tabview->add(tabview, -1, title, child); +} + +void ui_tabview_select(UIWIDGET tabview, int tab) { + UiMotifTabView *tabviewdata = NULL; + XtVaGetValues(tabview, XmNuserData, &tabviewdata, NULL); + if(tabviewdata) { + ui_motif_tabview_select(tabviewdata, tab); + } else { + fprintf(stderr, "ui_tabview_select: widget is not a tabview\n"); + } +} + +void ui_tabview_remove(UIWIDGET tabview, int tab) { + UiMotifTabView *tabviewdata = NULL; + XtVaGetValues(tabview, XmNuserData, &tabviewdata, NULL); + if(tabviewdata) { + ui_motif_tabview_remove(tabviewdata, tab); + } else { + fprintf(stderr, "ui_tabview_select: widget is not a tabview\n"); + } +} + +UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) { + UiMotifTabView *tabviewdata = NULL; + XtVaGetValues(tabview, XmNuserData, &tabviewdata, NULL); + if(tabviewdata) { + Arg args[16]; + int n = 0; + + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNtopWidget, tabviewdata->tabbar); n++; + + Widget grid = XtCreateManagedWidget("vbox", gridClass, tabviewdata->content, args, n); + + UiObject *newobj = ui_calloc(tabviewdata->obj->ctx, 1, sizeof(UiObject)); + newobj->ctx = tabviewdata->obj->ctx; + newobj->widget = grid; + UiContainerX *container = ui_box_container(newobj, grid, UI_BOX_VERTICAL); + newobj->container_begin = container; + newobj->container_end = container; + return newobj; + } else { + fprintf(stderr, "ui_tabview_select: widget is not a tabview\n"); + return NULL; + } +} + +void ui_motif_tabview_select(UiMotifTabView *tabview, int tab) { + UiTab *t = cxListAt(tabview->tabs, tab); + if(t) { + tabview->current_index = tab; + ui_motif_tabview_change_tab(tabview, t); + } +} + +static void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d) { + UiMotifTabView *tabview = NULL; + XtVaGetValues(widget, XmNuserData, &tabview, NULL); + ui_motif_tabview_change_tab(tabview, tab); +} + +void ui_motif_tabview_add_tab(UiMotifTabView *tabview, int index, const char *name, Widget child) { + UiTab tab; + + Arg args[16]; + int n = 0; + + XmString label = XmStringCreateLocalized((char*)name); + XtSetArg(args[n], XmNlabelString, label); n++; + XtSetArg(args[n], XmNshadowThickness, 0); n++; + XtSetArg(args[n], XmNhighlightThickness, 0); n++; + XtSetArg(args[n], XmNuserData, tabview); n++; + + Widget button = XmCreatePushButton(tabview->tabbar, "tab_button", args, n); + if(tabview->tabview != UI_TABVIEW_INVISIBLE) { + XtManageChild(button); + } + + if(tabview->height == 0) { + Dimension h; + XtVaGetValues( + button, + XmNarmColor, + &tabview->bg1, + XmNbackground, + &tabview->bg2, + XmNhighlightColor, + &tabview->fg1, + XmNheight, + &h, + NULL); + tabview->height = h + 2; // border + + XtVaSetValues(tabview->tabbar, XmNbackground, tabview->bg1, NULL); + } + + tab.tab_button = button; + tab.child = child; + size_t newtab_index = cxListSize(tabview->tabs); + cxListAdd(tabview->tabs, &tab); + UiTab *newtab = cxListAt(tabview->tabs, newtab_index); + + XtAddCallback( + button, + XmNactivateCallback, + (XtCallbackProc)ui_tab_button_callback, + newtab); + + if(newtab_index == 0) { + ui_motif_tabview_change_tab(tabview, newtab); + } else { + XtVaSetValues(button, XmNbackground, tabview->bg1, NULL); + } +} + +void ui_motif_tabview_remove(UiMotifTabView *tabview, int index) { + UiTab *tab = cxListAt(tabview->tabs, index); + if(tab) { + if(tab == tabview->current_tab) { + if(index > 0) { + ui_motif_tabview_select(tabview, index-1); + } else { + if(index < cxListSize(tabview->tabs)) { + ui_motif_tabview_select(tabview, index+1); + } else { + tabview->current_tab = NULL; + tabview->current_index = -1; + } + } + } + XtDestroyWidget(tab->tab_button); + XtDestroyWidget(tab->child); + cxListRemove(tabview->tabs, index); + } +} + +void ui_motif_tabview_change_tab(UiMotifTabView *tabview, UiTab *tab) { + if(tabview->current_tab) { + XtVaSetValues(tabview->current_tab->tab_button, XmNshadowThickness, 0, XmNbackground, tabview->bg1, NULL); + XtUnmanageChild(tabview->current_tab->child); + } + XtVaSetValues(tab->tab_button, XmNshadowThickness, 1, XmNbackground, tabview->bg2, NULL); + tabview->current_tab = tab; + tabview->current_index = (int)cxListFind(tabview->tabs, tab);; + XtManageChild(tab->child); +} + +Widget ui_tabview_container_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n) { + UiTabViewContainer *ct = (UiTabViewContainer*)ctn; + UiMotifTabView *tabview = ct->tabview; + int a = *n; + XtSetArg(args[a], XmNleftAttachment, XmATTACH_FORM); a++; + XtSetArg(args[a], XmNrightAttachment, XmATTACH_FORM); a++; + XtSetArg(args[a], XmNbottomAttachment, XmATTACH_FORM); a++; + XtSetArg(args[a], XmNtopAttachment, XmATTACH_WIDGET); a++; + XtSetArg(args[a], XmNtopWidget, tabview->tabbar); a++; + *n = a; + return tabview->form; +} + +void ui_tabview_container_add(UiContainerPrivate *ctn, Widget widget) { + +} + +/* -------------------- ScrolledWindow -------------------- */ + +Widget ui_scrolledwindow_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n) { + return ctn->widget; +} + +void ui_scrolledwindow_add(UiContainerPrivate *ctn, Widget widget) { + +} + +static UiContainerX* ui_scrolledwindow_container(UiObject *obj, Widget scrolledwindow) { + UiContainerPrivate *ctn = ui_malloc(obj->ctx, sizeof(UiContainerPrivate)); + memset(ctn, 0, sizeof(UiContainerPrivate)); + ctn->prepare = ui_scrolledwindow_prepare; + ctn->add = ui_scrolledwindow_add; + ctn->widget = scrolledwindow; + return (UiContainerX*)ctn; +} + +UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Arg xargs[16]; + int n = 0; + + XtSetArg(xargs[n], XmNscrollingPolicy, XmAUTOMATIC); n++; + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + Widget scrolledwindow = XmCreateScrolledWindow(parent, "scrolledwindow", xargs, n); + ui_container_add(ctn, scrolledwindow); + + UiContainerX *container = ui_scrolledwindow_container(obj, scrolledwindow); + uic_object_push_container(obj, container); + + return scrolledwindow; +} + +/* -------------------- Container Helper Functions -------------------- */ + +void ui_container_begin_close(UiObject *obj) { + UiContainerPrivate *ct = ui_obj_container(obj); + ct->container.close = 1; +} + +int ui_container_finish(UiObject *obj) { + UiContainerPrivate *ct = ui_obj_container(obj); + if(ct->container.close) { + ui_end_new(obj); + return 0; + } + return 1; +} + diff --git a/ui/motif/container.h b/ui/motif/container.h new file mode 100644 index 0000000..9dd73cc --- /dev/null +++ b/ui/motif/container.h @@ -0,0 +1,164 @@ +/* + * 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_APPLY_LAYOUT(layout, args) \ + layout.fill = args->fill; \ + layout.hexpand = args->hexpand; \ + layout.vexpand = args->vexpand; \ + layout.hfill = args->hfill; \ + layout.vfill = args->vfill; \ + layout.override_defaults = args->override_defaults; \ + layout.colspan = args->colspan; \ + layout.rowspan = args->rowspan + +typedef enum UiBoxOrientation UiBoxOrientation; + +enum UiContainerType { + UI_CONTAINER_GENERIC = 0, + UI_CONTAINER_TABVIEW +}; +typedef enum UiContainerType UiContainerType; + +#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) + +#define ui_obj_container(obj) (UiContainerPrivate*)obj->container_end + + +enum UiBoxOrientation { + UI_BOX_VERTICAL = 0, + UI_BOX_HORIZONTAL +}; + +typedef struct UiContainerPrivate UiContainerPrivate; + + +struct UiContainerPrivate { + UiContainerX container; + Widget (*prepare)(UiContainerPrivate*, UiLayout *layout, Arg *, int*); + void (*add)(UiContainerPrivate*, Widget); + Widget widget; + UiContainerType type; +}; + +typedef struct UiBoxContainer { + UiContainerPrivate container; + Dimension n; +} UiBoxContainer; + +typedef struct UiGridContainer { + UiContainerPrivate container; + Dimension x; + Dimension y; + UiBool def_hexpand; + UiBool def_vexpand; + UiBool def_hfill; + UiBool def_vfill; +} UiGridContainer; + +typedef struct UiTab { + Widget tab_button; + Widget child; +} UiTab; + +typedef struct UiMotifTabView UiMotifTabView; +struct UiMotifTabView { + UiObject *obj; + Widget form; + Widget tabbar; + Widget content; + Widget current; + UiTab *current_tab; + int current_index; + int height; + Pixel bg1; + Pixel bg2; + Pixel fg1; + GC gc; + int gc_initialized; + UiTabViewType tabview; + UiSubContainerType subcontainer; + CxList *tabs; + void (*select)(UiMotifTabView *tabview, int tab); + void (*add)(UiMotifTabView *tabview, int index, const char *name, Widget child); + void (*remove)(UiMotifTabView *tabview, int index); +}; + +typedef struct UiTabViewContainer { + UiContainerPrivate container; + UiMotifTabView *tabview; +} UiTabViewContainer; + +Widget ui_container_prepare(UiContainerPrivate *container, UiLayout *layout, Arg *args, int *n); +void ui_container_add(UiContainerPrivate *container, Widget widget); + +void ui_motif_tabview_select(UiMotifTabView *tabview, int tab); +void ui_motif_tabview_add_tab(UiMotifTabView *tabview, int index, const char *name, Widget child); +void ui_motif_tabview_remove(UiMotifTabView *tabview, int index); +void ui_motif_tabview_change_tab(UiMotifTabView *tabview, UiTab *tab); +int64_t ui_tabview_get(UiInteger *i); +void ui_tabview_set(UiInteger *i, int64_t value); + +Widget ui_tabview_container_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n); +void ui_tabview_container_add(UiContainerPrivate *ctn, Widget widget); + +UiContainerX* ui_box_container(UiObject *obj, Widget grid, UiBoxOrientation orientation); +Widget ui_vbox_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n); +Widget ui_hbox_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n); +void ui_box_container_add(UiContainerPrivate *ctn, Widget widget); + + +UiContainerX* ui_grid_container( + UiObject *obj, + Widget grid, + UiBool def_hexpand, + UiBool def_vexpand, + UiBool def_hfill, + UiBool def_vfill); +Widget ui_grid_container_prepare(UiContainerPrivate *ctn, UiLayout *layout, Arg *args, int *n); +void ui_grid_container_add(UiContainerPrivate *ctn, Widget widget); + +#ifdef __cplusplus +} +#endif + +#endif /* CONTAINER_H */ + diff --git a/ui/motif/dnd.c b/ui/motif/dnd.c new file mode 100644 index 0000000..44b9384 --- /dev/null +++ b/ui/motif/dnd.c @@ -0,0 +1,30 @@ +/* + * 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" + 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..fab9930 --- /dev/null +++ b/ui/motif/graphics.c @@ -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. + */ + +#include +#include +#include +#include + +#include "graphics.h" + +#include "container.h" diff --git a/ui/motif/graphics.h b/ui/motif/graphics.h new file mode 100644 index 0000000..addec59 --- /dev/null +++ b/ui/motif/graphics.h @@ -0,0 +1,46 @@ +/* + * 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 + + + +#ifdef __cplusplus +} +#endif + +#endif /* GRAPHICS_H */ + diff --git a/ui/motif/image.c b/ui/motif/image.c new file mode 100644 index 0000000..5f4e778 --- /dev/null +++ b/ui/motif/image.c @@ -0,0 +1,17 @@ +/* + * 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" + + + +void ui_image_ref(UIIMAGE img) { + // TODO +} + +void ui_image_unref(UIIMAGE img) { + // TODO +} 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..749c9e3 --- /dev/null +++ b/ui/motif/label.c @@ -0,0 +1,243 @@ +/* + * 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 + +#include "label.h" +#include "container.h" +#include "../common/context.h" +#include "../common/object.h" + +#include "Grid.h" + +static UIWIDGET label_create(UiObject *obj, UiLabelArgs *args, int align) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + + XtSetArg(xargs[n], XmNalignment, align); n++; + XmString label = NULL; + if(args->label) { + label = XmStringCreateLocalized((char*)args->label); + XtSetArg(xargs[n], XmNlabelString, label); n++; + } + + char *name = args->name ? (char*)args->name : "label"; + Widget w = XmCreateLabel(parent, name, xargs, n); + XtManageChild(w); + ui_container_add(ctn, w); + + XmStringFree(label); + return w; +} + +UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs *args) { + return label_create(obj, args, XmALIGNMENT_CENTER); +} + +UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs *args) { + return label_create(obj, args, XmALIGNMENT_BEGINNING); +} + +UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs *args) { + return label_create(obj, args, XmALIGNMENT_END); +} + + +/* -------------------------- progressbar/spiner -------------------------- */ + +static void ui_destroy_progressbar(Widget w, UiProgressBar *pb, XtPointer d) { + // TODO: free other stuff + free(pb); +} + +static void ui_progressbar_expose(Widget widget, UiProgressBar *pb, XtPointer c) { + Display *dp = XtDisplay(widget); + Window w = XtWindow(widget); + if(w == 0) { + return; + } + if(!pb->gc) { + XGCValues gcvals; + gcvals.foreground = pb->color; + pb->gc = XCreateGC(dp, w, (GCForeground), &gcvals); + } + + Dimension width = widget->core.width; + Dimension height = widget->core.height; + + double value = (pb->value - pb->min) / (pb->max - pb->min); + Dimension valueW = (double)width * value; + + XClearArea(dp, w, 0, 0, width, height, False); + XFillRectangle(dp, w, pb->gc, 0, 0, valueW, widget->core.height); +} + +UIWIDGET ui_progressbar_create(UiObject *obj, UiProgressbarArgs *args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + + char *name = args->name ? (char*)args->name : "progressbar"; + Widget frame = XmCreateFrame(parent, name, xargs, n); + ui_container_add(ctn, frame); + + // create a button and get some informations about the height, shadow, highlight, .... + // we want the frame to have the same dimensions as a normal button + Widget test = XmCreatePushButton(frame, "button", NULL, 0); + XtManageChild(test); + Dimension h, highlightThickness, shadowThickness; + Pixel highlightColor; + XtVaGetValues(test, XmNheight, &h, XmNhighlightThickness, &highlightThickness, + XmNshadowThickness, &shadowThickness, XmNhighlightColor, &highlightColor, NULL); + XtDestroyWidget(test); + + // adjust frame + XtVaSetValues(frame, XmNshadowThickness, shadowThickness, gridMarginLeft, highlightThickness, + gridMarginRight, highlightThickness, gridMarginTop, highlightThickness, + gridMarginBottom, highlightThickness, NULL); + + // create drawing area + Dimension da_height = h - 2*highlightThickness - 2*shadowThickness; + n = 0; + XtSetArg(xargs[n], XmNheight, da_height); n++; + Widget drawingArea = XmCreateDrawingArea(frame, "progressbar_drawingarea", xargs, n); + XtManageChild(drawingArea); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_DOUBLE); + + UiProgressBar *progressbarData = malloc(sizeof(UiProgressBar)); + progressbarData->widget = drawingArea; + progressbarData->min = args->min; + progressbarData->max = args->max == 0 ? 100 : args->max; + progressbarData->value = 50; + progressbarData->var = var; + progressbarData->color = highlightColor; + progressbarData->gc = NULL; // initialize on first expose + + if(var) { + UiDouble *d = var->value; + progressbarData->value = d->value; + d->obj = progressbarData; + d->get = ui_progressbar_get; + d->set = ui_progressbar_set; + } + + XtAddCallback( + drawingArea, + XmNexposeCallback, + (XtCallbackProc)ui_progressbar_expose, + progressbarData); + XtAddCallback( + drawingArea, + XmNresizeCallback, + (XtCallbackProc)ui_progressbar_expose, + progressbarData); + + + XtManageChild(frame); + return frame; +} + +double ui_progressbar_get(UiDouble *d) { + UiProgressBar *pb = d->obj; + d->value = pb->value; + return d->value; +} + +void ui_progressbar_set(UiDouble *d, double value) { + UiProgressBar *pb = d->obj; + d->value = value; + pb->value = value; + ui_progressbar_expose(pb->widget, pb, NULL); +} + + +UIWIDGET ui_progressspinner_create(UiObject* obj, UiProgressbarSpinnerArgs *args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + + XmString label = XmStringCreateSimple(""); + XtSetArg(xargs[n], XmNlabelString, label); n++; + XtSetArg(xargs[n], XmNalignment, XmALIGNMENT_END); n++; + XtSetArg(xargs[n], gridMinWidth, 40); n++; + + char *name = args->name ? (char*)args->name : "progresss_spinner"; + Widget w = XmCreateLabel(parent, name, xargs, n); + XtManageChild(w); + ui_container_add(ctn, w); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + if(var) { + UiInteger *value = var->value; + value->obj = w; + value->get = ui_progressspinner_get; + value->set = ui_progressspinner_set; + + if(value->value) { + ui_progressspinner_set(value, 1); + } + } + + + XmStringFree(label); + return w; +} + +int64_t ui_progressspinner_get(UiInteger *i) { + return i->value; +} + +void ui_progressspinner_set(UiInteger *i, int64_t value) { + Widget w = i->obj; + XmString label; + if(value) { + char str[4]; + snprintf(str, 4, "%c", 150); + label = XmStringCreateSimple(str); + } else { + label = XmStringCreateSimple(""); + } + XtVaSetValues(w, XmNlabelString, label, NULL); + XmStringFree(label); + i->value = value; +} diff --git a/ui/motif/label.h b/ui/motif/label.h new file mode 100644 index 0000000..a79dd64 --- /dev/null +++ b/ui/motif/label.h @@ -0,0 +1,61 @@ +/* + * 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 LABEL_H +#define LABEL_H + +#include "../ui/display.h" +#include "../common/context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiProgressBar { + Widget widget; + GC gc; + UiVar *var; + double min; + double max; + double value; + Pixel color; +} UiProgressBar; + + +double ui_progressbar_get(UiDouble *d); +void ui_progressbar_set(UiDouble *d, double value); + +int64_t ui_progressspinner_get(UiInteger *i); +void ui_progressspinner_set(UiInteger *i, int64_t value); + +#ifdef __cplusplus +} +#endif + +#endif /* LABEL_H */ + diff --git a/ui/motif/list.c b/ui/motif/list.c new file mode 100644 index 0000000..0bb1cbe --- /dev/null +++ b/ui/motif/list.c @@ -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. + */ + +#include +#include + +#include "container.h" + +#include "list.h" +#include "../common/object.h" + +static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata; + return getvalue(elm, col); +} + +/* +static void* model_getvalue(UiModel *model, UiList *list, void *elm, int row, int col, UiBool *freeResult) { + if(model->getvalue2) { + return model->getvalue2(list, elm, row, col, model->getvalue2data, freeResult); + } else if(model->getvalue) { + return model->getvalue(elm, col); + } + return NULL; +} +*/ + +UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + if(args->multiselection) { + XtSetArg(xargs[n], XmNselectionPolicy, XmEXTENDED_SELECT); n++; + } else { + XtSetArg(xargs[n], XmNselectionPolicy, XmSINGLE_SELECT); n++; + } + + char *name = args->name ? (char*)args->name : "listview"; + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + Widget widget = XmCreateScrolledList(parent, name, xargs, n); + XtManageChild(widget); + ui_container_add(ctn, widget); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + UiListView *listview = malloc(sizeof(UiListView)); + memset(listview, 0, sizeof(UiListView)); + listview->obj = obj; + listview->widget = widget; + if(args->getvalue2) { + listview->getvalue = args->getvalue2; + listview->getvaluedata = args->getvalue2data; + } else if(args->getvalue) { + listview->getvalue = getvalue_wrapper; + listview->getvaluedata = args->getvalue; + } else { + listview->getvalue = getvalue_wrapper; + listview->getvaluedata = ui_strmodel_getvalue; + } + listview->var = var; + listview->onactivate = args->onactivate; + listview->onactivatedata = args->onactivatedata; + listview->onselection = args->onselection; + listview->onselectiondata = args->onselectiondata; + + if(var) { + UiList *list = var->value; + list->obj = listview; + list->update = ui_listview_update; + list->getselection = ui_listview_getselection; + list->setselection = ui_listview_setselection; + ui_listview_update(list, 0); + } + + XtAddCallback( + widget, + XmNdestroyCallback, + (XtCallbackProc)ui_listview_destroy, + listview); + + XtAddCallback( + widget, + XmNdefaultActionCallback, + (XtCallbackProc)ui_listview_activate, + listview); + XtAddCallback( + widget, + XmNextendedSelectionCallback, + (XtCallbackProc)ui_listview_selection, + listview); + XtAddCallback( + widget, + XmNsingleSelectionCallback, + (XtCallbackProc)ui_listview_selection, + listview); + + return widget; +} + +void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d) { + // TODO +} + +static void list_callback(UiObject *obj, UiListSelection sel, ui_callback callback, void *userdata) { + UiEvent event; + event.obj = obj; + event.window = obj->window; + event.document = obj->ctx->document; + event.eventdata = &sel; + event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; + event.intval = sel.count > 0 ? sel.rows[0] : -1; + callback(&event, userdata); +} + +static void listview_save_selection(UiListView *listview, XmListCallbackStruct *cb) { + UiListSelection sel = { cb->selected_item_count, NULL }; + if(sel.count > 0) { + sel.rows = calloc(sel.count, sizeof(int)); + for(int i=0;iselected_item_positions[i]-1; + } + } + free(listview->current_selection.rows); + listview->current_selection = sel; +} + +void ui_listview_activate(Widget w, UiListView *listview, XmListCallbackStruct *cb) { + listview_save_selection(listview, cb); + if(listview->onactivate) { + list_callback(listview->obj, listview->current_selection, listview->onactivate, listview->onactivatedata); + } +} + +void ui_listview_selection(Widget w, UiListView *listview, XmListCallbackStruct *cb) { + listview_save_selection(listview, cb); + if(listview->onselection) { + list_callback(listview->obj, listview->current_selection, listview->onselection, listview->onselectiondata); + } +} + +static XmStringTable create_stringlist(UiList *list, ui_getvaluefunc2 getvalue, void *getvaluedata, 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(UiList *list, int i) { + UiListView *listview = list->obj; + + int count; + XmStringTable items = create_stringlist( + list, + listview->getvalue, + listview->getvaluedata, + &count); + + XtVaSetValues( + listview->widget, + XmNitems, count == 0 ? NULL : items, + XmNitemCount, + count, + NULL); + + for (int i=0;iobj; + UiListSelection sel = { listview->current_selection.count, NULL }; + if(sel.count > 0) { + sel.rows = calloc(sel.count, sizeof(int)); + memcpy(sel.rows, listview->current_selection.rows, sel.count*sizeof(int)); + } + return sel; +} + +void ui_listview_setselection(UiList *list, UiListSelection selection) { + ui_setop_enable(TRUE); + UiListView *listview = list->obj; + XmListDeselectAllItems(listview->widget); + for(int i=0;iwidget, selection.rows[i]+1, False); + } + ui_setop_enable(FALSE); +} + +void* ui_strmodel_getvalue(void *elm, int column) { + return column == 0 ? elm : NULL; +} + +/* ------------------------------- Drop Down ------------------------------- */ + +static void ui_dropdown_selection( + Widget w, + UiListView *listview, + XmComboBoxCallbackStruct *cb) +{ + int index = cb->item_position; + void *elm = NULL; + if(listview->var) { + UiList *list = listview->var->value; + elm = ui_list_get(list, index); + } + + UiEvent event; + event.obj = listview->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = elm; + event.eventdatatype = UI_EVENT_DATA_LIST_ELM; + event.intval = index; + if(listview->onactivate) { + listview->onactivate(&event, listview->onactivatedata); + } + if(listview->onselection) { + listview->onselection(&event, listview->onselectiondata); + } +} + +UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + char *name = args->name ? (char*)args->name : "dropdown"; + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + Widget widget = XmCreateDropDownList(parent, name, xargs, n); + XtManageChild(widget); + ui_container_add(ctn, widget); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + UiListView *listview = malloc(sizeof(UiListView)); + memset(listview, 0, sizeof(UiListView)); + listview->obj = obj; + listview->widget = widget; + if(args->getvalue2) { + listview->getvalue = args->getvalue2; + listview->getvaluedata = args->getvalue2data; + } else if(args->getvalue) { + listview->getvalue = getvalue_wrapper; + listview->getvaluedata = args->getvalue; + } else { + listview->getvalue = getvalue_wrapper; + listview->getvaluedata = ui_strmodel_getvalue; + } + listview->var = var; + listview->onactivate = args->onactivate; + listview->onactivatedata = args->onactivatedata; + listview->onselection = args->onselection; + listview->onselectiondata = args->onselectiondata; + + if(var) { + UiList *list = var->value; + list->obj = listview; + list->update = ui_listview_update; + list->getselection = ui_listview_getselection; + list->setselection = ui_listview_setselection; + ui_listview_update(list, 0); + } + + XtAddCallback( + widget, + XmNdestroyCallback, + (XtCallbackProc)ui_listview_destroy, + listview); + XtAddCallback( + widget, + XmNselectionCallback, + (XtCallbackProc)ui_dropdown_selection, + listview); + + return widget; +} diff --git a/ui/motif/list.h b/ui/motif/list.h new file mode 100644 index 0000000..2d88c72 --- /dev/null +++ b/ui/motif/list.h @@ -0,0 +1,79 @@ +/* + * 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 { + UiObject *obj; + Widget widget; + UiVar *var; + UiModel* model; + ui_getvaluefunc2 getvalue; + void *getvaluedata; + + UiListSelection current_selection; + + 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; +} UiListView; + +void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d); + +void ui_listview_activate(Widget w, UiListView *listview, XmListCallbackStruct *cb); +void ui_listview_selection(Widget w, UiListView *listview, XmListCallbackStruct *cb); + +void ui_listview_update(UiList *list, int i); +UiListSelection ui_listview_getselection(UiList *list); +void ui_listview_setselection(UiList *list, UiListSelection selection); + +void* ui_strmodel_getvalue(void *elm, int column); + +#ifdef __cplusplus +} +#endif + +#endif /* LIST_H */ + diff --git a/ui/motif/menu.c b/ui/motif/menu.c new file mode 100644 index 0000000..c2038a8 --- /dev/null +++ b/ui/motif/menu.c @@ -0,0 +1,349 @@ +/* + * 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 "../common/menu.h" +#include "../common/types.h" +#include "../ui/window.h" + +#include +#include + + +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 +}; + +void ui_create_menubar(UiObject *obj, Widget window) { + UiMenu *menus_begin = uic_get_menu_list(); + if(!menus_begin) { + return; + } + + Widget menubar = XmCreateMenuBar(window, "menubar", NULL, 0); + XtManageChild(menubar); + + UiMenu *ls = menus_begin; + while(ls) { + UiMenu *menu = ls; + add_menu_widget(menubar, 0, &menu->item, obj); + ls = (UiMenu*)ls->item.next; + } +} + +void ui_add_menu_items(Widget 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(Widget parent, int i, UiMenuItemI *item, UiObject *obj) { + UiMenu *menu = (UiMenu*)item; + Arg args[4]; + int n = 0; + + XmString s = NULL; + if(menu->label) { + s = XmStringCreateLocalized((char*)menu->label); + XtSetArg(args[n], XmNlabelString, s); n++; + } + + Widget submenu = XmVaCreateSimplePulldownMenu(parent, "menu_pulldown", i, NULL, NULL); + XtSetArg(args[n], XmNsubMenuId, submenu); n++; + Widget menuItem = XtCreateManagedWidget( + "menuitem", + xmCascadeButtonWidgetClass, + parent, + args, + n); + + + if(s) { + XmStringFree(s); + } + + ui_add_menu_items(submenu, i, menu, obj); +} + +void add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) { + UiMenuItem *it = (UiMenuItem*)item; + + XmString s = NULL; + Arg args[4]; + int n = 0; + if(it->label) { + s = XmStringCreateLocalized((char*)it->label); + XtSetArg(args[n], XmNlabelString, s); n++; + } + + Widget mitem = XtCreateManagedWidget( + "menubutton", + xmPushButtonWidgetClass, + parent, + args, + n); + if(s) { + XmStringFree(s); + } + + if(it->callback) { + UiEventData *eventdata = malloc(sizeof(UiEventData)); + eventdata->callback = it->callback; + eventdata->userdata = it->userdata; + eventdata->obj = obj; + eventdata->value = 0; + XtAddCallback( + mitem, + XmNactivateCallback, + (XtCallbackProc)ui_push_button_callback, + eventdata); + XtAddCallback( + mitem, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + eventdata); + } + + ui_set_widget_groups(obj->ctx, mitem, it->groups); +} + +void add_menuseparator_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) { + Widget s = XmCreateSeparatorGadget (p, "menuseparator", NULL, 0); + XtManageChild(s); +} + +void add_checkitem_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) { + UiMenuCheckItem *it = (UiMenuCheckItem*)item; + + Arg args[4]; + int n = 0; + XmString s = NULL; + if(it->label) { + s = XmStringCreateLocalized(it->label); + XtSetArg(args[n], XmNlabelString, s); n++; + } + + //XtSetArg(args[n], XmNvisibleWhenOff, 0); n++; + Widget checkbox = XtCreateManagedWidget( + "menutogglebutton", + xmToggleButtonWidgetClass, + p, + args, + n); + if(s) { + XmStringFree(s); + } + + ui_bind_togglebutton(obj, checkbox, it->varname, NULL, it->callback, it->userdata, 0); + + ui_set_widget_groups(obj->ctx, checkbox, it->groups); +} + +void add_radioitem_widget(Widget p, int index, UiMenuItemI *item, UiObject *obj) { + UiMenuRadioItem *it = (UiMenuRadioItem*)item; + + Arg args[4]; + int n = 0; + XmString s = NULL; + if(it->label) { + s = XmStringCreateLocalized(it->label); + XtSetArg(args[n], XmNlabelString, s); n++; + } + XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY_ROUND); n++; + + Widget button = XmCreateToggleButton(p, "menuradiobutton", args, n); + XtManageChild(button); + + ui_bind_radiobutton(obj, button, NULL, it->varname, it->callback, it->userdata, 0); +} + +static void menuitem_list_remove_binding(void *obj) { + UiActiveMenuItemList *ls = obj; + UiList *list = ls->var->value; + CxList *bindings = list->obj; + if(bindings) { + (void)cxListFindRemove(bindings, obj); + if(cxListSize(bindings) == 0) { + cxListFree(bindings); + list->obj = NULL; + list->update = NULL; + } + } +} + +void add_menuitem_list_widget(Widget p, int i, 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 = p; + ls->index = i; + ls->oldcount = 0; + ls->getvalue = il->getvalue; + ls->callback = il->callback; + ls->userdata = il->userdata; + ls->addseparator = il->addseparator; + + ls->var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); + if(ls->var) { + UiList *list = ls->var->value; + list->update = ui_menulist_update; + list->getselection = NULL; + list->setselection = NULL; + + // It is possible, that the UiVar is from a global shared context, + // used by multiple windows. To support this usecase, the list->obj + // binding object is a list of all connected UiActiveMenuItemList. + CxList *bindings = list->obj; + if(!bindings) { + bindings = cxLinkedListCreate(ls->var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS); + list->obj = bindings; + } + cxListAdd(bindings, ls); + + // The destruction of the toplevel obj must remove the menulist binding + uic_context_add_destructor(obj->ctx, menuitem_list_remove_binding, ls); + + ui_update_menuitem_list(ls); + } +} + +void ui_menulist_update(UiList *list, int ignored) { + CxList *bindings = list->obj; + CxIterator i = cxListIterator(bindings); + cx_foreach(UiActiveMenuItemList *, ls, i) { + ui_update_menuitem_list(ls); + } +} + +void ui_update_menuitem_list(UiActiveMenuItemList *list) { + XmString s = NULL; + Arg args[4]; + int n; + + UiList *ls; + if(list->var && list->var->value) { + ls = list->var->value; + } else { + return; + } + + 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]); + } + } + + void* elm = ui_list_first(ls); + int i = 0; + if(elm && list->addseparator) { + XtSetArg(args[0], XmNpositionIndex, list->index); + Widget s = XmCreateSeparatorGadget(list->menu, "menuseparator", args, 1); + XtManageChild(s); + i++; + } + + ui_getvaluefunc getvalue = list->getvalue; + int pos = list->index; + while(elm) { + n = 0; + char *label = (char*) (getvalue ? getvalue(elm, 0) : elm); + if(label) { + s = XmStringCreateLocalized(label); + XtSetArg(args[n], XmNlabelString, s); n++; + } + XtSetArg(args[n], XmNpositionIndex, pos+i); n++; + + Widget mitem = XtCreateManagedWidget( + "menubutton", + xmPushButtonWidgetClass, + list->menu, + args, + n); + if(s) { + XmStringFree(s); + } + + if(list->callback) { + UiEventData *eventdata = malloc(sizeof(UiEventData)); + eventdata->callback = list->callback; + eventdata->userdata = list->userdata; + eventdata->obj = list->object; + eventdata->value = 0; + XtAddCallback( + mitem, + XmNactivateCallback, + (XtCallbackProc)ui_push_button_callback, + eventdata); + XtAddCallback( + mitem, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + eventdata); + } + + elm = ui_list_next(ls); + i++; + } + + list->oldcount = i; +} diff --git a/ui/motif/menu.h b/ui/motif/menu.h new file mode 100644 index 0000000..efa99ce --- /dev/null +++ b/ui/motif/menu.h @@ -0,0 +1,75 @@ +/* + * 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" +#include "../common/context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiActiveMenuItemList UiActiveMenuItemList; +struct UiActiveMenuItemList { + UiObject *object; + Widget menu; + int index; + int oldcount; + UiVar *var; + ui_getvaluefunc getvalue; + ui_callback callback; + void *userdata; + bool addseparator; +}; + +typedef void(*ui_menu_add_f)(Widget, int, UiMenuItemI*, UiObject*); + +void ui_create_menubar(UiObject *obj, Widget window); +void ui_add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); + +void add_menu_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); +void add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj); +void add_menuitem_st_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj); +void add_menuseparator_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj); +void add_checkitem_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj); +void add_radioitem_widget(Widget p, int index, UiMenuItemI *item, UiObject *obj); +void add_checkitemnv_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj); +void add_menuitem_list_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj); + +void ui_menulist_update(UiList *list, int ignored); +void ui_update_menuitem_list(UiActiveMenuItemList *list); + +#ifdef __cplusplus +} +#endif + +#endif /* MENU_H */ + diff --git a/ui/motif/objs.mk b/ui/motif/objs.mk new file mode 100644 index 0000000..b1b51b2 --- /dev/null +++ b/ui/motif/objs.mk @@ -0,0 +1,50 @@ +# +# 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 += widget.o +MOTIFOBJ += container.o +MOTIFOBJ += menu.o +MOTIFOBJ += toolbar.o +MOTIFOBJ += button.o +MOTIFOBJ += label.o +MOTIFOBJ += text.o +MOTIFOBJ += list.o +MOTIFOBJ += graphics.o +MOTIFOBJ += range.o +MOTIFOBJ += dnd.o +MOTIFOBJ += image.o +MOTIFOBJ += Grid.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..81416cc --- /dev/null +++ b/ui/motif/range.c @@ -0,0 +1,36 @@ +/* + * 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" + diff --git a/ui/motif/range.h b/ui/motif/range.h new file mode 100644 index 0000000..0dbb77e --- /dev/null +++ b/ui/motif/range.h @@ -0,0 +1,46 @@ +/* + * 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 + + + +#ifdef __cplusplus +} +#endif + +#endif /* RANGE_H */ + diff --git a/ui/motif/stock.c b/ui/motif/stock.c new file mode 100644 index 0000000..190fdbd --- /dev/null +++ b/ui/motif/stock.c @@ -0,0 +1,36 @@ +/* + * 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 + + diff --git a/ui/motif/stock.h b/ui/motif/stock.h new file mode 100644 index 0000000..c3cdb7f --- /dev/null +++ b/ui/motif/stock.h @@ -0,0 +1,45 @@ +/* + * 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 + + + +#ifdef __cplusplus +} +#endif + +#endif /* STOCK_H */ + diff --git a/ui/motif/text.c b/ui/motif/text.c new file mode 100644 index 0000000..5c5db1f --- /dev/null +++ b/ui/motif/text.c @@ -0,0 +1,1045 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 + + +/* ------------------------------ Text Area ------------------------------ */ + +UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs *args) { + Arg xargs[16]; + int n = 0; + + XtSetArg(xargs[n], XmNeditMode, XmMULTI_LINE_EDIT); n++; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + char *name = args->name ? (char*)args->name : "textarea"; + XtSetArg(xargs[n], XmNwidth, 100); n++; + Widget widget = XmCreateScrolledText(parent, name, xargs, n); + XtManageChild(widget); + ui_container_add(ctn, widget); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_TEXT); + + UiTextArea *textarea = malloc(sizeof(UiTextArea)); + memset(textarea, 0, sizeof(UiTextArea)); + textarea->obj = obj; + textarea->var = var; + + if(var) { + UiText *value = var->value; + if(value->value.ptr) { + XmTextSetString(widget, value->value.ptr); + value->value.free(value->value.ptr); + value->value.ptr = NULL; + } + + value->save = ui_textarea_save; + value->restore = ui_textarea_restore; + value->destroy = ui_textarea_text_destroy; + 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 = widget; + + if(!value->data2) { + value->data2 = ui_create_undomgr(); + } + + XtAddCallback( + widget, + XmNmodifyVerifyCallback, + (XtCallbackProc)ui_text_modify_callback, + var); + } + + return widget; +} + +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_save(UiText *text) { + (void)ui_textarea_get(text); +} + +void ui_textarea_restore(UiText *text) { + if(text->value.ptr) { + ui_textarea_set(text, text->value.ptr); + } +} + +void ui_textarea_text_destroy(UiText *text) { + if(text->value.free) { + text->value.free(text->value.ptr); + } + if(text->data2) { + ui_destroy_undomgr(text->data2); + } +} + +void ui_textarea_set(UiText *text, const char *str) { + XmTextSetString(text->obj, (char*)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); +} + + + +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->obj->ctx, UI_GROUP_SELECTION); + } else { + ui_unset_group(textarea->obj->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->data2) { + value->data2 = ui_create_undomgr(); + } + + XmTextVerifyCallbackStruct *txv = (XmTextVerifyCallbackStruct*)data; + int type = txv->text->length > 0 ? UI_TEXTBUF_INSERT : UI_TEXTBUF_DELETE; + UiUndoMgr *mgr = value->data2; + 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->data2; + + 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->data2; + + 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; + } +} + + + +/* ------------------------------ Text Field ------------------------------ */ + +static UIWIDGET create_textfield(UiObject *obj, UiTextFieldArgs *args, int frameless, int password) { + Arg xargs[16]; + int n = 0; + + if(frameless) { + XtSetArg(xargs[n], XmNshadowThickness, 0); + n++; + } + if(password) { + // TODO + } + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + char *name = args->name ? (char*)args->name : "textfield"; + Widget textfield = XmCreateTextField(parent, name, xargs, n); + XtManageChild(textfield); + ui_container_add(ctn, textfield); + + ui_set_widget_groups(obj->ctx, textfield, args->groups); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); + if(var) { + UiString *value = (UiString*)var->value; + value->obj = textfield; + value->get = ui_textfield_get; + value->set = ui_textfield_set; + + if(value->value.ptr) { + ui_textfield_set(value, value->value.ptr); + } + } + + return textfield; +} + +UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs *args) { + return create_textfield(obj, args, FALSE, FALSE); +} + +UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs *args) { + return create_textfield(obj, args, TRUE, FALSE); +} + +UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs *args) { + return create_textfield(obj, args, FALSE, FALSE); +} + +char* ui_textfield_get(UiString *str) { + if(str->value.free) { + str->value.free(str->value.ptr); + } + char *value = XmTextFieldGetString(str->obj); + str->value.ptr = value; + str->value.free = (ui_freefunc)XtFree; + return value; +} + +void ui_textfield_set(UiString *str, const char *value) { + XmTextFieldSetString(str->obj, (void*)value); + if(str->value.free) { + str->value.free(str->value.ptr); + } + str->value.ptr = NULL; +} + + + + + +/* -------------------- path bar -------------------- */ + +#define XNECreateText(parent,name,args,count) XmCreateTextField(parent,name,args,count) +#define XNETextSetString(widget,value) XmTextFieldSetString(widget,value) +#define XNETextGetString(widget) XmTextFieldGetString(widget) +#define XNETextGetLastPosition(widget) XmTextFieldGetLastPosition(widget) +#define XNETextSetInsertionPosition(widget, i) XmTextFieldSetInsertionPosition(widget, i) +#define XNETextSetSelection(w, f, l, t) XmTextFieldSetSelection(w, f, l, t) + +typedef void(*updatedir_callback)(void*,char*,int); + +typedef struct PathBar { + Widget widget; + Widget textfield; + + Widget focus_widget; + + Widget left; + Widget right; + Dimension lw; + Dimension rw; + + int shift; + + UiPathElm *current_pathelms; + Widget *pathSegments; + size_t numSegments; + size_t segmentAlloc; + + char *path; + int selection; + Boolean input; + + int focus; + + updatedir_callback updateDir; + void *updateDirData; + + ui_pathelm_func getpathelm; + void *getpathelmdata; +} PathBar; + +void PathBarSetPath(PathBar *bar, const char *path); + +void pathbar_resize(Widget w, PathBar *p, XtPointer d) +{ + Dimension width, height; + XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); + + Dimension *segW = (void*)XtCalloc(p->numSegments, sizeof(Dimension)); + + Dimension maxHeight = 0; + + /* get width/height from all widgets */ + Dimension pathWidth = 0; + for(int i=0;inumSegments;i++) { + Dimension segWidth; + Dimension segHeight; + XtVaGetValues(p->pathSegments[i], XmNwidth, &segWidth, XmNheight, &segHeight, NULL); + segW[i] = segWidth; + pathWidth += segWidth; + if(segHeight > maxHeight) { + maxHeight = segHeight; + } + } + Dimension tfHeight; + XtVaGetValues(p->textfield, XmNheight, &tfHeight, NULL); + if(tfHeight > maxHeight) { + maxHeight = tfHeight; + } + + Boolean arrows = False; + if(pathWidth + 10 > width) { + arrows = True; + pathWidth += p->lw + p->rw; + } + + /* calc max visible widgets */ + int start = 0; + if(arrows) { + Dimension vis = p->lw+p->rw; + for(int i=p->numSegments;i>0;i--) { + Dimension segWidth = segW[i-1]; + if(vis + segWidth + 10 > width) { + start = i; + arrows = True; + break; + } + vis += segWidth; + } + } else { + p->shift = 0; + } + + int leftShift = 0; + if(p->shift < 0) { + if(start + p->shift < 0) { + leftShift = start; + start = 0; + p->shift = -leftShift; + } else { + leftShift = -p->shift; /* negative shift */ + start += p->shift; + } + } + + int x = 0; + if(arrows) { + XtManageChild(p->left); + XtManageChild(p->right); + x = p->lw; + } else { + XtUnmanageChild(p->left); + XtUnmanageChild(p->right); + } + + for(int i=0;inumSegments;i++) { + if(i >= start && i < p->numSegments - leftShift && !p->input) { + XtVaSetValues(p->pathSegments[i], XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); + x += segW[i]; + XtManageChild(p->pathSegments[i]); + } else { + XtUnmanageChild(p->pathSegments[i]); + } + } + + if(arrows) { + XtVaSetValues(p->left, XmNx, 0, XmNy, 0, XmNheight, maxHeight, NULL); + XtVaSetValues(p->right, XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); + } + + free(segW); + + Dimension rw, rh; + XtMakeResizeRequest(w, width, maxHeight, &rw, &rh); + + XtVaSetValues(p->textfield, XmNwidth, rw, XmNheight, rh, NULL); +} + +static void pathbarActivateTF(PathBar *p) +{ + XtUnmanageChild(p->left); + XtUnmanageChild(p->right); + XNETextSetSelection(p->textfield, 0, XNETextGetLastPosition(p->textfield), 0); + XtManageChild(p->textfield); + p->input = 1; + + XmProcessTraversal(p->textfield, XmTRAVERSE_CURRENT); + + pathbar_resize(p->widget, p, NULL); +} + +void PathBarActivateTextfield(PathBar *p) +{ + p->focus = 1; + pathbarActivateTF(p); +} + +void pathbar_input(Widget w, PathBar *p, XtPointer c) +{ + XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c; + XEvent *xevent = cbs->event; + + if (cbs->reason == XmCR_INPUT) { + if (xevent->xany.type == ButtonPress) { + p->focus = 0; + pathbarActivateTF(p); + } + } +} + +void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c) +{ + if(--p->focus < 0) { + p->input = False; + XtUnmanageChild(p->textfield); + } +} + +static cxmutstr 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; +} + +static char* ConcatPath(const char *path1, const char *path2) { + return concat_path_s(cx_str(path1), cx_str(path2)).ptr; +} + +void pathbar_pathinput(Widget w, PathBar *p, XtPointer d) +{ + char *newpath = XNETextGetString(p->textfield); + if(newpath) { + if(newpath[0] == '~') { + char *p = newpath+1; + char *home = getenv("HOME"); + char *cp = ConcatPath(home, p); + XtFree(newpath); + newpath = cp; + } else if(newpath[0] != '/') { + char curdir[2048]; + curdir[0] = 0; + getcwd(curdir, 2048); + char *cp = ConcatPath(curdir, newpath); + XtFree(newpath); + newpath = cp; + } + + /* update path */ + PathBarSetPath(p, newpath); + if(p->updateDir) { + p->updateDir(p->updateDirData, newpath, -1); + } + XtFree(newpath); + + /* hide textfield and show path as buttons */ + XtUnmanageChild(p->textfield); + pathbar_resize(p->widget, p, NULL); + + if(p->focus_widget) { + XmProcessTraversal(p->focus_widget, XmTRAVERSE_CURRENT); + } + } +} + +void pathbar_shift_left(Widget w, PathBar *p, XtPointer d) +{ + p->shift--; + pathbar_resize(p->widget, p, NULL); +} + +void pathbar_shift_right(Widget w, PathBar *p, XtPointer d) +{ + if(p->shift < 0) { + p->shift++; + } + pathbar_resize(p->widget, p, NULL); +} + +static void pathTextEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) { + PathBar *pb = data; + if(event->type == KeyReleaseMask) { + if(event->xkey.keycode == 9) { + XtUnmanageChild(pb->textfield); + pathbar_resize(pb->widget, pb, NULL); + *dispatch = False; + } else if(event->xkey.keycode == 36) { + pathbar_pathinput(pb->textfield, pb, NULL); + *dispatch = False; + } + } +} + +PathBar* CreatePathBar(Widget parent, ArgList args, int n) +{ + PathBar *bar = (PathBar*)XtMalloc(sizeof(PathBar)); + bar->path = NULL; + bar->updateDir = NULL; + bar->updateDirData = NULL; + + bar->focus_widget = NULL; + + bar->getpathelm = NULL; + bar->getpathelmdata = NULL; + bar->current_pathelms = NULL; + + bar->shift = 0; + + XtSetArg(args[n], XmNmarginWidth, 0); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + bar->widget = XmCreateDrawingArea(parent, "pathbar", args, n); + XtAddCallback( + bar->widget, + XmNresizeCallback, + (XtCallbackProc)pathbar_resize, + bar); + XtAddCallback( + bar->widget, + XmNinputCallback, + (XtCallbackProc)pathbar_input, + bar); + + Arg a[4]; + XtSetArg(a[0], XmNshadowThickness, 0); + XtSetArg(a[1], XmNx, 0); + XtSetArg(a[2], XmNy, 0); + bar->textfield = XNECreateText(bar->widget, "pbtext", a, 3); + bar->input = 0; + XtAddCallback( + bar->textfield, + XmNlosingFocusCallback, + (XtCallbackProc)pathbar_losingfocus, + bar); + XtAddCallback(bar->textfield, XmNactivateCallback, + (XtCallbackProc)pathbar_pathinput, bar); + XtAddEventHandler(bar->textfield, KeyPressMask | KeyReleaseMask, FALSE, pathTextEH, bar); + + XtSetArg(a[0], XmNarrowDirection, XmARROW_LEFT); + bar->left = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtSetArg(a[0], XmNarrowDirection, XmARROW_RIGHT); + bar->right = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtAddCallback( + bar->left, + XmNactivateCallback, + (XtCallbackProc)pathbar_shift_left, + bar); + XtAddCallback( + bar->right, + XmNactivateCallback, + (XtCallbackProc)pathbar_shift_right, + bar); + + Pixel bg; + XtVaGetValues(bar->textfield, XmNbackground, &bg, NULL); + XtVaSetValues(bar->widget, XmNbackground, bg, NULL); + + XtManageChild(bar->left); + XtManageChild(bar->right); + + XtVaGetValues(bar->left, XmNwidth, &bar->lw, NULL); + XtVaGetValues(bar->right, XmNwidth, &bar->rw, NULL); + + bar->segmentAlloc = 16; + bar->numSegments = 0; + bar->pathSegments = (Widget*)XtCalloc(16, sizeof(Widget)); + + bar->selection = 0; + + return bar; +} + +void PathBarChangeDir(Widget w, PathBar *bar, XtPointer c) +{ + XmToggleButtonSetState(bar->pathSegments[bar->selection], False, False); + + int i; + for(i=0;inumSegments;i++) { + if(bar->pathSegments[i] == w) { + bar->selection = i; + XmToggleButtonSetState(w, True, False); + break; + } + } + + UiPathElm elm = bar->current_pathelms[i]; + cxmutstr path = cx_strdup(cx_strn(elm.path, elm.path_len)); + if(bar->updateDir) { + XNETextSetString(bar->textfield, path.ptr); + bar->updateDir(bar->updateDirData, path.ptr, i); + } + free(path.ptr); +} + +static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { + for(int i=0;ipath) { + free(bar->path); + } + bar->path = strdup(path); + + for(int i=0;inumSegments;i++) { + XtDestroyWidget(bar->pathSegments[i]); + } + XtUnmanageChild(bar->textfield); + XtManageChild(bar->left); + XtManageChild(bar->right); + bar->input = False; + + Arg args[4]; + XmString str; + + bar->numSegments = 0; + + ui_pathelm_destroy(bar->current_pathelms, bar->numSegments); + size_t nelm = 0; + UiPathElm* path_elm = bar->getpathelm(bar->path, strlen(bar->path), &nelm, bar->getpathelmdata); + if (!path_elm) { + return; + } + bar->current_pathelms = path_elm; + bar->numSegments = nelm; + bar->pathSegments = realloc(bar->pathSegments, nelm * sizeof(Widget*)); + + for(int i=0;iwidget, "pbbutton", args, 3); + XtAddCallback( + button, + XmNvalueChangedCallback, + (XtCallbackProc)PathBarChangeDir, + bar); + XmStringFree(str); + + bar->pathSegments[i] = button; + } + + bar->selection = bar->numSegments-1; + XmToggleButtonSetState(bar->pathSegments[bar->selection], True, False); + + XNETextSetString(bar->textfield, (char*)path); + XNETextSetInsertionPosition(bar->textfield, XNETextGetLastPosition(bar->textfield)); + + pathbar_resize(bar->widget, bar, NULL); +} + +void PathBarDestroy(PathBar *pathbar) { + if(pathbar->path) { + XtFree(pathbar->path); + } + XtFree((void*)pathbar->pathSegments); + XtFree((void*)pathbar); +} + + +/* ---------------------------- Path Text Field ---------------------------- */ + +static void destroy_pathbar(Widget w, XtPointer *data, XtPointer d) { + PathBar *pathbar = (PathBar*)data; + // TODO: check if there is somonething missing + XtFree((void*)pathbar->pathSegments); + XtFree((void*)pathbar); +} + +// 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 pathbar_activate(void *data, char *path, int index) { + UiEventData *event = data; + UiEvent evt; + evt.obj = event->obj; + evt.window = evt.obj->window; + evt.document = evt.obj->ctx->document; + evt.eventdata = path; + evt.eventdatatype = UI_EVENT_DATA_STRING; + evt.intval = index; + event->callback(&evt, event->userdata); +} + +UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs *args) { + Arg xargs[16]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + // TODO: name + + + PathBar *pathbar = CreatePathBar(parent, xargs, n); + if(!args->getpathelm) { + pathbar->getpathelm= default_pathelm_func; + } else { + pathbar->getpathelm = args->getpathelm; + pathbar->getpathelmdata = args->getpathelmdata; + } + + + XtManageChild(pathbar->widget); + ui_container_add(ctn, pathbar->widget); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); + if (var) { + UiString* value = (UiString*)var->value; + value->obj = pathbar; + 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); + } + } + + if(args->onactivate) { + UiEventData *eventdata = malloc(sizeof(UiEventData)); + eventdata->callback = args->onactivate; + eventdata->userdata = args->onactivatedata; + eventdata->obj = obj; + eventdata->value = 0; + + pathbar->updateDir = pathbar_activate; + pathbar->updateDirData = eventdata; + + XtAddCallback( + pathbar->widget, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_eventdata, + eventdata); + } + + XtAddCallback( + pathbar->widget, + XmNdestroyCallback, + (XtCallbackProc)destroy_pathbar, + pathbar); + + return pathbar->widget; +} + +char* ui_path_textfield_get(UiString *str) { + PathBar *pathbar = str->obj; + str->value.free(str->value.ptr); + char *value = XmTextFieldGetString(pathbar->textfield); + str->value.ptr = value; + str->value.free = (ui_freefunc)XtFree; + return value; +} + +void ui_path_textfield_set(UiString *str, const char *value) { + PathBarSetPath(str->obj, value); +} diff --git a/ui/motif/text.h b/ui/motif/text.h new file mode 100644 index 0000000..a7e4f85 --- /dev/null +++ b/ui/motif/text.h @@ -0,0 +1,101 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 { + UiObject *obj; + UiVar *var; + int last_selection_state; +} UiTextArea; + +void ui_textarea_save(UiText *text); +void ui_textarea_restore(UiText *text); +void ui_textarea_text_destroy(UiText *text); +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, const char *value); + +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/motif/toolbar.c b/ui/motif/toolbar.c new file mode 100644 index 0000000..2860f13 --- /dev/null +++ b/ui/motif/toolbar.c @@ -0,0 +1,44 @@ +/* + * 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 +#include +#include + +#include "toolbar.h" +#include "button.h" +#include "stock.h" +#include "list.h" + +#include +#include +#include + +#include "../common/context.h" + diff --git a/ui/motif/toolbar.h b/ui/motif/toolbar.h new file mode 100644 index 0000000..8b9e3c2 --- /dev/null +++ b/ui/motif/toolbar.h @@ -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. + */ + +#ifndef TOOLBAR_H +#define TOOLBAR_H + +#include "../ui/toolbar.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* TOOLBAR_H */ + diff --git a/ui/motif/toolkit.c b/ui/motif/toolkit.c new file mode 100644 index 0000000..c2a1f2a --- /dev/null +++ b/ui/motif/toolkit.c @@ -0,0 +1,342 @@ +/* + * 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 +#include +#include + +#include "toolkit.h" +#include "toolbar.h" +#include "container.h" +#include "stock.h" +#include "../common/menu.h" +#include "../common/toolbar.h" +#include "../common/document.h" +#include "../common/properties.h" +#include + +#include +#include + +static XtAppContext app; +static Display *display; +static Widget active_window; +static const char *application_name; + +static ui_callback startup_func; +static void *startup_data; +static ui_callback open_func; +static void *open_data; +static ui_callback exit_func; +static void *exit_data; + +static ui_callback appclose_fnc; +static void *appclose_udata; + +static int is_toplevel_realized = 0; + +static int event_pipe[2]; + +static UiBool exit_on_shutdown; + +static String fallback[] = { + //"*fontList: -dt-interface system-medium-r-normal-s*utf*:", + "*renderTable: rt", + "*rt*fontType: FONT_IS_XFT", + "*rt*fontName: Sans", + "*rt*fontSize: 11", + + "*progresss_spinner*renderTable*fontType: FONT_IS_FONT", + "*progresss_spinner*renderTable*fontName: Cursor", + + "*window_frame.shadowType: SHADOW_ETCHED_OUT", + "*window_frame.shadowThickness: 1", + "*togglebutton.shadowThickness: 1", + "*togglebutton.highlightThickness: 2", + + "*ui_test.background: red", + NULL +}; + +void input_proc(XtPointer data, int *source, XtInputId *iid) { + void *ptr; + read(event_pipe[0], &ptr, sizeof(void*)); +} + +void ui_init(const char *appname, int argc, char **argv) { + application_name = appname; + uic_init_global_context(); + + XtToolkitInitialize(); + XtSetLanguageProc(NULL, NULL, NULL); + app = XtCreateApplicationContext(); + XtAppSetFallbackResources(app, fallback); + + display = XtOpenDisplay(app, NULL, appname, appname, NULL, 0, &argc, argv); + + uic_menu_init(); + uic_toolbar_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); +} + +const char* ui_appname() { + return application_name; +} + +Display* ui_motif_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_app_exit_on_shutdown(UiBool exitapp) { + exit_on_shutdown = exitapp; +} + +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(); + if(exit_on_shutdown) { + exit(0); + } +} + +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); + if(!XtIsRealized(obj->widget)) { + XtRealizeWidget(obj->widget); + obj->ref++; + } +} + +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) { + UiJob *job = data; + if(job->finish_callback) { + UiEvent event; + event.obj = job->obj; + event.window = job->obj->window; + event.document = job->obj->ctx->document; + event.intval = 0; + event.eventdata = NULL; + event.eventdatatype = 0; + 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) { + 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) { + 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; +} + +/* + * doesn't work with gnome anymore + */ +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); +} + +void ui_destroy_eventdata(Widget w, XtPointer data, XtPointer d) { + free(data); +} + +void ui_set_widget_groups(UiContext *ctx, Widget 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, Widget 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/motif/toolkit.h b/ui/motif/toolkit.h new file mode 100644 index 0000000..a2397e8 --- /dev/null +++ b/ui/motif/toolkit.h @@ -0,0 +1,105 @@ +/* + * 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 TOOLKIT_H +#define TOOLKIT_H + +#include +#include "../ui/toolkit.h" +#include "../common/context.h" +#include "../common/object.h" + +#ifdef __cplusplus +extern "C" { +#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; + int value; +} UiVarEventData; + +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(); + +Display* ui_motif_get_display(void); + +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); + +void ui_destroy_eventdata(Widget w, XtPointer data, XtPointer d); + +void ui_set_widget_groups(UiContext *ctx, Widget widget, const int *groups) ; +void ui_set_widget_ngroups(UiContext *ctx, Widget widget, const int *groups, size_t ngroups); + +#ifdef __cplusplus +} +#endif + +#endif /* TOOLKIT_H */ + diff --git a/ui/motif/widget.c b/ui/motif/widget.c new file mode 100644 index 0000000..0911a75 --- /dev/null +++ b/ui/motif/widget.c @@ -0,0 +1,78 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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/widget.h" + +#include "container.h" +#include "../common/context.h" +#include "../common/object.h" + + +UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) { + Arg xargs[64]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + Widget widget = create_widget(obj, args, userdata, parent, xargs, n); + XtManageChild(widget); + ui_container_add(ctn, widget); + + return widget; +} + + +UIEXPORT UIWIDGET ui_separator_create(UiObject *obj, UiWidgetArgs *args) { + Arg xargs[64]; + int n = 0; + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + char *name = args->name ? (char*)args->name : "separator"; + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + Widget widget = XmCreateSeparator(parent, name, xargs, n); + XtManageChild(widget); + ui_container_add(ctn, widget); + + return widget; +} + +void ui_widget_set_size(UIWIDGET w, int width, int height) { + +} + +void ui_widget_redraw(UIWIDGET w) { + +} diff --git a/ui/motif/window.c b/ui/motif/window.c new file mode 100644 index 0000000..7c3f638 --- /dev/null +++ b/ui/motif/window.c @@ -0,0 +1,144 @@ +/* + * 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 + +#include "toolkit.h" +#include "menu.h" +#include "toolbar.h" +#include "container.h" +#include "../ui/window.h" +#include "../common/context.h" + +#include "Grid.h" + +#include + +static int nwindows = 0; + +static int window_default_width = 600; +static int window_default_height = 500; + +void ui_window_widget_destroy(UiObject *obj) { + XtDestroyWidget(obj->widget); + uic_object_destroy(obj); + nwindows--; + if(nwindows == 0) { + ui_exit_mainloop(); + } +} + +static void window_close_handler(Widget window, void *udata, void *cdata) { + UiObject *obj = udata; + + uic_context_prepare_close(obj->ctx); + obj->ref--; + if(obj->ref > 0) { + XtUnmapWidget(obj->widget); + } else { + ui_window_widget_destroy(obj); + } +} + + +static UiObject* create_window(const char *title, void *window_data, Boolean simple) { + CxMempool *mp = cxMempoolCreateSimple(256); + const CxAllocator *a = mp->allocator; + UiObject *obj = uic_object_new_toplevel(); + obj->window = window_data; + obj->destroy = ui_window_widget_destroy; + + Arg args[16]; + int n = 0; + XtSetArg(args[n], XmNtitle, title); n++; + XtSetArg(args[n], XmNminWidth, 100); n++; + XtSetArg(args[n], XmNminHeight, 50); n++; + XtSetArg(args[n], XmNwidth, window_default_width); n++; + XtSetArg(args[n], XmNheight, window_default_height); n++; + + Widget toplevel = XtAppCreateShell( + ui_appname(), + "mainwindow", + //applicationShellWidgetClass, + vendorShellWidgetClass, + ui_motif_get_display(), + args, + n); + + Atom wm_delete_window; + wm_delete_window = XmInternAtom( + XtDisplay(toplevel), + "WM_DELETE_WINDOW", + 0); + XmAddWMProtocolCallback( + toplevel, + wm_delete_window, + window_close_handler, + obj); + + Widget window = XtVaCreateManagedWidget( + title, + xmMainWindowWidgetClass, + toplevel, + NULL); + + // menu + if(!simple) { + ui_create_menubar(obj, window); + } + + // content frame + n = 0; + Widget frame = XmCreateFrame(window, "window_frame", args, n); + XtManageChild(frame); + + Widget form = XmCreateForm(frame, "window_form", args, 0); + XtManageChild(form); + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + Widget vbox = XtCreateManagedWidget("window_vbox", gridClass, form, args, n); + UiContainerX *container = ui_box_container(obj, vbox, UI_BOX_VERTICAL); + uic_object_push_container(obj, container); + + obj->widget = toplevel; + 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); +} diff --git a/ui/motif/window.h b/ui/motif/window.h new file mode 100644 index 0000000..33bb66d --- /dev/null +++ b/ui/motif/window.h @@ -0,0 +1,44 @@ +/* + * 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 WINDOW_H +#define WINDOW_H + +#ifdef __cplusplus +extern "C" { +#endif + + + + +#ifdef __cplusplus +} +#endif + +#endif /* WINDOW_H */ + diff --git a/ui/qt/Makefile b/ui/qt/Makefile new file mode 100644 index 0000000..b1a7b08 --- /dev/null +++ b/ui/qt/Makefile @@ -0,0 +1,44 @@ +# +# 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/$(QT_PRO_FILE) + $(QMAKE) -o - $< > $(QT_MAKEFILE) + +$(UI_LIB): $(QT_MAKEFILE) $(OBJ) $(UI_LIB) FORCE + $(MAKE) -f $(QT_MAKEFILE) + $(AR) $(ARFLAGS) $(UI_LIB) $(OBJ) + +$(UI_SHLIB): $(QT_MAKEFILE) $(OBJ) $(UI_LIB_SH) $(UI_LIB) FORCE + $(CXX) -o $(UI_SHLIB) $(LDFLAGS) $(SHLIB_LDFLAGS) $(TK_LDFLAGS) $(OBJ) ../build/ui/qt/*.o -L../build/lib -lucx + +FORCE: + diff --git a/ui/qt/button.cpp b/ui/qt/button.cpp new file mode 100644 index 0000000..db00f6f --- /dev/null +++ b/ui/qt/button.cpp @@ -0,0 +1,239 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_create(UiObject* obj, UiButtonArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + QString str = QString::fromUtf8(args->label); + QPushButton *button = new QPushButton(str); + + if(args->onclick) { + UiEventWrapper *event = new UiEventWrapper(obj, args->onclick, args->onclickdata); + button->connect(button, SIGNAL(clicked()), event, SLOT(slot())); + button->connect(button, SIGNAL(destroyed()), event, SLOT(destroy())); + } + + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(button, layout); + + return button; +} + +void ui_button_set_label(UIWIDGET button, const char *label) { + QString str = QString::fromUtf8(label); + QAbstractButton *b = (QAbstractButton*)button; + b->setText(str); +} + +void ui_button_set_icon(UIWIDGET button, const char *icon) { + // TODO +} + +static void togglebutton_event(UiEvent *event, UiEventWrapper *wrapper) { + QPushButton *button = (QPushButton*)wrapper->customdata1; + event->intval = button->isChecked(); + if(wrapper->var) { + event->eventdata = wrapper->var->value; + event->eventdatatype = UI_EVENT_DATA_INTEGER_VALUE; + } +} + +UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + QString str = QString::fromUtf8(args->label); + QPushButton *button = new QPushButton(str); + button->setCheckable(true); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + + if(args->onchange) { + UiEventWrapper *event = new UiEventWrapper(obj, args->onchange, args->onchangedata); + event->var = var; + event->customdata1 = button; + event->prepare_event = togglebutton_event; + button->connect(button, SIGNAL(clicked()), event, SLOT(slot())); + button->connect(button, SIGNAL(destroyed()), event, SLOT(destroy())); + } + + if(var) { + UiInteger *i = (UiInteger*)var->value; + + if(i->value) { + button->setChecked(true); + } + + i->obj = button; + i->get = ui_togglebutton_get; + i->set = ui_togglebutton_set; + } + + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(button, layout); + + return button; +} + +int64_t ui_togglebutton_get(UiInteger *value) { + QPushButton *button = (QPushButton*)value->obj; + value->value = button->isChecked(); + return value->value; +} + +void ui_togglebutton_set(UiInteger *value, int64_t i) { + QPushButton *button = (QPushButton*)value->obj; + value->value = i; + if(i != 0) { + button->setChecked(true); + } +} + +static void checkbox_event(UiEvent *event, UiEventWrapper *wrapper) { + QPushButton *button = (QPushButton*)wrapper->customdata1; + event->intval = button->isChecked(); + if(wrapper->var) { + event->eventdata = wrapper->var->value; + event->eventdatatype = UI_EVENT_DATA_INTEGER_VALUE; + } +} + + +UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + QString str = QString::fromUtf8(args->label); + QCheckBox *checkbox = new QCheckBox(str); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + + if(args->onchange) { + UiEventWrapper *event = new UiEventWrapper(obj, args->onchange, args->onchangedata); + event->var = var; + event->customdata1 = checkbox; + event->prepare_event = checkbox_event; + checkbox->connect(checkbox, SIGNAL(clicked()), event, SLOT(slot())); + checkbox->connect(checkbox, SIGNAL(destroyed()), event, SLOT(destroy())); + } + + if(var) { + UiInteger *i = (UiInteger*)var->value; + + if(i->value) { + checkbox->setChecked(true); + } + + i->obj = checkbox; + i->get = ui_checkbox_get; + i->set = ui_checkbox_set; + } + + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(checkbox, layout); + + return checkbox; +} + +int64_t ui_checkbox_get(UiInteger *value) { + QPushButton *button = (QPushButton*)value->obj; + value->value = button->isChecked(); + return value->value; +} + +void ui_checkbox_set(UiInteger *value, int64_t i) { + QPushButton *button = (QPushButton*)value->obj; + value->value = i; + if(i != 0) { + button->setChecked(true); + } +} + + +static void radiobutton_event(UiEvent *event, UiEventWrapper *wrapper) { + if(wrapper->var) { + UiInteger *value = (UiInteger*)wrapper->var->value; + event->eventdata = value; + event->eventdatatype = UI_EVENT_DATA_INTEGER_VALUE; + event->intval = value->get(value); + } +} + +UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + QString str = QString::fromUtf8(args->label); + QRadioButton *button = new QRadioButton(str); + button->setAutoExclusive(false); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); + if(var) { + UiInteger *value = (UiInteger*)var->value; + QButtonGroup *buttonGroup = (QButtonGroup*)value->obj; + if(!buttonGroup) { + buttonGroup = new QButtonGroup(); + value->obj = buttonGroup; + } + int id = buttonGroup->buttons().size()+1; + buttonGroup->addButton(button, id); + if(value->value == id) { + button->setChecked(true); + } + value->get = ui_radiobutton_get; + value->set = ui_radiobutton_set; + } + + UiEventWrapper *event = new UiEventWrapper(obj, args->onchange, args->onchangedata); + event->var = var; + event->customdata1 = button; + event->prepare_event = togglebutton_event; + button->connect(button, SIGNAL(clicked()), event, SLOT(slot())); + button->connect(button, SIGNAL(destroyed()), event, SLOT(destroy())); + + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(button, layout); + + return button; +} + +int64_t ui_radiobutton_get(UiInteger *value) { + QButtonGroup *buttonGroup = (QButtonGroup*)value->obj; + value->value = buttonGroup->checkedId(); + return value->value; +} + +void ui_radiobutton_set(UiInteger *value, int64_t 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..862ad49 --- /dev/null +++ b/ui/qt/button.h @@ -0,0 +1,54 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 +#include + +extern "C" { + + +int64_t ui_togglebutton_get(UiInteger *value); +void ui_togglebutton_set(UiInteger *value, int64_t i); + +int64_t ui_checkbox_get(UiInteger *value); +void ui_checkbox_set(UiInteger *value, int64_t i); + +int64_t ui_radiobutton_get(UiInteger *value); +void ui_radiobutton_set(UiInteger *value, int64_t i); + +} + +#endif /* BUTTON_H */ + diff --git a/ui/qt/container.cpp b/ui/qt/container.cpp new file mode 100644 index 0000000..245ba19 --- /dev/null +++ b/ui/qt/container.cpp @@ -0,0 +1,277 @@ +/* + * 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 "../common/object.h" +#include "../common/container.h" + +#include + +#include +#include +#include +#include + +static void delete_container(UiContainerPrivate *ct) { + delete ct; +} + +void ui_obj_add_container(UiObject *obj, UiContainerPrivate *ct) { + UiContainerX *container = (UiContainerX*)ui_malloc(obj->ctx, sizeof(UiContainerX)); + container->close = 0; + container->container = ct; + container->prev = NULL; + container->next = NULL; + ct->container = container; + cxMempoolRegister(obj->ctx->mp, ct, (cx_destructor_func)delete_container); + uic_object_push_container(obj, container); +} + +/* ------------------------ margin helper ------------------------ */ + +QWidget* ui_widget_set_margin(QWidget *w, int left, int right, int top, int bottom) { + if((left | right | top | bottom) == 0) { + return w; + } + QWidget* wrapper = new QWidget; + QVBoxLayout* inner = new QVBoxLayout(wrapper); + inner->setContentsMargins(left, top, right, bottom); + inner->addWidget(w); + + // TODO: handle widget visibility changes + + return wrapper; +} + +/* -------------------- UiBoxContainer -------------------- */ + +UiBoxContainer::UiBoxContainer(QBoxLayout* box) { + this->box = box; + this->direction = box->direction(); + box->setContentsMargins(QMargins(0,0,0,0)); + box->setSpacing(0); +} + +void UiBoxContainer::add(QWidget* widget, UiLayout& layout) { + bool fill = layout.fill; + if(hasStretchedWidget && fill) { + fill = false; + fprintf(stderr, "UiError: container has 2 filled widgets"); + } + + uic_layout_setup_margin(&layout); + widget = ui_widget_set_margin(widget, layout.margin_left, layout.margin_right, layout.margin_top, layout.margin_bottom); + box->addWidget(widget); + if(direction == Qt::LeftToRight) { + box->setAlignment(widget, Qt::AlignTop); + } + + if(!hasStretchedWidget) { + QSpacerItem *newspace = new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + box->removeItem(space); + box->addSpacerItem(newspace); + space = newspace; + } + + if(fill) { + hasStretchedWidget = true; + } + current = widget; +} + +UIWIDGET ui_box(UiObject *obj, UiContainerArgs *args, QBoxLayout::Direction dir) { + UiContainerPrivate *ctn = (UiContainerPrivate*)ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + QWidget *widget = new QWidget(); + QBoxLayout *box = new QBoxLayout(dir); + widget->setLayout(box); + ctn->add(widget, layout); + + ui_obj_add_container(obj, new UiBoxContainer(box)); + + return widget; +} + +UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs *args) { + return ui_box(obj, args, QBoxLayout::TopToBottom); +} + +UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs *args) { + return ui_box(obj, args, QBoxLayout::LeftToRight); +} + + +/* -------------------- UiGridContainer -------------------- */ + +UiGridContainer::UiGridContainer( + QGridLayout *grid, + int margin, + int columnspacing, + int rowspacing, + bool def_hexpand, + bool def_vexpand, + bool def_hfill, + bool def_vfill) +{ + this->current = NULL; + this->grid = grid; + this->def_hexpand = def_hexpand; + this->def_vexpand = def_vexpand; + this->def_hfill = def_hfill; + this->def_vfill = def_vfill; + grid->setContentsMargins(QMargins(margin, margin, margin, margin)); + grid->setHorizontalSpacing(columnspacing); + grid->setVerticalSpacing(rowspacing); +} + +void UiGridContainer::add(QWidget* widget, UiLayout& layout) { + if(container->newline) { + x = 0; + y++; + container->newline = false; + } + + uic_layout_setup_expand_fill(&layout, def_hexpand, def_vexpand, def_hfill, def_vfill); + uic_layout_setup_margin(&layout); + + if(layout.hexpand) { + col_expanding = true; + } + if(layout.vexpand) { + row_expanding = true; + } + + if(layout.hexpand) { + grid->setColumnStretch(x, 1); + } + if(layout.vexpand) { + grid->setRowStretch(y, 1); + } + + Qt::Alignment alignment = 0; + if(!layout.hfill) { + alignment = Qt::AlignLeft; + } + if(!layout.vfill) { + alignment = Qt::AlignTop; + } + + int colspan = layout.colspan > 0 ? layout.colspan : 1; + int rowspan = layout.rowspan > 0 ? layout.rowspan : 1; + + widget = ui_widget_set_margin(widget, layout.margin_left, layout.margin_right, layout.margin_top, layout.margin_bottom); + grid->addWidget(widget, y, x, rowspan, colspan, alignment); + + if(x > max_x) { + max_x = x; + } + if(y > max_y) { + max_y = y; + } + + x += colspan; + + current = widget; +} + +void UiGridContainer::end() { + if(!col_expanding) { + QSpacerItem *filler = new QSpacerItem(0, 0); + x = max_x + 1; + grid->setColumnStretch(x, 1); + grid->addItem(filler, 0, x, 1, 1, 0); + } + if(!row_expanding) { + QSpacerItem *filler = new QSpacerItem(0, 0); + y++; + grid->setRowStretch(y, 1); + grid->addItem(filler, y, 0, 1, 1, 0); + } +} + +UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { + UiContainerPrivate *ctn = (UiContainerPrivate*)ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + QWidget *widget = new QWidget(); + QGridLayout *grid = new QGridLayout(); + widget->setLayout(grid); + ctn->add(widget, layout); + + ui_obj_add_container(obj, new UiGridContainer( + grid, + args->margin, + args->columnspacing, + args->rowspacing, + args->def_hexpand, + args->def_vexpand, + args->def_hfill, + args->def_vfill)); + + return widget; +} + + +/* ---------------------------- UiSidebar ---------------------------- */ + +UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) { + QVariant v = obj->widget->property("ui_sidebar"); + QDockWidget *dock = (QDockWidget*)v.value(); + if(!dock) { + fprintf(stderr, "Error: window is not configured for sidebar\n"); + return nullptr; + } + + QWidget *widget = new QWidget(); + QBoxLayout *box = new QBoxLayout(QBoxLayout::TopToBottom); + widget->setLayout(box); + dock->setWidget(widget); + + ui_obj_add_container(obj, new UiBoxContainer(box)); + + return dock; +} + +/* -------------------- Container Helper Functions -------------------- */ + +void ui_container_begin_close(UiObject *obj) { + obj->container_end->close = true; +} + +int ui_container_finish(UiObject *obj) { + if(obj->container_end->close) { + UiContainerPrivate *ctn = (UiContainerPrivate*)obj->container_end->container; + ctn->end(); + ui_end_new(obj); + return 0; + } + return 1; +} + diff --git a/ui/qt/container.h b/ui/qt/container.h new file mode 100644 index 0000000..867fc4c --- /dev/null +++ b/ui/qt/container.h @@ -0,0 +1,99 @@ +/* + * 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 "../ui/container.h" +#include "window.h" + +#include +#include +#include +#include +#include +#include + +#define ui_obj_container(obj) (UiContainerPrivate*)((UiContainerX*)obj->container_end)->container + +struct UiContainerPrivate { + UIWIDGET current; + UiContainerX *container; + + virtual void add(QWidget *widget, UiLayout& layout) = 0; + virtual void end() {} +}; + +class UiBoxContainer : public UiContainerPrivate { +public: + QBoxLayout *box; + bool hasStretchedWidget = false; + QSpacerItem *space; + QBoxLayout::Direction direction; + + UiBoxContainer(QBoxLayout *box); + + virtual void add(QWidget *widget, UiLayout& layout); +}; + +class UiGridContainer : public UiContainerPrivate { +public: + QGridLayout *grid; + int x = 0; + int y = 0; + bool def_hexpand; + bool def_vexpand; + bool def_hfill; + bool def_vfill; + bool col_expanding = false; + bool row_expanding = false; + int max_x; + int max_y; + + UiGridContainer( + QGridLayout *grid, + int margin, + int columnspacing, + int rowspacing, + bool def_hexpand, + bool def_vexpand, + bool def_hfill, + bool def_vfill); + + virtual void add(QWidget *widget, UiLayout& layout); + virtual void end(); +}; + +void ui_obj_add_container(UiObject *obj, UiContainerPrivate *ct); + +QWidget* ui_widget_set_margin(QWidget *w, int left, int right, int top, int bottom); + + +#endif /* CONTAINER_H */ + diff --git a/ui/qt/entry.cpp b/ui/qt/entry.cpp new file mode 100644 index 0000000..210f48f --- /dev/null +++ b/ui/qt/entry.cpp @@ -0,0 +1,197 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "entry.h" + +#include "container.h" +#include "../common/context.h" + +#include +#include + + + +UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + double min = args->min; + double max = args->max != 0 ? args->max : 100000; + + bool use_double = false; + UiVar *var = NULL; + UiVarType vartype = UI_VAR_SPECIAL; + if(args->varname) { + var = uic_get_var(obj->ctx, args->varname); + vartype = var->type; + if(var->type == UI_VAR_DOUBLE) { + use_double = true; + } else if(var->type == UI_VAR_RANGE) { + use_double = true; + } else if(var->type != UI_VAR_INTEGER) { + var = NULL; + fprintf(stderr, "UI Error: var '%s' has wrong type (must be int/double/range)\n", args->varname); + } + } + + if(!var) { + if(args->intvalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER); + vartype = UI_VAR_INTEGER; + } else if(args->doublevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE); + vartype = UI_VAR_DOUBLE; + use_double = true; + } else if(args->rangevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + use_double = true; + } else { + if(args->digits > 0) { + use_double = true; + } + } + } + + if(vartype == UI_VAR_RANGE) { + UiRange *r = (UiRange*)var->value; + min = r->min; + max = r->max; + } + + QAbstractSpinBox *widget = nullptr; + if(use_double) { + QDoubleSpinBox *spinbox = new QDoubleSpinBox(); + spinbox->setDecimals(args->digits); + if(args->step != 0) { + spinbox->setSingleStep(args->step); + } + spinbox->setMinimum(min); + spinbox->setMaximum(max); + widget = spinbox; + } else { + QSpinBox *spinbox = new QSpinBox(); + if(args->step != 0) { + spinbox->setSingleStep(args->step); + } + spinbox->setMinimum((int)min); + spinbox->setMaximum((int)max); + widget = spinbox; + } + + if(var) { + if(vartype == UI_VAR_INTEGER) { + UiInteger *value = (UiInteger*)var->value; + value->obj = widget; + if(value->value != 0) { + QSpinBox *spinbox = (QSpinBox*)widget; + spinbox->setValue(value->value); + } + value->get = ui_spinbox_int_get; + value->set = ui_spinbox_int_set; + } else if(vartype == UI_VAR_DOUBLE) { + UiDouble *value = (UiDouble*)var->value; + value->obj = widget; + if(value->value != 0) { + QDoubleSpinBox *spinbox = (QDoubleSpinBox*)widget; + spinbox->setValue(value->value); + } + value->get = ui_spinbox_double_get; + value->set = ui_spinbox_double_set; + } else if(vartype == UI_VAR_RANGE) { + UiRange *value = (UiRange*)var->value; + value->obj = widget; + QDoubleSpinBox *spinbox = (QDoubleSpinBox*)widget; + if(value->value != 0) { + spinbox->setValue(value->value); + } + if(value->min != value->max) { + spinbox->setRange(value->min, value->max); + } + if(value->extent != 0) { + spinbox->setSingleStep(value->extent); + } + value->get = ui_spinbox_range_get; + value->set = ui_spinbox_range_set; + value->setrange = ui_spinbox_range_setrange; + value->setextent = ui_spinbox_range_setextent; + } + } + + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(widget, layout); + return widget; +} + +int64_t ui_spinbox_int_get(UiInteger *i) { + QSpinBox *spinbox = (QSpinBox*)i->obj; + i->value = spinbox->value(); + return i->value; +} + +void ui_spinbox_int_set(UiInteger *i, int64_t value) { + QSpinBox *spinbox = (QSpinBox*)i->obj; + spinbox->setValue(value); + i->value = spinbox->value(); +} + +double ui_spinbox_double_get(UiDouble *d) { + QDoubleSpinBox *spinbox = (QDoubleSpinBox*)d->obj; + d->value = spinbox->value(); + return d->value; +} + +void ui_spinbox_double_set(UiDouble *d, double value) { + QDoubleSpinBox *spinbox = (QDoubleSpinBox*)d->obj; + spinbox->setValue(value); + d->value = spinbox->value(); +} + +double ui_spinbox_range_get(UiRange *range) { + QDoubleSpinBox *spinbox = (QDoubleSpinBox*)range->obj; + range->value = spinbox->value(); + return range->value; +} + +void ui_spinbox_range_set(UiRange *range, double value) { + QDoubleSpinBox *spinbox = (QDoubleSpinBox*)range->obj; + spinbox->setValue(value); + range->value = spinbox->value(); +} + +void ui_spinbox_range_setrange(UiRange *range, double min, double max) { + QDoubleSpinBox *spinbox = (QDoubleSpinBox*)range->obj; + spinbox->setRange(min, max); + range->min = min; + range->max = max; +} + +void ui_spinbox_range_setextent(UiRange *range, double extent) { + QDoubleSpinBox *spinbox = (QDoubleSpinBox*)range->obj; + spinbox->setSingleStep(extent); + range->extent = extent; +} diff --git a/ui/qt/entry.h b/ui/qt/entry.h new file mode 100644 index 0000000..5feefac --- /dev/null +++ b/ui/qt/entry.h @@ -0,0 +1,55 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ENTRY_H +#define ENTRY_H + +#include "../ui/entry.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int64_t ui_spinbox_int_get(UiInteger *i); +void ui_spinbox_int_set(UiInteger *i, int64_t value); + +double ui_spinbox_double_get(UiDouble *d); +void ui_spinbox_double_set(UiDouble *d, double value); + +double ui_spinbox_range_get(UiRange *range); +void ui_spinbox_range_set(UiRange *range, double value); +void ui_spinbox_range_setrange(UiRange *range, double min, double max); +void ui_spinbox_range_setextent(UiRange *range, double extent); + +#ifdef __cplusplus +} +#endif + +#endif /* ENTRY_H */ + diff --git a/ui/qt/graphics.cpp b/ui/qt/graphics.cpp new file mode 100644 index 0000000..f3a9de2 --- /dev/null +++ b/ui/qt/graphics.cpp @@ -0,0 +1,33 @@ +/* + * 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" + + + diff --git a/ui/qt/graphics.h b/ui/qt/graphics.h new file mode 100644 index 0000000..62d4df2 --- /dev/null +++ b/ui/qt/graphics.h @@ -0,0 +1,42 @@ +/* + * 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 + + +#endif /* GRAPHICS_H */ + diff --git a/ui/qt/image.cpp b/ui/qt/image.cpp new file mode 100644 index 0000000..1e4d699 --- /dev/null +++ b/ui/qt/image.cpp @@ -0,0 +1,39 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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" + + + +void ui_image_ref(UIIMAGE img) { + // TODO +} + +void ui_image_unref(UIIMAGE img) { + // TODO +} \ No newline at end of file diff --git a/ui/qt/image.h b/ui/qt/image.h new file mode 100644 index 0000000..40f6a0e --- /dev/null +++ b/ui/qt/image.h @@ -0,0 +1,36 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 "toolkit.h" +#include "../ui/image.h" + +#endif /* IMAGE_H */ + diff --git a/ui/qt/label.cpp b/ui/qt/label.cpp new file mode 100644 index 0000000..bddc1d4 --- /dev/null +++ b/ui/qt/label.cpp @@ -0,0 +1,63 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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" +#include "ui/display.h" + + +UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + QString str = QString::fromUtf8(args->label); + QLabel *widget = new QLabel(str); + + Qt::AlignmentFlag align = Qt::AlignCenter; + if(args->align == UI_ALIGN_LEFT) { + align = Qt::AlignLeft; + } else if(args->align == UI_ALIGN_RIGHT) { + align = Qt::AlignRight; + } + widget->setAlignment(align); + + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(widget, layout); + + 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); +} 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/list.cpp b/ui/qt/list.cpp new file mode 100644 index 0000000..94d3426 --- /dev/null +++ b/ui/qt/list.cpp @@ -0,0 +1,143 @@ +/* + * 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 "list.h" +#include "container.h" + +#include +#include +#include + +extern "C" void* ui_strmodel_getvalue(void *elm, int column) { + return column == 0 ? elm : NULL; +} + +static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata; + return getvalue(elm, col); +} + +static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + return NULL; +} + +UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + QListView *view = new QListView(); + ui_getvaluefunc2 getvalue = nullptr; + void *getvaluedata = nullptr; + if(args->getvalue2) { + getvalue = args->getvalue2; + getvaluedata = args->getvalue2data; + } else if(args->getvalue) { + getvalue = getvalue_wrapper; + getvaluedata = (void*)args->getvalue; + } else { + getvalue = getvalue_wrapper; + getvaluedata = (void*)ui_strmodel_getvalue; + } + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + ListModel *model = new ListModel(obj, view, var, getvalue, getvaluedata); + view->setModel(model); + + if(var) { + UiList *list = (UiList*)var->value; + list->update = ui_listmodel_update; + list->getselection = ui_listmodel_getselection; + list->setselection = ui_listmodel_setselection; + list->obj = model; + } + + model->setActivationCallback(args->onactivate, args->onactivatedata); + model->setSelectionCallback(args->onselection, args->onselectiondata); + + QItemSelectionModel *s = view->selectionModel(); + QObject::connect( + s, + SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), + model, + SLOT(selectionChanged(const QItemSelection &, const QItemSelection &))); + + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(view, layout); + + return view; +} + +UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + QTreeView *view = new QTreeView(); + view->setItemsExpandable(false); + view->setRootIsDecorated(false); + if(args->multiselection) { + view->setSelectionMode(QAbstractItemView::ExtendedSelection); + } + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + ui_getvaluefunc2 getvalue = args->getvalue2; + void *getvaluedata = args->getvalue2data; + if(!getvalue) { + if(args->getvalue) { + getvalue = getvalue_wrapper; + getvaluedata = (void*)args->getvalue; + } else { + getvalue = null_getvalue; + } + } + TableModel *model = new TableModel(obj, view, var, args->model, getvalue, getvaluedata); + view->setModel(model); + + if(var) { + UiList *list = (UiList*)var->value; + list->update = ui_listmodel_update; + list->getselection = ui_listmodel_getselection; + list->setselection = ui_listmodel_setselection; + list->obj = model; + } + + model->setActivationCallback(args->onactivate, args->onactivatedata); + model->setSelectionCallback(args->onselection, args->onselectiondata); + + QItemSelectionModel *s = view->selectionModel(); + QObject::connect( + s, + SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), + model, + SLOT(selectionChanged(const QItemSelection &, const QItemSelection &))); + + + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(view, layout); + + return view; +} diff --git a/ui/qt/list.h b/ui/qt/list.h new file mode 100644 index 0000000..664b32b --- /dev/null +++ b/ui/qt/list.h @@ -0,0 +1,40 @@ +/* + * 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 + + + +#endif /* TREE_H */ + diff --git a/ui/qt/menu.cpp b/ui/qt/menu.cpp new file mode 100644 index 0000000..4dbb7e1 --- /dev/null +++ b/ui/qt/menu.cpp @@ -0,0 +1,239 @@ +/* + * 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 "../common/menu.h" +#include "../ui/properties.h" +#include "../ui/window.h" +#include "stock.h" +#include "container.h" + + +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 +}; + +/* + * create all menu child items + */ +static void add_menu_items(QMenu *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(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) { + UiMenu *m = (UiMenu*)item; + QMenu *menu = parent->addMenu(m->label); + add_menu_items(menu, i, m, obj); +} + +static UiAction* create_action( + UiObject *obj, + const char *icon, + const char *label, + ui_callback callback, + void *userdata, + int *states) +{ + QString str = QString::fromUtf8(label); + UiAction *action = new UiAction(obj, str, callback, userdata); + if(icon) { + action->setIcon(QIcon::fromTheme(icon)); + action->setIconVisibleInMenu(true); + } + + if(states) { + size_t nstates = uic_group_array_size(states); + uic_add_group_widget_i(obj->ctx, action, (ui_enablefunc)ui_action_enable, states, nstates); + action->setEnabled(false); + } + + return action; +} + +void add_menuitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) { + UiMenuItem *it = (UiMenuItem*)item; + UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups); + parent->addAction(action); + QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger())); +} + +void add_menuseparator_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) { + parent->addSeparator(); +} + +void add_checkitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) { + UiMenuCheckItem *it = (UiMenuCheckItem*)item; + + UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups); + parent->addAction(action); + action->setCheckable(true); + action->prepare_event = ui_checkableaction_prepare_event; + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, it->varname, UI_VAR_INTEGER); + if(var) { + UiInteger *value = (UiInteger*)var->value; + value->obj = action; + value->get = ui_checkableaction_get; + value->set = ui_checkableaction_set; + + action->setChecked((bool)value->value); + } + action->var = var; +} + +void add_radioitem_widget(QMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { + UiMenuRadioItem *it = (UiMenuRadioItem*)item; + + UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups); + parent->addAction(action); + action->setCheckable(true); + action->prepare_event = ui_actiongroup_prepare_event; + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, it->varname, UI_VAR_INTEGER); + if(var) { + UiInteger *value = (UiInteger*)var->value; + QActionGroup *group = (QActionGroup*)value->obj; + if(!group) { + group = new QActionGroup(parent); + value->obj = group; + } + group->addAction(action); + value->get = ui_actiongroup_get; + value->set = ui_actiongroup_set; + if(value->value != 0) { + ui_actiongroup_set(value, value->value); + } + } + action->var; +} + +void ui_actiongroup_prepare_event(UiEvent *event, UiAction *action) { + if(action->var) { + UiInteger *value = (UiInteger*)action->var->value; + event->eventdata = value; + event->eventdatatype = UI_EVENT_DATA_INTEGER_VALUE; + event->intval = value->get(value); + } +} + +int64_t ui_actiongroup_get(UiInteger *value) { + QActionGroup *group = (QActionGroup*)value->obj; + auto actions = group->actions(); + int i = 1; + foreach(const QAction *action, actions) { + if(action->isChecked()) { + value->value = i; + break; + } + i++; + } + return value->value; +} + +void ui_actiongroup_set(UiInteger *value, int64_t i) { + QActionGroup *group = (QActionGroup*)value->obj; + auto actions = group->actions(); + if(i > 0) { + if(i-1 < actions.size()) { + actions[i]->setEnabled(true); + } + value->value = i; + } else { + foreach(QAction *action, actions) { + action->setEnabled(false); + } + value->value = 0; + } +} + +void add_menuitem_list_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) { + +} + + +void ui_add_menus(UiObject *obj, QMainWindow *window) { + UiMenu *menus_begin = uic_get_menu_list(); + if(menus_begin == NULL) { + return; + } + + QMenuBar *mb = window->menuBar(); + + UiMenu *ls = menus_begin; + while(ls) { + if(ls->item.type == UI_MENU) { + QMenu *menu = mb->addMenu(ls->label); + add_menu_items(menu, 0, ls, obj); + } + ls = (UiMenu*)ls->item.next; + } +} + +void ui_checkableaction_prepare_event(UiEvent *event, UiAction *action) { + if(action->var) { + event->eventdata = action->var->value; + event->eventdatatype = UI_EVENT_DATA_INTEGER_VALUE; + } + event->intval = action->isChecked(); +} + +int64_t ui_checkableaction_get(UiInteger *value) { + UiAction *action= (UiAction*)value->obj; + value->value = action->isChecked(); + return value->value; +} + +void ui_checkableaction_set(UiInteger *value, int64_t i) { + UiAction *action = (UiAction*)value->obj; + value->value = i; + if(i != 0) { + action->setChecked((bool)i); + } +} + diff --git a/ui/qt/menu.h b/ui/qt/menu.h new file mode 100644 index 0000000..9903ae0 --- /dev/null +++ b/ui/qt/menu.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. + */ + +#ifndef MENU_H +#define MENU_H + +#include "../ui/menu.h" +#include "../common/menu.h" + +#include "toolkit.h" + +#include +#include +#include +#include + +void ui_add_menus(UiObject *obj, QMainWindow *window); + +typedef void(*ui_menu_add_f)(QMenu*, int, UiMenuItemI*, UiObject*); + +void add_menu_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj); +void add_menuitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj); +void add_menuseparator_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj); +void add_checkitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj); +void add_radioitem_widget(QMenu *parent, int index, UiMenuItemI *item, UiObject *obj); +void add_checkitemnv_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj); +void add_menuitem_list_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj); + +void ui_checkableaction_prepare_event(UiEvent *event, UiAction *action); +int64_t ui_checkableaction_get(UiInteger *value); +void ui_checkableaction_set(UiInteger *value, int64_t i); + +void ui_actiongroup_prepare_event(UiEvent *event, UiAction *action); +int64_t ui_actiongroup_get(UiInteger *value); +void ui_actiongroup_set(UiInteger *value, int64_t i); + +#endif /* MENU_H */ + diff --git a/ui/qt/model.cpp b/ui/qt/model.cpp new file mode 100644 index 0000000..7b99134 --- /dev/null +++ b/ui/qt/model.cpp @@ -0,0 +1,266 @@ +/* + * 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" + + +ListModel::ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata){ + this->obj = obj; + this->view = view; + this->var = var; + this->getvalue = getvalue; + this->getvaluedata = getvaluedata; + this->onactivate = nullptr; + this->onactivatedata = nullptr; + this->onselection = nullptr; + this->onselectiondata = nullptr; +} + +void ListModel::setActivationCallback(ui_callback f, void *userdata) { + onactivate = f; + onactivatedata = userdata; +} + +void ListModel::setSelectionCallback(ui_callback f, void *userdata) { + onselection = f; + onselectiondata = userdata; +} + +void ListModel::update(int row) { + if(row >= 0) { + this->update(row); + } else { + this->beginResetModel(); + this->endResetModel(); + } +} + +int ListModel::rowCount(const QModelIndex& parent) const { + UiList *list = (UiList*)var->value; + return ui_list_count(list); +} + +QVariant ListModel::data(const QModelIndex &index, int role) const { + if(role == Qt::DisplayRole) { + UiList *ls = (UiList*)var->value; + void *rowData = ls->get(ls, index.row()); + if(rowData && getvalue) { + UiBool freeResult = false; + void *value = getvalue(ls, rowData, index.row(), 0, getvaluedata, &freeResult); + if(value) { + auto qs = QString::fromUtf8((char*)value); + if(freeResult) { + free(value); + } + return qs; + } + } + } + return QVariant(); +} + +void ListModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { + UiListSelection sel = ui_selection_model_to_selection(view->selectionModel()); + + UiEvent event; + event.obj = obj; + event.window = obj->window; + event.document = obj->ctx->document; + event.eventdata = &sel; + event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; + event.intval = sel.count; + event.set = ui_get_setop(); + + if(onactivate) { + onactivate(&event, onactivatedata); + } + if(onselection) { + onselection(&event, onselectiondata); + } + + free(sel.rows); +} + + + +TableModel::TableModel(UiObject *obj, QTreeView *view, UiVar *var, UiModel *model, ui_getvaluefunc2 getvalue, void *getvaluedata){ + this->obj = obj; + this->view = view; + this->var = var; + this->model = model; + this->getvalue = getvalue; + this->getvaluedata = getvaluedata; + this->onactivate = nullptr; + this->onactivatedata = nullptr; + this->onselection = nullptr; + this->onselectiondata = nullptr; +} + +void TableModel::setActivationCallback(ui_callback f, void *userdata) { + onactivate = f; + onactivatedata = userdata; +} + +void TableModel::setSelectionCallback(ui_callback f, void *userdata) { + onselection = f; + onselectiondata = userdata; +} + +void TableModel::update(int row) { + if(row >= 0) { + this->update(row); + } else { + this->beginResetModel(); + this->endResetModel(); + } +} + +int TableModel::rowCount(const QModelIndex& parent) const { + UiList *list = (UiList*)var->value; + return ui_list_count(list); +} + +int TableModel::columnCount(const QModelIndex &parent) const { + return model->columns; +} + +QVariant TableModel::data(const QModelIndex &index, int role) const { + if(role == Qt::DisplayRole) { + UiList *ls = (UiList*)var->value; + void *rowData = ls->get(ls, index.row()); + if(rowData) { + int col = index.column(); + UiBool freeResult = false; + void *value = getvalue(ls, rowData, index.row(), col, getvaluedata, &freeResult); + if(value) { + UiModelType type = model->types[col]; + switch(type) { + case UI_STRING: { + auto qs = QString::fromUtf8((char*)value); + if(freeResult) { + free(value); + } + return qs; + } + case UI_STRING_FREE: { + QString s = QString::fromUtf8((char*)value); + free(value); + return s; + } + case UI_INTEGER: { + intptr_t i = (intptr_t)value; + return QString::number(i); + } + case UI_ICON: { + break; // TODO + } + case UI_ICON_TEXT: { + break; // TODO + } + case UI_ICON_TEXT_FREE: { + break; // TODO + } + } + } + } + } + return QVariant(); +} + +QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const { + if(role == Qt::DisplayRole) { + char *label = model->titles[section]; + return QString::fromUtf8(label); + } + return QVariant(); +} + +void TableModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { + UiListSelection sel = ui_selection_model_to_selection(view->selectionModel()); + + UiEvent event; + event.obj = obj; + event.window = obj->window; + event.document = obj->ctx->document; + event.eventdata = &sel; + event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; + event.intval = sel.count; + event.set = ui_get_setop(); + + if(onactivate) { + onactivate(&event, onactivatedata); + } + if(onselection) { + onselection(&event, onselectiondata); + } + + free(sel.rows); +} + + + +UiListSelection ui_selection_model_to_selection(QItemSelectionModel *model) { + UiListSelection sel; + sel.rows = NULL; + sel.count = 0; + + if(model->hasSelection()) { + QModelIndexList indices = model->selectedIndexes(); + sel.count = indices.count(); + sel.rows = (int*)calloc(sel.count, sizeof(int)); + + int i = 0; + for (const QModelIndex &index : indices) { + sel.rows[i++] = index.row(); + } + } + + return sel; +} + +/* ---------------------- UiList implementation -----------------------------*/ + +void ui_listmodel_update(UiList *list, int row) { + ListModel *model = (ListModel*)list->obj; + model->update(row); +} + +void ui_listmodel_setselection(UiList *list, UiListSelection sel) { + ListModel *model = (ListModel*)list->obj; + QItemSelection selection; + for (int i=0;iindex(sel.rows[i]); + selection.select(index, index); + } + model->view->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); +} + +UiListSelection ui_listmodel_getselection(UiList *list) { + ListModel *model = (ListModel*)list->obj; + return ui_selection_model_to_selection(model->view->selectionModel()); +} diff --git a/ui/qt/model.h b/ui/qt/model.h new file mode 100644 index 0000000..8fa513e --- /dev/null +++ b/ui/qt/model.h @@ -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. + */ + +#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 + + ui_getvaluefunc2 getvalue; + void *getvaluedata; + ui_callback onactivate; + void *onactivatedata; + ui_callback onselection; + void *onselectiondata; + +public: + UiObject *obj; + UiVar *var; + QListView *view; + + ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata); + + void setActivationCallback(ui_callback f, void *userdata); + void setSelectionCallback(ui_callback f, void *userdata); + + void update(int row); + + 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 QAbstractListModel { + Q_OBJECT + + UiModel *model; + ui_getvaluefunc2 getvalue; + void *getvaluedata; + ui_callback onactivate; + void *onactivatedata; + ui_callback onselection; + void *onselectiondata; + +public: + UiObject *obj; + UiVar *var; + QTreeView *view; + + TableModel(UiObject *obj, QTreeView *view, UiVar *var, UiModel *model, ui_getvaluefunc2 getvalue, void *getvaluedata); + + void setActivationCallback(ui_callback f, void *userdata); + void setSelectionCallback(ui_callback f, void *userdata); + + void update(int row); + + 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; + +public slots: + void selectionChanged( + const QItemSelection & selected, + const QItemSelection & deselected); +}; + + +UiListSelection ui_selection_model_to_selection(QItemSelectionModel *model); + +extern "C" { + + void ui_listmodel_update(UiList *list, int row); + void ui_listmodel_setselection(UiList *list, UiListSelection sel); + UiListSelection ui_listmodel_getselection(UiList *list); + +} + +#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/qt5.pro b/ui/qt/qt5.pro new file mode 100644 index 0000000..553ddbd --- /dev/null +++ b/ui/qt/qt5.pro @@ -0,0 +1,73 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2025 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (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 + +QMAKE_CXXFLAGS += -I../ucx + +QT += core gui widgets + +DEFINES += UI_QT5 + +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 += list.cpp +SOURCES += button.cpp +SOURCES += label.cpp +SOURCES += graphics.cpp +SOURCES += widget.cpp +SOURCES += entry.cpp +SOURCES += image.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 += list.h +HEADERS += button.h +HEADERS += label.h +HEADERS += graphics.h +HEADERS += widget.h +HEADERS += entry.h +HEADERS += image.h + diff --git a/ui/qt/stock.cpp b/ui/qt/stock.cpp new file mode 100644 index 0000000..1ead20b --- /dev/null +++ b/ui/qt/stock.cpp @@ -0,0 +1,35 @@ +/* + * 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 "stock.h" +#include "../ui/properties.h" + + + + diff --git a/ui/qt/stock.h b/ui/qt/stock.h new file mode 100644 index 0000000..f80f1a4 --- /dev/null +++ b/ui/qt/stock.h @@ -0,0 +1,37 @@ +/* + * 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" + + + +#endif /* STOCK_H */ + diff --git a/ui/qt/text.cpp b/ui/qt/text.cpp new file mode 100644 index 0000000..32871a2 --- /dev/null +++ b/ui/qt/text.cpp @@ -0,0 +1,280 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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" + +/* + * Gets or creates a QTextDocument for the UiText value and initializes it + * with the UiText string value + */ +static QTextDocument* get_or_create_doc(UiText *value) { + QTextDocument *document = nullptr; + if(value->data1) { + document = (QTextDocument*)value->data1; + } else { + document = new QTextDocument(); + if(value->value.ptr) { + QString str = QString::fromUtf8(value->value.ptr); + document->setPlainText(str); + } + } + + if(value->value.free) { + value->value.free(value->value.ptr); + } + value->value.ptr = NULL; + value->value.free = NULL; + + return document; +} + +UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + QTextEdit *textarea = new QTextEdit(); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(textarea, layout); + + QTextDocument *document = nullptr; + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); + if(var) { + UiText *value = (UiText*)var->value; + + document = get_or_create_doc(value); + + value->save = ui_textarea_save; + value->restore = ui_textarea_restore; + value->destroy = ui_textarea_text_destroy; + 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->setselection = ui_textarea_setselection; + value->selection = ui_textarea_selection; + value->length = ui_textarea_length; + value->remove = ui_textarea_remove; + value->obj = textarea; + value->data1 = document; + } else { + document = new QTextDocument(); + } + + textarea->setDocument(document); + + return textarea; +} + +void ui_textarea_save(UiText *text) { + // NOOP +} + +void ui_textarea_restore(UiText *text) { + QTextEdit *textarea = (QTextEdit*)text->obj; + QTextDocument *document = get_or_create_doc(text); + textarea->setDocument(document); +} + +void ui_textarea_text_destroy(UiText *text) { + QTextDocument *document = (QTextDocument*)text->data1; + if(document) { + delete document; + } +} + +char* ui_textarea_get(UiText *text) { + // clean previous value + if(text->value.free) { + text->value.free(text->value.ptr); + } + + // get string + QTextDocument *doc = (QTextDocument*)text->data1; + QString str = doc->toPlainText(); + QByteArray array = str.toUtf8(); + const char *cstr = array.constData(); + + // store a copy of the string in the UiText value + text->value.ptr = strdup(cstr); + text->value.free = free; + return text->value.ptr; +} + +void ui_textarea_set(UiText *text, const char *str) { + if(text->value.free) { + text->value.free(text->value.ptr); + } + text->value.ptr = NULL; + text->value.free = NULL; + + QTextDocument *doc = (QTextDocument*)text->data1; + QString qstr = QString::fromUtf8(str); + doc->setPlainText(qstr); +} + +char* ui_textarea_getsubstr(UiText *text, int begin, int end) { + QTextDocument *doc = (QTextDocument*)text->data1; + QTextCursor cursor(doc); + cursor.setPosition(begin, QTextCursor::MoveAnchor); + cursor.setPosition(end, QTextCursor::KeepAnchor); + QString str = cursor.selectedText(); + QByteArray bytes = str.toUtf8(); + const char *cstr = bytes.constData(); + return cstr ? strdup(cstr) : NULL; +} + +void ui_textarea_insert(UiText *text, int pos, char *str) { + QTextDocument *doc = (QTextDocument*)text->data1; + QTextCursor cursor(doc); + cursor.setPosition(pos); + cursor.insertText(str); +} + +void ui_textarea_setposition(UiText *text, int pos) { + QTextEdit *textview = (QTextEdit*)text->obj; + QTextCursor cursor = textview->textCursor(); + cursor.setPosition(pos); + textview->setTextCursor(cursor); +} + +int ui_textarea_position(UiText *text) { + QTextEdit *textview = (QTextEdit*)text->obj; + QTextCursor cursor = textview->textCursor(); + return cursor.position(); +} + +void ui_textarea_selection(UiText *text, int *begin, int *end) { + QTextEdit *textview = (QTextEdit*)text->obj; + QTextCursor cursor = textview->textCursor(); + if(cursor.hasSelection()) { + if(begin) { + *begin = cursor.selectionStart(); + } + if(end) { + *end = cursor.selectionEnd(); + } + } +} + +void ui_textarea_setselection(UiText *text, int begin, int end) { + QTextEdit *textview = (QTextEdit*)text->obj; + QTextCursor cursor = textview->textCursor(); + cursor.setPosition(begin, QTextCursor::MoveAnchor); + cursor.setPosition(end, QTextCursor::KeepAnchor); + textview->setTextCursor(cursor); +} + +int ui_textarea_length(UiText *text) { + QTextDocument *doc = (QTextDocument*)text->data1; + return doc->characterCount(); +} + +void ui_textarea_remove(UiText *text, int begin, int end) { + QTextDocument *doc = (QTextDocument*)text->data1; + QTextCursor cursor(doc); + cursor.setPosition(begin); + cursor.setPosition(end, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); +} + +/* ------------------------------ TextField ------------------------------ */ + +static UIWIDGET create_textfield(UiObject *obj, UiTextFieldArgs *args, bool password, bool frameless) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + QLineEdit *textfield = new QLineEdit(); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(textfield, layout); + + if(password) { + textfield->setEchoMode(QLineEdit::Password); + } + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); + if(var) { + UiString *value = (UiString*)var->value; + if(value->value.ptr) { + QString str = QString::fromUtf8(value->value.ptr); + textfield->setText(str); + if(value->value.free) { + value->value.free(value->value.ptr); + } + value->value.ptr = NULL; + } + + value->set = ui_textfield_set; + value->get = ui_textfield_get; + value->obj = textfield; + } + + return textfield; +} + +UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs *args) { + return create_textfield(obj, args, false, false); +} + +UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs *args) { + return create_textfield(obj, args, false, true); +} + +UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs *args) { + return create_textfield(obj, args, true, false); +} + +char* ui_textfield_get(UiString *str) { + QLineEdit *textfield = (QLineEdit*)str->obj; + QString qstr = textfield->text(); + + if(str->value.free) { + str->value.free(str->value.ptr); + } + QByteArray array = qstr.toUtf8(); + const char *cstr = array.constData(); + str->value.ptr = strdup(cstr); + str->value.free = free; + + return str->value.ptr; +} + +void ui_textfield_set(UiString *str, const char *value) { + QLineEdit *textfield = (QLineEdit*)str->obj; + QString qstr = QString::fromUtf8(value); + textfield->setText(qstr); + + if(str->value.free) { + str->value.free(str->value.ptr); + } + str->value.ptr = NULL; +} diff --git a/ui/qt/text.h b/ui/qt/text.h new file mode 100644 index 0000000..fcfe612 --- /dev/null +++ b/ui/qt/text.h @@ -0,0 +1,62 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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" { + +void ui_textarea_save(UiText *text); +void ui_textarea_restore(UiText *text); +void ui_textarea_text_destroy(UiText *text); +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_setselection(UiText *text, int begin, int end); +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, const char *value); + +} + + + +#endif /* TEXT_H */ + diff --git a/ui/qt/toolbar.cpp b/ui/qt/toolbar.cpp new file mode 100644 index 0000000..2cb1589 --- /dev/null +++ b/ui/qt/toolbar.cpp @@ -0,0 +1,133 @@ +/* + * 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 "toolbar.h" +#include "menu.h" +#include "stock.h" + +static void add_items(UiObject *obj, QToolBar *toolbar, CxList *defaults, CxMap *items); +static void create_item(UiObject *obj, QToolBar *toolbar, UiToolbarItemI *i); + +QToolBar* ui_create_toolbar(UiObject *obj) { + 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); + + if(!items || cxMapSize(items) == 0) { + return nullptr; + } + + QToolBar *toolbar = new QToolBar(); + add_items(obj, toolbar, left_defaults, items); + add_items(obj, toolbar, center_defaults, items); + add_items(obj, toolbar, right_defaults, items); + + + return toolbar; +} + +static void add_items(UiObject *obj, QToolBar *toolbar, CxList *defaults, CxMap *items) { + CxIterator i = cxListIterator(defaults); + cx_foreach(char *, name, i) { + UiToolbarItemI *item = (UiToolbarItemI*)cxMapGet(items, name); + if(item) { + create_item(obj, toolbar, item); + } else { + fprintf(stderr, "UI Error: unknown toolbar item '%s'\n", name); + } + } +} + +static void create_item(UiObject *obj, QToolBar *toolbar, UiToolbarItemI *i) { + switch(i->type) { + case UI_TOOLBAR_ITEM: { + ui_toolbar_add_item(obj, toolbar, (UiToolbarItem*)i); + break; + } + case UI_TOOLBAR_TOGGLEITEM: { + ui_toolbar_add_toggleitem(obj, toolbar, (UiToolbarToggleItem*)i); + break; + } + case UI_TOOLBAR_MENU: { + ui_toolbar_add_menu(obj, toolbar, (UiToolbarMenuItem*)i); + break; + } + default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type); + } +} + +void ui_toolbar_add_item(UiObject *obj, QToolBar *toolbar, UiToolbarItem *item) { + QAction *action = new QAction(); + if(item->args.label) { + action->setText(item->args.label); + } + if(item->args.icon) { + action->setIcon(QIcon::fromTheme(item->args.icon)); + } + toolbar->addAction(action); + + UiEventWrapper *event = new UiEventWrapper(obj, item->args.onclick, item->args.onclickdata); + action->connect(action, SIGNAL(triggered()), event, SLOT(slot())); + action->connect(action, SIGNAL(destroyed()), event, SLOT(destroy())); +} + +static void toolbar_togglebutton_event(UiEvent *event, UiEventWrapper *wrapper) { + QAction *action = (QAction*)wrapper->customdata1; + event->intval = action->isChecked(); + if(wrapper->var) { + event->eventdata = wrapper->var->value; + event->eventdatatype = UI_EVENT_DATA_INTEGER_VALUE; + } +} + +void ui_toolbar_add_toggleitem(UiObject *obj, QToolBar *toolbar, UiToolbarToggleItem *item) { + QAction *action = new QAction(); + action->setCheckable(true); + if(item->args.label) { + action->setText(item->args.label); + } + if(item->args.icon) { + action->setIcon(QIcon::fromTheme(item->args.icon)); + } + toolbar->addAction(action); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, nullptr, item->args.varname, UI_VAR_INTEGER); + UiEventWrapper *event = new UiEventWrapper(obj, item->args.onchange, item->args.onchangedata); + event->var = var; + event->customdata1 = action; + event->prepare_event = toolbar_togglebutton_event; + action->connect(action, SIGNAL(triggered()), event, SLOT(slot())); + action->connect(action, SIGNAL(destroyed()), event, SLOT(destroy())); +} + +void ui_toolbar_add_menu(UiObject *obj, QToolBar *toolbar, UiToolbarMenuItem *item) { + +} diff --git a/ui/qt/toolbar.h b/ui/qt/toolbar.h new file mode 100644 index 0000000..749e84f --- /dev/null +++ b/ui/qt/toolbar.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 TOOLBAR_H +#define TOOLBAR_H + +#include "toolkit.h" +#include "../ui/toolbar.h" +#include "../common/toolbar.h" +#include + +QToolBar* ui_create_toolbar(UiObject *obj); + +void ui_toolbar_add_item(UiObject *obj, QToolBar *toolbar, UiToolbarItem *item); +void ui_toolbar_add_toggleitem(UiObject *obj, QToolBar *toolbar, UiToolbarToggleItem *item); +void ui_toolbar_add_menu(UiObject *obj, QToolBar *toolbar, UiToolbarMenuItem *item); + +#endif /* TOOLBAR_H */ + diff --git a/ui/qt/toolkit.cpp b/ui/qt/toolkit.cpp new file mode 100644 index 0000000..c1fa90a --- /dev/null +++ b/ui/qt/toolkit.cpp @@ -0,0 +1,202 @@ +/* + * 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" +#include "../common/menu.h" +#include "../common/toolbar.h" + +static const char *application_name; + +static ui_callback startup_func; +static void *startup_data; +static ui_callback open_func; +static void *open_data; +static ui_callback exit_func; +static void *exit_data; + +static int is_toplevel_realized = 0; + +static int app_argc; +static char **app_argv; +static QApplication *application = NULL; + +static UiBool exit_on_shutdown; + +void ui_init(const char *appname, int argc, char **argv) { + application_name = appname; + + app_argc = argc; + app_argv = argv; + application = new QApplication(app_argc, app_argv); + + 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_app_exit_on_shutdown(UiBool exitapp) { + exit_on_shutdown = exitapp; +} + +void ui_main() { + if(startup_func) { + startup_func(NULL, startup_data); + } + application->exec(); + if(exit_func) { + exit_func(NULL, exit_data); + } + uic_store_app_properties(); + + delete application; + + if(exit_on_shutdown) { + exit(0); + } +} + +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) { + +} + + + +/* --------------------- Implemtation UiEventWrapper --------------------- */ + +UiEventWrapper::UiEventWrapper(UiObject *obj, ui_callback f, void* userdata) { + this->obj = obj; + this->callback = f; + this->userdata = userdata; +} + +void UiEventWrapper::slot() { + if(!callback) { + return; + } + + UiEvent e; + e.obj = obj; + e.window = obj->window; + e.document = obj->ctx->document; + e.eventdata = NULL; + e.eventdatatype = 0; + e.intval = 0; + e.set = ui_get_setop(); + if(prepare_event) { + prepare_event(&e, this); + } + callback(&e, userdata); + + // TODO: notify var observers +} + +void UiEventWrapper::destroy() { + delete this; +} + + +/* --------------------- Implemtation UiAction --------------------- */ + +UiAction::UiAction(UiObject *obj, QString &label, ui_callback f, void *userdata) : QAction(label, NULL) { + this->obj = obj; + this->callback = f; + this->userdata = userdata; +} + +UiAction::~UiAction() { + // TODO: unbind var +} + +void UiAction::trigger() { + if(!callback) { + return; + } + + UiEvent e; + e.obj = obj; + e.window = obj->window; + e.document = obj->ctx->document; + e.eventdata = NULL; + e.eventdatatype = 0; + e.intval = 0; + e.set = ui_get_setop(); + if(prepare_event) { + prepare_event(&e, this); + } + callback(&e, userdata); + + // TODO: notify var observers +} + +// ui_enablefunc for UiAction +void ui_action_enable(UiAction *action, int enable) { + action->setEnabled((bool)enable); +} + diff --git a/ui/qt/toolkit.h b/ui/qt/toolkit.h new file mode 100644 index 0000000..1db8df7 --- /dev/null +++ b/ui/qt/toolkit.h @@ -0,0 +1,96 @@ +/* + * 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; + +typedef void (*ui_prepare_event_func)(UiEvent *event, UiEventWrapper *wrapper); + +class UiEventWrapper : public QObject { + Q_OBJECT + + UiObject *obj; + ui_callback callback; + void *userdata; + +public: + UiVar *var; + + void *customdata1 = nullptr; + void *customdata2 = nullptr; + int customvalue1 = 0; + int customvalue2 = 0; + + ui_prepare_event_func prepare_event = nullptr; + + UiEventWrapper(UiObject *obj, ui_callback f, void *userdata); + +public slots: + void slot(); + void destroy(); +}; + +class UiAction; + +typedef void (*ui_prepare_action_event_func)(UiEvent *event, UiAction *action); + +class UiAction : public QAction { + Q_OBJECT + + UiObject *obj; + ui_callback callback; + void *userdata; + +public: + UiVar *var; + + ui_prepare_action_event_func prepare_event = nullptr; + void *customdata1 = nullptr; + void *customdata2 = nullptr; + int customvalue1 = 0; + int customvalue2 = 0; + + UiAction(UiObject *obj, QString &label, ui_callback f, void *userdata); + ~UiAction(); + +private slots: + void trigger(); +}; + +void ui_action_enable(UiAction *action, int enable); + +#endif /* TOOLKIT_H */ + diff --git a/ui/qt/widget.cpp b/ui/qt/widget.cpp new file mode 100644 index 0000000..0032a0e --- /dev/null +++ b/ui/qt/widget.cpp @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "widget.h" + +#include "container.h" +#include "../common/context.h" + +UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) { + UIWIDGET widget = create_widget(obj, args, userdata); + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(widget, layout); + return widget; +} + +UIWIDGET ui_separator_create(UiObject *obj, UiWidgetArgs *args) { + QFrame *separator = new QFrame(); + separator->setFrameShape(QFrame::HLine); + separator->setFrameShadow(QFrame::Sunken); + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(separator, layout); + + return separator; +} + +void ui_set_enabled(UIWIDGET widget, int enabled) { + widget->setEnabled(enabled); +} + +void ui_set_visible(UIWIDGET widget, int visible) { + widget->setVisible(visible); +} + +void ui_widget_set_size(UIWIDGET w, int width, int height) { + w->resize(width >= 0 ? width : w->width(), height >= 0 ? height : w->height()); +} + +void ui_widget_redraw(UIWIDGET w) { + w->repaint(); +} diff --git a/ui/qt/widget.h b/ui/qt/widget.h new file mode 100644 index 0000000..f9ed0bf --- /dev/null +++ b/ui/qt/widget.h @@ -0,0 +1,38 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WIDGET_H +#define WIDGET_H + +#include "toolkit.h" + +#include "../ui/widget.h" + + +#endif /* WIDGET_H */ + diff --git a/ui/qt/window.cpp b/ui/qt/window.cpp new file mode 100644 index 0000000..38570d5 --- /dev/null +++ b/ui/qt/window.cpp @@ -0,0 +1,151 @@ +/* + * 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 "../common/object.h" + +#include "window.h" +#include "menu.h" +#include "toolbar.h" +#include "container.h" + +#include +#include +#include +#include +#include + +static UiObject* create_window(const char *title, void *window_data, bool simple, bool sidebar = false) { + UiObject *obj = uic_object_new_toplevel(); + obj->window = window_data; + obj->next = NULL; + + QMainWindow *window = new QMainWindow(); + window->setWindowTitle(title); + obj->widget = window; + + if(!simple) { + ui_add_menus(obj, window); + QToolBar *toolbar = ui_create_toolbar(obj); + if(toolbar) { + window->addToolBar(Qt::TopToolBarArea, toolbar); + } + } + + QBoxLayout *box = new QVBoxLayout(); + QWidget *boxWidget = new QWidget(); + boxWidget->setLayout(box); + window->setCentralWidget(boxWidget); + ui_obj_add_container(obj, new UiBoxContainer(box)); + if(sidebar) { + QDockWidget *dock = new QDockWidget(); + window->addDockWidget(Qt::LeftDockWidgetArea, dock); + window->setProperty("ui_sidebar", QVariant::fromValue((void*)dock)); + } + + obj->widget = window; + return obj; +} + +UiObject* ui_window(const 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); +} + +UiObject *ui_sidebar_window(const char *title, void *window_data) { + return create_window(title, window_data, false, true); +} + +void ui_dialog_create(UiObject *parent, UiDialogArgs *args) { + if(args->input || args->password) { + // TODO: QInputDialog + } else { + QMessageBox msgBox; + if(args->title) { + msgBox.setWindowTitle(args->title); + } + if(args->content) { + msgBox.setText(args->content); + } + QPushButton *btn1; + QPushButton *btn2; + if(args->button1_label) { + btn1 = msgBox.addButton(args->button1_label, QMessageBox::ActionRole); + } + if(args->button2_label) { + btn2 = msgBox.addButton(args->button2_label, QMessageBox::ActionRole); + } + if(args->closebutton_label) { + msgBox.addButton(args->closebutton_label, QMessageBox::DestructiveRole); + } + + msgBox.exec(); + + UiEvent evt; + evt.obj = parent; + evt.document = evt.obj->ctx->document; + evt.window = evt.obj->window; + evt.eventdata = NULL; + evt.eventdatatype = 0; + evt.intval = 0; + if(msgBox.clickedButton() == btn1) { + evt.intval = 1; + } else if(msgBox.clickedButton() == btn2) { + evt.intval = 2; + } + if(args->result) { + args->result(&evt, args->resultdata); + } + } +} + +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..693f730 --- /dev/null +++ b/ui/ui/button.h @@ -0,0 +1,164 @@ +/* + * 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 + +enum UiLinkType { + UI_LINK_TEXT = 0, + UI_LINK_BUTTON +}; +typedef enum UiLinkType UiLinkType; + +typedef struct UiButtonArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + const char *label; + const char *icon; + const char *tooltip; + UiLabelType labeltype; + ui_callback onclick; + void *onclickdata; + + const int *groups; +} UiButtonArgs; + +typedef struct UiToggleArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + const char *label; + const char *icon; + const char *tooltip; + UiLabelType labeltype; + UiInteger *value; + const char *varname; + ui_callback onchange; + void *onchangedata; + int enable_group; + + const int *groups; +} UiToggleArgs; + +typedef struct UiLinkButtonArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + const char *label; + const char *uri; + UiString *value; + const char *varname; + ui_callback onclick; + void *onclickdata; + UiBool nofollow; + UiLinkType type; + + const int *groups; +} UiLinkButtonArgs; + +#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__ } ) +#define ui_linkbutton(obj, ...) ui_linkbutton_create(obj, &(UiLinkButtonArgs){ __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); +UIEXPORT UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args); + +UIEXPORT void ui_button_set_label(UIWIDGET button, const char *label); +UIEXPORT void ui_button_set_icon(UIWIDGET button, const char *icon); + +UIEXPORT void ui_linkbutton_value_set(UiString *str, const char *label, const char *uri); +UIEXPORT void ui_linkbutton_value_set_label(UiString *str, const char *label); +UIEXPORT void ui_linkbutton_value_set_uri(UiString *str, const char *uri); +UIEXPORT void ui_linkbutton_value_set_visited(UiString *str, UiBool visited); + +UIEXPORT void ui_linkbutton_set_label(UIWIDGET button, const char *label); +UIEXPORT void ui_linkbutton_set_uri(UIWIDGET button, const char *label); +UIEXPORT void ui_linkbutton_set_visited(UIWIDGET button, UiBool visited); +UIEXPORT char* ui_linkbutton_get_label(UIWIDGET button); +UIEXPORT char* ui_linkbutton_get_uri(UIWIDGET button); +UIEXPORT UiBool ui_linkbutton_get_visited(UIWIDGET button); + +#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..3355850 --- /dev/null +++ b/ui/ui/container.h @@ -0,0 +1,422 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#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 { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + int spacing; + int columnspacing; + int rowspacing; + UiBool def_hfill; + UiBool def_vfill; + UiBool def_hexpand; + UiBool def_vexpand; +} UiContainerArgs; + +typedef struct UiFrameArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + UiSubContainerType subcontainer; + + int padding; + int spacing; + int columnspacing; + int rowspacing; + + const char* label; + UiBool isexpanded; +} UiFrameArgs; + +typedef struct UiTabViewArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + UiTabViewType tabview; + ui_callback onchange; + void *onchangedata; + + UiSubContainerType subcontainer; + + UiInteger *value; + const char* varname; + + int padding; + int spacing; + int columnspacing; + int rowspacing; +} UiTabViewArgs; + +typedef struct UiHeaderbarArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + UiBool showtitle; + UiBool showwindowbuttons; + + UiHeaderbarAlternative alternative; + int alt_spacing; +} UiHeaderbarArgs; + +typedef struct UiSidebarArgs { + const char *name; + const char *style_class; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int spacing; +} UiSidebarArgs; + +typedef struct UiSplitPaneArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + int spacing; + int columnspacing; + int rowspacing; + + int initial_position; + const char *position_property; + UiInteger *value; + const char* varname; + int max_panes; +} UiSplitPaneArgs; + +typedef struct UiItemListContainerArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + int spacing; + + int sub_margin; + int sub_spacing; + int sub_columnspacing; + int sub_rowspacing; + + UiList *value; + const char *varname; + /* + * void create_ui(UiObject *obj, int index, void *elm, void *userdata) + * + * UI constructor for each list element + * + * This callback is executed for each list element. A new UiObject is + * created for every item. + */ + void (*create_ui)(UiObject *, int, void *, void *); + void *userdata; + + /* + * ItemList container type + * Only UI_CONTAINER_VBOX or UI_CONTAINER_HBOX are supported + */ + UiSubContainerType container; + + /* + * item root container + */ + UiSubContainerType subcontainer; +} UiItemListContainerArgs; + + +typedef struct UiLayout UiLayout; +struct UiLayout { + UiBool fill; + char *label; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; +}; + +struct UiContainerX { + void *container; + UiBool close; + UiBool newline; + UiContainerX *prev; + UiContainerX *next; +}; + + +#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_sidebar(obj, ...) for(ui_sidebar_create(obj, &(UiSidebarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_left_panel(obj, ...) for(ui_left_panel_create(obj, &(UiSidebarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_right_panel(ob, ...) for(ui_right_panel_create(obj, &(UiSidebarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) + +#define ui_vbox0(obj) for(ui_vbox_create(obj, &(UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_hbox0(obj) for(ui_hbox_create(obj, &(UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_grid0(obj) for(ui_grid_create(obj, &(UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_frame0(obj) for(ui_frame_create(obj, &(UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_expander0(obj) for(ui_expande_create(obj, &(UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_scrolledwindow0(obj) for(ui_scrolledwindow_create(obj, &(UiFrameArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_tabview0(obj) for(ui_tabview_create(obj, &(UiTabViewArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_headerbar0(obj) for(ui_headerbar_create(obj, &(UiHeaderbarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_sidebar0(obj) for(ui_sidebar_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_left_panel0(obj) for(ui_left_panel_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_right_panel0(obj) for(ui_right_panel_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) + + +#define ui_vbox_w(obj, w, ...) for(w = ui_vbox_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_hbox_w(obj, w, ...) for(w = ui_hbox_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_grid_w(obj, w, ...) for(w = ui_grid_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_tabview_w(obj, w, ...) for(w = ui_tabview_create(obj, &(UiTabViewArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_scrolledwindow_w(obj, w, ...) for(w = ui_scrolledwindow_create(obj, &(UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) + +#define ui_hsplitpane(obj, ...) for(ui_hsplitpane_create(obj, &(UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_vsplitpane(obj, ...) for(ui_vsplitpane_create(obj, &(UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_hsplitpane0(obj) for(ui_hsplitpane_create(obj, &(UiSplitPaneArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_vsplitpane0(obj) for(ui_vsplitpane_create(obj, &(UiSplitPaneArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) + +#define ui_hsplitpane_w(obj, w, ...) for(w = ui_hsplitpane_create(obj, &(UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_vsplitpane_w(obj, w, ...) for(w = ui_vsplitpane_create(obj, &(UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) + +#define ui_tab(obj, label) for(ui_tab_create(obj, label);ui_container_finish(obj);ui_container_begin_close(obj)) + +#define ui_headerbar_start(obj) for(ui_headerbar_start_create(obj);ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_headerbar_center(obj) for(ui_headerbar_center_create(obj);ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_headerbar_end(obj) for(ui_headerbar_end_create(obj);ui_container_finish(obj);ui_container_begin_close(obj)) + +#define ui_itemlist(obj, ...) ui_itemlist_create(obj, &(UiItemListContainerArgs) { __VA_ARGS__} ) + +UIEXPORT void ui_end(UiObject *obj); // deprecated +UIEXPORT void ui_end_new(UiObject *obj); // TODO: rename to ui_end + +UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs *args); +UIEXPORT UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs *args); +UIEXPORT UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args); +UIEXPORT UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args); +UIEXPORT UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs *args); +UIEXPORT UIWIDGET ui_scrolledwindow_create(UiObject *obj, UiFrameArgs *args); + +UIEXPORT UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs *args); +UIEXPORT void ui_tab_create(UiObject *obj, const char* title); +UIEXPORT void ui_tabview_select(UIWIDGET tabview, int tab); +UIEXPORT void ui_tabview_remove(UIWIDGET tabview, int tab); +UIEXPORT UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index); + +UIEXPORT UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs *args); +UIEXPORT void ui_headerbar_start_create(UiObject *obj); +UIEXPORT void ui_headerbar_center_create(UiObject *obj); +UIEXPORT void ui_headerbar_end_create(UiObject *obj); + +UIEXPORT UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args); +UIEXPORT UIWIDGET ui_left_panel_create(UiObject *obj, UiSidebarArgs *args); +UIEXPORT UIWIDGET ui_right_panel_create(UiObject *obj, UiSidebarArgs *args); + +UIEXPORT UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs *args); + +UIEXPORT UIWIDGET ui_hsplitpane_create(UiObject *obj, UiSplitPaneArgs *args); +UIEXPORT UIWIDGET ui_vsplitpane_create(UiObject *obj, UiSplitPaneArgs *args); + +UIEXPORT void ui_splitpane_set_visible(UIWIDGET splitpane, int child_index, UiBool visible); + +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_layout_fill(obj, 1); \ + 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.override_defaults) ui_layout_override_defaults(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 + +#define UI_APPLY_LAYOUT2(obj, args) \ + if(args->fill) ui_layout_fill(obj, 1); \ + 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->override_defaults) ui_layout_override_defaults(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 + +#define UI_ARGS2LAYOUT(args) { \ + .fill = args->fill, \ + .hexpand = args->hexpand, \ + .vexpand = args->vexpand, \ + .hfill = args->hfill, \ + .vfill = args->vfill, \ + .override_defaults = args->override_defaults, \ + .margin = args->margin, \ + .margin_left = args->margin_left, \ + .margin_right = args->margin_right, \ + .margin_top = args->margin_top, \ + .margin_bottom = args->margin_bottom, \ + .colspan = args->colspan, \ + .rowspan = args->rowspan } + +#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..1ed1235 --- /dev/null +++ b/ui/ui/display.h @@ -0,0 +1,148 @@ +/* + * 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 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 { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + const char* label; + UiAlignment align; + UiLabelStyle style; + UiString* value; + const char* varname; +} UiLabelArgs; + +typedef struct UiProgressbarArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + int width; + const char *name; + const char *style_class; + + double min; + double max; + UiDouble* value; + const char* varname; +} UiProgressbarArgs; + +typedef struct UiProgressbarSpinnerArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + 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); + +/* 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..6dd6a0a --- /dev/null +++ b/ui/ui/entry.h @@ -0,0 +1,85 @@ +/* + * 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 UiSpinBoxArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + int width; + const char *name; + const char *style_class; + + double step; + int digits; + double min; + double max; + UiInteger *intvalue; + UiDouble* doublevalue; + UiRange *rangevalue; + const char* varname; + ui_callback onchange; + void* onchangedata; + + const int *groups; +} UiSpinBoxArgs; + + + +UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args); + +#define ui_spinbox(obj, ...) ui_spinbox_create(obj, &(UiSpinBoxArgs){ __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..e0063b9 --- /dev/null +++ b/ui/ui/graphics.h @@ -0,0 +1,101 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#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; +}; + +typedef struct UiDrawingAreaArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + int width; + int height; + ui_drawfunc draw; + void *drawdata; + ui_callback onclick; + void *onclickdata; + ui_callback onmotion; + void *onmotiondata; +} UiDrawingAreaArgs; + +#define ui_drawingarea(obj, ...) ui_drawingarea_create(obj, &(UiDrawingAreaArgs) { __VA_ARGS__ } ) + +UIEXPORT UIWIDGET ui_drawingarea_create(UiObject *obj, UiDrawingAreaArgs *args); +UIEXPORT void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height); +UIEXPORT void ui_drawingarea_redraw(UIWIDGET drawingarea); + +// text layout +UIEXPORT UiTextLayout* ui_text(UiGraphics *g); +UIEXPORT void ui_text_free(UiTextLayout *text); +UIEXPORT void ui_text_setstring(UiTextLayout *layout, char *str); +UIEXPORT void ui_text_setstringl(UiTextLayout *layout, char *str, int len); +UIEXPORT void ui_text_setfont(UiTextLayout *layout, const char *font, int size); +UIEXPORT void ui_text_getsize(UiTextLayout *layout, int *width, int *height); +UIEXPORT void ui_text_setwidth(UiTextLayout *layout, int width); + +// drawing functions +UIEXPORT void ui_graphics_color(UiGraphics *g, int red, int green, int blue); +UIEXPORT void ui_draw_line(UiGraphics *g, int x1, int y1, int x2, int y2); +UIEXPORT void ui_draw_rect(UiGraphics *g, int x, int y, int w, int h, UiBool fill); +UIEXPORT 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..9bc86a4 --- /dev/null +++ b/ui/ui/icons.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 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" +#define UI_ICON_GO_UP "go-up" +#define UI_ICON_GO_DOWN "go-down" + +#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" +#define UI_ICON_GO_UP "Up" +#define UI_ICON_GO_DOWN "" // TODO: implement workaround for missing down symbol + +#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..77d923f --- /dev/null +++ b/ui/ui/image.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 UI_IMAGE_H +#define UI_IMAGE_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define UI_IMAGE_OBJECT_TYPE "image" + +#ifdef UI_GTK +#define UIIMAGE GdkPixbuf* +#else +#define UIIMAGE void* +#endif + + +typedef struct UiImageViewerArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + UiBool scrollarea; + UiBool autoscale; + UiBool adjustwidgetsize; + UiBool useradjustable; + int image_padding; + int image_padding_left; + int image_padding_right; + int image_padding_top; + int image_padding_bottom; + UiGeneric *value; + const char *varname; + UiMenuBuilder *contextmenu; + + ui_callback onbuttonpress; + void *onbuttonpressdata; + ui_callback onbuttonrelease; + void *onbuttonreleasedata; +} UiImageViewerArgs; + +#define ui_imageviewer(obj, ...) ui_imageviewer_create(obj, &(UiImageViewerArgs){ __VA_ARGS__ } ) + +UIEXPORT UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs *args); + +UIEXPORT UIWIDGET ui_imageviewer_reset(UIWIDGET w); +UIEXPORT UIWIDGET ui_imageviewer_set_autoscale(UIWIDGET w, UiBool set); +UIEXPORT UIWIDGET ui_imageviewer_set_adjustwidgetsize(UIWIDGET w, UiBool set); +UIEXPORT UIWIDGET ui_imageviewer_set_useradjustable(UIWIDGET w, UiBool set); + +UIEXPORT int ui_image_load_file(UiGeneric *obj, const char *path); +UIEXPORT int ui_image_load_data(UiGeneric *obj, const void *imgdata, size_t size); + +UIEXPORT void ui_image_ref(UIIMAGE img); +UIEXPORT void ui_image_unref(UIIMAGE img); + +#ifdef __cplusplus +} +#endif + +#endif /* UI_IMAGE_H */ + diff --git a/ui/ui/menu.h b/ui/ui/menu.h new file mode 100644 index 0000000..9dde9b4 --- /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* icon; + + ui_callback onclick; + void* onclickdata; + + const int* groups; +} UiMenuItemArgs; + +typedef struct UiMenuToggleItemArgs { + const char* label; + 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; + UiBool addseparator; +} 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_ref(UiMenuBuilder *builder); +UIEXPORT void ui_menubuilder_unref(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..ffb10dd --- /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(const char *path); +void ui_pixmaps_dir(const char *path); + +void ui_load_lang(const char *locale); +void ui_load_lang_def(char *locale, char *default_locale); + +char* uistr(const char *name); +char* uistr_n(const 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..6bceadf --- /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); // TODO +UIWIDGET ui_vscrollbar(UiObject *obj, UiRange *range, ui_callback f, void *userdata); // TODO + + + +#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..ac5a2c1 --- /dev/null +++ b/ui/ui/text.h @@ -0,0 +1,163 @@ +/* + * 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 { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + int width; + int height; + const char *name; + const char *style_class; + + UiText *value; + const char *varname; + ui_callback onchange; + void *onchangedata; + + const int *groups; +} UiTextAreaArgs; + +typedef struct UiTextFieldArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + int width; + const char *name; + const char *style_class; + + UiString* value; + const char *varname; + ui_callback onchange; + void *onchangedata; + ui_callback onactivate; + void *onactivatedata; + + 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 { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + 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..26a79e6 --- /dev/null +++ b/ui/ui/toolbar.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_TOOLBAR_H +#define UI_TOOLBAR_H + +#include "toolkit.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiToolbarItemArgs { + const char *label; + const char *icon; + const char *tooltip; + + ui_callback onclick; + void* onclickdata; + + const int *groups; + const int *visibility_states; +} UiToolbarItemArgs; + +typedef struct UiToolbarToggleItemArgs { + const char *label; + const char *icon; + const char *tooltip; + + const char *varname; + ui_callback onchange; + void *onchangedata; + + const int *groups; + const int *visibility_states; +} UiToolbarToggleItemArgs; + +typedef struct UiToolbarMenuArgs { + const char *label; + const char *icon; + const char *tooltip; + const int *visibility_states; +} UiToolbarMenuArgs; + +enum UiToolbarPos { + UI_TOOLBAR_LEFT = 0, + UI_TOOLBAR_CENTER, + UI_TOOLBAR_RIGHT, + UI_TOOLBAR_SIDEBAR_LEFT, + UI_TOOLBAR_SIDEBAR_RIGHT, + UI_TOOLBAR_RIGHTPANEL_LEFT, + UI_TOOLBAR_RIGHTPANEL_CENTER, + UI_TOOLBAR_RIGHTPANEL_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..c0555c0 --- /dev/null +++ b/ui/ui/toolkit.h @@ -0,0 +1,698 @@ +/* + * 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 +#include + +#ifdef UI_COCOA + +#include +#ifdef __OBJC__ +#import +#endif +typedef void* UIWIDGET; // NSView* +typedef void* UIWINDOW; // NSWindow* +typedef void* UIMENU; // NSMenu* + +#elif UI_GTK2 || UI_GTK3 || UI_GTK4 +#define UI_GTK + +#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 defined(UI_QT4) || defined(UI_QT5) +#define UI_QT + +#ifdef __cplusplus + +#include +#include +#include + +#define UIWIDGET QWidget* +#define UIWINDOW QWidget* +#define UIMENU QMenu* +#else /* __cplusplus */ +#define UIWIDGET void* +#define UIWINDOW void* +#define UIMENU void* +#endif + +#elif UI_MOTIF + +#include +#define UIWIDGET Widget +#define UIMENU Widget + + +#elif UI_WIN32 + +#include "win32.h" + +#define UIWIDGET W32Widget* +#define UIWINDOW void* +#define UIMENU void* + +#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 */ +#ifndef __cplusplus +typedef _Bool UiBool; +#else +typedef bool UiBool; +#endif + +typedef struct UiObject UiObject; +typedef struct UiContainerX UiContainerX; +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; + +typedef struct UiTextStyle UiTextStyle; +typedef struct UiColor UiColor; + +/* 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 { + UI_DEFAULT = 0, + UI_ON, + UI_OFF +} UiTri; + +enum UiMouseEventType { UI_PRESS = 0, UI_PRESS2 }; + +typedef enum UiLabelType { + UI_LABEL_DEFAULT, + UI_LABEL_TEXT, + UI_LABEL_ICON, + UI_LABEL_TEXT_ICON +} UiLabelType; + +typedef enum UiDnDAction { + UI_DND_ACTION_NONE, + UI_DND_ACTION_COPY, + UI_DND_ACTION_MOVE, + UI_DND_ACTION_LINK, + UI_DND_ACTION_CUSTOM +} UiDnDAction; + +enum UiAlignment { + UI_ALIGN_DEFAULT = 0, + UI_ALIGN_LEFT, + UI_ALIGN_RIGHT, + UI_ALIGN_CENTER +}; + +typedef enum UiAlignment UiAlignment; + +typedef void(*ui_callback)(UiEvent*, void*); /* event, user data */ + +typedef void*(*ui_getvaluefunc)(void *elm, int col); +typedef void*(*ui_getvaluefunc2)(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult); +typedef UiBool(*ui_getstylefunc)(UiList *list, void *elm, int row, int col, void *userdata, UiTextStyle *style); + +typedef int(*ui_threadfunc)(void*); + +typedef void(*ui_freefunc)(void*); + +typedef void(*ui_enablefunc)(void*, int); + +typedef void (*ui_destructor_func)(void *memory); + + +struct UiObject { + /* + * native widget + */ + UIWIDGET widget; + +#if defined(UI_COCOA) || defined(UI_WINUI) + /* + * native window object + */ + UIWINDOW wobj; +#endif + + /* + * user window data + */ + void *window; + + /* + * window context + */ + UiContext *ctx; + + /* + * container list + * TODO: remove old UiContainer and rename UiContainerX to UiContainer + */ + UiContainerX *container_begin; + UiContainerX *container_end; + + /* + * next container object + */ + UiObject *next; + + /* + * obj destroy func + */ + void (*destroy)(UiObject *obj); + + /* + * reference counter + */ + unsigned int ref; +}; + +struct UiTabbedPane { + /* + * native widget + */ + UIWIDGET widget; + + /* + * current document + */ + void *document; + + /* + * parent context + */ + UiContext *ctx; +}; + +struct UiEvent { + UiObject *obj; + void *document; + void *window; + void *eventdata; + int eventdatatype; + int intval; + int set; +}; + +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 (*save)(UiText*); + void (*destroy)(UiText*); + void (*restore)(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 (*setselection)(UiText*, int, int); /* text, begin, end */ + 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; + int datatype; + void *data1; + void *data2; + // TODO: replacefunc, ... + UiObserver *observers; +}; + +/* UiText.datatype */ +#define UI_TEXT_TYPE_BUFFER 1 + +struct UiGeneric { + void* (*get)(UiGeneric*); + const char* (*get_type)(UiGeneric*); + int (*set)(UiGeneric*, void *, const char *type); + void (*destroy)(UiGeneric*); + void *obj; + + void *value; + const char *type; + UiObserver *observers; +}; + +typedef void (*ui_list_init_func)(UiContext *ctx, UiList *list, void *userdata); + +/* + * 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; +}; + +struct UiFileList { + char **files; + size_t nfiles; +}; + +typedef struct UiCondVar { + void *data; + int intdata; +} UiCondVar; + +enum UiEventType { + UI_EVENT_DATA_NULL = 0, + UI_EVENT_DATA_POINTER, + UI_EVENT_DATA_STRING, + UI_EVENT_DATA_INTEGER_VALUE, + UI_EVENT_DATA_STRING_VALUE, + UI_EVENT_DATA_TEXT_VALUE, + UI_EVENT_DATA_DOUBLE_VALUE, + UI_EVENT_DATA_RANGE_VALUE, + UI_EVENT_DATA_LIST_SELECTION, + UI_EVENT_DATA_LIST_ELM, + UI_EVENT_DATA_DND, + UI_EVENT_DATA_SUBLIST, + UI_EVENT_DATA_FILE_LIST +}; + +#define UI_COLOR(r, g, b) (UiColor){r, g, b} +struct UiColor { + uint8_t red; + uint8_t green; + uint8_t blue; +}; + +#define UI_TEXT_STYLE_BOLD 1 +#define UI_TEXT_STYLE_ITALIC 2 +#define UI_TEXT_STYLE_UNDERLINE 4 + +struct UiTextStyle { + uint32_t text_style; + UiColor fg; + UiBool fg_set; +}; + + +UIEXPORT void ui_init(const char *appname, int argc, char **argv); +UIEXPORT const char* ui_appname(); + +UIEXPORT void ui_add_styledata(const char *styledata, int len); + +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 UiContext* ui_context_parent(UiContext *ctx); + +UIEXPORT void ui_object_ref(UiObject *obj); +UIEXPORT int 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 int ui_app_save_settings(void); +UIEXPORT void ui_app_exit_on_shutdown(UiBool exitapp); + +UIEXPORT void ui_main(void); +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_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_document(UiContext *ctx, void *document); + +UIEXPORT void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...); +UIEXPORT void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, int *groups, int ngroups); +UIEXPORT void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, int *states, int nstates); + +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); +UIEXPORT void ui_reg_destructor(UiContext *ctx, void *data, ui_destructor_func destr); +UIEXPORT void ui_set_destructor(void *mem, ui_destructor_func destr); + +// types + +UIEXPORT UiInteger* ui_int_new(UiContext *ctx, const char *name); +UIEXPORT UiDouble* ui_double_new(UiContext *ctx, const char *name); +UIEXPORT UiString* ui_string_new(UiContext *ctx, const char *name); +UIEXPORT UiText* ui_text_new(UiContext *ctx, const char *name); +UIEXPORT UiRange* ui_range_new(UiContext *ctx, const char *name); +UIEXPORT UiGeneric* ui_generic_new(UiContext *ctx, const 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_range_set(UiRange *r, double value); +UIEXPORT void ui_range_set_range(UiRange *r, double min, double max); +UIEXPORT void ui_range_set_extent(UiRange *r, double extent); +UIEXPORT double ui_range_get(UiRange *r); +UIEXPORT double ui_range_get_min(UiRange *r); +UIEXPORT double ui_range_get_max(UiRange *r); +UIEXPORT double ui_range_get_extent(UiRange *r); +UIEXPORT void ui_generic_set_image(UiGeneric *g, void *img); +UIEXPORT void* ui_generic_get_image(UiGeneric *g); + +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, const char *name); +UIEXPORT UiList* ui_list_new2(UiContext *ctx, const char *name, ui_list_init_func init, void *userdata); +UIEXPORT void ui_list_free(UiList *list); +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_update_row(UiList *list, int row); +UIEXPORT UiListSelection ui_list_get_selection(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? + + + +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(const 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); + +UIEXPORT void ui_setop_enable(int set); +UIEXPORT int ui_get_setop(void); + + +UIEXPORT void ui_global_list_initializer(ui_list_init_func func, void *userdata); +UIEXPORT void ui_list_class_set_first(UiList *list, void*(*first)(UiList *list)); +UIEXPORT void ui_list_class_set_next(UiList *list, void*(*next)(UiList *list)); +UIEXPORT void ui_list_class_set_get(UiList *list, void*(*get)(UiList *list, int i)); +UIEXPORT void ui_list_class_set_count(UiList *list, int(*count)(UiList *list)); +UIEXPORT void ui_list_class_set_data(UiList *list, void *data); +UIEXPORT void ui_list_class_set_iter(UiList *list, void *iter); + +#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..33a903c --- /dev/null +++ b/ui/ui/tree.h @@ -0,0 +1,339 @@ +/* + * 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 struct UiSourceListArgs UiSourceListArgs; + +typedef struct UiSubList UiSubList; +typedef struct UiSubListItem UiSubListItem; + +typedef enum UiModelType { + UI_STRING = 0, + UI_STRING_FREE, + UI_INTEGER, + UI_ICON, + UI_ICON_TEXT, + UI_ICON_TEXT_FREE, + UI_STRING_EDITABLE, + UI_BOOL_EDITABLE +} UiModelType; + +typedef struct UiCellValue { + union { + const char *string; + int64_t i; + UiBool b; + }; + UiModelType type; +} UiCellValue; + +typedef UiBool (*ui_list_savefunc)(UiList *list, int row, int col, UiCellValue *value, void *userdata); + +struct UiModel { + /* + * number of columns + */ + int columns; + + /* + * current allocation size (internal) + */ + int alloc; + + /* + * 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; +}; + +struct UiListCallbacks { + /* + * selection callback + */ + ui_callback activate; + + /* + * cursor callback + */ + ui_callback selection; + + /* + * userdata for all callbacks + */ + void *userdata; +}; + +struct UiListArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + int width; + int height; + + const char *name; + const char *style_class; + UiList* list; + const char* varname; + UiModel* model; + char **static_elements; + size_t static_nelm; + ui_getvaluefunc getvalue; + ui_getvaluefunc2 getvalue2; + void *getvalue2data; + ui_getstylefunc getstyle; + void *getstyledata; + 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; + UiBool multiselection; + UiMenuBuilder *contextmenu; + ui_list_savefunc onsave; + void *onsavedata; + + const int *groups; +}; + +typedef void (*ui_sublist_getvalue_func)(UiList *list, void *sublist_userdata, void *rowdata, int index, UiSubListItem *item, void *userdata); + +struct UiSubList { + UiList *value; + const char *varname; + const char *header; + UiBool separator; + void *userdata; +}; + +typedef struct UiSubListEventData { + UiList *list; + int sublist_index; + int row_index; + void *row_data; + void *sublist_userdata; + void *event_data; +} UiSubListEventData; + +/* + * list item members must be filled by the sublist getvalue func + * all members must be allocated (by malloc, strdup, ...) the pointer + * will be passed to free + */ +struct UiSubListItem { + char *icon; + char *label; + char *button_icon; + char *button_label; + UiMenuBuilder *button_menu; + char *badge; + void *eventdata; +}; + +struct UiSourceListArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + int width; + int height; + const char *name; + const char *style_class; + + const int *groups; + + /* + * static list of sublists + * a sublist must have a varname or a value + * + * the last entry in the list must contain all NULL values or numsublists + * must contain the number of sublists + * + * sublists can be NULL, in which case sublists are dynamically loaded + * from dynamic_sublist/varname + */ + UiSubList *sublists; + /* + * optional number of sublists + * if the value is 0, it is assumed, that sublists is null-terminated + * (last item contains only NULL values) + */ + size_t numsublists; + + /* + * list value, that contains UiSubList* elements + */ + UiList *dynamic_sublist; + + /* + * load sublists dynamically from a variable with the specified name + */ + const char *varname; + + + /* + * callback for each list item, that should fill all necessary + * UiSubListItem fields + */ + ui_sublist_getvalue_func getvalue; + + /* + * getvalue_func userdata + */ + void *getvaluedata; + + /* + * is a sublist header a selectable item + */ + UiBool header_is_item; + + /* + * activated when a list item is selected + */ + ui_callback onactivate; + void *onactivatedata; + + /* + * activated, when the additional list item button is clicked + */ + ui_callback onbuttonclick; + void *onbuttonclickdata; + + UiMenuBuilder *contextmenu; +}; + +#define UI_SUBLIST(...) (UiSubList){ __VA_ARGS__ } +#define UI_SUBLISTS(...) (UiSubList[]){ __VA_ARGS__, (UiSubList){NULL,NULL,NULL,0} } + + +/* + * Creates an UiModel, that specifies columns for a table widget. + * + * For each column a column type (UiModelType) and a title string + * (char*) must be specified. The column list must be terminated + * with -1. + * + * UiModel *model = ui_model(ctx, UI_STRING, "Column 1", UI_STRING, "Column 2", -1); + */ +UIEXPORT UiModel* ui_model(UiContext *ctx, ...); +UIEXPORT UiModel* ui_model_new(UiContext *ctx); +UIEXPORT void ui_model_add_column(UiContext *ctx, UiModel *model, UiModelType type, const char *title, int width); +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__ } ) +#define ui_sourcelist(obj, ...) ui_sourcelist_create(obj, &(UiSourceListArgs) { __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); + +UIEXPORT void ui_listview_select(UIWIDGET listview, int index); +UIEXPORT void ui_combobox_select(UIWIDGET dropdown, int index); + +UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args); + +UIEXPORT void ui_sublist_item_set_icon(UiSubListItem *item, const char *icon); +UIEXPORT void ui_sublist_item_set_label(UiSubListItem *item, const char *label); +UIEXPORT void ui_sublist_item_set_button_icon(UiSubListItem *item, const char *button_icon); +UIEXPORT void ui_sublist_item_set_button_label(UiSubListItem *item, const char *button_label); +UIEXPORT void ui_sublist_item_set_button_menu(UiSubListItem *item, UiMenuBuilder *menu); +UIEXPORT void ui_sublist_item_set_badge(UiSubListItem *item, const char *badge); +UIEXPORT void ui_sublist_item_set_eventdata(UiSubListItem *item, void *eventdata); + + + +/* + * Only relevant for some language bindings + */ +typedef void(*ui_sourcelist_update_func)(void); + +/* + * The sourcelist update callback is called after any source list + * sublist update is completed + */ +UIEXPORT void ui_sourcelist_set_update_callback(ui_sourcelist_update_func cb); + +#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..57c8025 --- /dev/null +++ b/ui/ui/ui.h @@ -0,0 +1,54 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UI_H +#define UI_H + +#include "toolkit.h" +#include "widget.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" + +#include "webview.h" + +#endif /* UI_H */ + diff --git a/ui/ui/webview.h b/ui/ui/webview.h new file mode 100644 index 0000000..9636165 --- /dev/null +++ b/ui/ui/webview.h @@ -0,0 +1,107 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_WEBVIEW_H +#define UI_WEBVIEW_H + +#include "toolkit.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * WebView type string used by UiGeneric + */ +#define UI_WEBVIEW_OBJECT_TYPE "webview" + +/* + * UiWebViewData* is returned by a webviews UiGeneric->get function + */ +typedef struct UiWebViewData UiWebViewData; + +typedef struct UiWebviewArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + UiGeneric *value; + const char *varname; + + const int* groups; +} UiWebviewArgs; + +#define ui_webview(obj, ...) ui_webview_create(obj, &(UiWebviewArgs){ __VA_ARGS__ } ) + +UIEXPORT UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args); + +UIEXPORT void ui_webview_load_url(UiGeneric *g, const char *url); + +UIEXPORT void ui_webview_load_content( + UiGeneric *g, + const char *uri, + const char *content, + size_t contentlength, + const char *mimetype, + const char *encoding); + +/* + * Frees a UiWebViewData object returned by a webviews UiGeneric->get function + */ +UIEXPORT void ui_webview_data_free(UiWebViewData *data); + +UIEXPORT void ui_webview_reload(UiGeneric *g); +UIEXPORT UiBool ui_webview_can_go_back(UiGeneric *g); +UIEXPORT UiBool ui_webview_can_go_forward(UiGeneric *g); +UIEXPORT void ui_webview_go_back(UiGeneric *g); +UIEXPORT void ui_webview_go_forward(UiGeneric *g); +UIEXPORT const char * ui_webview_get_uri(UiGeneric *g); +UIEXPORT void ui_webview_enable_javascript(UiGeneric *g, UiBool enable); +UIEXPORT void ui_webview_set_zoom(UiGeneric *g, double zoom); +UIEXPORT double ui_webview_get_zoom(UiGeneric *g); + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_WEBVIEW_H */ + diff --git a/ui/ui/widget.h b/ui/ui/widget.h new file mode 100644 index 0000000..aea5b8e --- /dev/null +++ b/ui/ui/widget.h @@ -0,0 +1,89 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_WIDGET_H +#define UI_WIDGET_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiWidgetArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + const char *name; + const char *style_class; +} UiWidgetArgs; + +#ifdef UI_GTK +typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void *userdata); +#elif defined(UI_QT) +typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void *userdata); +#elif defined(UI_MOTIF) +typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void *userdata, Widget parent, Arg *a, int n); +#elif defined(UI_COCOA) +typedef UIWIDGET (*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void *userdata); +#elif defined(UI_WINUI) +typedef UIWIDGET(*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void *userdata); +#elif defined(UI_WIN32) +typedef UIWIDGET(*ui_createwidget_func)(UiObject *obj, UiWidgetArgs *args, void *userdata); +#endif +UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args); + +#define ui_customwidget(obj, create_widget, userdata, ...) ui_customwidget_create(obj, create_widget, userdata, &(UiWidgetArgs) { __VA_ARGS__ }) + + +UIEXPORT UIWIDGET ui_separator_create(UiObject *obj, UiWidgetArgs *args); + +#define ui_separator(obj, ...) ui_separator_create(obj, &(UiWidgetArgs){ __VA_ARGS__ } ) + +UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled); +UIEXPORT void ui_set_visible(UIWIDGET widget, int visible); + +UIEXPORT void ui_widget_set_size(UIWIDGET w, int width, int height); +UIEXPORT void ui_widget_redraw(UIWIDGET w); + + +#ifdef __cplusplus +} +#endif + +#endif /* UI_WIDGET_H */ + diff --git a/ui/ui/win32.h b/ui/ui/win32.h new file mode 100644 index 0000000..54d0543 --- /dev/null +++ b/ui/ui/win32.h @@ -0,0 +1,71 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_WIN32_H +#define UI_WIN32_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define UIEXPORT __declspec(dllexport) + +typedef struct W32WidgetClass W32WidgetClass; +typedef struct W32Widget W32Widget; +typedef struct W32Size W32Size; + +typedef void (*W32LayoutFunc)(void *, int, int); + +struct W32Size { + int width; + int height; +}; + +struct W32WidgetClass { + void (*eventproc)(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + void (*show)(W32Widget *widget, BOOLEAN show); + void (*enable)(W32Widget *widget, BOOLEAN enable); + W32Size (*get_preferred_size)(W32Widget *widget); + void (*destroy)(W32Widget *widget); +}; + +struct W32Widget { + W32WidgetClass *wclass; + HWND hwnd; + void *userdata; + void (*layout)(void *layout, int width, int height); + void *layoutmanager; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* UI_WIN32_H */ diff --git a/ui/ui/window.h b/ui/ui/window.h new file mode 100644 index 0000000..83e85a3 --- /dev/null +++ b/ui/ui/window.h @@ -0,0 +1,107 @@ +/* + * 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_sidebar_window(const char *title, void *window_data); +UIEXPORT UiObject *ui_splitview_window(const char *title, UiBool sidebar); +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); +UIEXPORT void ui_window_default_size(int width, int height); + +UIEXPORT void ui_splitview_window_set_pos(UiObject *obj, int pos); +UIEXPORT int ui_splitview_window_get_pos(UiObject *obj); +UIEXPORT void ui_splitview_window_set_default_pos(int pos); +UIEXPORT void ui_splitview_window_use_property(UiBool enable); +UIEXPORT void ui_splitview_window_set_visible(UiObject *obj, int pane, UiBool visible); + +#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 */ + -- 2.47.3