--- a/ui/win32/menu.c Tue Jan 27 12:23:40 2026 +0100 +++ b/ui/win32/menu.c Tue Jan 27 13:08:30 2026 +0100 @@ -55,7 +55,6 @@ menu = next && next->type == UI_MENU ? (UiMenu*)next : NULL; } - return hMenu; } @@ -259,10 +258,106 @@ } } +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 ui_add_menu_list(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + UiMenuItemList *il = (UiMenuItemList*)item; + const CxAllocator *a = obj->ctx->allocator; + + UiVar *var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); + if (!var) { + return; + } + + UiActiveMenuItemList *ls = cxMalloc( + a, + sizeof(UiActiveMenuItemList)); + ls->object = obj; + ls->menu = parent; + ls->command_ids = cxArrayListCreate(a, sizeof(uint64_t), 16); + ls->index = pos; + ls->getvalue = il->getvalue; + ls->callback = il->callback; + ls->userdata = il->userdata; + ls->addseparator = il->addseparator; + ls->var = var; + + UiList *list = ls->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(ls->var->from_ctx->mp->allocator, CX_STORE_POINTERS); + list->obj = bindings; + } + cxListAdd(bindings, ls); + + // The destruction of the toplevel obj must remove the menulist binding + uic_context_add_destructor(obj->ctx, menuitem_list_remove_binding, ls); + + ui_update_menuitem_list(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(UiActiveMenuItemList *list) { + UiObject *obj = list->object; + + UiList *ls; + if(list->var && list->var->value) { + ls = list->var->value; + } else { + return; + } + + CxIterator i = cxListIterator(list->command_ids); + cx_foreach(uint64_t *, id, i) { + DeleteMenu(list->menu, *id, MF_BYCOMMAND); + } + cxListClear(list->command_ids); // TODO: we could reuse some of the ids + + ui_getvaluefunc getvalue = list->getvalue; + void* elm = ui_list_first(ls); + + int pos = list->index; + while(elm) { + char *label = (char*) (getvalue ? getvalue(elm, 0) : elm); + if (!label) { + label = ""; + } + + uint64_t id = ++obj->ctx->command_id_counter; + InsertMenu(list->menu, pos++, MF_STRING, id, label); + cxListAdd(list->command_ids, &id); + + elm = ui_list_next(ls); + } +} + + void ui_add_menu_checklist(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { }