add authentication dialog

Wed, 23 Oct 2024 17:40:45 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 23 Oct 2024 17:40:45 +0200
changeset 56
294d5515583a
parent 55
1ce14068ef31
child 57
d5a7dbc945ef

add authentication dialog

application/Makefile file | annotate | diff | comparison | revisions
application/application.h file | annotate | diff | comparison | revisions
application/connect.c file | annotate | diff | comparison | revisions
application/connect.h file | annotate | diff | comparison | revisions
application/davcontroller.c file | annotate | diff | comparison | revisions
application/davcontroller.h file | annotate | diff | comparison | revisions
application/window.c file | annotate | diff | comparison | revisions
application/window.h file | annotate | diff | comparison | revisions
ucx/mempool.c file | annotate | diff | comparison | revisions
ui/common/condvar.c file | annotate | diff | comparison | revisions
ui/common/condvar.h file | annotate | diff | comparison | revisions
ui/common/context.c file | annotate | diff | comparison | revisions
ui/common/context.h file | annotate | diff | comparison | revisions
ui/common/objs.mk 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/headerbar.c 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/window.c file | annotate | diff | comparison | revisions
ui/ui/button.h file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
ui/ui/window.h file | annotate | diff | comparison | revisions
--- a/application/Makefile	Wed Oct 23 10:37:43 2024 +0200
+++ b/application/Makefile	Wed Oct 23 17:40:45 2024 +0200
@@ -34,7 +34,6 @@
 SRC  = main.c
 SRC += application.c
 SRC += config.c
-SRC += connect.c
 SRC += davcontroller.c
 SRC += system.c
 SRC += window.c
--- a/application/application.h	Wed Oct 23 10:37:43 2024 +0200
+++ b/application/application.h	Wed Oct 23 17:40:45 2024 +0200
@@ -135,6 +135,15 @@
     char *value_full;
 } DavPropertyList;
 
+typedef struct SessionAuthData {
+    UiObject *obj;
+    UiCondVar *cond;
+    DavSession *sn;
+    char *user;
+    char *password;
+} SessionAuthData;
+
+
 void application_init(void);
 
 /*
--- a/application/connect.c	Wed Oct 23 10:37:43 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-/*
- * 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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "connect.h"
-
-#include <libidav/utils.h>
-
-#include <cx/string.h>
-#include <cx/utils.h>
-
-DavSession* connect_to_repo(DavContext *ctx, DavCfgRepository *repo, const char *path, dav_auth_func authfunc) {
-    cxmutstr decodedpw = dav_repository_get_decodedpassword(repo);
-    
-    char *user = repo->user.value.ptr;
-    char *password = decodedpw.ptr;
-    
-    if(!user && !password) {
-        if(!get_stored_credentials(repo->stored_user.value.ptr, &user, &password)) {
-            get_location_credentials(repo, path, &user, &password);
-        }
-    }
-    
-    DavSession *sn = dav_session_new_auth(ctx, repo->url.value.ptr, user, password);
-    if(password) {
-        free(password);
-    }
-    
-    sn->flags = dav_repository_get_flags(repo);
-    sn->key = dav_context_get_key(ctx, repo->default_key.value.ptr);
-    // TODO: reactivate
-    //curl_easy_setopt(sn->handle, CURLOPT_HTTPAUTH, repo->authmethods);
-    curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version);
-    if(repo->cert.value.ptr) {
-        curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert.value.ptr);
-    }
-    /*
-    if(!repo->verification.value || cmd_getoption(a, "insecure")) {
-        curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYPEER, 0);
-        curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYHOST, 0);
-    }
-    */
-    /*
-    if(!cmd_getoption(a, "noinput")) {
-        dav_session_set_authcallback(sn, authfunc, repo);
-    }
-    */
-    return sn;
-}
-
-int request_auth(DavSession *sn, void *userdata) {
-    DavCfgRepository *repo = userdata;
-    
-    cxstring user = {NULL, 0};
-    char ubuf[256];
-    if(repo->user.value.ptr) {
-       user = cx_strcast(repo->user.value);
-    } else {
-        fprintf(stderr, "User: ");
-        fflush(stderr);
-        user = cx_str(fgets(ubuf, 256, stdin));
-    }
-    if(!user.ptr) {
-        return 0;
-    }
-    if(user.length > 0 && user.ptr[user.length-1] == '\n') {
-        user.length--;
-    }
-    if(user.length > 0 && user.ptr[user.length-1] == '\r') {
-        user.length--;
-    }
-    
-    char *password = util_password_input("Password: ");
-    if(!password || strlen(password) == 0) {
-        return 0;
-    }
-    
-    dav_session_set_auth_s(sn, user, cx_str(password));
-    free(password);
-    
-    return 0;
-}
--- a/application/connect.h	Wed Oct 23 10:37:43 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-/*
- * 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 DAV_CONNECT_H
-#define DAV_CONNECT_H
-
-#include "config.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-DavSession* connect_to_repo(DavContext *ctx, DavCfgRepository *repo, const char *path, dav_auth_func authfunc);
-
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* DAV_CONNECT_H */
-
--- a/application/davcontroller.c	Wed Oct 23 10:37:43 2024 +0200
+++ b/application/davcontroller.c	Wed Oct 23 17:40:45 2024 +0200
@@ -93,8 +93,46 @@
     char *dav_path = util_concat_path(browser->repo_base, path);
     ui_set(browser->path, dav_path);
     free(dav_path);
+    
+    SessionAuthData *auth = cxMalloc(sn->mp->allocator, sizeof(SessionAuthData));
+    auth->obj = ui;
+    auth->cond = ui_condvar_create();
+    auth->sn = sn;
+    auth->user = repo->user.value.ptr ? cx_strdup_a(sn->mp->allocator, cx_strcast(repo->user.value)).ptr : NULL;
+    auth->password = NULL;
+    dav_session_set_authcallback(sn, jobthr_davbrowser_auth, auth);
+    
+    davbrowser_query_path(ui, browser, path);
+}
 
-    davbrowser_query_path(ui, browser, path);
+// ------------------------------ davbrowser_auth ------------------------------
+
+static int davbrowser_auth_dialog(void *data) {
+    SessionAuthData *auth = data;
+    auth_dialog(auth);
+    return 0;
+}
+
+void davbrowser_auth_set_user_pwd(SessionAuthData *auth, const char *user, const char *password) {
+    dav_session_free(auth->sn, auth->user);
+    dav_session_free(auth->sn, auth->password);
+    auth->user = user ? dav_session_strdup(auth->sn, user) : NULL;
+    auth->password = password ? dav_session_strdup(auth->sn, password) : NULL;
+}
+
+int jobthr_davbrowser_auth(DavSession *sn, void *data) {
+    SessionAuthData *auth = data;
+    
+    ui_call_mainthread(davbrowser_auth_dialog, auth);
+    ui_condvar_wait(auth->cond);
+    
+    if(auth->cond->intdata) {
+        dav_session_set_auth(sn, auth->user, auth->password);
+    }
+    dav_session_free(auth->sn, auth->password);
+    auth->password = NULL;
+    
+    return 0;
 }
 
 
--- a/application/davcontroller.h	Wed Oct 23 10:37:43 2024 +0200
+++ b/application/davcontroller.h	Wed Oct 23 17:40:45 2024 +0200
@@ -45,6 +45,9 @@
 
 DavBrowser* davbrowser_create(UiObject *toplevel);
 
+int jobthr_davbrowser_auth(DavSession *sn, void *data);
+void davbrowser_auth_set_user_pwd(SessionAuthData *auth, const char *user, const char *password);
+
 void davbrowser_set_collection(UiObject *ui, DavBrowser *browser, DavResource *collection);
 
 void davbrowser_connect2repo(UiObject *ui, DavBrowser *browser, DavCfgRepository *repo, const char *path);
--- a/application/window.c	Wed Oct 23 10:37:43 2024 +0200
+++ b/application/window.c	Wed Oct 23 17:40:45 2024 +0200
@@ -35,6 +35,8 @@
 
 #include <libidav/utils.h>
 
+#include <cx/printf.h>
+
 static UiIcon* folder_icon;
 static UiIcon* file_icon;
 
@@ -205,6 +207,64 @@
     return NULL;
 }
 
+
+typedef struct AuthDialogWindow {
+    UiString *user;
+    UiString *password;
+} AuthDialogWindow;
+
+static void auth_dialog_action(UiEvent *event, void *data) {
+    SessionAuthData *auth = data;
+    AuthDialogWindow *wdata = event->window;
+    int result = 0;
+    if(event->intval == 4) {
+        char *user = ui_get(wdata->user);
+        char *password = ui_get(wdata->password);
+        davbrowser_auth_set_user_pwd(auth, user, password);
+        result = 1;
+    }
+    ui_condvar_signal(auth->cond, NULL, result);
+    ui_close(event->obj);
+}
+
+void auth_dialog(SessionAuthData *auth) {
+    UiObject *obj = ui_dialog_window(auth->obj,
+            .title = "Authentication", 
+            .lbutton1 = "Cancel",
+            .rbutton4 = "Connect",
+            .default_button = 4,
+            .show_closebutton = UI_OFF,
+            .onclick = auth_dialog_action,
+            .onclickdata = auth);
+    
+    AuthDialogWindow *wdata = ui_malloc(obj->ctx, sizeof(AuthDialogWindow));
+    wdata->user = ui_string_new(obj->ctx, NULL);
+    wdata->password = ui_string_new(obj->ctx, NULL);
+    obj->window = wdata;
+    
+    ui_grid(obj, .margin = 20, .columnspacing = 12, .rowspacing = 12) {
+        cxmutstr heading = cx_asprintf("Authentication required for: %s", auth->sn->base_url);
+        ui_llabel(obj, .label = heading.ptr, .colspan = 2);
+        free(heading.ptr);
+        ui_newline(obj);
+        
+        ui_llabel(obj, .label = "User");
+        ui_textfield(obj, .value = wdata->user, .hexpand = TRUE);
+        ui_newline(obj);
+        
+        ui_llabel(obj, .label = "Password");
+        ui_passwordfield(obj, .value = wdata->password, .hexpand = TRUE);
+    }
+     
+    if(auth->user) {
+        ui_set(wdata->user, auth->user);
+    }
+    
+    ui_show(obj);
+}
+
+
+
 static UiPathElm* dav_get_pathelm(const char *full_path, size_t len, size_t *ret_nelm, void* data) {
     cxstring fpath = cx_strn(full_path, len);
     int protocol = 0;
--- a/application/window.h	Wed Oct 23 10:37:43 2024 +0200
+++ b/application/window.h	Wed Oct 23 17:40:45 2024 +0200
@@ -59,6 +59,9 @@
 void resourceviewer_new(DavBrowser *browser, const char *path, DavResourceViewType type);
 void* resourceviewer_proplist_getvalue(DavPropertyList *property, int col);
 
+
+void auth_dialog(SessionAuthData *auth);
+
 void action_go_back(UiEvent *event, void *data);
 void action_go_forward(UiEvent *event, void *data);
 
--- a/ucx/mempool.c	Wed Oct 23 10:37:43 2024 +0200
+++ b/ucx/mempool.c	Wed Oct 23 17:40:45 2024 +0200
@@ -113,6 +113,7 @@
         void *p,
         void *ptr
 ) {
+    if (!ptr) return;
     struct cx_mempool_s *pool = p;
 
     struct cx_mempool_memory_s *mem = (struct cx_mempool_memory_s *)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/condvar.c	Wed Oct 23 17:40:45 2024 +0200
@@ -0,0 +1,70 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "condvar.h"
+
+
+
+UiCondVar* ui_condvar_create(void) {
+    UiPosixCondVar *var = malloc(sizeof(UiPosixCondVar));
+    var->var.data = NULL;
+    var->var.intdata = 0;
+    var->set = 0;
+    pthread_mutex_init(&var->lock, NULL);
+    pthread_cond_init(&var->cond, NULL);
+    return (UiCondVar*)var;
+}
+
+void ui_condvar_wait(UiCondVar *var) {
+    UiPosixCondVar *p = (UiPosixCondVar*)var;
+    pthread_mutex_lock(&p->lock);
+    if(!p->set) {
+        pthread_cond_wait(&p->cond, &p->lock);
+    }
+    p->set = 0;
+    pthread_mutex_unlock(&p->lock);
+    
+}
+
+void ui_condvar_signal(UiCondVar *var, void *data, int intdata) {
+    UiPosixCondVar *p = (UiPosixCondVar*)var;
+    pthread_mutex_lock(&p->lock);
+    p->var.data = data;
+    p->var.intdata = intdata;
+    p->set = 1;
+    pthread_cond_signal(&p->cond);
+    pthread_mutex_unlock(&p->lock);
+}
+
+void ui_condvar_destroy(UiCondVar *var) {
+    UiPosixCondVar *p = (UiPosixCondVar*)var;
+    pthread_mutex_destroy(&p->lock);
+    pthread_cond_destroy(&p->cond);
+    free(p);
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/condvar.h	Wed Oct 23 17:40:45 2024 +0200
@@ -0,0 +1,53 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef UIC_CONDVAR_H
+#define UIC_CONDVAR_H
+
+#include "../ui/toolkit.h"
+
+#include <pthread.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiPosixCondVar {
+    UiCondVar var;
+    int set;
+    pthread_mutex_t lock;
+    pthread_cond_t cond;
+} UiPosixCondVar;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UIC_CONDVAR_H */
+
--- a/ui/common/context.c	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/common/context.c	Wed Oct 23 17:40:45 2024 +0200
@@ -523,19 +523,28 @@
     cxListDestroy(groups);
 }
 
+size_t uic_group_array_size(const int *groups) {
+    int i;
+    for(i=0;groups[i] >= 0;i++) { }
+    return i;
+}
+
 void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups) {
+    uic_add_group_widget_i(ctx, widget, enable, cxListAt(groups, 0), cxListSize(groups));
+}
+
+void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups) {
     const CxAllocator *a = ctx->allocator;
     UiGroupWidget gw;
     
     gw.widget = widget;
     gw.enable = enable;
-    gw.numgroups = cxListSize(groups);
-    gw.groups = cxCalloc(a, gw.numgroups, sizeof(int));
+    gw.numgroups = numgroups;
+    gw.groups = cxCalloc(a, numgroups, sizeof(int));
     
     // copy groups
-    int *intgroups = cxListAt(groups, 0);
-    if(intgroups) {
-        memcpy(gw.groups, intgroups, gw.numgroups * sizeof(int));
+    if(groups) {
+        memcpy(gw.groups, groups, gw.numgroups * sizeof(int));
     }
     
     cxListAdd(ctx->group_widgets, &gw);
@@ -554,7 +563,7 @@
 }
 
 void ui_free(UiContext *ctx, void *ptr) {
-    if(ctx) {
+    if(ctx && ptr) {
         cxFree(ctx->allocator, ptr);
     }
 }
--- a/ui/common/context.h	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/common/context.h	Wed Oct 23 17:40:45 2024 +0200
@@ -134,8 +134,10 @@
 
 void uic_remove_bound_var(UiContext *ctx, UiVar *var);
 
+size_t uic_group_array_size(const int *groups);
 void uic_check_group_widgets(UiContext *ctx);
 void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups);
+void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups);
 
 
 #ifdef	__cplusplus
--- a/ui/common/objs.mk	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/common/objs.mk	Wed Oct 23 17:40:45 2024 +0200
@@ -39,6 +39,7 @@
 COMMON_OBJ += toolbar.o
 COMMON_OBJ += ucx_properties.o
 COMMON_OBJ += threadpool.o
+COMMON_OBJ += condvar.o
 
 TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)%)
 TOOLKITSOURCE += $(COMMON_OBJ:%.o=common/%.c)
--- a/ui/gtk/button.c	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/gtk/button.c	Wed Oct 23 17:40:45 2024 +0200
@@ -59,7 +59,8 @@
         const char *label,
         const char *icon,
         ui_callback onclick,
-        void *userdata)
+        void *userdata,
+        int event_value)
 {
     GtkWidget *button = gtk_button_new_with_label(label);
     ui_button_set_icon_name(button, icon);
@@ -69,7 +70,7 @@
         event->obj = obj;
         event->userdata = userdata;
         event->callback = onclick;
-        event->value = 0;
+        event->value = event_value;
         event->customdata = NULL;
 
         g_signal_connect(
@@ -89,8 +90,9 @@
 
 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);
+    GtkWidget *button = ui_create_button(obj, args.label, args.icon, args.onclick, args.onclickdata, 0);
     ui_set_name_and_style(button, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, button, args.groups);
     UI_APPLY_LAYOUT1(current, args);
     current->container->add(current->container, button, FALSE);
     return button;
@@ -232,6 +234,7 @@
     
     ui_setup_togglebutton(current, widget, args.label, args.icon, args.varname, args.value, args.onchange, args.onchangedata);
     ui_set_name_and_style(widget, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, widget, args.groups);
     
     UI_APPLY_LAYOUT1(current, args);
     current->container->add(current->container, widget, FALSE);
@@ -282,6 +285,9 @@
             args.onchange,
             args.onchangedata);
     
+    ui_set_name_and_style(widget, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, widget, args.groups);
+    
     UI_APPLY_LAYOUT1(current, args);
     current->container->add(current->container, widget, FALSE);
     
@@ -343,6 +349,7 @@
     
     GtkWidget *rbutton = RADIOBUTTON_NEW(rg, args.label); 
     ui_set_name_and_style(rbutton, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, rbutton, args.groups);
     if(rgroup) {
 #if GTK_MAJOR_VERSION >= 4
         if(rg) {
--- a/ui/gtk/button.h	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/gtk/button.h	Wed Oct 23 17:40:45 2024 +0200
@@ -46,7 +46,8 @@
         const char *label,
         const char *icon,
         ui_callback onclick,
-        void *userdata);
+        void *userdata,
+        int event_value);
 
 void ui_setup_togglebutton(
         UiObject *obj,
--- a/ui/gtk/container.c	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/gtk/container.c	Wed Oct 23 17:40:45 2024 +0200
@@ -242,7 +242,7 @@
 
 
 
-static GtkWidget* box_set_margin(GtkWidget *box, int margin) {
+GtkWidget* ui_box_set_margin(GtkWidget *box, int margin) {
     GtkWidget *ret = box;
 #if GTK_MAJOR_VERSION >= 3
 #if GTK_MAJOR_VERSION * 1000 + GTK_MINOR_VERSION >= 3012
@@ -270,7 +270,7 @@
     
     GtkWidget *box = type == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args.spacing) : ui_gtk_hbox_new(args.spacing);
     ui_set_name_and_style(box, args.name, args.style_class);
-    GtkWidget *widget = args.margin > 0 ? box_set_margin(box, args.margin) : box;
+    GtkWidget *widget = args.margin > 0 ? ui_box_set_margin(box, args.margin) : box;
     ct->add(ct, widget, TRUE);
     
     UiObject *newobj = uic_object_new(obj, box);
@@ -288,7 +288,7 @@
     return ui_box_create(obj, args, UI_CONTAINER_HBOX);
 }
 
-static GtkWidget* create_grid(int colspacing, int rowspacing) {
+GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing) {
 #if GTK_MAJOR_VERSION >= 3
     GtkWidget *grid = gtk_grid_new();
     gtk_grid_set_column_spacing(GTK_GRID(grid), colspacing);
@@ -306,9 +306,9 @@
     UI_APPLY_LAYOUT1(current, args);
     GtkWidget *widget;
     
-    GtkWidget *grid = create_grid(args.columnspacing, args.rowspacing);
+    GtkWidget *grid = ui_create_grid_widget(args.columnspacing, args.rowspacing);
     ui_set_name_and_style(grid, args.name, args.style_class);
-    widget = box_set_margin(grid, args.margin);
+    widget = ui_box_set_margin(grid, args.margin);
     current->container->add(current->container, widget, TRUE);
     
     UiObject *newobj = uic_object_new(obj, grid);
@@ -621,13 +621,13 @@
             break;
         }
         case UI_CONTAINER_GRID: {
-            sub = create_grid(data->columnspacing, data->rowspacing);
+            sub = ui_create_grid_widget(data->columnspacing, data->rowspacing);
             newobj->container = ui_grid_container(newobj, sub);
             break;
         }
     }
     newobj->widget = sub;
-    GtkWidget *widget = box_set_margin(sub, data->margin);
+    GtkWidget *widget = ui_box_set_margin(sub, data->margin);
     
     data->add_tab(data->widget, tab_index, name, widget);
     
--- a/ui/gtk/container.h	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/gtk/container.h	Wed Oct 23 17:40:45 2024 +0200
@@ -137,11 +137,13 @@
 UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame);
 void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
+GtkWidget* ui_box_set_margin(GtkWidget *box, int margin);
 UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, UiSubContainerType type);
 
 UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type);
 void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
+GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing);
 UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid);
 void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill);
 
--- a/ui/gtk/headerbar.c	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/gtk/headerbar.c	Wed Oct 23 17:40:45 2024 +0200
@@ -114,7 +114,7 @@
         UiObject *obj,
         enum UiToolbarPos pos)
 {
-    GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.onclick, item->args.onclickdata);
+    GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.onclick, item->args.onclickdata, 0);
     WIDGET_ADD_CSS_CLASS(button, "flat");
     headerbar_add(headerbar, box, button, pos);
 }
--- a/ui/gtk/toolkit.c	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/gtk/toolkit.c	Wed Oct 23 17:40:45 2024 +0200
@@ -166,8 +166,11 @@
 }
 
 void ui_close(UiObject *obj) {
-    // TODO
-    //gtk_widget_destroy(obj->widget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+    gtk_window_close(GTK_WINDOW(obj->widget));
+#else
+    gtk_widget_destroy(obj->widget);
+#endif
 }
 
 
@@ -345,6 +348,9 @@
 ".pathbar-button-inactive {\n"
 "  color: alpha(currentColor, 0.5);"
 "}\n"
+".ui_test {\n"
+"  background-color: red;\n"
+"}\n"
 ;
 
 #elif GTK_MAJOR_VERSION == 3
@@ -409,3 +415,13 @@
         
     }
 }
+
+void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups) {
+    if(!groups) {
+        return;
+    }
+    
+    size_t ngroups = uic_group_array_size(groups);
+    uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups);
+    ui_set_enabled(widget, FALSE);
+}
--- a/ui/gtk/toolkit.h	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/gtk/toolkit.h	Wed Oct 23 17:40:45 2024 +0200
@@ -59,7 +59,7 @@
 #define WINDOW_DESTROY(window) gtk_window_destroy(GTK_WINDOW(window))
 #define WINDOW_SET_CONTENT(window, child) gtk_window_set_child(GTK_WINDOW(window), child)
 #define BOX_ADD(box, child) gtk_box_append(GTK_BOX(box), child)
-#define BOX_ADD_EXPAND(box, child) gtk_widget_set_hexpand(child, TRUE); gtk_box_append(GTK_BOX(box), child)
+#define BOX_ADD_EXPAND(box, child) gtk_widget_set_hexpand(child, TRUE); gtk_widget_set_vexpand(child, TRUE); gtk_box_append(GTK_BOX(box), child)
 #define BOX_ADD_NO_EXPAND(box, child) gtk_box_append(GTK_BOX(box), child)
 #define ENTRY_SET_TEXT(entry, text) gtk_editable_set_text(GTK_EDITABLE(entry), text)
 #define ENTRY_GET_TEXT(entry) gtk_editable_get_text(GTK_EDITABLE(entry))
@@ -73,8 +73,8 @@
 #define WINDOW_DESTROY(window) gtk_widget_destroy(window)
 #define WINDOW_SET_CONTENT(window, child) gtk_container_add(GTK_CONTAINER(window), child)
 #define BOX_ADD(box, child) gtk_container_add(GTK_CONTAINER(box), child)
-#define BOX_ADD_EXPAND(box, child) gtk_box_pack_end(GTK_BOX(box), child, TRUE, TRUE, 0)
-#define BOX_ADD_NO_EXPAND(box, child) gtk_box_pack_end(GTK_BOX(box), child, TRUE, FALSE, 0)
+#define BOX_ADD_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, TRUE, 0)
+#define BOX_ADD_NO_EXPAND(box, child) gtk_box_pack_start(GTK_BOX(box), child, TRUE, FALSE, 0)  
 #define ENTRY_SET_TEXT(entry, text) gtk_entry_set_text(GTK_ENTRY(entry), text)
 #define ENTRY_GET_TEXT(entry) gtk_entry_get_text(GTK_ENTRY(entry))
 #define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new(NULL, NULL)
@@ -159,6 +159,7 @@
 int ui_get_scalefactor();
 
 void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *style);
+void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups);
 
 void ui_destroy_userdata(GtkWidget *object, void *userdata);
 void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data);
--- a/ui/gtk/window.c	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/gtk/window.c	Wed Oct 23 17:40:45 2024 +0200
@@ -42,6 +42,7 @@
 #include "toolbar.h"
 #include "container.h"
 #include "headerbar.h"
+#include "button.h"
 
 static int nwindows = 0;
 
@@ -586,3 +587,158 @@
     ui_gtkfilechooser(obj, GTK_FILE_CHOOSER_ACTION_SAVE, 0, name, file_selected_callback, cbdata);
 }
 
+#if GTK_CHECK_VERSION(4, 10, 0)
+#define DIALOG_NEW() gtk_window_new()
+#else
+#define DIALOG_NEW() gtk_dialog_new()
+
+static void ui_dialogwindow_response(GtkDialog* self, gint response_id, gpointer user_data) {
+    UiEventData *event = user_data;
+    // TODO: do we need to check if response_id == GTK_RESPONSE_DELETE_EVENT?
+    if(event->callback) {
+        UiEvent e;
+        e.obj = event->obj;
+        e.window = event->obj->window;
+        e.document = event->obj->ctx->document;
+        e.eventdata = NULL;
+        e.intval = event->value;
+        event->callback(&e, event->userdata);
+    }
+}
+
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+#define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(headerbar), set)
+#else
+#define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(headerbar), set)
+#endif
+
+
+
+UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) {
+    GtkWidget *dialog = DIALOG_NEW();
+    gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget));
+    if(args.modal != UI_OFF) {
+        gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+    }
+    
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); 
+    obj->ctx = uic_context(obj, mp);
+    obj->widget = dialog;
+    nwindows++;
+    
+    if(args.title != NULL) {
+        gtk_window_set_title(GTK_WINDOW(dialog), args.title);
+    }
+    
+#if ! GTK_CHECK_VERSION(4, 10, 0)
+    UiEventData *event = malloc(sizeof(UiEventData));
+    event->obj = obj;
+    event->userdata = args.onclickdata;
+    event->callback = args.onclick;
+    event->value = 0;
+    event->customdata = NULL;
+
+    g_signal_connect(dialog, "response", G_CALLBACK(ui_dialogwindow_response), event);
+    g_signal_connect(
+            dialog,
+            "destroy",
+            G_CALLBACK(ui_destroy_userdata),
+            event);
+#endif
+    
+    g_signal_connect(
+            dialog,
+            "destroy",
+            G_CALLBACK(ui_exit_event),
+            obj);
+    
+#if GTK_MAJOR_VERSION < 4
+    GtkWidget *c = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+    gtk_container_remove(GTK_CONTAINER(dialog), c);
+#endif
+    
+    GtkWidget *content_vbox = ui_gtk_vbox_new(0);
+    obj->container = ui_box_container(obj, content_vbox, UI_CONTAINER_VBOX);
+    if(args.lbutton1 || args.lbutton2 || args.rbutton3 || args.rbutton4) {
+#if GTK_CHECK_VERSION(3, 10, 0)
+        if(args.titlebar_buttons != UI_OFF) {
+            GtkWidget *headerbar = gtk_header_bar_new();
+            gtk_window_set_titlebar(GTK_WINDOW(dialog), headerbar);
+            if(args.show_closebutton == UI_OFF) {
+                HEADERBAR_SHOW_CLOSEBUTTON(headerbar, FALSE);
+            }
+            
+            if(args.lbutton1) {
+                GtkWidget *button = ui_create_button(obj, args.lbutton1, NULL, args.onclick, args.onclickdata, 1);
+                gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button);
+                if(args.default_button == 1) {
+                    WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                }
+            }
+            if(args.lbutton2) {
+                GtkWidget *button = ui_create_button(obj, args.lbutton2, NULL, args.onclick, args.onclickdata, 2);
+                gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button);
+                if(args.default_button == 2) {
+                    WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                }
+            }
+            
+            if(args.rbutton4) {
+                GtkWidget *button = ui_create_button(obj, args.rbutton4, NULL, args.onclick, args.onclickdata, 4);
+                gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button);
+                if(args.default_button == 4) {
+                    WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                }
+            }
+            if(args.rbutton3) {
+                GtkWidget *button = ui_create_button(obj, args.rbutton3, NULL, args.onclick, args.onclickdata, 3);
+                gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button);
+                if(args.default_button == 3) {
+                    WIDGET_ADD_CSS_CLASS(button, "suggested-action");
+                }
+            }
+            WINDOW_SET_CONTENT(obj->widget, content_vbox);
+            return obj;
+        }
+#endif
+        GtkWidget *vbox = ui_gtk_vbox_new(0);
+        WINDOW_SET_CONTENT(obj->widget, vbox);
+        
+        GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+        
+        GtkWidget *grid = ui_create_grid_widget(10, 10);
+        GtkWidget *widget = ui_box_set_margin(grid, 16);
+        gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE); 
+        
+        if(args.lbutton1) {
+            GtkWidget *button = ui_create_button(obj, args.lbutton1, NULL, args.onclick, args.onclickdata, 1);
+            gtk_grid_attach(GTK_GRID(grid), button, 0, 0, 1, 1);
+        }
+        if(args.lbutton2) {
+            GtkWidget *button = ui_create_button(obj, args.lbutton2, NULL, args.onclick, args.onclickdata, 2);
+            gtk_grid_attach(GTK_GRID(grid), button, 1, 0, 1, 1);
+        }
+        GtkWidget *space = gtk_label_new(NULL);
+        gtk_widget_set_hexpand(space, TRUE);
+        gtk_grid_attach(GTK_GRID(grid), space, 2, 0, 1, 1);
+        if(args.rbutton3) {
+            GtkWidget *button = ui_create_button(obj, args.rbutton3, NULL, args.onclick, args.onclickdata, 3);
+            gtk_grid_attach(GTK_GRID(grid), button, 3, 0, 1, 1);
+        }
+        if(args.rbutton3) {
+            GtkWidget *button = ui_create_button(obj, args.rbutton4, NULL, args.onclick, args.onclickdata, 4);
+            gtk_grid_attach(GTK_GRID(grid), button, 4, 0, 1, 1);
+        }
+        
+        BOX_ADD_EXPAND(vbox, content_vbox);   
+        BOX_ADD_NO_EXPAND(vbox, separator);
+        BOX_ADD_NO_EXPAND(vbox, widget);
+    } else {
+        WINDOW_SET_CONTENT(obj->widget, content_vbox);
+    }
+    
+    return obj;
+}
--- a/ui/ui/button.h	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/ui/button.h	Wed Oct 23 17:40:45 2024 +0200
@@ -50,6 +50,8 @@
     UiLabelType labeltype;
     ui_callback onclick;
     void* onclickdata;
+    
+    const int* groups;
 } UiButtonArgs;
 
 typedef struct UiToggleArgs {
@@ -69,6 +71,8 @@
     const char* varname;
     ui_callback onchange;
     void* onchangedata;
+    
+    const int* groups;
 } UiToggleArgs;
  
 #define ui_button(obj, ...) ui_button_create(obj, (UiButtonArgs){ __VA_ARGS__ } )
--- a/ui/ui/toolkit.h	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/ui/toolkit.h	Wed Oct 23 17:40:45 2024 +0200
@@ -147,7 +147,7 @@
 
 #define UI_GROUP_SELECTION  20000
 
-#define UI_GROUPS(...) (int[]){ __VA_ARGS__, -1 }
+#define UI_GROUPS(...) (const int[]){ __VA_ARGS__, -1 }
     
 /* public types */
 typedef int UiBool;
@@ -202,7 +202,7 @@
 
 typedef void(*ui_freefunc)(void*);
 
-typedef void(*ui_enablefunc)(void*, UiBool);
+typedef void(*ui_enablefunc)(void*, int);
 
 struct UiObject {
     /*
@@ -402,6 +402,11 @@
     size_t nfiles;
 };
 
+typedef struct UiCondVar {
+    void *data;
+    int intdata;
+} UiCondVar;
+
 
 UIEXPORT void ui_init(const char *appname, int argc, char **argv);
 UIEXPORT const char* ui_appname();
@@ -539,6 +544,11 @@
 UIEXPORT char* ui_getappdir();
 UIEXPORT char* ui_configfile(char *name);
 
+UIEXPORT UiCondVar* ui_condvar_create(void);
+UIEXPORT void ui_condvar_wait(UiCondVar *var);
+UIEXPORT void ui_condvar_signal(UiCondVar *var, void *data, int intdata);
+UIEXPORT void ui_condvar_destroy(UiCondVar *var);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/ui/window.h	Wed Oct 23 10:37:43 2024 +0200
+++ b/ui/ui/window.h	Wed Oct 23 17:40:45 2024 +0200
@@ -50,8 +50,32 @@
     void *resultdata;
 } UiDialogArgs;
 
+typedef struct UiDialogWindowArgs {
+    UiTri modal;
+    UiTri titlebar_buttons;
+    UiTri show_closebutton;
+    const char *title;
+    const char *lbutton1;
+    const char *lbutton2;
+    const char *rbutton3;
+    const char *rbutton4;
+    const int *lbutton1_groups;
+    const int *lbutton2_groups;
+    const int *rbutton3_groups;
+    const int *rbutton4_groups;
+    int default_button;
+    int width;
+    int height;
+    ui_callback onclick;
+    void *onclickdata;
+} UiDialogWindowArgs;
+
 UIEXPORT UiObject* ui_window(const char *title, void *window_data);
 UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data);
+UIEXPORT UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args);
+
+#define ui_dialog_window(parent, ...) ui_dialog_window_create(parent, (UiDialogWindowArgs){ __VA_ARGS__ });
+#define ui_dialog_window0(parent) ui_dialog_window_create(parent, (UiDialogWindowArgs){ 0 });
 
 UIEXPORT void ui_window_size(UiObject *obj, int width, int height);
 

mercurial