shithub: orca

Download patch

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