--- 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);