implement UI for editing properties, relates to #497

Thu, 28 Nov 2024 18:03:12 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 28 Nov 2024 18:03:12 +0100
changeset 97
5a3d27b8e6b0
parent 96
493959648de6
child 98
16e84fac48bd

implement UI for editing properties, relates to #497

application/Makefile file | annotate | diff | comparison | revisions
application/application.h file | annotate | diff | comparison | revisions
application/davcontroller.c file | annotate | diff | comparison | revisions
application/davcontroller.h file | annotate | diff | comparison | revisions
application/main.c file | annotate | diff | comparison | revisions
application/window.c file | annotate | diff | comparison | revisions
application/window.h file | annotate | diff | comparison | revisions
application/xml.c file | annotate | diff | comparison | revisions
application/xml.h file | annotate | diff | comparison | revisions
libidav/methods.c file | annotate | diff | comparison | revisions
libidav/resource.c file | annotate | diff | comparison | revisions
libidav/xml.c file | annotate | diff | comparison | revisions
ui/common/document.c file | annotate | diff | comparison | revisions
ui/gtk/button.c file | annotate | diff | comparison | revisions
ui/gtk/text.c file | annotate | diff | comparison | revisions
--- a/application/Makefile	Wed Nov 27 18:53:11 2024 +0100
+++ b/application/Makefile	Thu Nov 28 18:03:12 2024 +0100
@@ -41,6 +41,7 @@
 SRC += window.c
 SRC += settings.c
 SRC += appsettings.c
+SRC += xml.c
 
 OBJ = $(SRC:%.c=../build/application/%.$(OBJ_EXT))
 
@@ -50,5 +51,5 @@
 	$(CC) -o ../build/bin/idav$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx -lidav $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS)
 
 ../build/application/%.$(OBJ_EXT): %.c
-	$(CC) ../ucx $(CFLAGS) $(TK_CFLAGS) $(DAV_CFLAGS) -o $@ -c $<
+	$(CC) $(CFLAGS) $(TK_CFLAGS) $(DAV_CFLAGS) -o $@ -c $<
 
--- a/application/application.h	Wed Nov 27 18:53:11 2024 +0100
+++ b/application/application.h	Thu Nov 28 18:03:12 2024 +0100
@@ -48,6 +48,8 @@
 #define APP_STATE_BROWSER_SELECTION 110
     
 #define RESOURCEVIEWER_STATE_MODIFIED 2000
+#define RESOURCEVIEWER_STATE_PROP_SELECTED 2001
+#define RESOURCEVIEWER_STATE_PROP_XML 2002
     
 typedef struct DavApp {
     DavConfig *dav_config;
@@ -108,12 +110,28 @@
     UiList *resources;
 } DavBrowser;
 
+
+/*
+ * resource property list entry
+ */
+typedef struct DavPropertyList {
+    char *ns;
+    char *name;
+    char *value_simplified;
+    char *value_full;
+    DavXmlNode *xml;
+    DavBool update;
+    DavBool isnew;
+} DavPropertyList;
+
 /*
  * resource view window document object
  */
 typedef struct DavResourceViewer {
+    UiObject *obj;
     UiContext *ctx;
     DavSession *sn;
+    UiThreadpool *dav_queue;
     DavResource *current;
     char *path;
     DavResourceViewType type;
@@ -133,23 +151,27 @@
     UiString *info_etag;
     UiString *info_size;
     
+    UiInteger *property_type;
+    UiString  *property_ns;
+    UiString  *property_name;
+    UiString  *property_nsdef;
+    UiText    *property_value;
+    UiString  *property_errormsg;
+    
+    DavPropertyList *selected_property;
+    DavPropertyList *edit_property;
+    
     int error;
     char *message_str;
     
     CxBuffer *text_content;
     char *tmp_file;
     
+    bool properties_modified;
     bool loaded;
     bool window_closed;
 } DavResourceViewer;
 
-typedef struct DavPropertyList {
-    char *ns;
-    char *name;
-    char *value_simplified;
-    char *value_full;
-} DavPropertyList;
-
 typedef struct SessionAuthData {
     UiObject *obj;
     UiCondVar *cond;
--- a/application/davcontroller.c	Wed Nov 27 18:53:11 2024 +0100
+++ b/application/davcontroller.c	Thu Nov 28 18:03:12 2024 +0100
@@ -44,6 +44,7 @@
 DavBrowser* davbrowser_create(UiObject *toplevel) {
     DavBrowser *doc = ui_document_new(sizeof(DavBrowser));
     UiContext *ctx = ui_document_context(doc);
+    CxMempool *mp = ui_cx_mempool(ctx);
     doc->window = toplevel;
     doc->ctx = ctx;
 
@@ -52,6 +53,7 @@
     doc->navstack_pos = 0;
 
     doc->dav_queue = ui_threadpool_create(1);
+    cxMempoolRegister(mp, doc->dav_queue, (cx_destructor_func)ui_threadpool_destroy);
     
     doc->res_open_inprogress = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 4);
 
@@ -871,12 +873,16 @@
 
 
 
-DavResourceViewer* dav_resourceviewer_create(DavSession *sn, const char *path, DavResourceViewType type) {
+DavResourceViewer* dav_resourceviewer_create(UiObject *toplevel, DavSession *sn, const char *path, DavResourceViewType type) {
     DavResourceViewer *doc = ui_document_new(sizeof(DavResourceViewer));
     UiContext *ctx = ui_document_context(doc);
+    CxMempool *mp = ui_cx_mempool(ctx);
+    doc->obj = toplevel;
     doc->ctx = ctx;
     
     doc->sn = dav_session_clone(sn);
+    doc->dav_queue = ui_threadpool_create(1);
+    cxMempoolRegister(mp, doc->dav_queue, (cx_destructor_func)ui_threadpool_destroy);
     doc->path = strdup(path);
     doc->type = type;
     
@@ -896,6 +902,13 @@
     doc->info_etag = ui_string_new(ctx, "info_etag");
     doc->info_size = ui_string_new(ctx, "info_size");
     
+    doc->property_type = ui_int_new(ctx, NULL);
+    doc->property_ns = ui_string_new(ctx, NULL);
+    doc->property_name = ui_string_new(ctx, NULL);
+    doc->property_nsdef = ui_string_new(ctx, NULL);
+    doc->property_value = ui_text_new(ctx, NULL);
+    doc->property_errormsg = ui_string_new(ctx, NULL);
+        
     return doc;
 }
 
@@ -1017,8 +1030,13 @@
         DavPropertyList *prop = ui_malloc(doc->ctx, sizeof(DavPropertyList));
         prop->ns = properties[i].ns ? ui_strdup(doc->ctx, properties[i].ns) : NULL;
         prop->name = ui_strdup(doc->ctx, properties[i].name);
+        prop->value_simplified = NULL;
+        prop->value_full = NULL;
+        prop->update = FALSE;
+        prop->isnew = FALSE;
 
         DavXmlNode *xval = dav_get_property_ns(res, prop->ns, prop->name);
+        prop->xml = xval;
         if(xval) {
             if(dav_xml_isstring(xval)) {
                 char *value = dav_xml_getstring(xval);
@@ -1090,6 +1108,7 @@
 
 static void uithr_upload_text_finished(UiEvent *event, void *data) {
     ResourceViewerUploadFile *upload = data;
+    ui_object_unref(event->obj);
     
     if(upload->error) {
         cxmutstr errormsg = cx_asprintf("Upload failed: %d", upload->sn->error); // TODO: add full error message
@@ -1098,31 +1117,129 @@
         ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED);
     }
     
-    dav_session_destroy(upload->sn);
-    free(upload->path);
     free(upload->text.ptr);
     free(upload);
 }
 
+static int jobthr_store_properties(void *data) {
+    DavResourceViewer *res = data;
+    res->error = dav_store(res->current);
+    return 0;
+}
+
+static void uithr_store_properties_finished(UiEvent *event, void *data) {
+    DavResourceViewer *res = data;
+    ui_object_unref(event->obj);
+    if(res->error) {
+        cxmutstr errormsg = cx_asprintf("Proppatch failed: %d", res->sn->error); // TODO: add full error message
+        ui_dialog(event->obj, .title = "Error", .content = errormsg.ptr, .closebutton_label = "OK");
+        free(errormsg.ptr);
+        ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED);
+        res->properties_modified = TRUE;
+    } else {
+        CxList *properties = res->properties->data;
+        CxIterator i = cxListIterator(properties);
+        cx_foreach(DavPropertyList *, prop, i) {
+            prop->update = FALSE;
+            prop->isnew = FALSE;
+        }
+    }
+}
+
 void dav_resourceviewer_save(UiObject *ui, DavResourceViewer *res) {
     if(res->type == DAV_RESOURCE_VIEW_TEXT) {
-        DavSession *newsn = dav_session_clone(res->current->session);
         ResourceViewerUploadFile *upload = malloc(sizeof(ResourceViewerUploadFile));
         upload->ui = ui;
-        upload->sn = newsn;
-        upload->path = strdup(res->current->path);
+        upload->sn = res->sn;
+        upload->path = res->current->path;
         char *text = ui_get(res->text);
-        size_t textlen = strlen(text);
-        upload->text = cx_strdup(cx_strn(text, textlen));
-        ui_job(ui, jobthr_upload_text, upload, uithr_upload_text_finished, upload);
-        ui_unset_group(ui->ctx, RESOURCEVIEWER_STATE_MODIFIED);
+        upload->text = cx_strdup(cx_str(text));
+        ui_object_ref(res->obj);
+        ui_threadpool_job(res->dav_queue, ui, jobthr_upload_text, upload, uithr_upload_text_finished, upload);
     }
+    
+    if(res->properties_modified) {
+        CxList *properties = res->properties->data;
+        CxIterator i = cxListIterator(properties);
+        cx_foreach(DavPropertyList *, prop, i) {
+            if(prop->update) {
+                if(prop->value_full) {
+                    // text
+                    dav_set_string_property_ns(res->current, prop->ns, prop->name, prop->value_full);
+                } else {
+                    // xml
+                    dav_set_property_ns(res->current, prop->ns, prop->name, prop->xml);
+                }
+            }
+        }
+        
+        ui_object_ref(res->obj);
+        ui_threadpool_job(res->dav_queue, ui, jobthr_store_properties, res, uithr_store_properties_finished, res);
+    }
+    
+    ui_unset_group(ui->ctx, RESOURCEVIEWER_STATE_MODIFIED);
 }
 
 void dav_resourceviewer_destroy(DavResourceViewer *res) {
     
 }
 
+void dav_resourceviewer_property_remove(DavResourceViewer *res, DavPropertyList *prop) {
+    if(!prop->isnew) {
+        dav_remove_property_ns(res->current, prop->ns, prop->name);
+        ui_set_group(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED);
+        res->properties_modified = TRUE;
+    }
+    
+    CxList *properties = res->properties->data;
+    cxListFindRemove(properties, prop);
+    ui_free(res->ctx, prop->ns);
+    ui_free(res->ctx, prop->name);
+    ui_free(res->ctx, prop->value_simplified);
+    ui_free(res->ctx, prop->value_full);
+    // prop->xml freed by DavSession
+    ui_free(res->ctx, prop);
+    ui_list_update(res->properties);
+}
+
+void dav_resourceviewer_property_update_text(DavResourceViewer *res, DavPropertyList *prop, const char *text) {
+    ui_free(res->ctx, prop->value_simplified);
+    ui_free(res->ctx, prop->value_full);
+    prop->xml = NULL;
+    prop->value_full = ui_strdup(res->ctx, text);
+    prop->value_simplified = NULL;
+    prop->update = TRUE;
+    
+    ui_set_group(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED);
+    res->properties_modified = TRUE;
+    ui_list_update(res->properties);
+}
+
+void dav_resourceviewer_property_update_xml(DavResourceViewer *res, DavPropertyList *prop, DavXmlNode *xml) {
+    
+}
+
+void dav_resourceviewer_property_add_text(DavResourceViewer *res, const char *ns, const char *name, const char *text) {
+    DavPropertyList *prop = ui_malloc(res->ctx, sizeof(DavPropertyList));
+    prop->ns = ui_strdup(res->ctx, ns);
+    prop->name = ui_strdup(res->ctx, name);
+    prop->value_simplified = NULL;
+    prop->value_full = ui_strdup(res->ctx, text);
+    prop->xml = NULL;
+    prop->isnew = TRUE;
+    prop->update = TRUE;
+    
+    ui_list_append(res->properties, prop);
+    ui_set_group(res->obj->ctx, RESOURCEVIEWER_STATE_MODIFIED);
+    res->properties_modified = TRUE;
+    ui_list_update(res->properties);
+}
+
+void dav_resourceviewer_property_add_xml(DavResourceViewer *res, const char *ns, const char *name, const char *nsdef, DavXmlNode *xml) {
+    
+}
+
+
 
 uint64_t dav_transfer_speed(TransferProgress *progress, time_t current) {
     size_t bytes = progress->transferred_bytes - progress->speedtest_bytes;
--- a/application/davcontroller.h	Wed Nov 27 18:53:11 2024 +0100
+++ b/application/davcontroller.h	Thu Nov 28 18:03:12 2024 +0100
@@ -91,7 +91,7 @@
 void davbrowser_rename(UiObject *ui, DavBrowser *browser, UiListSelection selection);
 
 
-DavResourceViewer* dav_resourceviewer_create(DavSession *sn, const char *path, DavResourceViewType type);
+DavResourceViewer* dav_resourceviewer_create(UiObject *toplevel, DavSession *sn, const char *path, DavResourceViewType type);
 
 void dav_resourceviewer_load(UiObject *ui, DavResourceViewer *res);
 
@@ -99,6 +99,13 @@
 
 void dav_resourceviewer_destroy(DavResourceViewer *res);
 
+void dav_resourceviewer_property_remove(DavResourceViewer *res, DavPropertyList *prop);
+void dav_resourceviewer_property_update_text(DavResourceViewer *res, DavPropertyList *prop, const char *text);
+void dav_resourceviewer_property_update_xml(DavResourceViewer *res, DavPropertyList *prop, DavXmlNode *xml);
+void dav_resourceviewer_property_add_text(DavResourceViewer *res, const char *ns, const char *name, const char *text);
+void dav_resourceviewer_property_add_xml(DavResourceViewer *res, const char *ns, const char *name, const char *nsdef, DavXmlNode *xml);
+
+
 uint64_t dav_transfer_speed(TransferProgress *progress, time_t current);
 
 #ifdef	__cplusplus
--- a/application/main.c	Wed Nov 27 18:53:11 2024 +0100
+++ b/application/main.c	Thu Nov 28 18:03:12 2024 +0100
@@ -46,6 +46,7 @@
     application_init();
     ui_onstartup(application_startup, NULL);
 
+
     ui_main();
 
     sys_uninit();
--- a/application/window.c	Wed Nov 27 18:53:11 2024 +0100
+++ b/application/window.c	Thu Nov 28 18:03:12 2024 +0100
@@ -30,6 +30,7 @@
 
 #include "davcontroller.h"
 #include "appsettings.h"
+#include "xml.h"
 
 #include <ui/stock.h>
 #include <ui/dnd.h>
@@ -188,17 +189,13 @@
     UiObject *win = ui_simple_window(name, NULL);
     ui_window_size(win, 600, 600);
     
-    // TODO: when properties can be modified, always add the headerbar
-    if(type == DAV_RESOURCE_VIEW_TEXT) {
-        ui_headerbar(win, .showtitle = TRUE) {
-            ui_headerbar_start(win) {
-                ui_button(win, .label = "Save", .style_class = "suggested-action", .onclick = action_resourceviewer_save, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_MODIFIED));
-            }
+    ui_headerbar(win, .showtitle = TRUE) {
+        ui_headerbar_start(win) {
+            ui_button(win, .label = "Save", .onclick = action_resourceviewer_save, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_MODIFIED));
         }
     }
     
-    
-    DavResourceViewer *doc = dav_resourceviewer_create(browser->sn, path, type);
+    DavResourceViewer *doc = dav_resourceviewer_create(win, browser->sn, path, type);
     ui_attach_document(win->ctx, doc);
     ui_context_closefunc(win->ctx, resourceviewer_close, doc);
     
@@ -255,7 +252,12 @@
                 ui_tab(win, "Properties") {
                     UiModel* model = ui_model(win->ctx, UI_STRING, "Namespace", UI_STRING, "Name", UI_STRING, "Value", -1);
                     model->getvalue = (ui_getvaluefunc) resourceviewer_proplist_getvalue;
-                    ui_table(win, .fill = UI_ON, .model = model, .varname = "properties");
+                    ui_table(win, .fill = UI_ON, .model = model, .varname = "properties", .onselection = action_resourceviewer_property_select, .onactivate = action_resourceviewer_property_activate);
+                    ui_hbox(win, .fill = UI_OFF, .margin = 4, .spacing = 4) {
+                        ui_button(win, .label = "Add", .onclick = action_resourceviewer_property_add);
+                        ui_button(win, .label = "Edit", .onclick = action_resourceviewer_property_edit, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_SELECTED));
+                        ui_button(win, .label = "Remove", .onclick = action_resourceviewer_property_remove, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_SELECTED));
+                    }
                 }
             }
         }
@@ -545,3 +547,196 @@
     DavResourceViewer *doc = event->document;
     dav_resourceviewer_save(event->obj, doc);
 }
+
+void action_resourceviewer_property_select(UiEvent *event, void *data) {
+    DavResourceViewer *doc = event->document;
+    UiListSelection *selection = event->eventdata;
+    if(selection->count == 1) {
+        ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_SELECTED);
+        doc->selected_property = ui_list_get(doc->properties, selection->rows[0]);
+    } else {
+        ui_unset_group(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_SELECTED);
+        doc->selected_property = NULL;
+    }
+}
+
+void action_resourceviewer_property_activate(UiEvent *event, void *data) {
+    action_resourceviewer_property_select(event, data);
+    action_resourceviewer_property_edit(event, data);
+}
+
+
+typedef struct PropertyDialog {
+    UiInteger *type;
+    UiString *ns;
+    UiString *name;
+    UiText *value;
+} PropertyDialog;
+
+static void propertydialog_action(UiEvent *event, void *data) {
+    DavResourceViewer *res = data;
+    if(event->intval == 4) {
+        char *ns = ui_get(res->property_ns);
+        char *name = ui_get(res->property_name);
+        int type = ui_get(res->property_type);
+        char *nsdef = ui_get(res->property_nsdef);
+        char *value = ui_get(res->property_value);
+        
+        if(strlen(ns) == 0) {
+            ui_set(res->property_errormsg, "Namespace must not be empty!");
+            return;
+        }
+        if(strlen(name) == 0) {
+            ui_set(res->property_errormsg, "Name must not be empty!");
+            return;
+        }
+        
+        char *textvalue = NULL;
+        DavXmlNode *xmlvalue = NULL;
+        if(type == 0) {
+            // text value
+            textvalue = value;
+        } else {
+            // xml value
+        }
+        
+        DavBool add = FALSE;
+        if(res->edit_property) {
+            if(strcmp(res->edit_property->ns, ns) || strcmp(res->edit_property->name, name)) {
+                // name or namespace changed, remove existing and create new property
+                dav_resourceviewer_property_remove(res, res->edit_property);
+                add = TRUE;
+            }
+        } else {
+            add = TRUE;
+        }
+        
+        if(add) {
+            if(textvalue) {
+                dav_resourceviewer_property_add_text(res, ns, name, textvalue);
+            } else {
+                dav_resourceviewer_property_add_xml(res, ns, name, nsdef, xmlvalue);
+            }
+        } else {
+            if(textvalue) {
+                dav_resourceviewer_property_update_text(res, res->edit_property, textvalue);
+            } else {
+                dav_resourceviewer_property_update_xml(res, res->edit_property, xmlvalue);
+            }
+        }
+    }
+    ui_close(event->obj);
+}
+
+static void prop_type_changed(UiEvent *event, void *data) {
+    DavResourceViewer *res = data;
+    switch(ui_get(res->property_type)) {
+        case 0: {
+            ui_unset_group(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_XML);
+            break;
+        }
+        case 1: {
+            ui_set_group(event->obj->ctx, RESOURCEVIEWER_STATE_PROP_XML);
+            char *ns = ui_get(res->property_ns);
+            char *nsdef = ui_get(res->property_nsdef);
+            if(strlen(nsdef) == 0) {
+                cxmutstr def = cx_asprintf("xmlns:x0=\"%s\"", ns);
+                ui_set(res->property_nsdef, def.ptr);
+                free(def.ptr);
+            }
+            
+            break;
+        }
+    }
+}
+
+static void edit_property_dialog(DavResourceViewer *res, const char *title, DavPropertyList *prop) {
+    res->edit_property = prop;
+    
+    UiObject *obj = ui_dialog_window(res->obj,
+            .title = title,
+            .show_closebutton = UI_OFF,
+            .lbutton1 = "Cancel",
+            .rbutton4 = "Save",
+            .default_button = 4,
+            .onclick = propertydialog_action,
+            .onclickdata = res,
+            .width = 600,
+            .height = 500);
+    
+    ui_grid(obj, .margin = 16, .columnspacing = 8, .rowspacing = 12) {
+        ui_llabel(obj, .label = "Namespace");
+        ui_textfield(obj, .hexpand = TRUE, .value = res->property_ns);
+        ui_newline(obj);
+        
+        ui_llabel(obj, .label = "Property Name");
+        ui_textfield(obj, .hexpand = TRUE, .value = res->property_name);
+        ui_newline(obj);
+        
+        ui_llabel(obj, .label = "Type");
+        ui_hbox(obj, .spacing = 8, .colspan = 2) {
+            ui_radiobutton(obj, .label = "Text", .value = res->property_type, .onchange = prop_type_changed, .onchangedata = res);
+            ui_radiobutton(obj, .label = "XML", .value = res->property_type, .onchange = prop_type_changed, .onchangedata = res);
+        }
+        ui_newline(obj);
+        
+        ui_llabel(obj, .label = "Namespace Declarations");
+        ui_textfield(obj, .hexpand = TRUE, .value = res->property_nsdef, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_XML));
+        ui_newline(obj);
+        
+        ui_textarea(obj, .value = res->property_value, .hexpand = TRUE, .vexpand = TRUE, .colspan = 2);
+        ui_newline(obj);
+        
+        ui_llabel(obj, .colspan = 2, .value = res->property_errormsg);
+    }
+    
+    if(prop && prop->ns && prop->name) {
+        ui_set(res->property_ns, prop->ns);
+        ui_set(res->property_name, prop->name);
+        if(prop->value_full) {
+            ui_set(res->property_type, 0);
+            ui_set(res->property_nsdef, "");
+            ui_set(res->property_value, prop->value_full);
+            ui_unset_group(obj->ctx, RESOURCEVIEWER_STATE_PROP_XML);
+        } else if(prop->xml) {
+            ui_set(res->property_type, 1);
+            cxmutstr xml;
+            cxmutstr nsdef;
+            property_xml2str(prop->xml, prop->ns, &xml, &nsdef);
+            ui_set(res->property_nsdef, nsdef.ptr);
+            ui_set(res->property_value, xml.ptr);
+            free(xml.ptr);
+            free(nsdef.ptr);
+            ui_set_group(obj->ctx, RESOURCEVIEWER_STATE_PROP_XML);
+        }
+    } else {
+        ui_set(res->property_ns, "");
+        ui_set(res->property_name, "");
+        ui_set(res->property_nsdef, "");
+        ui_set(res->property_type, 0);
+        ui_set(res->property_value, "");
+    }
+    
+    ui_set(res->property_errormsg, "");
+    
+    ui_show(obj);
+}
+
+void action_resourceviewer_property_add(UiEvent *event, void *data) {
+    DavResourceViewer *doc = event->document;
+    edit_property_dialog(doc, "Add Property", NULL);
+}
+
+void action_resourceviewer_property_edit(UiEvent *event, void *data) {
+    DavResourceViewer *doc = event->document;
+    edit_property_dialog(doc, "Edit Property", doc->selected_property);
+}
+
+void action_resourceviewer_property_remove(UiEvent *event, void *data) {
+    DavResourceViewer *doc = event->document;
+    if(!doc->selected_property) {
+        return; // shouldn't happen
+    }
+    dav_resourceviewer_property_remove(doc, doc->selected_property);
+}
+
--- a/application/window.h	Wed Nov 27 18:53:11 2024 +0100
+++ b/application/window.h	Thu Nov 28 18:03:12 2024 +0100
@@ -80,6 +80,11 @@
 
 void action_resourceviewer_text_modified(UiEvent *event, void *data);
 void action_resourceviewer_save(UiEvent *event, void *data);
+void action_resourceviewer_property_select(UiEvent *event, void *data);
+void action_resourceviewer_property_activate(UiEvent *event, void *data);
+void action_resourceviewer_property_add(UiEvent *event, void *data);
+void action_resourceviewer_property_edit(UiEvent *event, void *data);
+void action_resourceviewer_property_remove(UiEvent *event, void *data);
 
 #ifdef	__cplusplus
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/xml.c	Thu Nov 28 18:03:12 2024 +0100
@@ -0,0 +1,79 @@
+/*
+ * 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 <cx/printf.h>
+#include <cx/hash_map.h>
+#include <cx/buffer.h>
+
+#include <libidav/xml.h>
+
+#include "xml.h"
+
+
+
+
+void property_xml2str(DavXmlNode *content, const char *namespace, cxmutstr *out_xmlstr, cxmutstr *out_nsdef) {
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 2048, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND);
+    
+    CxMap *nsmap = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    cxMapPut(nsmap, namespace, strdup("x0"));
+    
+    dav_print_node(&buf, (cx_write_func)cxBufferWrite, nsmap, content);
+    
+    cxmutstr ret = cx_mutstrn(buf.space, buf.size);
+    cxBufferPut(&buf, 0);
+    
+    
+    CxBuffer nsbuf;
+    cxBufferInit(&nsbuf, NULL, 2048, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND);
+    CxIterator i = cxMapIterator(nsmap);
+    int addSpace = 0;
+    cx_foreach(CxMapEntry *, entry, i) {
+        const char *ns = entry->key->data;
+        const char *pre = entry->value;
+        if(addSpace) {
+            cxBufferPut(&nsbuf, ' ');
+        } else {
+            addSpace = 1;
+        }
+        cx_bprintf(&nsbuf, "xmlns:%s=\"%s\"", pre, ns);
+    }
+    cxmutstr nsdef = cx_mutstrn(nsbuf.space, nsbuf.size);
+    cxBufferPut(&nsbuf, 0);
+    
+    // cleanup namespace map
+    cxMapDestroy(nsmap);
+    
+    
+    *out_xmlstr = ret;
+    *out_nsdef = nsdef;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application/xml.h	Thu Nov 28 18:03:12 2024 +0100
@@ -0,0 +1,47 @@
+/*
+ * 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 APP_XML_H
+#define APP_XML_H
+
+#include <libidav/webdav.h>
+#include <cx/string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void property_xml2str(DavXmlNode *content, const char *namespace, cxmutstr *out_xmlstr, cxmutstr *out_nsdef);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* APP_XML_H */
+
--- a/libidav/methods.c	Wed Nov 27 18:53:11 2024 +0100
+++ b/libidav/methods.c	Thu Nov 28 18:03:12 2024 +0100
@@ -836,7 +836,7 @@
     {
         char prefix[8];
         int pfxnum = 0;
-        if (data->set) {
+        if (data->set && cxListSize(data->set) > 0) {
             CxIterator i = cxListIterator(data->set);
             cx_foreach(DavProperty*, p, i) {
                 if (strcmp(p->ns->name, "DAV:")) {
@@ -845,7 +845,7 @@
                 }
             }
         }
-        if (data->remove) {
+        if (data->remove && cxListSize(data->remove) > 0) {
             CxIterator i = cxListIterator(data->remove);
             cx_foreach(DavProperty*, p, i) {
                 if (strcmp(p->ns->name, "DAV:")) {
--- a/libidav/resource.c	Wed Nov 27 18:53:11 2024 +0100
+++ b/libidav/resource.c	Thu Nov 28 18:03:12 2024 +0100
@@ -1043,7 +1043,7 @@
     // store properties
     int r = 0;
     sn->error = DAV_OK;
-    if(data->set || data->remove) {
+    if(data->set || data->remove > 0) {
         CxBuffer *request = create_proppatch_request(data);
         CxBuffer *response = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
         //printf("request:\n%.*s\n\n", request->pos, request->space);
--- a/libidav/xml.c	Wed Nov 27 18:53:11 2024 +0100
+++ b/libidav/xml.c	Thu Nov 28 18:03:12 2024 +0100
@@ -174,8 +174,8 @@
                 prefix = cxMapGet(nsmap, cx_hash_key_str(node->namespace));
                 if(!prefix) {
                     cxmutstr newpre = cx_asprintf("x%zu", cxMapSize(nsmap)+1);
-                    // TODO: fix namespace declaration
-                    //ucx_map_cstr_put(nsmap, node->namespace, newpre.ptr);
+                    // TODO: fix
+                    //cxMapPut(nsmap, node->namespace, newpre.ptr);
                     prefix = newpre.ptr;
                     prefix_fr = prefix;
                     cx_fprintf(
--- a/ui/common/document.c	Wed Nov 27 18:53:11 2024 +0100
+++ b/ui/common/document.c	Thu Nov 28 18:03:12 2024 +0100
@@ -86,12 +86,13 @@
     CxMempool *mp = cxMempoolCreate(256, NULL);
     const CxAllocator *a = mp->allocator;
     UiContext *ctx = cxCalloc(a, 1, sizeof(UiContext));
+    ctx->mp = mp;
     ctx->attach_document = uic_context_attach_document;
     ctx->detach_document2 = uic_context_detach_document2;
     ctx->allocator = a;
     ctx->vars = cxHashMapCreate(a, CX_STORE_POINTERS, 16);
     
-    void *document = cxCalloc(a, 1, size);
+    void *document = cxCalloc(a, size, 1);
     cxMapPut(documents, cx_hash_key(&document, sizeof(void*)), ctx);
     return document;
 }
--- a/ui/gtk/button.c	Wed Nov 27 18:53:11 2024 +0100
+++ b/ui/gtk/button.c	Thu Nov 28 18:03:12 2024 +0100
@@ -391,6 +391,23 @@
     event->callback(&e, event->userdata);    
 }
 
+typedef struct UiRadioButtonData {
+    UiInteger *value;
+    UiVarEventData *eventdata;
+    UiBool first;
+} UiRadioButtonData;
+
+static void destroy_radiobutton(GtkWidget *w, UiRadioButtonData *data) {
+    ui_destroy_vardata(w, data->eventdata);
+    if(data->first) {
+        g_slist_free(data->value->obj);
+        data->value->obj = NULL;
+        data->value->get = NULL;
+        data->value->set = NULL;
+    }
+    free(data);
+}
+
 UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs args) {
     UiObject* current = uic_current_obj(obj);
     
@@ -435,18 +452,21 @@
         event->callback = NULL;
         event->userdata = NULL;
         
+        UiRadioButtonData *rbdata = malloc(sizeof(UiRadioButtonData));
+        rbdata->value = rgroup;
+        rbdata->eventdata = event;
+        rbdata->first = first;
+        
         g_signal_connect(
                 rbutton,
                 "toggled",
                 G_CALLBACK(ui_radio_obs),
                 event);
-        if(first) {
-            g_signal_connect(
+        g_signal_connect(
                 rbutton,
                 "destroy",
-                G_CALLBACK(ui_destroy_vardata),
-                event);
-        }
+                G_CALLBACK(destroy_radiobutton),
+                rbdata);
     }
     
     if(args.onchange) {
--- a/ui/gtk/text.c	Wed Nov 27 18:53:11 2024 +0100
+++ b/ui/gtk/text.c	Thu Nov 28 18:03:12 2024 +0100
@@ -112,8 +112,8 @@
     current->container->add(current->container, scroll_area, TRUE);
     
     // bind value
-    UiText *value = var->value;
-    if(value) {
+    if(var) {
+        UiText *value = var->value;
         GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_area));
         
         if(value->value.ptr) {
@@ -165,7 +165,9 @@
 }
 
 void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) {
-    ui_destroy_boundvar(textarea->ctx, textarea->var);
+    if(textarea->var) {
+        ui_destroy_boundvar(textarea->ctx, textarea->var);
+    }
     free(textarea);
 }
 

mercurial