--- a/ui/common/context.c Sun Dec 07 20:00:33 2025 +0100 +++ b/ui/common/context.c Sat Dec 13 15:58:58 2025 +0100 @@ -64,8 +64,8 @@ ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16); ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, CX_STORE_POINTERS); - ctx->group_widgets = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, sizeof(UiGroupWidget)); - ctx->groups = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32); + ctx->state_widgets = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, sizeof(UiStateWidget)); + ctx->states = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32); ctx->attach_document = uic_context_attach_document; ctx->detach_document2 = uic_context_detach_document; @@ -92,11 +92,17 @@ } void uic_context_prepare_close(UiContext *ctx) { - cxListClear(ctx->groups); - cxListClear(ctx->group_widgets); + cxListClear(ctx->states); + cxListClear(ctx->state_widgets); } void uic_context_attach_document(UiContext *ctx, void *document) { + if(ctx->single_document_mode) { + if(ctx->document) { + uic_context_detach_document(ctx, ctx->document); + } + } + cxListAdd(ctx->documents, document); ctx->document = document; @@ -114,7 +120,7 @@ UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key); if(docvar) { // bind var to document var - uic_copy_binding(var, docvar, TRUE); + uic_copy_var_binding(var, docvar, TRUE); cxIteratorFlagRemoval(i); } } @@ -124,16 +130,19 @@ } static void uic_context_unbind_vars(UiContext *ctx) { + ui_onchange_events_enable(FALSE); CxMapIterator mi = cxMapIterator(ctx->vars); cx_foreach(CxMapEntry*, entry, mi) { + printf("detach %.*s\n", (int)entry->key->len, (char*)entry->key->data); UiVar *var = entry->value; // var->from && var->from_ctx && var->from_ctx != ctx uic_save_var(var); if(var->from) { - uic_copy_binding(var, var->from, FALSE); + uic_copy_var_binding(var, var->from, FALSE); cxMapPut(var->from->from_ctx->vars, *entry->key, var->from); var->from = NULL; } + uic_unbind_var(var); } if(ctx->documents) { @@ -143,6 +152,8 @@ uic_context_unbind_vars(subctx); } } + + ui_onchange_events_enable(TRUE); } void uic_context_detach_document(UiContext *ctx, void *document) { @@ -197,6 +208,14 @@ return ctx_getvar(ctx, key); } +UiVar* uic_get_var_t(UiContext *ctx,const char *name, UiVarType type) { + UiVar *var = uic_get_var(ctx, name); + if(var && var->type == type) { + return var; + } + return NULL; +} + UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) { UiVar *var = uic_get_var(ctx, name); if(var) { @@ -213,8 +232,9 @@ var->original_value = NULL; var->from = NULL; var->from_ctx = ctx; - - cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var); + var->bound = FALSE; + + cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var); // TODO: use another destructor that cleans the value (UiString free, UiText destroy, ...) cxMapPut(ctx->vars, name, var); @@ -266,6 +286,40 @@ return val; } +// destroys a value, that was created by uic_create_value +void uic_destroy_value(UiContext *ctx, UiVarType type, void *value) { + switch(type) { + default: { + ui_free(ctx, value); + break; + } + case UI_VAR_SPECIAL: break; + case UI_VAR_STRING: { + UiString *s = value; + if(s->value.free) { + s->value.free(s->value.ptr); + } + ui_free(ctx, value); + } + case UI_VAR_TEXT: { + UiText *t = value; + if(t->value.free) { + t->value.free(t->value.ptr); + t->value.free = NULL; + t->value.ptr = NULL; + } + if(t->destroy) { + t->destroy(t); + } + ui_free(ctx, value); + } + case UI_VAR_LIST: { + ui_list_free(ctx, value); + break; + } + } +} + UiVar* uic_widget_var(UiContext *toplevel, UiContext *current, void *value, const char *varname, UiVarType type) { if (value) { @@ -278,7 +332,7 @@ } -void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc) { +void uic_copy_var_binding(UiVar *from, UiVar *to, UiBool copytodoc) { // check type if(from->type != to->type) { fprintf(stderr, "UI Error: var has incompatible type.\n"); @@ -301,32 +355,36 @@ } } - ui_setop_enable(TRUE); + uic_copy_value_binding(from->type, fromvalue, tovalue); +} + +void uic_copy_value_binding(UiVarType type, void *from, void *to) { + ui_setop_enable(TRUE); // copy binding - // we don't copy the observer, because the from var has never one - switch(from->type) { + // we don't copy the observer, because the from value never has oberservers + switch(type) { default: fprintf(stderr, "uic_copy_binding: wtf!\n"); break; case UI_VAR_SPECIAL: break; case UI_VAR_INTEGER: { - UiInteger *f = fromvalue; - UiInteger *t = tovalue; + UiInteger *f = from; + UiInteger *t = to; if(!f->obj) break; uic_int_copy(f, t); t->set(t, t->value); break; } case UI_VAR_DOUBLE: { - UiDouble *f = fromvalue; - UiDouble *t = tovalue; + UiDouble *f = from; + UiDouble *t = to; if(!f->obj) break; uic_double_copy(f, t); t->set(t, t->value); break; } case UI_VAR_STRING: { - UiString *f = fromvalue; - UiString *t = tovalue; + UiString *f = from; + UiString *t = to; if(!f->obj) break; uic_string_copy(f, t); char *tvalue = t->value.ptr ? t->value.ptr : ""; @@ -335,23 +393,23 @@ break; } case UI_VAR_TEXT: { - UiText *f = fromvalue; - UiText *t = tovalue; + UiText *f = from; + UiText *t = to; if(!f->obj) break; uic_text_copy(f, t); t->restore(t); break; } case UI_VAR_LIST: { - UiList *f = fromvalue; - UiList *t = tovalue; + UiList *f = from; + UiList *t = to; uic_list_copy(f, t); ui_list_update(t); break; } case UI_VAR_RANGE: { - UiRange *f = fromvalue; - UiRange *t = tovalue; + UiRange *f = from; + UiRange *t = to; if(!f->obj) break; uic_range_copy(f, t); t->setextent(t, t->extent); @@ -360,8 +418,8 @@ break; } case UI_VAR_GENERIC: { - UiGeneric *f = fromvalue; - UiGeneric *t = tovalue; + UiGeneric *f = from; + UiGeneric *t = to; if(!f->obj) break; uic_generic_copy(f, t); t->set(t, t->value, t->type); @@ -398,59 +456,56 @@ } } +const char *uic_type2str(UiVarType type) { + switch(type) { + default: return ""; + case UI_VAR_INTEGER: return "int"; + case UI_VAR_DOUBLE: return "double"; + case UI_VAR_STRING: return "string"; + case UI_VAR_TEXT: return "text"; + case UI_VAR_LIST: return "list"; + case UI_VAR_RANGE: return "range"; + case UI_VAR_GENERIC: return "generic"; + } +} + void uic_reg_var(UiContext *ctx, const char *name, UiVarType type, void *value) { - // 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 - b = ucx_map_cstr_get(rootctx->bound, name); - if(b) { - // a widget is bound to a var with this name - // if ctx is the root context we can remove the var from bound - // because set_doc or detach can't fuck things up - if(ctx == rootctx) { - ucx_map_cstr_remove(ctx->bound, name); - // TODO: free stuff - } + UiVar *var = cxMapGet(ctx->vars, name); + if(!var) { + // create new var and add it to the context var map + var = ui_malloc(ctx, sizeof(UiVar)); + cxMapPut(ctx->vars, name, var); + } else { + // override var with new value + if(var->type != type) { + fprintf(stderr, "Error: var %s type mismatch: %s - %s\n", name, uic_type2str(var->type), uic_type2str(type)); + return; } + if(var->bound) { + fprintf(stderr, "Error: var %s already bound\n", name); + return; + } + UiInteger *prev_value = var->value; + uic_copy_value_binding(type, prev_value, value); + uic_destroy_value(var->from_ctx, var->type, var->value); } - */ - // create new var and add it to doc's vars - UiVar *var = ui_malloc(ctx, sizeof(UiVar)); var->type = type; var->value = value; var->from = NULL; var->from_ctx = ctx; - size_t oldcount = cxMapSize(ctx->vars); - cxMapPut(ctx->vars, name, var); - if(cxMapSize(ctx->vars) != 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); - } - */ + var->bound = TRUE; } -void uic_remove_bound_var(UiContext *ctx, UiVar *var) { - // TODO +// public API + +void* ui_context_get_document(UiContext *ctx) { + return ctx->document; } - -// public API +void ui_context_single_attachment_mode(UiContext *ctx, UiBool enable) { + ctx->single_document_mode = enable; +} void ui_attach_document(UiContext *ctx, void *document) { uic_context_attach_document(ctx, document); @@ -478,48 +533,48 @@ } -void ui_set_group(UiContext *ctx, int group) { - if(!cxListIndexValid(ctx->groups, cxListFind(ctx->groups, &group))) { - cxListAdd(ctx->groups, &group); +void ui_set_state(UiContext *ctx, int state) { + if(!cxListIndexValid(ctx->states, cxListFind(ctx->states, &state))) { + cxListAdd(ctx->states, &state); } // enable/disable group widgets - uic_check_group_widgets(ctx); + uic_check_state_widgets(ctx); } -void ui_unset_group(UiContext *ctx, int group) { - int i = cxListFind(ctx->groups, &group); +void ui_unset_state(UiContext *ctx, int state) { + int i = cxListFind(ctx->states, &state); if(i != -1) { - cxListRemove(ctx->groups, i); + cxListRemove(ctx->states, i); } // enable/disable group widgets - uic_check_group_widgets(ctx); + uic_check_state_widgets(ctx); } -int* ui_active_groups(UiContext *ctx, int *ngroups) { - *ngroups = cxListSize(ctx->groups); - return cxListAt(ctx->groups, 0); +int* ui_active_states(UiContext *ctx, int *nstates) { + *nstates = cxListSize(ctx->states); + return cxListAt(ctx->states, 0); } -void uic_check_group_widgets(UiContext *ctx) { +void uic_check_state_widgets(UiContext *ctx) { int ngroups = 0; - int *groups = ui_active_groups(ctx, &ngroups); + int *groups = ui_active_states(ctx, &ngroups); - CxIterator i = cxListIterator(ctx->group_widgets); - cx_foreach(UiGroupWidget *, gw, i) { - char *check = calloc(1, gw->numgroups); + CxIterator i = cxListIterator(ctx->state_widgets); + cx_foreach(UiStateWidget *, gw, i) { + char *check = calloc(1, gw->numstates); for(int i=0;i<ngroups;i++) { - for(int k=0;k<gw->numgroups;k++) { - if(groups[i] == gw->groups[k]) { + for(int k=0;k<gw->numstates;k++) { + if(groups[i] == gw->states[k]) { check[k] = 1; } } } int enable = 1; - for(int i=0;i<gw->numgroups;i++) { + for(int i=0;i<gw->numstates;i++) { if(check[i] == 0) { enable = 0; break; @@ -530,70 +585,70 @@ } } -void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) { +void ui_widget_set_states(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) { if(enable == NULL) { enable = (ui_enablefunc)ui_set_enabled; } - // get groups - CxList *groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16); + // get states + CxList *states = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16); va_list ap; va_start(ap, enable); - int group; - while((group = va_arg(ap, int)) != -1) { - cxListAdd(groups, &group); + int state; + while((state = va_arg(ap, int)) != -1) { + cxListAdd(states, &state); } va_end(ap); - uic_add_group_widget(ctx, widget, enable, groups); + uic_add_state_widget(ctx, widget, enable, states); - cxListFree(groups); + cxListFree(states); } -void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *groups, int ngroups) { +void ui_widget_set_states2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *states, int nstates) { if(enable == NULL) { enable = (ui_enablefunc)ui_set_enabled; } - CxList *ls = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), ngroups); - for(int i=0;i<ngroups;i++) { - cxListAdd(ls, groups+i); + CxList *ls = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), nstates); + for(int i=0;i<nstates;i++) { + cxListAdd(ls, states+i); } - uic_add_group_widget(ctx, widget, enable, ls); + uic_add_state_widget(ctx, widget, enable, ls); cxListFree(ls); } void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, const int *states, int nstates) { - ui_widget_set_groups2(ctx, widget, (ui_enablefunc)ui_set_visible, states, nstates); + ui_widget_set_states2(ctx, widget, (ui_enablefunc)ui_set_visible, states, nstates); } -size_t uic_group_array_size(const int *groups) { +size_t uic_state_array_size(const int *states) { int i; - for(i=0;groups[i] >= 0;i++) { } + for(i=0;states[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_state_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *states) { + uic_add_state_widget_i(ctx, widget, enable, cxListAt(states, 0), cxListSize(states)); } -void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups) { +void uic_add_state_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *states, size_t numstates) { const CxAllocator *a = ctx->allocator; - UiGroupWidget gw; + UiStateWidget gw; gw.widget = widget; gw.enable = enable; - gw.numgroups = numgroups; - gw.groups = cxCalloc(a, numgroups, sizeof(int)); + gw.numstates = numstates; + gw.states = cxCalloc(a, numstates, sizeof(int)); - // copy groups - if(groups) { - memcpy(gw.groups, groups, gw.numgroups * sizeof(int)); + // copy states + if(states) { + memcpy(gw.states, states, gw.numstates * sizeof(int)); } - cxListAdd(ctx->group_widgets, &gw); + cxListAdd(ctx->state_widgets, &gw); } -void uic_remove_group_widget(UiContext *ctx, void *widget) { - (void)cxListFindRemove(ctx->group_widgets, widget); +void uic_remove_state_widget(UiContext *ctx, void *widget) { + (void)cxListFindRemove(ctx->state_widgets, widget); } UIEXPORT void *ui_allocator(UiContext *ctx) { @@ -638,3 +693,33 @@ void ui_set_destructor(void *mem, ui_destructor_func destr) { cxMempoolSetDestructor(mem, (cx_destructor_func)destr); } + +UiInteger* ui_get_int_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_INTEGER); + return var ? var->value : NULL; +} + +UiDouble* ui_get_double_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_DOUBLE); + return var ? var->value : NULL; +} + +UiString* ui_get_string_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_STRING); + return var ? var->value : NULL; +} + +UiText* ui_get_text_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_TEXT); + return var ? var->value : NULL; +} + +UiRange* ui_get_range_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_RANGE); + return var ? var->value : NULL; +} + +UiGeneric* ui_get_generic_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_GENERIC); + return var ? var->value : NULL; +}