update toolkit: gtk4 improvements

Sun, 29 Sep 2024 13:32:51 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 29 Sep 2024 13:32:51 +0200
changeset 44
473954dc6b74
parent 43
ef01d2c90128
child 45
ab71409644b0

update toolkit: gtk4 improvements

configure file | annotate | diff | comparison | revisions
make/cc.mk file | annotate | diff | comparison | revisions
make/clang.mk file | annotate | diff | comparison | revisions
make/gcc.mk file | annotate | diff | comparison | revisions
make/osx.mk file | annotate | diff | comparison | revisions
make/project.xml file | annotate | diff | comparison | revisions
make/suncc.mk file | annotate | diff | comparison | revisions
ui/common/context.h file | annotate | diff | comparison | revisions
ui/common/menu.c file | annotate | diff | comparison | revisions
ui/common/menu.h file | annotate | diff | comparison | revisions
ui/gtk/button.c file | annotate | diff | comparison | revisions
ui/gtk/button.h file | annotate | diff | comparison | revisions
ui/gtk/container.c file | annotate | diff | comparison | revisions
ui/gtk/container.h file | annotate | diff | comparison | revisions
ui/gtk/display.c file | annotate | diff | comparison | revisions
ui/gtk/draw_cairo.c file | annotate | diff | comparison | revisions
ui/gtk/graphics.c file | annotate | diff | comparison | revisions
ui/gtk/headerbar.c file | annotate | diff | comparison | revisions
ui/gtk/headerbar.h file | annotate | diff | comparison | revisions
ui/gtk/image.c file | annotate | diff | comparison | revisions
ui/gtk/image.h file | annotate | diff | comparison | revisions
ui/gtk/menu.c file | annotate | diff | comparison | revisions
ui/gtk/menu.h file | annotate | diff | comparison | revisions
ui/gtk/objs.mk file | annotate | diff | comparison | revisions
ui/gtk/range.c file | annotate | diff | comparison | revisions
ui/gtk/text.c file | annotate | diff | comparison | revisions
ui/gtk/text.h file | annotate | diff | comparison | revisions
ui/gtk/toolbar.c file | annotate | diff | comparison | revisions
ui/gtk/toolbar.h file | annotate | diff | comparison | revisions
ui/gtk/toolkit.c file | annotate | diff | comparison | revisions
ui/gtk/toolkit.h file | annotate | diff | comparison | revisions
ui/gtk/tree.c file | annotate | diff | comparison | revisions
ui/gtk/window.c file | annotate | diff | comparison | revisions
ui/ui/icons.h file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
--- a/configure	Mon Jun 17 21:20:58 2024 +0200
+++ b/configure	Sun Sep 29 13:32:51 2024 +0200
@@ -72,7 +72,7 @@
 Options:
   --debug                 add extra compile flags for debug builds
   --release               add extra compile flags for release builds
-  --toolkit=(gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|motif)
+  --toolkit=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|motif)
 
 __EOF__
 }
@@ -508,6 +508,32 @@
     dep_checked_openssl=1
     return 0
 }
+dependency_error_libadwaita()
+{
+    print_check_msg "$dep_checked_libadwaita" "checking for libadwaita... "
+    # dependency libadwaita
+    while true
+    do
+        if [ -z "$PKG_CONFIG" ]; then
+            break
+        fi
+        if test_pkg_config "libadwaita-1" "" "" "" ; then
+            TEMP_CFLAGS="$TEMP_CFLAGS `"$PKG_CONFIG" --cflags libadwaita-1`"
+            TEMP_LDFLAGS="$TEMP_LDFLAGS `"$PKG_CONFIG" --libs libadwaita-1`"
+        else
+            break
+        fi
+        TEMP_CFLAGS="$TEMP_CFLAGS -DUI_GTK4 -DUI_LIBADWAITA"
+        TEMP_LDFLAGS="$TEMP_LDFLAGS -lpthread"
+        print_check_msg "$dep_checked_libadwaita" "yes\n"
+        dep_checked_libadwaita=1
+        return 1
+    done
+
+    print_check_msg "$dep_checked_libadwaita" "no\n"
+    dep_checked_libadwaita=1
+    return 0
+}
 dependency_error_motif()
 {
     print_check_msg "$dep_checked_motif" "checking for motif... "
@@ -751,6 +777,21 @@
 #
 # OPTION VALUES
 #
+checkopt_toolkit_libadwaita()
+{
+    VERR=0
+    if dependency_error_libadwaita ; then
+        VERR=1
+    fi
+    if [ $VERR -ne 0 ]; then
+        return 1
+    fi
+    cat >> "$TEMP_DIR/make.mk" << __EOF__
+TOOLKIT = gtk
+GTKOBJ = draw_cairo.o
+__EOF__
+    return 0
+}
 checkopt_toolkit_gtk4()
 {
     VERR=0
@@ -978,6 +1019,14 @@
     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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/cc.mk	Sun Sep 29 13:32:51 2024 +0200
@@ -0,0 +1,14 @@
+#
+# cc toolchain config
+#
+
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
\ No newline at end of file
--- a/make/clang.mk	Mon Jun 17 21:20:58 2024 +0200
+++ b/make/clang.mk	Sun Sep 29 13:32:51 2024 +0200
@@ -2,8 +2,13 @@
 # clang toolchain config
 #
 
-CFLAGS = 
-LDFLAGS = 
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
 
 SHLIB_CFLAGS = -fPIC
 SHLIB_LDFLAGS = -shared
--- a/make/gcc.mk	Mon Jun 17 21:20:58 2024 +0200
+++ b/make/gcc.mk	Sun Sep 29 13:32:51 2024 +0200
@@ -2,8 +2,13 @@
 # gcc toolchain config
 #
 
-CFLAGS = 
-LDFLAGS = 
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
 
 SHLIB_CFLAGS = -fPIC
 SHLIB_LDFLAGS = -shared
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/osx.mk	Sun Sep 29 13:32:51 2024 +0200
@@ -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
--- a/make/project.xml	Mon Jun 17 21:20:58 2024 +0200
+++ b/make/project.xml	Sun Sep 29 13:32:51 2024 +0200
@@ -45,7 +45,11 @@
 		<pkgconfig>openssl</pkgconfig>
 	</dependency>
 	
-
+	<dependency name="libadwaita">
+		<pkgconfig>libadwaita-1</pkgconfig>
+		<cflags>-DUI_GTK4 -DUI_LIBADWAITA</cflags>
+		<ldflags>-lpthread</ldflags>
+	</dependency>
 	<dependency name="gtk4">
 		<pkgconfig>gtk+-4.0</pkgconfig>
 		<cflags>-DUI_GTK3</cflags>
@@ -123,6 +127,11 @@
 	
 	<target name="tk">
 		<option arg="toolkit">
+			<value str="libadwaita">
+				<dependencies>libadwaita</dependencies>
+				<make>TOOLKIT = gtk</make>
+				<make>GTKOBJ = draw_cairo.o</make>
+			</value>
 			<value str="gtk4">
 				<dependencies>gtk4</dependencies>
 				<make>TOOLKIT = gtk</make>
--- a/make/suncc.mk	Mon Jun 17 21:20:58 2024 +0200
+++ b/make/suncc.mk	Sun Sep 29 13:32:51 2024 +0200
@@ -2,8 +2,13 @@
 # suncc toolchain
 #
 
-CFLAGS = 
-LDFLAGS = 
+CFLAGS =
+CXXFLAGS =
+DEBUG_CC_FLAGS = -g
+DEBUG_CXX_FLAGS = -g
+RELEASE_CC_FLAGS = -O3 -DNDEBUG
+RELEASE_CXX_FLAGS = -O3 -DNDEBUG
+LDFLAGS =
 
 SHLIB_CFLAGS = -Kpic
 SHLIB_LDFLAGS = -G
--- a/ui/common/context.h	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/common/context.h	Sun Sep 29 13:32:51 2024 +0200
@@ -77,9 +77,15 @@
     
     char          *title;
     
-#if UI_GTK2 || UI_GTK3
+#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;
--- a/ui/common/menu.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/common/menu.c	Sun Sep 29 13:32:51 2024 +0200
@@ -38,6 +38,8 @@
 static UiMenu *menus_end;
 static CxList *current;
 
+static int menu_item_counter = 0;
+
 static void add_menu(UiMenu *menu) {
     cx_linked_list_add(
             (void**)&menus_begin,
@@ -57,6 +59,10 @@
             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;
 }
@@ -86,6 +92,7 @@
     
     // 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;
@@ -118,6 +125,7 @@
     }
 
     UiMenuItem* item = malloc(sizeof(UiMenuItem));
+    mitem_set_id(&item->item);
     item->item.prev = NULL;
     item->item.next = NULL;
     item->item.type = UI_MENU_ITEM;
@@ -138,6 +146,7 @@
     }
     
     UiMenuItemI  *item = malloc(sizeof(UiMenuItemI));
+    item->id[0] = 0;
     item->prev = NULL;
     item->next = NULL;
     item->type = UI_MENU_SEPARATOR;
@@ -151,6 +160,7 @@
     }
     
     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;
@@ -172,6 +182,7 @@
     }
 
     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;
@@ -193,6 +204,7 @@
     }
     
     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;
@@ -210,6 +222,7 @@
     }
 
     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;
@@ -226,6 +239,7 @@
     }
 
     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;
--- a/ui/common/menu.h	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/common/menu.h	Sun Sep 29 13:32:51 2024 +0200
@@ -61,6 +61,7 @@
     UiMenuItemI    *prev;
     UiMenuItemI    *next;
     UiMenuItemType type;
+    char           id[8];
 };
 
 struct UiMenu {
--- a/ui/gtk/button.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/button.c	Sun Sep 29 13:32:51 2024 +0200
@@ -54,20 +54,21 @@
 #endif
 }
 
-UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs args) {
-    UiObject* current = uic_current_obj(obj);
-    GtkWidget *button = gtk_button_new();
-    if(args.label) {
-        gtk_button_set_label(GTK_BUTTON(button), args.label);
-    }
-    ui_button_set_icon_name(button, args.icon);
-
+GtkWidget* ui_create_button(
+        UiObject *obj,
+        const char *label,
+        const char *icon,
+        ui_callback onclick,
+        void *userdata)
+{
+    GtkWidget *button = gtk_button_new_with_label(label);
+    ui_button_set_icon_name(button, icon);
     
-    if(args.onclick) {
+    if(onclick) {
         UiEventData *event = malloc(sizeof(UiEventData));
         event->obj = obj;
-        event->userdata = args.onclickdata;
-        event->callback = args.onclick;
+        event->userdata = userdata;
+        event->callback = onclick;
         event->value = 0;
         event->customdata = NULL;
 
@@ -83,9 +84,14 @@
                 event);
     }
     
+    return button;
+}
+
+UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    GtkWidget *button = ui_create_button(obj, args.label, args.icon, args.onclick, args.onclickdata);
     UI_APPLY_LAYOUT1(current, args);
     current->container->add(current->container, button, FALSE);
-    
     return button;
 }
 
@@ -112,32 +118,73 @@
     gtk_toggle_button_set_active(button, value != 0 ? TRUE : FALSE);
 }
 
-void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event) {
+void ui_toggled_obs(void *widget, UiVarEventData *event) {
+    UiInteger *i = event->var->value;
     UiEvent e;
     e.obj = event->obj;
     e.window = event->obj->window;
     e.document = event->obj->ctx->document;
     e.eventdata = event->var->value;
-    e.intval = gtk_toggle_tool_button_get_active(widget);
+    e.intval = i->get(i);  
     
-    UiInteger *i = event->var->value;
     ui_notify_evt(i->observers, &e);
 }
 
-static UIWIDGET togglebutton_create(UiObject *obj, GtkWidget *widget, UiToggleArgs args) {
-    UiObject* current = uic_current_obj(obj);
+static void ui_toggled_callback(GtkToggleButton *widget, UiEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = gtk_toggle_button_get_active(widget);
+    event->callback(&e, event->userdata);    
+}
+
+void ui_setup_togglebutton(
+        UiObject *obj,
+        GtkWidget *togglebutton,
+        const char *label,
+        const char *icon,
+        const char *varname,
+        UiInteger *value,
+        ui_callback onchange,
+        void *onchangedata)
+{
+    if(label) {
+        gtk_button_set_label(GTK_BUTTON(togglebutton), label);
+    }
+    ui_button_set_icon_name(togglebutton, icon);
     
-    if(args.label) {
-        gtk_button_set_label(GTK_BUTTON(widget), args.label);
-    }
-    ui_button_set_icon_name(widget, args.icon);
-    
-    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
+    ui_bind_togglebutton(
+            obj,
+            togglebutton,
+            ui_toggle_button_get,
+            ui_toggle_button_set,
+            varname,
+            value,
+            (ui_toggled_func)ui_toggled_callback,
+            onchange,
+            onchangedata);
+}
+
+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)
+{
+    UiObject* current = uic_current_obj(obj);
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, value, varname, UI_VAR_INTEGER);
     if (var) {
         UiInteger* value = (UiInteger*)var->value;
         value->obj = widget;
-        value->get = ui_toggle_button_get;
-        value->set = ui_toggle_button_set;
+        value->get = getfunc;
+        value->set = setfunc;
         
         UiVarEventData *event = malloc(sizeof(UiVarEventData));
         event->obj = obj;
@@ -148,7 +195,7 @@
 
         g_signal_connect(
                 widget,
-                "clicked",
+                "toggled",
                 G_CALLBACK(ui_toggled_obs),
                 event);
         g_signal_connect(
@@ -158,6 +205,32 @@
                 event);
     }
     
+    if(onchange) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = onchangedata;
+        event->callback = onchange;
+        event->value = 0;
+        event->customdata = NULL;
+        
+        g_signal_connect(
+                widget,
+                "toggled",
+                G_CALLBACK(toggled_callback),
+                event);
+        g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+}
+
+static UIWIDGET togglebutton_create(UiObject *obj, GtkWidget *widget, UiToggleArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    ui_setup_togglebutton(current, widget, args.label, args.icon, args.varname, args.value, args.onchange, args.onchangedata);
+    
     UI_APPLY_LAYOUT1(current, args);
     current->container->add(current->container, widget, FALSE);
     
@@ -168,9 +241,56 @@
     return togglebutton_create(obj, gtk_toggle_button_new(), args);
 }
 
+#if GTK_MAJOR_VERSION >= 4
+
+int64_t ui_check_button_get(UiInteger *integer) {
+    GtkCheckButton *button = integer->obj;
+    integer->value = (int)gtk_check_button_get_active(button);
+    return integer->value;
+}
+
+void ui_check_button_set(UiInteger *integer, int64_t value) {
+    GtkCheckButton *button = integer->obj;
+    integer->value = value;
+    gtk_check_button_set_active(button, value != 0 ? TRUE : FALSE);
+}
+
+static void ui_checkbox_callback(GtkCheckButton *widget, UiEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = gtk_check_button_get_active(widget);
+    event->callback(&e, event->userdata);    
+}
+
+UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    GtkWidget *widget = gtk_check_button_new_with_label(args.label); 
+    ui_bind_togglebutton(
+            obj,
+            widget,
+            ui_check_button_get,
+            ui_check_button_set,
+            args.varname,
+            args.value,
+            (ui_toggled_func)ui_checkbox_callback,
+            args.onchange,
+            args.onchangedata);
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, widget, FALSE);
+    
+    return widget;
+}
+
+#else
 UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) {
     return togglebutton_create(obj, gtk_check_button_new(), args);
 }
+#endif
 
 UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) {
 #ifdef UI_GTK3
@@ -180,10 +300,27 @@
 #endif
 }
 
-
+#if GTK_MAJOR_VERSION >= 4
+#define RADIOBUTTON_NEW(group, label) gtk_check_button_new_with_label(label)
+#define RADIOBUTTON_SET_GROUP(button, group) 
+#define RADIOBUTTON_GET_GROUP(button) GTK_CHECK_BUTTON(button)
+#define RADIOBUTTON_GET_ACTIVE(button) gtk_check_button_get_active(GTK_CHECK_BUTTON(button))
+#else
+#define RADIOBUTTON_NEW(group, label) gtk_radio_button_new_with_label(group, label)
+#define RADIOBUTTON_SET_GROUP(button, group) /* noop */
+#define RADIOBUTTON_GET_GROUP(button) gtk_radio_button_get_group(GTK_RADIO_BUTTON(button))
+#define RADIOBUTTON_GET_ACTIVE(button) gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))
+#endif
 
-
-
+static void radiobutton_toggled(void *widget, UiEventData *event) {
+    UiEvent e;
+    e.obj = event->obj;
+    e.window = event->obj->window;
+    e.document = event->obj->ctx->document;
+    e.eventdata = NULL;
+    e.intval = RADIOBUTTON_GET_ACTIVE(widget);
+    event->callback(&e, event->userdata);    
+}
 
 UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs args) {
     UiObject* current = uic_current_obj(obj);
@@ -191,12 +328,7 @@
     GSList *rg = NULL;
     UiInteger *rgroup;
     
-    UiVar* var = NULL;
-    if (args.value) {
-        var = uic_create_value_var(current->ctx, args.value);
-    } else if (args.varname) {
-        var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER);
-    }
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
     
     UiBool first = FALSE;
     if(var) {
@@ -207,10 +339,18 @@
         }
     }
     
-    GtkWidget *rbutton = gtk_radio_button_new_with_label(rg, args.label ? args.label : "");
-    rg = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rbutton));
-    
+    GtkWidget *rbutton = RADIOBUTTON_NEW(rg, args.label); 
     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;
@@ -226,7 +366,7 @@
         
         g_signal_connect(
                 rbutton,
-                "clicked",
+                "toggled",
                 G_CALLBACK(ui_radio_obs),
                 event);
         if(first) {
@@ -238,13 +378,33 @@
         }
     }
     
+    if(args.onchange) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = args.onchangedata;
+        event->callback = args.onchange;
+        event->value = 0;
+        event->customdata = NULL;
+        
+        g_signal_connect(
+                rbutton,
+                "toggled",
+                G_CALLBACK(radiobutton_toggled),
+                event);
+        g_signal_connect(
+                rbutton,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
     UiContainer *ct = uic_get_current_container(obj);
     ct->add(ct, rbutton, FALSE);
     
     return rbutton;
 }
 
-void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event) {
+void ui_radio_obs(GtkToggleButton *widget, UiVarEventData *event) {
     UiInteger *i = event->var->value;
     
     UiEvent e;
@@ -257,6 +417,41 @@
     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;
@@ -290,4 +485,4 @@
     
     value->value = i;
 }
-
+#endif
--- a/ui/gtk/button.h	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/button.h	Sun Sep 29 13:32:51 2024 +0200
@@ -36,20 +36,50 @@
 #ifdef	__cplusplus
 extern "C" {
 #endif
+    
+void ui_button_set_icon_name(GtkWidget *button, const char *icon_name);
 
-void ui_button_set_icon_name(GtkWidget *button, const char *icon_name);
+typedef void (*ui_toggled_func)(void*, void*);
+
+GtkWidget* ui_create_button(
+        UiObject *obj,
+        const char *label,
+        const char *icon,
+        ui_callback onclick,
+        void *userdata);
+
+void ui_setup_togglebutton(
+        UiObject *obj,
+        GtkWidget *togglebutton,
+        const char *label,
+        const char *icon,
+        const char *varname,
+        UiInteger *value,
+        ui_callback onchange,
+        void *onchangedata);
+
+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);
     
 // event wrapper
 void ui_button_clicked(GtkWidget *widget, UiEventData *event);
 
 
-void ui_toggled_obs(GtkToggleToolButton *widget, UiVarEventData *event);
+void ui_toggled_obs(void *widget, UiVarEventData *event);
 
 UIWIDGET ui_checkbox_var(UiObject *obj, char *label, UiVar *var);
 
 UIWIDGET ui_radiobutton_var(UiObject *obj, char *label, UiVar *var);
 
-void ui_radio_obs(GtkToggleToolButton *widget, UiVarEventData *event);
+void ui_radio_obs(GtkToggleButton *widget, UiVarEventData *event);
 
 int64_t ui_radiobutton_get(UiInteger *value);
 void ui_radiobutton_set(UiInteger *value, int64_t i);
--- a/ui/gtk/container.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/container.c	Sun Sep 29 13:32:51 2024 +0200
@@ -52,7 +52,7 @@
 }
 
 GtkWidget* ui_gtk_vbox_new(int spacing) {
-#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION >= 3
     return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing);
 #else
     return gtk_vbox_new(FALSE, spacing);
@@ -60,7 +60,7 @@
 }
 
 GtkWidget* ui_gtk_hbox_new(int spacing) {
-#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION >= 3
     return gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing);
 #else
     return gtk_hbox_new(FALSE, spacing);
@@ -70,13 +70,14 @@
 
 
 /* -------------------- Box Container -------------------- */
-UiContainer* ui_box_container(UiObject *obj, GtkWidget *box) {
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type) {
     UiBoxContainer *ct = cxCalloc(
             obj->ctx->allocator,
             1,
             sizeof(UiBoxContainer));
     ct->container.widget = box;
     ct->container.add = ui_box_container_add;
+    ct->type = type;
     return (UiContainer*)ct;
 }
 
@@ -95,7 +96,22 @@
     }
     
     UiBool expand = fill;
+#if GTK_MAJOR_VERSION >= 4
+    gtk_box_append(GTK_BOX(ct->widget), widget);
+    GtkAlign align = expand ? GTK_ALIGN_FILL : GTK_ALIGN_START; 
+    if(bc->type == UI_CONTAINER_VBOX) {
+        gtk_widget_set_valign(widget, align);
+        gtk_widget_set_vexpand(widget, expand);
+        gtk_widget_set_hexpand(widget, TRUE);
+    } else if(bc->type == UI_CONTAINER_HBOX) {
+        gtk_widget_set_halign(widget, align);
+        gtk_widget_set_hexpand(widget, expand);
+        gtk_widget_set_vexpand(widget, TRUE);
+    }
+    
+#else
     gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0);
+#endif
     
     ui_reset_layout(ct->layout);
     ct->current = widget;
@@ -108,14 +124,12 @@
             sizeof(UiGridContainer));
     ct->container.widget = grid;
     ct->container.add = ui_grid_container_add;
-#ifdef UI_GTK2
-    ct->width = 0;
-    ct->height = 1;
-#endif
+    UI_GTK_V2(ct->width = 0);
+    UI_GTK_V2(ct->height = 1);
     return (UiContainer*)ct;
 }
 
-#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION >= 3
 void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
     UiGridContainer *grid = (UiGridContainer*)ct;
     
@@ -134,12 +148,8 @@
         vexpand = ct->layout.vexpand;
     }
     
-    if(hexpand) {
-        gtk_widget_set_hexpand(widget, TRUE);
-    }
-    if(vexpand) {
-        gtk_widget_set_vexpand(widget, TRUE);
-    }
+    gtk_widget_set_hexpand(widget, hexpand);
+    gtk_widget_set_vexpand(widget, vexpand);
     
     int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1;
     int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1;
@@ -202,11 +212,7 @@
 
 void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
     // TODO: check if the widget implements GtkScrollable
-#ifdef UI_GTK3
-    gtk_container_add(GTK_CONTAINER(ct->widget), widget);
-#else
-    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ct->widget), widget);
-#endif
+    SCROLLEDWINDOW_SET_CHILD(ct->widget, widget);
     ui_reset_layout(ct->layout);
     ct->current = widget;
 }
@@ -235,8 +241,8 @@
 
 static GtkWidget* box_set_margin(GtkWidget *box, int margin) {
     GtkWidget *ret = box;
-#ifdef UI_GTK3
-#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12
+#if GTK_MAJOR_VERSION >= 3
+#if GTK_MAJOR_VERSION * 1000 + GTK_MINOR_VERSION >= 3012
     gtk_widget_set_margin_start(box, margin);
     gtk_widget_set_margin_end(box, margin);
 #else
@@ -264,7 +270,7 @@
     ct->add(ct, widget, TRUE);
     
     UiObject *newobj = uic_object_new(obj, box);
-    newobj->container = ui_box_container(obj, box);
+    newobj->container = ui_box_container(obj, box, type);
     uic_obj_add(obj, newobj);
     
     return widget;
@@ -278,43 +284,26 @@
     return ui_box_create(obj, args, UI_CONTAINER_HBOX);
 }
 
-
+static GtkWidget* create_grid(int colspacing, int rowspacing) {
+#if GTK_MAJOR_VERSION >= 3
+    GtkWidget *grid = gtk_grid_new();
+    gtk_grid_set_column_spacing(GTK_GRID(grid), colspacing);
+    gtk_grid_set_row_spacing(GTK_GRID(grid), rowspacing);
+#else
+    GtkWidget *grid = gtk_table_new(1, 1, FALSE);
+    gtk_table_set_col_spacings(GTK_TABLE(grid), colspacing);
+    gtk_table_set_row_spacings(GTK_TABLE(grid), rowspacing);
+#endif
+    return grid;
+}
 
 UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) {
     UiObject* current = uic_current_obj(obj);
     UI_APPLY_LAYOUT1(current, args);
     GtkWidget *widget;
     
-#ifdef UI_GTK3
-    GtkWidget *grid = gtk_grid_new();
-    gtk_grid_set_column_spacing(GTK_GRID(grid), args.columnspacing);
-    gtk_grid_set_row_spacing(GTK_GRID(grid), args.rowspacing);
-#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12
-    gtk_widget_set_margin_start(grid, args.margin);
-    gtk_widget_set_margin_end(grid, args.margin);
-#else
-    gtk_widget_set_margin_left(grid, args.margin);
-    gtk_widget_set_margin_right(grid, args.margin);
-#endif
-    gtk_widget_set_margin_top(grid, args.margin);
-    gtk_widget_set_margin_bottom(grid, args.margin);
-    
-    widget = grid;
-#elif defined(UI_GTK2)
-    GtkWidget *grid = gtk_table_new(1, 1, FALSE);
-    
-    gtk_table_set_col_spacings(GTK_TABLE(grid), columnspacing);
-    gtk_table_set_row_spacings(GTK_TABLE(grid), rowspacing);
-    
-    if(margin > 0) {
-        GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1);
-        gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin);
-        gtk_container_add(GTK_CONTAINER(a), grid);
-        widget = a;
-    } else {
-        widget = grid;
-    }
-#endif
+    GtkWidget *grid = create_grid(args.columnspacing, args.rowspacing);
+    widget = box_set_margin(grid, args.margin);
     current->container->add(current->container, widget, TRUE);
     
     UiObject *newobj = uic_object_new(obj, grid);
@@ -329,7 +318,7 @@
     UiObject* current = uic_current_obj(obj);
     UI_APPLY_LAYOUT1(current, args);
     
-    GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
+    GtkWidget *sw = SCROLLEDWINDOW_NEW();
     UiObject *newobj = uic_object_new(obj, sw);
     newobj->container = ui_scrolledwindow_container(obj, sw);
     uic_obj_add(obj, newobj);
@@ -345,7 +334,7 @@
 /* -------------------- Splitpane -------------------- */
 
 static GtkWidget* create_paned(UiOrientation orientation) {
-#ifdef UI_GTK3
+#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);
--- a/ui/gtk/container.h	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/container.h	Sun Sep 29 13:32:51 2024 +0200
@@ -78,6 +78,7 @@
 
 typedef struct UiBoxContainer {
     UiContainer container;
+    UiSubContainerType type;
     UiBool has_fill;
 } UiBoxContainer;
 
@@ -111,7 +112,7 @@
 
 UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, UiSubContainerType type);
 
-UiContainer* ui_box_container(UiObject *obj, GtkWidget *box);
+UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type);
 void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
 UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid);
--- a/ui/gtk/display.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/display.c	Sun Sep 29 13:32:51 2024 +0200
@@ -36,7 +36,7 @@
 #include "../ui/display.h"
 
 static void set_alignment(GtkWidget *widget, float xalign, float yalign) {
-#if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16
+#if GTK_MAJOR_VERSION >= 4 || (GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16)
     gtk_label_set_xalign(GTK_LABEL(widget), xalign);
     gtk_label_set_yalign(GTK_LABEL(widget), yalign);
 #else
@@ -106,7 +106,7 @@
 }
 
 UIWIDGET ui_separator_deprecated(UiObject *obj) {
-#if UI_GTK3
+#if GTK_MAJOR_VERSION >= 3
     GtkWidget *widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
 #else
     GtkWidget *widget = gtk_hseparator_new();
--- a/ui/gtk/draw_cairo.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/draw_cairo.c	Sun Sep 29 13:32:51 2024 +0200
@@ -33,17 +33,19 @@
 
 #include "draw_cairo.h"
 
-#if UI_GTK3 || UI_GTK4
-gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) {
+
+#if GTK_MAJOR_VERSION >= 3
+static void ui_drawingarea_draw(
+        GtkDrawingArea *area,
+        cairo_t *cr,
+        int width,
+        int height,
+        gpointer data)
+{
     UiCairoGraphics g;
-#ifdef UI_GTK4
-    g.g.width = gtk_widget_get_width(w);
-    g.g.height = gtk_widget_get_height(w);
-#else
-    g.g.width = gtk_widget_get_allocated_width(w);
-    g.g.height = gtk_widget_get_allocated_height(w);
-#endif
-    g.widget = w;
+    g.g.width = width;
+    g.g.height = height;
+    g.widget = GTK_WIDGET(area);
     g.cr = cr;
     
     UiDrawEvent *event = data;
@@ -53,7 +55,14 @@
     ev.document = event->obj->ctx->document;
     
     event->callback(&ev, &g.g, event->userdata);
-    
+}
+#endif
+
+#if UI_GTK3
+gboolean ui_drawingarea_expose(GtkWidget *w, cairo_t *cr, void *data) {
+    int width = gtk_widget_get_allocated_width(w);
+    int height = gtk_widget_get_allocated_height(w);
+    ui_drawingarea_draw(GTK_DRAWING_AREA(w), cr, width, height, data);
     return FALSE;
 }
 #endif
@@ -80,7 +89,9 @@
 // function from graphics.h
 
 void ui_connect_draw_handler(GtkWidget *widget, UiDrawEvent *event) {
-#if UI_GTK3 || UI_GTK4
+#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),
--- a/ui/gtk/graphics.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/graphics.c	Sun Sep 29 13:32:51 2024 +0200
@@ -51,6 +51,7 @@
 }
 
 
+#if GTK_MAJOR_VERSION <= 3
 static gboolean widget_button_pressed(
         GtkWidget *widget,
         GdkEvent *event,
@@ -82,14 +83,18 @@
     }
     return TRUE;
 }
+#endif
 
 void ui_drawingarea_getsize(UIWIDGET drawingarea, int *width, int *height) {
-#ifdef UI_GTK3
-        *width = gtk_widget_get_allocated_width(drawingarea);
-        *height = gtk_widget_get_allocated_height(drawingarea);
+#if GTK_MAJOR_VERSION >= 4
+    *width = gtk_widget_get_width(drawingarea);
+    *height = gtk_widget_get_height(drawingarea);
+#elif GTK_MAJOR_VERSION == 3
+    *width = gtk_widget_get_allocated_width(drawingarea);
+    *height = gtk_widget_get_allocated_height(drawingarea);
 #else
-        *width = drawingarea->allocation.width;
-        *height = drawingarea->allocation.height;
+    *width = drawingarea->allocation.width;
+    *height = drawingarea->allocation.height;
 #endif
 }
 
@@ -98,6 +103,9 @@
 }
 
 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));
@@ -114,6 +122,7 @@
     } else {
          // TODO: warning
     }
+#endif
 }
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/headerbar.c	Sun Sep 29 13:32:51 2024 +0200
@@ -0,0 +1,172 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "headerbar.h"
+
+#include "button.h"
+#include "menu.h"
+
+#if GTK_MAJOR_VERSION >= 3
+
+#ifdef UI_LIBADWAITA
+#define UI_HEADERBAR AdwHeaderBar*
+#define UI_HEADERBAR_CAST(h) ADW_HEADER_BAR(h)
+#define UI_HEADERBAR_PACK_START(h, w) adw_header_bar_pack_start(ADW_HEADER_BAR(h), w)
+#define UI_HEADERBAR_PACK_END(h, w) adw_header_bar_pack_end(ADW_HEADER_BAR(h), w)
+#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) adw_header_bar_set_title_widget(ADW_HEADER_BAR(h), w)
+#else
+#define UI_HEADERBAR GtkHeaderBar*
+#define UI_HEADERBAR_CAST(h) GTK_HEADER_BAR(h)
+#define UI_HEADERBAR_PACK_START(h, w) gtk_header_bar_pack_start(GTK_HEADER_BAR(h), w)
+#define UI_HEADERBAR_PACK_END(h, w) gtk_header_bar_pack_end(GTK_HEADER_BAR(h), w)
+#if GTK_MAJOR_VERSION >= 4
+#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_title_widget(GTK_HEADER_BAR(h), w)
+#else
+#define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_custom_title(GTK_HEADER_BAR(h), w)
+#endif
+#endif
+    
+void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar) {
+    CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
+    CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
+    CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
+    
+    ui_headerbar_add_items(obj, headerbar, left_defaults, UI_TOOLBAR_LEFT);
+    ui_headerbar_add_items(obj, headerbar, center_defaults, UI_TOOLBAR_CENTER);
+    ui_headerbar_add_items(obj, headerbar, right_defaults, UI_TOOLBAR_RIGHT);
+}
+
+static void create_item(UiObject *obj, GtkWidget *headerbar, GtkWidget *box, UiToolbarItemI *i, enum UiToolbarPos pos) {
+    switch(i->type) {
+        case UI_TOOLBAR_ITEM: {
+            ui_add_headerbar_item(headerbar, box, (UiToolbarItem*)i, obj, pos);
+            break;
+        }
+        case UI_TOOLBAR_TOGGLEITEM: {
+            ui_add_headerbar_toggleitem(headerbar, box, (UiToolbarToggleItem*)i, obj, pos);
+            break;
+        }
+        case UI_TOOLBAR_MENU: {
+            ui_add_headerbar_menu(headerbar, box, (UiToolbarMenuItem*)i, obj, pos);
+            break;
+        }
+        default: fprintf(stderr, "toolbar item type unimplemented: %d\n", (int)i->type);
+    }
+}
+
+static void headerbar_add(GtkWidget *headerbar, GtkWidget *box, GtkWidget *item, enum UiToolbarPos pos) {
+    switch(pos) {
+        case UI_TOOLBAR_LEFT: {
+            UI_HEADERBAR_PACK_START(headerbar, item);
+            break;
+        }
+        case UI_TOOLBAR_CENTER: {
+            
+#if GTK_MAJOR_VERSION >= 4
+            gtk_box_append(GTK_BOX(box), item);
+#else
+            gtk_box_pack_start(GTK_BOX(box), item, 0, 0, 0);
+#endif
+            break;
+        }
+        case UI_TOOLBAR_RIGHT: {
+            UI_HEADERBAR_PACK_END(headerbar, item);
+            break;
+        }
+    }
+}
+
+void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos) {
+    GtkWidget *box = NULL;
+    
+    if(pos == UI_TOOLBAR_CENTER && cxListSize(items) > 0) {
+        box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+        UI_HEADERBAR_SET_TITLE_WIDGET(headerbar, box);
+    }
+    
+    CxIterator i = pos == UI_TOOLBAR_RIGHT ? cxListBackwardsIterator(items) : cxListIterator(items);
+    cx_foreach(char*, def, i) {
+        UiToolbarItemI* item = uic_toolbar_get_item(def);
+        if (!item) {
+            fprintf(stderr, "unknown toolbar item: %s\n", def);
+            continue;
+        }
+        create_item(obj, headerbar, box, item, pos);
+    }
+}
+
+void ui_add_headerbar_item(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos)
+{
+    GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.onclick, item->args.onclickdata);
+    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();
+    WIDGET_ADD_CSS_CLASS(button, "flat");
+    ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.varname, NULL, item->args.onchange, item->args.onchangedata);
+    headerbar_add(headerbar, box, button, pos);
+}
+
+void ui_add_headerbar_menu(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarMenuItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos)
+{
+    GtkWidget *menubutton = gtk_menu_button_new();
+    
+    if(item->args.label) {
+        gtk_menu_button_set_label(GTK_MENU_BUTTON(menubutton), item->args.label);
+    }
+    if(item->args.icon) {
+        gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(menubutton), item->args.icon); 
+    }
+   
+    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));
+    
+    headerbar_add(headerbar, box, menubutton, pos);
+}
+    
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/gtk/headerbar.h	Sun Sep 29 13:32:51 2024 +0200
@@ -0,0 +1,75 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HEADERBAR_H
+#define HEADERBAR_H
+
+#include "toolkit.h"
+#include "../ui/toolbar.h"
+#include "../common/toolbar.h"
+#include <cx/list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if GTK_MAJOR_VERSION >= 3
+    
+void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar);
+
+void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos);
+
+void ui_add_headerbar_item(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos);
+
+void ui_add_headerbar_toggleitem(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarToggleItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos);
+
+void ui_add_headerbar_menu(
+        GtkWidget *headerbar,
+        GtkWidget *box,
+        UiToolbarMenuItem *item,
+        UiObject *obj,
+        enum UiToolbarPos pos);
+    
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* HEADERBAR_H */
+
--- a/ui/gtk/image.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/image.c	Sun Sep 29 13:32:51 2024 +0200
@@ -39,10 +39,16 @@
 
 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 = gtk_icon_theme_get_default();
+    icon_theme = ICONTHEME_GET_DEFAULT();
 }
 
 // **** deprecated functions ****
@@ -62,7 +68,9 @@
 // **** deprecated2****
 
 static UiIcon* get_icon(const char *name, int size, int scale) {
-#ifdef UI_SUPPORTS_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);
@@ -116,6 +124,16 @@
     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;
+        icon->pixbuf = gdk_pixbuf_new_from_file(g_file_get_path(file), &error);
+    }
+    return icon->pixbuf;
+}
+#else
 GdkPixbuf* ui_icon_pixbuf(UiIcon *icon) {
     if(!icon->pixbuf) {
         GError *error = NULL;
@@ -123,7 +141,9 @@
     }
     return icon->pixbuf;
 }
+#endif
 
+/*
 UiImage* ui_icon_image(UiIcon *icon) {
     GError *error = NULL;
     GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
@@ -134,6 +154,7 @@
     }
     return NULL;
 }
+*/
 
 /*
 UiImage* ui_image(const char *filename) {
--- a/ui/gtk/image.h	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/image.h	Sun Sep 29 13:32:51 2024 +0200
@@ -41,7 +41,11 @@
 
     
 struct UiIcon {
+#if GTK_MAJOR_VERSION >= 4
+    GtkIconPaintable *info;
+#else
     GtkIconInfo *info;
+#endif
     GdkPixbuf *pixbuf;
 };
 
--- a/ui/gtk/menu.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/menu.c	Sun Sep 29 13:32:51 2024 +0200
@@ -42,8 +42,7 @@
 #include <cx/linked_list.h>
 #include <cx/array_list.h>
 
-#if UI_GTK2 || UI_GTK3
-
+#if GTK_MAJOR_VERSION <= 3
 
 static ui_menu_add_f createMenuItem[] = {
     /* UI_MENU                 */ add_menu_widget,
@@ -501,4 +500,194 @@
     }
 }
 
-#endif /* UI_GTK2 || UI_GTK3 */
+#endif /* GTK_MAJOR_VERSION <= 3 */
+
+
+
+#if GTK_MAJOR_VERSION >= 4
+
+
+
+static ui_gmenu_add_f createMenuItem[] = {
+    /* UI_MENU                 */ ui_gmenu_add_menu,
+    /* UI_MENU_ITEM            */ ui_gmenu_add_menuitem,
+    /* UI_MENU_CHECK_ITEM      */ ui_gmenu_add_checkitem,
+    /* UI_MENU_RADIO_ITEM      */ ui_gmenu_add_radioitem,
+    /* UI_MENU_ITEM_LIST       */ ui_gmenu_add_menuitem_list,
+    /* UI_MENU_CHECKITEM_LIST  */ ui_gmenu_add_menuitem_list,
+    /* UI_MENU_RADIOITEM_LIST  */ ui_gmenu_add_menuitem_list,
+    /* UI_MENU_SEPARATOR       */ ui_gmenu_add_menuseparator
+};
+
+void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj) {
+    UiMenuItemI *it = menu->items_begin;
+    int index = 0;
+    while(it) {
+        createMenuItem[it->type](parent, index, it, obj);
+        it = it->next;
+        index++;
+    }
+}
+
+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));
+}
+
+void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItem *i = (UiMenuItem*)item;
+
+    GSimpleAction *action = g_simple_action_new(item->id, NULL);
+    g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
+    
+    if(i->callback != NULL) {
+        UiEventData *event = malloc(sizeof(UiEventData));
+        event->obj = obj;
+        event->userdata = i->userdata;
+        event->callback = i->callback;
+        event->value = 0;
+        event->customdata = NULL;
+
+        g_signal_connect(
+                action,
+                "activate",
+                G_CALLBACK(ui_activate_event_wrapper),
+                event);
+        g_signal_connect(
+                obj->widget,
+                "destroy",
+                G_CALLBACK(ui_destroy_userdata),
+                event);
+    }
+    
+    char action_name[32];
+    snprintf(action_name, 32, "win.%s", item->id);
+    g_menu_append(parent, i->label, action_name);
+}
+
+void ui_gmenu_add_menuseparator(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+    
+}
+
+void ui_gmenu_add_checkitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuCheckItem *checkitem = (UiMenuCheckItem*)item;
+    
+    // TODO
+}
+
+void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+    
+}
+
+void ui_gmenu_add_menuitem_list(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItemList *il = (UiMenuItemList*)item; 
+    
+    const CxAllocator *a = obj->ctx->allocator;
+    
+    UiActiveGMenuItemList *ls = cxMalloc(
+            a,
+            sizeof(UiActiveGMenuItemList));
+    
+    ls->object = obj;
+    ls->menu = p;
+    ls->index = index;
+    ls->oldcount = 0;
+    ls->getvalue = il->getvalue;
+    
+    UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+    ls->var = var;
+    UiList *list = var->value;
+    
+    ls->callback = il->callback;
+    ls->userdata = il->userdata;
+    
+    list->observers = ui_add_observer(
+            list->observers,
+            (ui_callback)ui_update_gmenu_item_list,
+            ls);
+    
+    GSimpleAction *action = g_simple_action_new(item->id, g_variant_type_new("i"));
+    g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
+    snprintf(ls->action, 32, "win.%s", item->id);
+    
+    
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->obj = obj;
+    event->userdata = il->userdata;
+    event->callback = il->callback;
+    event->customdata = var;
+    event->value = 0;
+    
+    g_signal_connect(
+            action,
+            "activate",
+            G_CALLBACK(ui_menu_list_item_activate_event_wrapper),
+            event);
+    g_signal_connect(
+            obj->widget,
+            "destroy",
+            G_CALLBACK(ui_destroy_userdata),
+            event);
+    
+    ui_update_gmenu_item_list(NULL, ls);
+}
+
+void ui_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event) {
+    int intval = event->value;
+    if(parameter && g_variant_is_of_type(parameter, G_VARIANT_TYPE_INT32)) {
+        intval = g_variant_get_int32(parameter);
+    }
+    
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = event->obj->window;
+    evt.document = event->obj->ctx->document;
+    evt.eventdata = event->customdata;
+    evt.intval = intval;
+    event->callback(&evt, event->userdata);    
+}
+
+void ui_menu_list_item_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event) {
+    int index = g_variant_get_int32(parameter);
+    UiVar *var = event->customdata;
+    UiList *list = var->value;
+    
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = event->obj->window;
+    evt.document = event->obj->ctx->document;
+    evt.eventdata = ui_list_get(list, index);
+    evt.intval = index;
+    event->callback(&evt, event->userdata);    
+    
+}
+
+void ui_update_gmenu_item_list(UiEvent *event, UiActiveGMenuItemList *list) {
+    // remove old items
+    for(int i=0;i<list->oldcount;i++) {
+        g_menu_remove(list->menu, list->index);
+    }
+    UiList *ls = list->var->value;
+    
+    // add list items
+    ui_getvaluefunc getvalue = list->getvalue;
+    int i = 0;
+    void* elm = ui_list_first(ls);
+    while(elm) {
+        char *label = (char*) (getvalue ? getvalue(elm, 0) : elm);
+        
+        GMenuItem *item = g_menu_item_new(label, NULL);
+        GVariant *v = g_variant_new("i", i);
+        g_menu_item_set_action_and_target_value(item, list->action, v);
+        g_menu_insert_item(list->menu, list->index+i, item);
+        
+        elm = ui_list_next(ls);
+        i++;
+    }
+    
+    list->oldcount = i;
+}
+
+#endif
--- a/ui/gtk/menu.h	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/menu.h	Sun Sep 29 13:32:51 2024 +0200
@@ -34,10 +34,12 @@
 #include <cx/list.h>
 #include "toolkit.h"
 
+
 #ifdef	__cplusplus
 extern "C" {
 #endif
     
+#if GTK_MAJOR_VERSION <= 3
 
 typedef struct UiActiveMenuItemList UiActiveMenuItemList;
 
@@ -73,6 +75,41 @@
 int64_t ui_checkitem_get(UiInteger *i);
 void ui_checkitem_set(UiInteger *i, int64_t value);
 
+#endif /* GTK_MAJOR_VERSION <= 3 */
+
+#if GTK_MAJOR_VERSION >= 4
+
+typedef void(*ui_gmenu_add_f)(GMenu *, int, UiMenuItemI*, UiObject*);
+
+typedef struct UiActiveGMenuItemList UiActiveGMenuItemList;
+struct UiActiveGMenuItemList {
+    UiObject         *object;
+    GMenu            *menu;
+    char             action[32];
+    int              index;
+    int              oldcount;
+    UiVar            *var;
+    ui_getvaluefunc  getvalue;
+    ui_callback      callback;
+    void             *userdata;
+};
+
+void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj);
+
+void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_menuseparator(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_checkitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+void ui_gmenu_add_menuitem_list(GMenu *p, int index, UiMenuItemI *item, UiObject *obj);
+
+void ui_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event);
+void ui_menu_list_item_activate_event_wrapper(GSimpleAction* self, GVariant* parameter, UiEventData *event);
+void ui_update_gmenu_item_list(UiEvent *event, UiActiveGMenuItemList *list);
+
+#endif
+
+
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/gtk/objs.mk	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/objs.mk	Sun Sep 29 13:32:51 2024 +0200
@@ -44,6 +44,7 @@
 GTKOBJ += range.o
 GTKOBJ += entry.o
 GTKOBJ += dnd.o
+GTKOBJ += headerbar.o
 
 TOOLKITOBJS += $(GTKOBJ:%=$(GTK_OBJPRE)%)
 TOOLKITSOURCE += $(GTKOBJ:%.o=gtk/%.c)
--- a/ui/gtk/range.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/range.c	Sun Sep 29 13:32:51 2024 +0200
@@ -36,7 +36,7 @@
 
 
 static UIWIDGET ui_scrollbar(UiObject *obj, UiOrientation orientation, UiRange *range, ui_callback f, void *userdata) {
-#ifdef UI_GTK3
+#if GTK_MAJOR_VERSION >= 3
     GtkWidget *scrollbar = gtk_scrollbar_new(orientation == UI_HORIZONTAL ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, NULL);
 #else
     GtkWidget *scrollbar;
@@ -124,7 +124,7 @@
 #else
     gtk_adjustment_set_page_size(a, extent);
 #endif
-#if !(GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 18)
+#if GTK_MAJOR_VERSION * 100 + GTK_MIMOR_VERSION < 318
     gtk_adjustment_changed(a);
 #endif
     range->extent = extent;
--- a/ui/gtk/text.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/text.c	Sun Sep 29 13:32:51 2024 +0200
@@ -82,17 +82,17 @@
                 G_CALLBACK(ui_textarea_destroy),
                 uitext);
     
-    GtkWidget *scroll_area = gtk_scrolled_window_new (NULL, NULL);
+    GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
     gtk_scrolled_window_set_policy(
             GTK_SCROLLED_WINDOW(scroll_area),
             GTK_POLICY_AUTOMATIC,
             GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
-    gtk_container_add(GTK_CONTAINER(scroll_area), text_area);
+    SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area);
     
     // font and padding
     PangoFontDescription *font;
     font = pango_font_description_from_string("Monospace");
-    gtk_widget_modify_font(text_area, font);
+    //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);
@@ -180,7 +180,7 @@
 }
 
 UIWIDGET ui_textarea_gettextwidget(UIWIDGET textarea) {
-    return gtk_bin_get_child(GTK_BIN(textarea));
+    return SCROLLEDWINDOW_GET_CHILD(textarea);
 }
 
 char* ui_textarea_get(UiText *text) {
@@ -557,7 +557,10 @@
                 uitext);
     
     if(args.width > 0) {
+        // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
         gtk_entry_set_width_chars(GTK_ENTRY(textfield), args.width);
+#endif
     }
     if(frameless) {
         // TODO: gtk2legacy workaroud
@@ -573,7 +576,7 @@
     if(var) {
         UiString *value = var->value;
         if(value->value.ptr) {
-            gtk_entry_set_text(GTK_ENTRY(textfield), value->value.ptr);
+            ENTRY_SET_TEXT(textfield, value->value.ptr);
             value->value.free(value->value.ptr);
             value->value.ptr = NULL;
             value->value.free = NULL;
@@ -631,13 +634,13 @@
     if(str->value.ptr) {
         str->value.free(str->value.ptr);
     }
-    str->value.ptr = g_strdup(gtk_entry_get_text(str->obj));
+    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) {
-    gtk_entry_set_text(str->obj, value);
+    ENTRY_SET_TEXT(str->obj, value);
     if(str->value.ptr) {
         str->value.free(str->value.ptr);
         str->value.ptr = NULL;
@@ -688,17 +691,6 @@
     return elms;
 }
 
-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 void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) {
     for(int i=0;i<nelm;i++) {
         free(elms[i].name);
@@ -708,13 +700,77 @@
 }
 
 static void ui_path_textfield_destroy(GtkWidget *object, UiPathTextField *pathtf) {
-    free(pathtf->current_path);
+    free(pathtf->hbox);
     g_object_unref(pathtf->entry);
     free(pathtf);
 }
 
+void ui_path_button_clicked(GtkWidget *widget, UiEventData *event) {
+    UiPathElm *elm = event->customdata;
+    cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len));
+    UiEvent evt;
+    evt.obj = event->obj;
+    evt.window = evt.obj->window;
+    evt.document = evt.obj->ctx->document;
+    evt.eventdata = elm->path;
+    evt.intval = event->value;
+    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);
+    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);
+    free(name.ptr);
+
+    if(pathtf->onactivate) {
+        UiEventData *eventdata = malloc(sizeof(UiEventData));
+        eventdata->callback = pathtf->onactivate;
+        eventdata->userdata = pathtf->onactivatedata;
+        eventdata->obj = pathtf->obj;
+        eventdata->customdata = elm;
+        eventdata->value = i;
+
+        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 = gtk_entry_get_text(GTK_ENTRY(pathtf->entry));
+    const gchar *text = ENTRY_GET_TEXT(pathtf->entry);
     if(strlen(text) == 0) {
         return;
     }
@@ -736,6 +792,168 @@
     }
 }
 
+#if GTK_MAJOR_VERSION >= 4
+
+static void pathbar_show_hbox(GtkWidget *widget, UiPathTextField *pathtf) {
+    if(pathtf->current_path) {
+        gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+        ENTRY_SET_TEXT(pathtf->entry, pathtf->current_path);
+    }
+}
+
+static gboolean ui_path_textfield_key_controller(
+        GtkEventControllerKey* self,
+        guint keyval,
+        guint keycode,
+        GdkModifierType state,
+        UiPathTextField *pathtf)
+{
+    if(keyval == GDK_KEY_Escape) {
+        pathbar_show_hbox(NULL, pathtf);
+    }
+    return FALSE;
+}
+
+UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    UiPathTextField *pathtf = malloc(sizeof(UiPathTextField));
+    memset(pathtf, 0, sizeof(UiPathTextField));
+    pathtf->obj = obj;
+    pathtf->getpathelm = args.getpathelm;
+    pathtf->getpathelmdata = args.getpathelmdata;
+    pathtf->onactivate = args.onactivate;
+    pathtf->onactivatedata = args.onactivatedata;
+    pathtf->ondragcomplete = args.ondragcomplete;
+    pathtf->ondragcompletedata = args.ondragcompletedata;
+    pathtf->ondragstart = args.ondragstart;
+    pathtf->ondragstartdata = args.ondragstartdata;
+    pathtf->ondrop = args.ondrop;
+    pathtf->ondropdata = args.ondropsdata;
+    
+    if(!pathtf->getpathelm) {
+        pathtf->getpathelm = default_pathelm_func;
+        pathtf->getpathelmdata = NULL;
+    }
+    
+    pathtf->stack = gtk_stack_new();
+    gtk_widget_set_name(pathtf->stack, "path-textfield-box");
+    
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, pathtf->stack, FALSE);
+    
+    pathtf->entry_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    pathtf->entry = gtk_entry_new();
+    gtk_box_append(GTK_BOX(pathtf->entry_box), pathtf->entry);
+    gtk_widget_set_hexpand(pathtf->entry, TRUE);
+    
+    GtkWidget *cancel_button = gtk_button_new_from_icon_name("window-close-symbolic");
+    gtk_widget_add_css_class(cancel_button, "flat");
+    gtk_widget_add_css_class(cancel_button, "pathbar-extra-button");
+    gtk_box_append(GTK_BOX(pathtf->entry_box), cancel_button);
+    g_signal_connect(
+                cancel_button,
+                "clicked",
+                G_CALLBACK(pathbar_show_hbox),
+                pathtf);
+    
+    gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+    g_object_ref(pathtf->entry); // for compatibility with older pathbar version
+    g_signal_connect(
+            pathtf->entry,
+            "activate",
+            G_CALLBACK(ui_path_textfield_activate),
+            pathtf);
+    
+    GtkEventController *entry_cancel = gtk_event_controller_key_new();
+    gtk_widget_add_controller(pathtf->entry, entry_cancel);
+    g_signal_connect(entry_cancel, "key-pressed", G_CALLBACK(ui_path_textfield_key_controller), pathtf);
+    
+    gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+    
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
+    if (var) {
+        UiString* value = (UiString*)var->value;
+        value->obj = pathtf;
+        value->get = ui_path_textfield_get;
+        value->set = ui_path_textfield_set;
+        
+        if(value->value.ptr) {
+            char *str = strdup(value->value.ptr);
+            ui_string_set(value, str);
+            free(str);
+        }
+    }
+    
+    return pathtf->stack;    
+}
+
+static void pathbar_pressed(
+        GtkGestureClick* self,
+        gint n_press,
+        gdouble x,
+        gdouble y,
+        UiPathTextField *pathtf)
+{
+    gtk_stack_set_visible_child(GTK_STACK(pathtf->stack), pathtf->entry_box);
+    gtk_widget_grab_focus(pathtf->entry);
+}
+
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf) {
+    // recreate button hbox
+    if(pathtf->hbox) {
+        gtk_stack_remove(GTK_STACK(pathtf->stack), pathtf->hbox);
+    }
+    pathtf->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    gtk_box_set_homogeneous(GTK_BOX(pathtf->hbox), FALSE);
+    gtk_stack_add_child(GTK_STACK(pathtf->stack), pathtf->hbox);
+    gtk_widget_set_name(pathtf->hbox, "pathbar");
+    
+    // add buttons for path elements
+    for (int i=0;i<pathtf->current_nelm;i++) {
+        UiPathElm *elm = &pathtf->current_pathelms[i];
+        
+        GtkWidget *button = ui_path_elm_button(pathtf, elm, i);
+        gtk_widget_add_css_class(button, "flat");
+        
+        gtk_box_append(GTK_BOX(pathtf->hbox), button);
+        
+        if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), CX_STR("/"))) {
+            gtk_box_append(GTK_BOX(pathtf->hbox), gtk_label_new("/"));
+        }
+    }
+    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
@@ -833,38 +1051,7 @@
     return hbox;
 }
 
-void ui_path_button_clicked(GtkWidget *widget, UiEventData *event) {
-    UiPathElm *elm = event->customdata;
-    cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len));
-    UiEvent evt;
-    evt.obj = event->obj;
-    evt.window = evt.obj->window;
-    evt.document = evt.obj->ctx->document;
-    evt.eventdata = elm->path;
-    evt.intval = event->value;
-    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);
-    pathtf->current_pathelms = path_elm;
-    pathtf->current_nelm = nelm;
-    
+int ui_pathtextfield_update_widget(UiPathTextField* pathtf) {
     GtkWidget *buttonbox = create_path_button_box();
     
     // switch from entry to buttonbox or remove current buttonbox
@@ -876,34 +1063,9 @@
     gtk_box_pack_start(GTK_BOX(pathtf->hbox), buttonbox, FALSE, FALSE, 0);
     pathtf->buttonbox = buttonbox;
     
-    for (int i=0;i<nelm;i++) {
-        UiPathElm *elm = &path_elm[i];
-        
-        cxmutstr name = cx_strdup(cx_strn(elm->name, elm->name_len));
-        GtkWidget *button = gtk_button_new_with_label(name.ptr);
-        free(name.ptr);
-        
-        if(pathtf->onactivate) {
-            UiEventData *eventdata = malloc(sizeof(UiEventData));
-            eventdata->callback = pathtf->onactivate;
-            eventdata->userdata = pathtf->onactivatedata;
-            eventdata->obj = pathtf->obj;
-            eventdata->customdata = elm;
-            eventdata->value = i;
-            
-            g_signal_connect(
-                    button,
-                    "clicked",
-                    G_CALLBACK(ui_path_button_clicked),
-                    eventdata);
-            
-            g_signal_connect(
-                    button,
-                    "destroy",
-                    G_CALLBACK(ui_destroy_userdata),
-                    eventdata);
-        }
-        
+    for (int i=0;i<pathtf->current_nelm;i++) {
+        UiPathElm *elm = &pathtf->current_pathelms[i];
+        GtkWidget *button = ui_path_elm_button(pathtf, elm, i);
         gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 0);
     }
     
@@ -912,19 +1074,21 @@
     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(gtk_entry_get_text(GTK_ENTRY(tf->entry)));
+    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;
-    gtk_entry_set_text(GTK_ENTRY(tf->entry), value);
+    ENTRY_SET_TEXT(tf->entry, value);
     ui_pathtextfield_update(tf, value);
     if(str->value.ptr) {
         str->value.free(str->value.ptr);
--- a/ui/gtk/text.h	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/text.h	Sun Sep 29 13:32:51 2024 +0200
@@ -75,9 +75,13 @@
 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;
@@ -136,6 +140,7 @@
 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);
 
--- a/ui/gtk/toolbar.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/toolbar.c	Sun Sep 29 13:32:51 2024 +0200
@@ -361,9 +361,9 @@
     CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
     CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
     
-    ui_headerbar_add_items(obj, headerbar, items, left_defaults);
-    ui_headerbar_add_items(obj, headerbar, items, center_defaults);
-    ui_headerbar_add_items(obj, headerbar, items, right_defaults);
+    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;
 }
@@ -388,7 +388,7 @@
 }
 
 
-void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults) {
+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) {
--- a/ui/gtk/toolbar.h	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/toolbar.h	Sun Sep 29 13:32:51 2024 +0200
@@ -127,7 +127,7 @@
 
 GtkWidget* ui_create_headerbar(UiObject *obj);
 
-void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxMap *items, CxList *defaults);
+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);
--- a/ui/gtk/toolkit.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/toolkit.c	Sun Sep 29 13:32:51 2024 +0200
@@ -46,8 +46,8 @@
 
 #include <pthread.h>
 
-#ifndef UI_GTK2
-static GtkApplication *app;
+#ifdef UI_APPLICATION
+UI_APPLICATION app;
 #endif
 
 static const char *application_name;
@@ -67,22 +67,24 @@
 static int scale_factor = 1;
 
 UIEXPORT void ui_init(const char *appname, int argc, char **argv) {
+    application_name = appname;
     uic_init_global_context();
     
+#if GTK_MAJOR_VERSION >= 4
+    gtk_init();
+#else
     gtk_init(&argc, &argv);
-    application_name = appname;
+#endif
     
     ui_css_init();
-    
     uic_docmgr_init();
-    
     uic_toolbar_init();
-    
     ui_image_init();
-    
     uic_load_app_properties();
     
-#ifdef UI_SUPPORTS_SCALE
+#if GTK_MAJOR_VERSION >= 4
+    scale_factor = 1; // TODO
+#elif defined(UI_SUPPORTS_SCALE)
     scale_factor = gdk_monitor_get_scale_factor(
             gdk_display_get_primary_monitor(gdk_display_get_default()));
 #endif
@@ -121,14 +123,11 @@
 #endif
 
 void ui_main() {
-#ifndef UI_GTK2
+#ifdef UI_APPLICATION
     cxmutstr appid = cx_asprintf(
             "ui.%s",
             application_name ? application_name : "application1");
-    
-    app = gtk_application_new(
-            appid.ptr,
-            G_APPLICATION_FLAGS_NONE);
+    app = UI_APPLICATION_NEW(appid.ptr);
     g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
     g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
     g_application_run(G_APPLICATION (app), 0, NULL);
@@ -153,17 +152,22 @@
 }
 
 GtkApplication* ui_get_application() {
-    return app;
+    return GTK_APPLICATION(app);
 }
 #endif
 
 void ui_show(UiObject *obj) {
     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
 }
 
 void ui_close(UiObject *obj) {
-    gtk_widget_destroy(obj->widget);
+    // TODO
+    //gtk_widget_destroy(obj->widget);
 }
 
 
@@ -226,24 +230,38 @@
 }
 
 void ui_set_show_all(UIWIDGET widget, int value) {
+    // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
     gtk_widget_set_no_show_all(widget, !value);
+#endif
 }
 
 void ui_set_visible(UIWIDGET widget, int visible) {
+    // TODO: gtk4
+#if GTK_MAJOR_VERSION <= 3
     if(visible) {
         gtk_widget_set_no_show_all(widget, FALSE);
         gtk_widget_show_all(widget);
     } else {
         gtk_widget_hide(widget);
     }
+#endif
 }
 
 void ui_clipboard_set(char *str) {
+#if GTK_MAJOR_VERSION >= 4
+    // TODO: gtk4: needs widget
+#else
     GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
     gtk_clipboard_set_text(cb, str, strlen(str));
+#endif
 }
 
 char* ui_clipboard_get() {
+#if GTK_MAJOR_VERSION >= 4
+    // TODO: gtk4: needs widget
+    return NULL;
+#else
     GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
     char *str = gtk_clipboard_wait_for_text(cb);
     if(str) {
@@ -253,6 +271,7 @@
     } else {
         return NULL;
     }
+#endif
 }
 
 int ui_get_scalefactor() {
@@ -295,12 +314,44 @@
 
 static GtkCssProvider* ui_gtk_css_provider;
 
+#if GTK_MAJOR_VERSION == 4
+static const char *ui_gtk_css = 
+"#path-textfield-box {\n"
+"  background-color: alpha(currentColor, 0.1);"
+"  border-radius: 6px;"
+"  padding: 0px;"
+"}\n"
+".pathbar-extra-button {\n"
+"  border-top-right-radius: 6px;"
+"  border-bottom-right-radius: 6px;"
+"  border-top-left-radius: 0px;"
+"  border-bottom-left-radius: 0px;"
+"}\n"
+"#pathbar button {\n"
+"  margin: 3px;"
+"  border-radius: 4px;"
+"  padding-top: 0px;"
+"  padding-bottom: 0px;"
+"  padding-left: 8px;"
+"  padding-right: 8px;"
+"}\n"
+"#path-textfield-box entry {\n"
+"  background-color: #00000000;"
+"  border-top-left-radius: 6px;"
+"  border-bottom-left-radius: 6px;"
+"  border-top-right-radius: 0px;"
+"  border-bottom-right-radius: 0px;"
+"}"
+;
+
+#elif GTK_MAJOR_VERSION == 3
 static const char *ui_gtk_css = 
 "#path-textfield-box {"
 "  background-color: @theme_base_color;"
 "  border-radius: 5px;"
 "  padding: 0px;"
 "}";
+#endif
 
 void ui_css_init(void) {
     ui_gtk_css_provider = gtk_css_provider_new();
@@ -325,7 +376,7 @@
 #endif /* GTK_MINOR_VERSION < 12 */
     
     GdkDisplay *display = gdk_display_get_default();
-    gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(ui_gtk_css_provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
+    gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(ui_gtk_css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
     
 #endif /* UI_GTK4 */
 }
--- a/ui/gtk/toolkit.h	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/toolkit.h	Sun Sep 29 13:32:51 2024 +0200
@@ -39,6 +39,71 @@
     
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
 
+  
+#if GLIB_MAJOR_VERSION * 1000 + GLIB_MINOR_VERSION > 74
+#define UI_G_APPLICATION_FLAGS G_APPLICATION_DEFAULT_FLAGS
+#else
+#define UI_G_APPLICATION_FLAGS G_APPLICATION_FLAGS_NONE
+#endif
+    
+#ifdef UI_LIBADWAITA
+#define UI_APPLICATION AdwApplication*
+#define UI_APPLICATION_NEW(id) adw_application_new(id, UI_G_APPLICATION_FLAGS)
+#elif GTK_MAJOR_VERSION >= 3  
+#define UI_APPLICATION GtkApplication*
+#define UI_APPLICATION_NEW(id) gtk_application_new(id, UI_G_APPLICATION_FLAGS)
+#endif
+    
+#if GTK_MAJOR_VERSION >= 4
+#define WINDOW_SHOW(window) gtk_window_present(GTK_WINDOW(window))
+#define WINDOW_DESTROY(window) gtk_window_destroy(GTK_WINDOW(window))
+#define WINDOW_SET_CONTENT(window, child) gtk_window_set_child(GTK_WINDOW(window), child)
+#define BOX_ADD(box, child) gtk_box_append(GTK_BOX(box), child)
+#define 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 WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_widget_add_css_class(w, cssclass)
+#else
+#define WINDOW_SHOW(window) gtk_widget_show_all(window)
+#define WINDOW_DESTROY(window) gtk_widget_destroy(window)
+#define WINDOW_SET_CONTENT(window, child) gtk_container_add(GTK_CONTAINER(window), child)
+#define BOX_ADD(box, child) gtk_box_pack_end(GTK_BOX(box), child, TRUE, TRUE, 0)
+#define ENTRY_SET_TEXT(entry, text) gtk_entry_set_text(GTK_ENTRY(entry), text)
+#define ENTRY_GET_TEXT(entry) gtk_entry_get_text(GTK_ENTRY(entry))
+#define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new(NULL, NULL)
+#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_container_add(GTK_CONTAINER(sw), child)
+#define SCROLLEDWINDOW_GET_CHILD(sw) gtk_bin_get_child(GTK_BIN(sw))
+#define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_style_context_add_class(gtk_widget_get_style_context(w), cssclass)
+#endif
+    
+#ifdef UI_GTK2
+#undef SCROLLEDWINDOW_SET_CHILD
+#define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), child)
+#endif
+    
+#if GTK_MAJOR_VERSION >= 4
+#define UI_GTK_SINCE_V4(st) st
+#define UI_GTK_SINCE_V3(st) 
+#define UI_GTK_V2(st)
+#define UI_GTK_V3(st)
+#define UI_GTK_V4(st) st
+#elif GTK_MAJOR_VERSION >= 3
+#define UI_GTK_SINCE_V4(st) st
+#define UI_GTK_SINCE_V3(st) st 
+#define UI_GTK_V2(st)
+#define UI_GTK_V3(st) st
+#define UI_GTK_V4(st)
+#else
+#define UI_GTK_SINCE_V4(st) 
+#define UI_GTK_SINCE_V3(st) 
+#define UI_GTK_V2(st) st
+#define UI_GTK_V3(st) 
+#define UI_GTK_V4(st)
+#endif
+
+
 typedef struct UiEventData {
     UiObject    *obj;
     ui_callback callback;
@@ -64,7 +129,7 @@
 typedef enum UiOrientation UiOrientation;
 enum UiOrientation { UI_HORIZONTAL = 0, UI_VERTICAL };
 
-#ifndef UI_GTK2
+#ifdef UI_APPLICATION
 void ui_app_quit();
 GtkApplication* ui_get_application();
 #endif
--- a/ui/gtk/tree.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/tree.c	Sun Sep 29 13:32:51 2024 +0200
@@ -95,6 +95,9 @@
                     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);
@@ -104,14 +107,19 @@
                         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
+                        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;
-                        UiIcon *icon = data;
                         if(!icon->pixbuf && icon->info) {
                             GError *error = NULL;
                             GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error);
@@ -120,6 +128,7 @@
                         g_value_init(&pixbufvalue, G_TYPE_OBJECT);
                         g_value_set_object(&pixbufvalue, icon->pixbuf);
                         gtk_list_store_set_value(store, &iter, c, &pixbufvalue);
+#endif
                         c++;
                         
                         char *str = model->getvalue(elm, c);
@@ -205,12 +214,12 @@
     }
     
     // add widget to the current container
-    GtkWidget *scroll_area = gtk_scrolled_window_new(NULL, NULL);
+    GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
     gtk_scrolled_window_set_policy(
             GTK_SCROLLED_WINDOW(scroll_area),
             GTK_POLICY_AUTOMATIC,
             GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
-    gtk_container_add(GTK_CONTAINER(scroll_area), view);
+    SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
     
     UI_APPLY_LAYOUT1(current, args);
     current->container->add(current->container, scroll_area, FALSE);
@@ -222,6 +231,7 @@
     return scroll_area;
 }
 
+/*
 static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) {
     printf("drag begin\n");
     
@@ -236,13 +246,16 @@
     printf("drag end\n");
     
 }
+*/
 
+/*
 static GtkTargetEntry targetentries[] =
     {
       { "STRING",        0, 0 },
       { "text/plain",    0, 1 },
       { "text/uri-list", 0, 2 },
     };
+*/
 
 UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) {
     UiObject* current = uic_current_obj(obj);
@@ -356,12 +369,12 @@
     gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
     
     // add widget to the current container
-    GtkWidget *scroll_area = gtk_scrolled_window_new(NULL, NULL);
+    GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
     gtk_scrolled_window_set_policy(
             GTK_SCROLLED_WINDOW(scroll_area),
             GTK_POLICY_AUTOMATIC,
             GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
-    gtk_container_add(GTK_CONTAINER(scroll_area), view);
+    SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
     
     UI_APPLY_LAYOUT1(current, args);
     current->container->add(current->container, scroll_area, FALSE);
@@ -375,11 +388,7 @@
 
 
 GtkWidget* ui_get_tree_widget(UIWIDGET widget) {
-    GList *c = gtk_container_get_children(GTK_CONTAINER(widget));
-    if(c) {
-        return c->data;
-    }
-    return NULL;
+    return SCROLLEDWINDOW_GET_CHILD(widget);
 }
 
 static char** targets2array(char *target0, va_list ap, int *nelm) {
@@ -402,6 +411,7 @@
     return targets;
 }
 
+/*
 static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) {
     GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry));
     for(int i=0;i<nelm;i++) {
@@ -409,6 +419,7 @@
     }
     return targets;
 }
+*/
 
 void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { 
     va_list ap;
--- a/ui/gtk/window.c	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/gtk/window.c	Sun Sep 29 13:32:51 2024 +0200
@@ -41,6 +41,7 @@
 #include "menu.h"
 #include "toolbar.h"
 #include "container.h"
+#include "headerbar.h"
 
 static int nwindows = 0;
 
@@ -72,8 +73,10 @@
 static UiObject* create_window(const char *title, void *window_data, UiBool simple) {
     CxMempool *mp = cxBasicMempoolCreate(256);
     UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); 
-    
-#ifndef UI_GTK2
+   
+#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);
@@ -83,6 +86,10 @@
     obj->ctx = uic_context(obj, mp);
     obj->window = window_data;
     
+#if GTK_CHECK_VERSION(4, 0, 0)
+    obj->ctx->action_map = G_ACTION_MAP(obj->widget);
+#endif
+    
     if(title != NULL) {
         gtk_window_set_title(GTK_WINDOW(obj->widget), title);
     }
@@ -108,6 +115,18 @@
             obj);
     
     GtkWidget *vbox = ui_gtk_vbox_new(0);
+#ifdef UI_LIBADWAITA
+    GtkWidget *toolbar_view = adw_toolbar_view_new();
+    adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), toolbar_view);
+    adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(toolbar_view), vbox);
+    
+    GtkWidget *headerbar = adw_header_bar_new();
+    adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar);
+    
+    ui_fill_headerbar(obj, headerbar);
+#elif GTK_MAJOR_VERSION >= 4
+    WINDOW_SET_CONTENT(obj->widget, vbox);
+#else
     gtk_container_add(GTK_CONTAINER(obj->widget), vbox);
     
     if(!simple) {
@@ -130,9 +149,12 @@
         //GtkWidget *hb = ui_create_headerbar(obj);
         //gtk_window_set_titlebar(GTK_WINDOW(obj->widget), hb);
     }
+#endif
     
     // window content
     // the content has a (TODO: not yet) configurable frame
+    // TODO: really? why
+    /*
     GtkWidget *frame = gtk_frame_new(NULL);
     gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
     gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
@@ -141,6 +163,10 @@
     GtkWidget *content_box = ui_gtk_vbox_new(0);
     gtk_container_add(GTK_CONTAINER(frame), content_box);
     obj->container = ui_box_container(obj, content_box);
+    */
+    GtkWidget *content_box = ui_gtk_vbox_new(0);
+    BOX_ADD(GTK_BOX(vbox), content_box);
+    obj->container = ui_box_container(obj, content_box, UI_CONTAINER_VBOX);
     
     nwindows++;
     return obj;
@@ -173,7 +199,7 @@
     
     if(data->customdata) {
         GtkWidget *entry = data->customdata;
-        evt.eventdata = (void*)gtk_entry_get_text(GTK_ENTRY(entry));
+        evt.eventdata = (void*)ENTRY_GET_TEXT(GTK_ENTRY(entry));
         
     }
     
@@ -186,7 +212,7 @@
         data->callback(&evt, data->userdata);
     }
     
-    gtk_widget_destroy(GTK_WIDGET(self));
+    WINDOW_DESTROY(GTK_WIDGET(self));
 }
 
 void ui_dialog_create(UiObject *parent, UiDialogArgs args) {
@@ -208,13 +234,13 @@
     GtkWidget *content_area = gtk_dialog_get_content_area(dialog);
     if(args.content) {
         GtkWidget *label = gtk_label_new(args.content);
-        gtk_container_add(GTK_CONTAINER(content_area), label);
+        BOX_ADD(content_area, label);
     }
     
     GtkWidget *textfield = NULL;
     if(args.input) {
         textfield = gtk_entry_new();
-        gtk_container_add(GTK_CONTAINER(content_area), textfield);
+        BOX_ADD(content_area, textfield);
     }
     
     UiEventData *event = malloc(sizeof(UiEventData));
@@ -229,9 +255,15 @@
                            G_CALLBACK(ui_dialog_response),
                            event);
     
-    gtk_widget_show_all(GTK_WIDGET(dialog_w));
+    WINDOW_SHOW(GTK_WIDGET(dialog_w));
 }
 
+
+#if GTK_MAJOR_VERSION >= 4
+static void ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {
+    // TODO
+}
+#else
 static void ui_gtkfilechooser(UiObject *obj, GtkFileChooserAction action, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {
     char *button;
     char *title;
@@ -272,7 +304,7 @@
     if((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) {
         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
     }
-       
+    
     UiEvent evt;
     evt.obj = obj;
     evt.document = evt.obj->ctx->document;
@@ -310,6 +342,7 @@
     
     gtk_widget_destroy(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, file_selected_callback, cbdata);
--- a/ui/ui/icons.h	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/ui/icons.h	Sun Sep 29 13:32:51 2024 +0200
@@ -35,7 +35,7 @@
 extern "C" {
 #endif
 
-#ifdef UI_GTK3
+#ifdef UI_GTK
     
 #define UI_ICON_HOME "go-home"
 #define UI_ICON_NEW_WINDOW "list-add"
@@ -50,7 +50,7 @@
 #define UI_ICON_GO_BACK "go-previous"
 #define UI_ICON_GO_FORWARD "go-next"
     
-#endif /* UI_GTK3 */
+#endif /* UI_GTK */
     
     
 
--- a/ui/ui/toolkit.h	Mon Jun 17 21:20:58 2024 +0200
+++ b/ui/ui/toolkit.h	Sun Sep 29 13:32:51 2024 +0200
@@ -56,6 +56,10 @@
 
 #define UI_GTK
 
+#ifdef UI_LIBADWAITA
+#include <adwaita.h>
+#endif
+
 #elif UI_MOTIF
 
 #include <Xm/XmAll.h> 

mercurial