rework uic_copy_binding and refactore menu itemlists to bind directly to lists without using observers

7 days ago

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 06 Apr 2025 13:28:35 +0200 (7 days ago)
changeset 553
90e38db0c755
parent 552
131059d41671
child 554
28e490aa15e3

rework uic_copy_binding and refactore menu itemlists to bind directly to lists without using observers

application/main.c 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/types.c file | annotate | diff | comparison | revisions
ui/gtk/menu.c file | annotate | diff | comparison | revisions
ui/gtk/menu.h file | annotate | diff | comparison | revisions
--- a/application/main.c	Sun Apr 06 08:50:27 2025 +0200
+++ b/application/main.c	Sun Apr 06 13:28:35 2025 +0200
@@ -37,7 +37,149 @@
 #include "cx/string.h"
 #include "cx/list.h"
 
-#if !defined(UI_COCOA) && !defined(UI_MOTIF) && !defined(UI_WIN32) && !defined(UI_QT)
+#define UI_TEST
+
+#ifdef UI_TEST
+
+#define STATE_DOC 10
+#define STATE_SUB 100
+
+typedef struct ToplevelDoc {
+    UiContext *ctx;
+    
+    UiList *doclist;
+    
+    void *current;
+} ToplevelDoc;
+
+typedef struct Document {
+    UiContext *ctx;
+    
+    UiString *name;
+    char *name_str;
+    
+    UiInteger *i;
+    UiString *s;
+    UiText *t;
+    UiList *ls1;
+    UiList *ls2;
+} Document;
+
+
+
+Document* create_doc(int i) {
+    Document *doc = ui_document_new(sizeof(Document));
+    doc->ctx = ui_document_context(doc);
+    doc->name = ui_string_new(doc->ctx, "name");
+    doc->i = ui_int_new(doc->ctx, "int");
+    doc->s = ui_string_new(doc->ctx, "string");
+    doc->t = ui_text_new(doc->ctx, "text");
+    doc->ls1 = ui_list_new(doc->ctx, "list1");
+    doc->ls2 = ui_list_new(doc->ctx, "list2");
+    
+    ui_list_append(doc->ls1, "Hello");
+    ui_list_append(doc->ls1, "World");
+    ui_list_append(doc->ls1, "Test");
+    
+    for(int x=0;x<(i+1)*2;x++) {
+        char buf[32];
+        snprintf(buf, 32, "%d", x);
+        ui_list_append(doc->ls2, strdup(buf));
+    }
+    
+    return doc;
+}
+
+ToplevelDoc* create_toplevel() {
+    ToplevelDoc *doc = ui_document_new(sizeof(ToplevelDoc));
+    doc->ctx = ui_document_context(doc);
+    doc->doclist = ui_list_new(doc->ctx, "doclist");
+    
+    for(int i=0;i<3;i++) {
+        Document *sub = create_doc(i);
+        char buf[32];
+        snprintf(buf, 32, "%d", i);
+        ui_set(sub->name, buf);
+        sub->name_str = strdup(buf);
+        ui_list_append(doc->doclist, sub);
+    }
+    
+    return doc;
+}
+
+static void* doclist_getvalue(void *elm, int col) {
+    Document *doc = elm;
+    return doc->name_str;
+}
+
+static void action_document_selected(UiEvent *event, void *userdata) {
+    Document *sub = event->eventdata;
+    ToplevelDoc *doc = event->document;
+    if(doc->current) {
+        ui_detach_document2(doc->ctx, doc->current);
+    }
+    ui_attach_document(doc->ctx, sub);
+    doc->current = sub;
+}
+
+void application_startup(UiEvent *event, void *data) {
+    UiObject *obj = ui_window("Test", NULL);
+    
+    ui_grid(obj, .margin = 10, .columnspacing = 10, .rowspacing = 10) {
+        ui_hbox(obj, .colspan = 2, .spacing = 10) {
+            ui_combobox(obj, .varname = "doclist", .getvalue = doclist_getvalue, .onactivate = action_document_selected);
+            ui_textfield(obj, .varname = "name");
+            ui_button(obj, .colspan = 4, .label = "Test Groups", .groups = UI_GROUPS(STATE_DOC, STATE_SUB));
+        }
+        ui_newline(obj);
+        
+        ui_rlabel(obj, .label = "Integer", .vfill = TRUE);
+        ui_togglebutton(obj, .label = "Test", .varname = "int", .hfill = TRUE);
+        ui_newline(obj);
+        
+        
+        ui_rlabel(obj, .label = "String", .vfill = TRUE);
+        ui_textfield(obj, .varname = "string", .hfill = TRUE);
+        ui_newline(obj);
+        
+        ui_rlabel(obj, .label = "Text");
+        UIWIDGET textarea = ui_textarea(obj, .varname = "text", .hfill =TRUE);
+        ui_widget_set_size(textarea, 300, 300);
+        ui_newline(obj);
+        
+        ui_rlabel(obj, .label = "List 1", .vfill = TRUE);
+        ui_combobox(obj, .varname = "list1");
+    }
+    
+    ToplevelDoc *doc = create_toplevel();
+    ui_attach_document(obj->ctx, doc);
+    
+    ui_show(obj);
+}
+
+int main(int argc, char** argv) { 
+    ui_init("app1", argc, argv);
+    ui_onstartup(application_startup, NULL);
+    
+    ui_set_property("ui.gtk.window.showtitle", "main");
+    
+    // menu
+    ui_menu("File") {
+        ui_menuitem(.label = "Static Item");
+        ui_menuseparator();
+        ui_menu_itemlist(.varname = "list2");
+    }
+    
+    ui_main();
+    
+    return (EXIT_SUCCESS);
+}
+
+
+#endif
+
+
+#if !defined(UI_COCOA) && !defined(UI_MOTIF) && !defined(UI_WIN32) && !defined(UI_QT) && !defined(UI_TEST)
 
 typedef struct {
     UiString *str1;
--- a/ui/common/context.c	Sun Apr 06 08:50:27 2025 +0200
+++ b/ui/common/context.c	Sun Apr 06 13:28:35 2025 +0200
@@ -120,11 +120,12 @@
     CxMapIterator mi = cxMapIterator(ctx->vars);
     cx_foreach(CxMapEntry*, entry, mi) {
         UiVar *var = entry->value;
-        if(var->from && var->from_ctx && var->from_ctx != ctx) {
+        // var->from && var->from_ctx && var->from_ctx != ctx
+        if(var->from) {
             uic_save_var2(var);
             uic_copy_binding(var, var->from, FALSE);
-            cxMapPut(var->from_ctx->vars_unbound, *entry->key, var->from);
-            var->from_ctx = ctx;
+            cxMapPut(var->from->from_ctx->vars_unbound, *entry->key, var->from);
+            var->from = NULL;
         }
     }
     
@@ -209,6 +210,7 @@
     var = ui_malloc(ctx, sizeof(UiVar));
     var->type = type;
     var->value = uic_create_value(ctx, type);
+    var->original_value = NULL;
     var->from = NULL;
     var->from_ctx = ctx;
 
@@ -227,6 +229,7 @@
     var->from = NULL;
     var->from_ctx = ctx;
     var->value = value;
+    var->original_value = NULL;
     var->type = UI_VAR_SPECIAL;
     return var;
 }
@@ -286,10 +289,19 @@
     }
     
     void *fromvalue = from->value;
+    void *tovalue = to->value;
     // update var
     if(copytodoc) {
-        to->from = from;
-        to->from_ctx = from->from_ctx;
+        to->from = from; // from which UiVar are the bindings copied
+        from->original_value = fromvalue; // save original value otherwise it would be lost
+        // widgets store a reference to the UiVar with their value
+        // the UiVar object must be updated to contain the current value object
+        from->value = tovalue;
+    } else {
+        if(to->original_value) {
+            to->value = to->original_value;
+            tovalue = to->value;
+        }
     }
     
     ui_setop_enable(TRUE);
@@ -301,7 +313,7 @@
         case UI_VAR_SPECIAL: break;
         case UI_VAR_INTEGER: {
             UiInteger *f = fromvalue;
-            UiInteger *t = to->value;
+            UiInteger *t = tovalue;
             if(!f->obj) break;
             uic_int_copy(f, t);
             t->set(t, t->value);
@@ -309,7 +321,7 @@
         }
         case UI_VAR_DOUBLE: {
             UiDouble *f = fromvalue;
-            UiDouble *t = to->value;
+            UiDouble *t = tovalue;
             if(!f->obj) break;
             uic_double_copy(f, t);
             t->set(t, t->value);
@@ -317,48 +329,32 @@
         }
         case UI_VAR_STRING: {
             UiString *f = fromvalue;
-            UiString *t = to->value;
+            UiString *t = tovalue;
             if(!f->obj) break;
             uic_string_copy(f, t);
             char *tvalue = t->value.ptr ? t->value.ptr : "";
+            char *fvalue = f->value.ptr ? f->value.ptr : "";
             t->set(t, tvalue);
             break;
         }
         case UI_VAR_TEXT: {
             UiText *f = fromvalue;
-            UiText *t = to->value;
+            UiText *t = tovalue;
             if(!f->obj) break;
             uic_text_copy(f, t);
             t->restore(t);
             break;
         }
-        case UI_VAR_LIST: {
-            // TODO: not sure how correct this is
-
-            UiList *f = from->value;
-            UiList *t = to->value;
-            if (f->obj) {
-                t->obj = f->obj;
-                t->update = f->update;
-                t->getselection = f->getselection;
-                t->setselection = f->setselection;
-            }
-
-            UiVar tmp = *from;
-            *from = *to;
-            *to = tmp;
-
-            UiList* t2 = to->value;
-            if(t->update) {
-                t->update(t, -1);
-            }
-            ui_notify(t2->observers, NULL); // TODO: why not t?
-            
+        case UI_VAR_LIST: {         
+            UiList *f = fromvalue;
+            UiList *t = tovalue;
+            uic_list_copy(f, t);
+            ui_list_update(t);
             break;
         }
         case UI_VAR_RANGE: {
             UiRange *f = fromvalue;
-            UiRange *t = to->value;
+            UiRange *t = tovalue;
             if(!f->obj) break;
             uic_range_copy(f, t);
             t->setextent(t, t->extent);
@@ -368,7 +364,7 @@
         }
         case UI_VAR_GENERIC: {
             UiGeneric *f = fromvalue;
-            UiGeneric *t = to->value;
+            UiGeneric *t = tovalue;
             if(!f->obj) break;
             uic_generic_copy(f, t);
             t->set(t, t->value, t->type);
--- a/ui/common/context.h	Sun Apr 06 08:50:27 2025 +0200
+++ b/ui/common/context.h	Sun Apr 06 13:28:35 2025 +0200
@@ -93,8 +93,9 @@
 // UiVar replacement, rename it to UiVar when finished
 struct UiVar {
     void      *value;
+    void      *original_value;
     UiVarType type;
-    UiVar    *from;
+    UiVar     *from;
     UiContext *from_ctx;
 };
 
@@ -117,6 +118,8 @@
 
 void uic_context_attach_document(UiContext *ctx, void *document);
 void uic_context_detach_document2(UiContext *ctx, void *document);
+void uic_context_attach_context(UiContext *ctx, UiContext *doc_ctx);
+void uic_context_detach_context(UiContext *ctx, UiContext *doc_ctx);
 void uic_context_detach_all(UiContext *ctx);
 
 UiVar* uic_get_var(UiContext *ctx, const char *name);
--- a/ui/common/types.c	Sun Apr 06 08:50:27 2025 +0200
+++ b/ui/common/types.c	Sun Apr 06 13:28:35 2025 +0200
@@ -452,6 +452,8 @@
 
 void uic_list_copy(UiList *from, UiList *to) {
     to->update = from->update;
+    to->getselection = from->getselection;
+    to->setselection = from->setselection;
     to->obj = from->obj;
 }
 
--- a/ui/gtk/menu.c	Sun Apr 06 08:50:27 2025 +0200
+++ b/ui/gtk/menu.c	Sun Apr 06 13:28:35 2025 +0200
@@ -233,6 +233,20 @@
 }
 */
 
+static void menuitem_list_remove_binding(void *obj) {
+    UiActiveMenuItemList *ls = obj;
+    UiList *list = ls->var->value;
+    CxList *bindings = list->obj;
+    if(bindings) {
+        (void)cxListFindRemove(bindings, obj);
+        if(cxListSize(bindings) == 0) {
+            cxListFree(bindings);
+            list->obj = NULL;
+            list->update = NULL;
+        }
+    }
+}
+
 void add_menuitem_list_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
     UiMenuItemList *il = (UiMenuItemList*)item;
     const CxAllocator *a = obj->ctx->allocator;
@@ -247,21 +261,45 @@
     ls->oldcount = 0;
     ls->getvalue = il->getvalue;
     
-    UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
-    ls->list = var->value;
+    //UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+    UiVar* var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST);
+    ls->var = var;
+    if(var) {
+        UiList *list = var->value;
+        list->update = ui_menulist_update;
+        list->getselection = NULL;
+        list->setselection = NULL;
+        
+        // It is possible, that the UiVar is from a global shared context,
+        // used by multiple windows. To support this usecase, the list->obj
+        // binding object is a list of all connected UiActiveMenuItemList.
+        CxList *bindings = list->obj;
+        if(!bindings) {
+            bindings = cxLinkedListCreate(var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS);
+            list->obj = bindings;
+        }
+        cxListAdd(bindings, ls);
+        
+        // The destruction of the toplevel obj must remove the menulist binding
+        cxMempoolRegister(obj->ctx->mp, ls, menuitem_list_remove_binding);
+        
+        ui_update_menuitem_list(ls);
+    }
     
     ls->callback = il->callback;
     ls->userdata = il->userdata;
-    
-    UiObserver *observer = ui_observer_new((ui_callback)ui_update_menuitem_list, ls);
-    ls->list->observers = ui_obsvlist_add(ls->list->observers, observer);
-    uic_list_register_observer_destructor(obj->ctx, ls->list, observer);
-    
-    ui_update_menuitem_list(NULL, ls);
+}
+
+void ui_menulist_update(UiList *list, int ignored) {
+    CxList *bindings = list->obj;
+    CxIterator i = cxListIterator(bindings);
+    cx_foreach(UiActiveMenuItemList *, ls, i) {
+        ui_update_menuitem_list(ls);
+    }
 }
 
 
-void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) {    
+void ui_update_menuitem_list(UiActiveMenuItemList *list) {    
     // remove old items
     if(list->oldcount > 0) {
         int i = 0;
@@ -276,7 +314,9 @@
         }
     }
     
-    void* elm = ui_list_first(list->list);
+    UiList *ls = list->var->value;
+    
+    void* elm = ui_list_first(ls);
     if(elm) {
         GtkWidget *widget = gtk_separator_menu_item_new();
         gtk_menu_shell_insert(list->menu, widget, list->index);
@@ -312,7 +352,7 @@
                 event);
         }
         
-        elm = ui_list_next(list->list);
+        elm = ui_list_next(ls);
         i++;
     }
     
@@ -506,6 +546,20 @@
     
 }
 
+static void menuitem_list_remove_binding(void *obj) {
+    UiActiveGMenuItemList *ls = obj;
+    UiList *list = ls->var->value;
+    CxList *bindings = list->obj;
+    if(bindings) {
+        (void)cxListFindRemove(bindings, obj);
+        if(cxListSize(bindings) == 0) {
+            cxListFree(bindings);
+            list->obj = NULL;
+            list->update = NULL;
+        }
+    }
+}
+
 void ui_gmenu_add_menuitem_list(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
     UiMenuItemList *il = (UiMenuItemList*)item; 
     
@@ -521,17 +575,34 @@
     ls->oldcount = 0;
     ls->getvalue = il->getvalue;
     
-    UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+    //UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+    UiVar* var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST);
     ls->var = var;
-    UiList *list = var->value;
+    if(var) {
+        UiList *list = var->value;
+        list->update = ui_menulist_update;
+        list->getselection = NULL;
+        list->setselection = NULL;
+        
+        // It is possible, that the UiVar is from a global shared context,
+        // used by multiple windows. To support this usecase, the list->obj
+        // binding object is a list of all connected UiActiveMenuItemList.
+        CxList *bindings = list->obj;
+        if(!bindings) {
+            bindings = cxLinkedListCreate(var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS);
+            list->obj = bindings;
+        }
+        cxListAdd(bindings, ls);
+        
+        // The destruction of the toplevel obj must remove the menulist binding
+        cxMempoolRegister(obj->ctx->mp, ls, menuitem_list_remove_binding);
+        
+        ui_update_gmenu_item_list(ls);
+    }
     
     ls->callback = il->callback;
     ls->userdata = il->userdata;
     
-    UiObserver *observer = ui_observer_new((ui_callback)ui_update_gmenu_item_list, ls);
-    list->observers = ui_obsvlist_add(list->observers, observer);
-    uic_list_register_observer_destructor(obj->ctx, list, observer);
-    
     GSimpleAction *action = g_simple_action_new(item->id, g_variant_type_new("i"));
     g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
     snprintf(ls->action, 32, "win.%s", item->id);
@@ -554,8 +625,6 @@
             "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) {
@@ -588,7 +657,15 @@
     
 }
 
-void ui_update_gmenu_item_list(UiEvent *event, UiActiveGMenuItemList *list) {
+void ui_menulist_update(UiList *list, int ignored) {
+    CxList *bindings = list->obj;
+    CxIterator i = cxListIterator(bindings);
+    cx_foreach(UiActiveGMenuItemList *, ls, i) {
+        ui_update_gmenu_item_list(ls);
+    }
+}
+
+void ui_update_gmenu_item_list(UiActiveGMenuItemList *list) {
     // remove old items
     for(int i=0;i<list->oldcount;i++) {
         g_menu_remove(list->menu, list->index);
--- a/ui/gtk/menu.h	Sun Apr 06 08:50:27 2025 +0200
+++ b/ui/gtk/menu.h	Sun Apr 06 13:28:35 2025 +0200
@@ -55,10 +55,10 @@
     GtkMenuShell     *menu;
     int              index;
     int              oldcount;
-    UiList           *list;
-    ui_getvaluefunc getvalue;
-    ui_callback     callback;
-    void            *userdata;
+    UiVar            *var;
+    ui_getvaluefunc  getvalue;
+    ui_callback      callback;
+    void             *userdata;
 };
 
 void ui_add_menu_items(GtkWidget *parent, int i, UiMenu *menu, UiObject *obj);
@@ -72,7 +72,8 @@
 void add_checkitemnv_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
 void add_menuitem_list_widget(GtkWidget *p, int i, UiMenuItemI *item, UiObject *obj);
 
-void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list);
+void ui_menulist_update(UiList *list, int ignored);
+void ui_update_menuitem_list(UiActiveMenuItemList *list);
 void ui_menu_event_wrapper(GtkMenuItem *item, UiEventData *event);
 void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event);
 int64_t ui_checkitem_get(UiInteger *i);
@@ -108,7 +109,8 @@
 
 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);
+void ui_menulist_update(UiList *list, int ignored);
+void ui_update_gmenu_item_list(UiActiveGMenuItemList *list);
 
 #endif
 

mercurial