--- a/ui/gtk/container.c Mon Dec 30 12:50:52 2024 +0100 +++ b/ui/gtk/container.c Mon Dec 30 19:48:05 2024 +0100 @@ -946,6 +946,126 @@ +/* -------------------- ItemList Container -------------------- */ + +static void remove_item(void *data, void *item) { + UiGtkItemListContainer *ct = data; + UiObject *obj = item; + if(ct->remove_items) { + BOX_REMOVE(ct->widget, obj->widget); + } + uic_object_destroy(obj); +} + +static void update_itemlist(UiList *list, int c) { + UiGtkItemListContainer *ct = list->obj; + + CxMap *new_items = cxHashMapCreateSimple(CX_STORE_POINTERS); + new_items->collection.advanced_destructor = remove_item; + new_items->collection.destructor_data = ct; + + // only create new widgets for new elements, so at first we have + // to find which elements are new + // check which elements in the list are already in the container + void *elm = list->first(list); + int j = 0; + while(elm) { + CxHashKey key = cx_hash_key(&elm, sizeof(void*)); + UiObject *item_obj = cxMapRemoveAndGet(ct->current_items, key); + if(item_obj) { + g_object_ref(G_OBJECT(item_obj->widget)); + BOX_REMOVE(ct->widget, item_obj->widget); + cxMapPut(new_items, key, item_obj); + } + elm = list->next(list); + j++; + } + + // ct->current_items only contains elements, that are not in the list + cxMapDestroy(ct->current_items); // calls destructor remove_item + ct->current_items = new_items; + + // add all items + int index = 0; + elm = list->first(list); + while(elm) { + CxHashKey key = cx_hash_key(&elm, sizeof(void*)); + UiObject *item_obj = cxMapGet(ct->current_items, key); + if(item_obj) { + // re-add previously created widget + ui_box_container_add(ct->container, item_obj->widget, FALSE); + } else { + // create new widget and object for this list element + CxMempool *mp = cxBasicMempoolCreate(256); + const CxAllocator *a = mp->allocator; + UiObject *obj = cxCalloc(a, 1, sizeof(UiObject)); + obj->ctx = uic_context(obj, mp); + obj->window = NULL; + obj->widget = ui_subcontainer_create( + ct->subcontainer, + obj, + ct->spacing, + ct->columnspacing, + ct->rowspacing, + ct->margin); + ui_box_container_add(ct->container, obj->widget, FALSE); + if(ct->create_ui) { + ct->create_ui(obj, index, elm, ct->userdata); + } + cxMapPut(new_items, key, obj); + } + elm = list->next(list); + index++; + } +} + +static void destroy_itemlist_container(GtkWidget *w, UiGtkItemListContainer *container) { + container->remove_items = FALSE; + cxMapDestroy(container->current_items); + free(container); +} + +UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs args) { + UiObject *current = uic_current_obj(obj); + UiContainer *ct = current->container; + UI_APPLY_LAYOUT1(current, args); + + GtkWidget *box = args.container == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args.spacing) : ui_gtk_hbox_new(args.spacing); + ui_set_name_and_style(box, args.name, args.style_class); + GtkWidget *widget = args.margin > 0 ? ui_box_set_margin(box, args.margin) : box; + ct->add(ct, widget, TRUE); + + UiGtkItemListContainer *container = malloc(sizeof(UiGtkItemListContainer)); + container->parent = obj; + container->widget = box; + container->container = ui_box_container(current, box, args.container); + container->create_ui = args.create_ui; + container->userdata = args.userdata; + container->subcontainer = args.subcontainer; + container->current_items = cxHashMapCreateSimple(CX_STORE_POINTERS); + container->current_items->collection.advanced_destructor = remove_item; + container->current_items->collection.destructor_data = container; + container->margin = args.sub_margin; + container->spacing = args.sub_spacing; + container->columnspacing = args.sub_columnspacing; + container->rowspacing = args.sub_rowspacing; + container->remove_items = TRUE; + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_LIST); + if(var) { + UiList *list = var->value; + list->obj = container; + list->update = update_itemlist; + update_itemlist(list, 0); + } + g_signal_connect( + box, + "destroy", + G_CALLBACK(destroy_itemlist_container), + container); + + return box; +}