7 days ago
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