ui/common/context.c

changeset 163
b70e2a77dea0
parent 154
8a4451fcb736
child 164
1d912f78fd1d
--- a/ui/common/context.c	Sat Dec 05 11:54:58 2020 +0100
+++ b/ui/common/context.c	Sat Dec 05 17:50:22 2020 +0100
@@ -44,8 +44,8 @@
     ctx->obj = toplevel;
     ctx->vars = ucx_map_new_a(mp->allocator, 16);
     
-    ctx->set_document = uic_context_set_document;
-    ctx->detach_document = uic_context_detach_document;
+    ctx->attach_document = uic_context_attach_document;
+    ctx->detach_document2 = uic_context_detach_document2;
     
 #ifdef UI_GTK
     if(toplevel->widget) {
@@ -61,139 +61,126 @@
     return ctx->parent ? uic_root_context(ctx->parent) : ctx;
 }
 
-void uic_context_set_document(UiContext *ctx, void *document) {
-    if(ctx->document == document) {
-        return;
-    }
-    if(ctx->document) {
-        uic_context_detach_document(ctx);
-    }
-    ctx->document = document;
+void uic_context_attach_document(UiContext *ctx, void *document) {
+    ctx->documents = ucx_list_append_a(ctx->mempool->allocator, ctx->documents, document);
+    ctx->document = ctx->documents->data;
+    
+    UiContext *doc_ctx = ui_document_context(document);
     
-    UiContext *docctx = ui_document_context(document);
-    if(!docctx) {
-        fprintf(stderr, "UiError: uic_context_set_document: pointer is not a document\n");
-        return;
+    // check if any parent context has an unbound variable with the same name
+    // as any document variable
+    UiContext *var_ctx = ctx;
+    while(var_ctx) {
+        if(var_ctx->vars_unbound && var_ctx->vars_unbound->count > 0) {
+            UcxMapIterator i = ucx_map_iterator(var_ctx->vars_unbound);
+            UiVar *var;
+            // rmkeys holds all keys, that shall be removed from vars_unbound
+            UcxKey *rmkeys = calloc(var_ctx->vars_unbound->count, sizeof(UcxKey));
+            size_t numkeys = 0;
+            UCX_MAP_FOREACH(key, var, i) {
+                UiVar *docvar = ucx_map_get(doc_ctx->vars, key);
+                if(docvar) {
+                    // bind var to document var
+                    uic_copy_binding(var, docvar, TRUE);
+                    rmkeys[numkeys++] = key; // save the key for removal
+                }
+            }
+            // now that we may have bound some vars to the document,
+            // we can remove them from the unbound map
+            for(size_t k=0;k<numkeys;k++) {
+                ucx_map_remove(var_ctx->vars_unbound, rmkeys[k]);
+            }
+        }
+        
+        var_ctx = ctx->parent;
     }
-    docctx->obj = ctx->obj;
-    docctx->parent = ctx;
-    
-    UiContext *root = uic_root_context(ctx);
-    if(!root->bound || root->bound->count == 0) {
-        return;
+}
+
+static void uic_context_unbind_vars(UiContext *ctx) {
+    UcxMapIterator i = ucx_map_iterator(ctx->vars);
+    UiVar *var;
+    UCX_MAP_FOREACH(key, var, i) {
+        if(var->from && var->from_ctx) {
+            uic_save_var2(var);
+            uic_copy_binding(var, var->from, FALSE);
+            ucx_map_put(var->from_ctx->vars_unbound, key, var->from);
+            var->from_ctx = ctx;
+        }
     }
     
-    // there are bound variables in the root ctx
-    // copy bindings with correct name to doc vars
-    UcxMapIterator i = ucx_map_iterator(docctx->vars);
-    UiVar *var;
-    UCX_MAP_FOREACH(key, var, i) {
-        UiVar *v = ucx_map_get(root->bound, key);
-        if(v) {
-            // copy binding: after this, all doc vars with names of previously
-            // bound variables are bound to the widgets
-            // the widgets still hold a pointer to the root ctx vars, but this
-            // vars have a pointer to the document variable value - confusing
-            uic_copy_binding(v, var, TRUE);
-        }
+    UCX_FOREACH(elm, ctx->documents) {
+        UiContext *subctx = ui_document_context(elm->data);
+        uic_context_unbind_vars(subctx);
     }
 }
 
-void uic_context_detach_document(UiContext *ctx) {
-    if(!ctx->document) {
-        return;
+void uic_context_detach_document2(UiContext *ctx, void *document) {
+    // find the document in the documents list
+    UcxList *doc = NULL;
+    UCX_FOREACH(elm, ctx->documents) {
+        if(elm->data == document) {
+            doc = elm;
+            break;
+        }
+    }
+    if(!doc) {
+        return; // document is not a subdocument of this context
     }
     
-    UiContext *docctx = ui_document_context(ctx->document);
-    if(!docctx) {
-        fprintf(stderr, "UiError: Cannot detach document: no context\n");
-        return;
-    }
-    ctx->document = NULL;
-    docctx->parent = NULL;
-    docctx->obj = NULL;
+    ctx->documents = ucx_list_remove_a(ctx->mempool->allocator, ctx->documents, doc);
+    ctx->document = ctx->documents ? ctx->documents->data : NULL;
     
-    // unbind all vars
-    UcxMapIterator i = ucx_map_iterator(docctx->vars);
-    UiVar *var;
-    UCX_MAP_FOREACH(key, var, i) {
-        uic_save_var(var);
-        if(var->from) {
-            // restore old root bound var val
-            var->from->value = var->from->orig_val;
-            var->from->orig_val = NULL;
-            // copy
-            uic_copy_binding(var, var->from, FALSE);
+    UiContext *docctx = ui_document_context(document);
+    uic_context_unbind_vars(docctx); // unbind all doc/subdoc vars from the parent
+}
+
+void uic_context_detach_all(UiContext *ctx) {
+    UcxList *ls = ucx_list_clone(ctx->documents, NULL, NULL);
+    UCX_FOREACH(elm, ls) {
+        ctx->detach_document2(ctx, elm->data);
+    }
+    ucx_list_free(ls);
+}
+
+static UiVar* ctx_getvar(UiContext *ctx, UcxKey key) {
+    UiVar *var = ucx_map_get(ctx->vars, key);
+    if(!var) {
+        UCX_FOREACH(elm, ctx->documents) {
+            UiContext *subctx = ui_document_context(elm->data);
+            var = ctx_getvar(subctx, key);
+            if(var) {
+                break;
+            }
         }
-        uic_unbind_var(var);
     }
+    return var;
 }
 
 UiVar* uic_get_var(UiContext *ctx, char *name) {
-    // check document variables first
-    UiVar *var = NULL;
-    UiContext *doc_ctx = ui_document_context(ctx->document);
-    if(doc_ctx) {
-        var = uic_get_var(doc_ctx, name);
-    }
-    
-    // check variables of this context
-    if(!var) {
-        var = ucx_map_cstr_get(ctx->vars, name);
-    }
-    
-    return var;
+    UcxKey key = ucx_key(name, strlen(name));
+    return ctx_getvar(ctx, key);
 }
 
 UiVar* uic_create_var(UiContext *ctx, char *name, UiVarType type) {
-    // check if this context has a var with this name
-    // otherweise add it to the bound map
-    UiVar *cv = ucx_map_cstr_get(ctx->vars, name);
-    if(cv) {
-        if(cv->type == type) {
-            return cv;
+    UiVar *var = uic_get_var(ctx, name);
+    if(var) {
+        if(var->type == type) {
+            return var;
         } else {
-            fprintf(stderr, "UiError: var '%s' already exists with different type\n", name);
-        }
-    }
-    
-    UiVar *var;
-    if(ctx->bound) {
-        var = ucx_map_cstr_get(ctx->bound, name);
-        if(var) {
-            if(var->type != type) {
-                fprintf(stderr, "UiError: var '%s' already bound with different type\n", name);
-            } else {
-                return var;
-            }
+            fprintf(stderr, "UiError: var '%s' already bound with different type\n", name);
         }
     }
     
-    // create var and add it to the bound map
-    // this map contains vars that are created by widgets
-    // the vars can later be moved to subdocuments
     var = ui_malloc(ctx, sizeof(UiVar));
     var->type = type;
     var->value = uic_create_value(ctx, type);
-    var->orig_val = NULL;
-    var->from = NULL; // not connected to a doc var
-    
-    if(!ctx->bound) {
-        ctx->bound = ucx_map_new_a(ctx->mempool->allocator, 16);
+    var->from = NULL;
+    var->from_ctx = ctx;
+
+    if(!ctx->vars_unbound) {
+        ctx->vars_unbound = ucx_map_new_a(ctx->mempool->allocator, 16);
     }
-    ucx_map_cstr_put(ctx->bound, name, var);
-    
-    // if a subdocument has a variable with this name, we must copy
-    // the binding to the doc var
-    UiContext *doc_ctx = ui_document_context(ctx->document);
-    if(doc_ctx) {
-        UiVar *docvar = uic_get_var(doc_ctx, name);
-        if(docvar) {
-            var->orig_val = var->value;
-            var->value = docvar->value;
-            docvar->from = var;
-        }
-    }
+    ucx_map_cstr_put(ctx->vars_unbound, name, var);
     
     return var;
 }
@@ -230,11 +217,6 @@
 }
 
 void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) {
-    if(copytodoc && from->from) {
-        fprintf(stderr, "UiError: var already connected to a document\n");
-        return;
-    }
-    
     // check type
     if(from->type != to->type) {
         fprintf(stderr, "UI Error: var has incompatible type.\n");
@@ -245,9 +227,7 @@
     // update var
     if(copytodoc) {
         to->from = from;
-    
-        from->orig_val = from->value;
-        from->value = to->value;
+        to->from_ctx = from->from_ctx;
     }
     
     // copy binding
@@ -311,8 +291,9 @@
     }
 }
 
-void uic_save_var(UiVar *var) {
+void uic_save_var2(UiVar *var) {
     switch(var->type) {
+        case UI_VAR_SPECIAL: break;
         case UI_VAR_INTEGER: uic_int_save(var->value); break;
         case UI_VAR_DOUBLE: uic_double_save(var->value); break;
         case UI_VAR_STRING: uic_string_save(var->value); break;
@@ -334,8 +315,14 @@
 }
 
 void uic_reg_var(UiContext *ctx, char *name, UiVarType type, void *value) {
-    UiContext *rootctx = uic_root_context(ctx);
-    
+    // TODO: do we need/want this? Why adding vars to a context after
+    // widgets reference these? Workarounds:
+    // 1. add vars to ctx before creating ui
+    // 2. create ui, create new document with vars, attach doc
+    // also it would be possible to create a function, that scans unbound vars
+    // and connects them to available vars
+    /*
+    UiContext *rootctx = uic_root_context(ctx); 
     UiVar *b = NULL;
     if(rootctx->bound) {
         // some widgets are already bound to some vars
@@ -350,33 +337,46 @@
             }
         }
     }
+    */
     
     // create new var and add it to doc's vars
     UiVar *var = ui_malloc(ctx, sizeof(UiVar));
-    var->from = NULL;
-    var->orig_val = NULL;
     var->type = type;
     var->value = value;
+    var->from = NULL;
+    var->from_ctx = ctx;
     size_t oldcount = ctx->vars->count;
     ucx_map_cstr_put(ctx->vars, name, var);
     if(ctx->vars->count != oldcount + 1) {
         fprintf(stderr, "UiError: var '%s' already exists\n", name);
     }
     
+    // TODO: remove?
     // a widget is already bound to a var with this name
     // copy the binding (like uic_context_set_document)
+    /*
     if(b) {
         uic_copy_binding(b, var, TRUE);
     }
+    */
 }
 
 void uic_remove_bound_var(UiContext *ctx, UiVar *var) {
     // TODO: implement
+    printf("TODO: implement uic_remove_bound_var\n");
 }
 
 
 // public API
 
+void ui_attach_document(UiContext *ctx, void *document) {
+    uic_context_attach_document(ctx, document);
+}
+
+void ui_detach_document2(UiContext *ctx, void *document) {
+    uic_context_detach_document2(ctx, document);
+}
+
 void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata) {
     ctx->close_callback = fnc;
     ctx->close_data = udata;

mercurial