ref: 44e888f7dad3d813e6bea2b50ed1f1d46eee6796
parent: 864d763e906cf39fd322a6a83aa5d4641d86c37b
author: cancel <cancel@cancel.fm>
date: Sun Jan 5 19:48:15 EST 2020
Change Qmenu items allocation to be dynamic
--- a/term_util.c
+++ b/term_util.c
@@ -68,11 +68,17 @@
Qblock qblock;
};
+struct Qmenu_item_extra {
+ U8 owns_string : 1;
+ U8 is_spacer : 1;
+};
+
struct Qmenu {
Qblock qblock;
MENU* ncurses_menu;
- ITEM* ncurses_items[32];
+ ITEM** ncurses_items;
Usz items_count;
+ Usz items_cap;
ITEM* initial_item;
int id;
};
@@ -301,8 +307,9 @@
Qmenu* qm = (Qmenu*)malloc(sizeof(Qmenu));
qblock_init(&qm->qblock, Qblock_type_qmenu);
qm->ncurses_menu = NULL;
- qm->ncurses_items[0] = NULL;
+ qm->ncurses_items = NULL;
qm->items_count = 0;
+ qm->items_cap = 0;
qm->initial_item = NULL;
qm->id = id;
return qm;
@@ -309,24 +316,68 @@
}
void qmenu_destroy(Qmenu* qm) { qmenu_free(qm); }
int qmenu_id(Qmenu const* qm) { return qm->id; }
+static ORCA_FORCE_NO_INLINE void
+qmenu_allocitems(Qmenu* qm, Usz count, ITEM*** out_items,
+ struct Qmenu_item_extra** out_extras) {
+ Usz old_count = qm->items_count;
+ // Add 1 for the extra null terminator guy
+ Usz new_count = old_count + count + 1;
+ Usz items_cap = qm->items_cap;
+ ITEM** items = qm->ncurses_items;
+ if (new_count > items_cap) {
+ // todo overflow check, realloc fail check
+ Usz old_cap = items_cap;
+ Usz new_cap = count < 16 ? 16 : orca_round_up_power2(new_count);
+ Usz new_size = new_cap * (sizeof(ITEM*) + sizeof(struct Qmenu_item_extra));
+ ITEM** new_items = (ITEM**)realloc(items, new_size);
+ if (!new_items)
+ exit(1);
+ items = new_items;
+ items_cap = new_cap;
+ // Move old extras data to new position
+ Usz old_extras_offset = sizeof(ITEM*) * old_cap;
+ Usz new_extras_offset = sizeof(ITEM*) * new_cap;
+ Usz old_extras_size = sizeof(struct Qmenu_item_extra) * old_count;
+ memmove((char*)items + new_extras_offset, (char*)items + old_extras_offset,
+ old_extras_size);
+ qm->ncurses_items = new_items;
+ qm->items_cap = new_cap;
+ }
+ // Not using new_count here in order to leave an extra 1 for the null
+ // terminator as required by ncurses.
+ qm->items_count = old_count + count;
+ Usz extras_offset = sizeof(ITEM*) * items_cap;
+ *out_items = items + old_count;
+ *out_extras =
+ (struct Qmenu_item_extra*)((char*)items + extras_offset) + old_count;
+}
+ORCA_FORCE_STATIC_INLINE struct Qmenu_item_extra*
+qmenu_item_extras_ptr(Qmenu* qm) {
+ Usz offset = sizeof(ITEM*) * qm->items_cap;
+ return (struct Qmenu_item_extra*)((char*)qm->ncurses_items + offset);
+}
void qmenu_set_title(Qmenu* qm, char const* title) {
qblock_set_title(&qm->qblock, title);
}
void qmenu_add_choice(Qmenu* qm, int id, char const* text) {
assert(id >= Qmenu_first_valid_user_choice_id);
- ITEM* item = new_item(text, NULL);
- set_item_userptr(item, (void*)(intptr_t)(id));
- qm->ncurses_items[qm->items_count] = item;
- ++qm->items_count;
- qm->ncurses_items[qm->items_count] = NULL;
+ ITEM** items;
+ struct Qmenu_item_extra* extras;
+ qmenu_allocitems(qm, 1, &items, &extras);
+ items[0] = new_item(text, NULL);
+ set_item_userptr(items[0], (void*)(intptr_t)(id));
+ extras[0].owns_string = false;
+ extras[0].is_spacer = false;
}
void qmenu_add_spacer(Qmenu* qm) {
- ITEM* item = new_item(" ", NULL);
- item_opts_off(item, O_SELECTABLE);
- set_item_userptr(item, (void*)(intptr_t)Qmenu_spacer_unique_id);
- qm->ncurses_items[qm->items_count] = item;
- ++qm->items_count;
- qm->ncurses_items[qm->items_count] = NULL;
+ ITEM** items;
+ struct Qmenu_item_extra* extras;
+ qmenu_allocitems(qm, 1, &items, &extras);
+ items[0] = new_item(" ", NULL);
+ item_opts_off(items[0], O_SELECTABLE);
+ set_item_userptr(items[0], (void*)(intptr_t)Qmenu_spacer_unique_id);
+ extras[0].owns_string = false;
+ extras[0].is_spacer = true;
}
void qmenu_set_current_item(Qmenu* qm, int id) {
ITEM* found = NULL;
@@ -356,8 +407,12 @@
// spacer item. This will probably only ever occur as a programming error,
// but we should try to avoid having to deal with qmenu_push_to_nav()
// returning a non-ignorable error for now.
- if (qm->ncurses_items[0] == NULL)
+ if (qm->items_count == 0)
qmenu_add_spacer(qm);
+ // Allocating items always leaves an extra available item at the end. This is
+ // so we can assign a NULL to it here, since ncurses requires the array to be
+ // null terminated instead of using a count.
+ qm->ncurses_items[qm->items_count] = NULL;
qm->ncurses_menu = new_menu(qm->ncurses_items);
set_menu_mark(qm->ncurses_menu, " > ");
set_menu_fore(qm->ncurses_menu, A_BOLD);
@@ -386,6 +441,7 @@
for (Usz i = 0; i < qm->items_count; ++i) {
free_item(qm->ncurses_items[i]);
}
+ free(qm->ncurses_items);
free(qm);
}