@@ 2,7 2,8 @@
#include "oso.h"
#include <ctype.h>
#include <form.h>
-#include <menu.h>
+
+ORCA_NOINLINE static void qmenu_reprint(Qmenu *qm);
void term_util_init_colors() {
if (has_colors()) {
@@ 38,20 39,18 @@ struct Qmsg {
Qmsg_dismiss_mode dismiss_mode;
};
-struct Qmenu_item_extra {
- int user_id;
+typedef struct Qmenu_item {
+ char const *text;
+ int id;
U8 owns_string : 1, is_spacer : 1;
-};
+} Qmenu_item;
struct Qmenu {
Qblock qblock;
- MENU *ncurses_menu;
- ITEM **ncurses_items;
+ Qmenu_item *items;
Usz items_count, items_cap;
- ITEM *initial_item;
- int id;
- // Flag for right-padding hack. Temp until we do our own menus
- U8 has_submenu_item : 1;
+ int current_item, id;
+ U8 needs_reprint : 1, is_frontmost : 1;
};
struct Qform {
@@ 175,18 174,26 @@ bool qnav_draw(void) {
getmaxyx(stdscr, term_h, term_w);
for (Usz i = 0; i < qnav_stack.count; ++i) {
Qblock *qb = qnav_stack.blocks[i];
- if (qnav_stack.occlusion_dirty) {
- bool is_frontmost = i == qnav_stack.count - 1;
+ bool is_frontmost = i == qnav_stack.count - 1;
+ if (qnav_stack.occlusion_dirty)
qblock_print_frame(qb, is_frontmost);
- switch (qb->tag) {
- case Qblock_type_qmsg:
- break;
- case Qblock_type_qmenu:
- qmenu_set_displayed_active(qmenu_of(qb), is_frontmost);
- break;
- case Qblock_type_qform:
- break;
+ switch (qb->tag) {
+ case Qblock_type_qmsg:
+ break;
+ case Qblock_type_qmenu: {
+ Qmenu *qm = qmenu_of(qb);
+ if (qm->is_frontmost != is_frontmost) {
+ qm->is_frontmost = is_frontmost;
+ qm->needs_reprint = 1;
+ }
+ if (qm->needs_reprint) {
+ qmenu_reprint(qm);
+ qm->needs_reprint = 0;
}
+ break;
+ }
+ case Qblock_type_qform:
+ break;
}
touchwin(qb->outer_window); // here? or after continue?
if (term_h < 1 || term_w < 1)
@@ 222,7 229,7 @@ void qblock_print_title(Qblock *qb, char const *title, int attr) {
wattr_get(qb->outer_window, &attrs, &pair, NULL);
wattrset(qb->outer_window, attr);
waddch(qb->outer_window, ' ');
- wprintw(qb->outer_window, title);
+ waddstr(qb->outer_window, title);
waddch(qb->outer_window, ' ');
wattr_set(qb->outer_window, attrs, pair, NULL);
}
@@ 343,91 350,65 @@ Qmsg *qmsg_of(Qblock *qb) { return ORCA_CONTAINER_OF(qb, Qmsg, qblock); }
Qmenu *qmenu_create(int id) {
Qmenu *qm = (Qmenu *)malloc(sizeof(Qmenu));
qblock_init(&qm->qblock, Qblock_type_qmenu);
- qm->ncurses_menu = NULL;
- qm->ncurses_items = NULL;
+ qm->items = NULL;
qm->items_count = 0;
qm->items_cap = 0;
- qm->initial_item = NULL;
+ qm->current_item = 0;
qm->id = id;
- qm->has_submenu_item = 0;
+ qm->needs_reprint = 1;
+ qm->is_frontmost = 0;
return qm;
}
void qmenu_destroy(Qmenu *qm) { qmenu_free(qm); }
int qmenu_id(Qmenu const *qm) { return qm->id; }
-static ORCA_NOINLINE void
-qmenu_allocitems(Qmenu *qm, Usz count, Usz *out_idx, ITEM ***out_items,
- struct Qmenu_item_extra **out_extras) {
+static ORCA_NOINLINE Qmenu_item *qmenu_allocitems(Qmenu *qm, Usz count) {
Usz old_count = qm->items_count;
- // Add 1 for the extra null terminator guy
- Usz new_count = old_count + count + 1;
+ if (old_count > SIZE_MAX - count) // overflow
+ exit(1);
+ Usz new_count = old_count + count;
Usz items_cap = qm->items_cap;
- ITEM **items = qm->ncurses_items;
+ Qmenu_item *items = qm->items;
if (new_count > items_cap) {
// todo overflow check, realloc fail check
- Usz old_cap = items_cap;
Usz new_cap = new_count < 32 ? 32 : 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);
+ Usz new_size = new_cap * sizeof(Qmenu_item);
+ Qmenu_item *new_items = (Qmenu_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 = 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_idx = old_count;
- *out_items = items + old_count;
- *out_extras =
- (struct Qmenu_item_extra *)((char *)items + extras_offset) + old_count;
-}
-ORCA_FORCEINLINE static 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);
-}
-// Get the curses menu item user pointer out, turn it to an int, and use it as
-// an index into the 'extras' arrays.
-ORCA_FORCEINLINE static struct Qmenu_item_extra *
-qmenu_itemextra(struct Qmenu_item_extra *extras, ITEM *item) {
- return extras + (int)(intptr_t)(item_userptr(item));
+ qm->items_count = new_count;
+ return items + old_count;
+}
+ORCA_NOINLINE static void qmenu_reprint(Qmenu *qm) {
+ WINDOW *win = qm->qblock.content_window;
+ Qmenu_item *items = qm->items;
+ bool isfront = qm->is_frontmost;
+ werase(win);
+ for (Usz i = 0, n = qm->items_count; i < n; ++i) {
+ bool iscur = items[i].id == qm->current_item;
+ wattrset(win, isfront ? iscur ? A_BOLD : A_NORMAL : A_DIM);
+ wmove(win, (int)i, iscur ? 1 : 3);
+ if (iscur)
+ waddstr(win, "> ");
+ waddstr(win, items[i].text);
+ }
}
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 != 0);
- Usz idx;
- ITEM **items;
- struct Qmenu_item_extra *extras;
- qmenu_allocitems(qm, 1, &idx, &items, &extras);
- items[0] = new_item(text, NULL);
- set_item_userptr(items[0], (void *)(uintptr_t)idx);
- extras[0].user_id = id;
- extras[0].owns_string = false;
- extras[0].is_spacer = false;
-}
-void qmenu_add_submenu(Qmenu *qm, int id, char const *text) {
- assert(id != 0);
- qm->has_submenu_item = true; // don't add +1 right padding to subwindow
- Usz idx;
- ITEM **items;
- struct Qmenu_item_extra *extras;
- qmenu_allocitems(qm, 1, &idx, &items, &extras);
- items[0] = new_item(text, ">");
- set_item_userptr(items[0], (void *)(uintptr_t)idx);
- extras[0].user_id = id;
- extras[0].owns_string = false;
- extras[0].is_spacer = false;
+ Qmenu_item *item = qmenu_allocitems(qm, 1);
+ item->text = text;
+ item->id = id;
+ item->owns_string = false;
+ item->is_spacer = false;
+ if (!qm->current_item)
+ qm->current_item = id;
}
void qmenu_add_printf(Qmenu *qm, int id, char const *fmt, ...) {
va_list ap;
@@ 442,85 423,42 @@ void qmenu_add_printf(Qmenu *qm, int id, char const *fmt, ...) {
va_end(ap);
if (printedsize != textsize)
exit(1); // todo better handling?
- Usz idx;
- ITEM **items;
- struct Qmenu_item_extra *extras;
- qmenu_allocitems(qm, 1, &idx, &items, &extras);
- items[0] = new_item(buffer, NULL);
- set_item_userptr(items[0], (void *)(uintptr_t)idx);
- extras[0].user_id = id;
- extras[0].owns_string = true;
- extras[0].is_spacer = false;
+ Qmenu_item *item = qmenu_allocitems(qm, 1);
+ item->text = buffer;
+ item->id = id;
+ item->owns_string = true;
+ item->is_spacer = false;
+ if (!qm->current_item)
+ qm->current_item = id;
}
void qmenu_add_spacer(Qmenu *qm) {
- Usz idx;
- ITEM **items;
- struct Qmenu_item_extra *extras;
- qmenu_allocitems(qm, 1, &idx, &items, &extras);
- items[0] = new_item(" ", NULL);
- item_opts_off(items[0], O_SELECTABLE);
- set_item_userptr(items[0], (void *)(uintptr_t)idx);
- extras[0].user_id = 0;
- extras[0].owns_string = false;
- extras[0].is_spacer = true;
+ Qmenu_item *item = qmenu_allocitems(qm, 1);
+ item->text = " ";
+ item->id = 0;
+ item->owns_string = false;
+ item->is_spacer = true;
}
void qmenu_set_current_item(Qmenu *qm, int id) {
- ITEM **items = qm->ncurses_items;
- struct Qmenu_item_extra *extras = qmenu_item_extras_ptr(qm);
- ITEM *found = NULL;
- for (Usz i = 0, n = qm->items_count; i < n; i++) {
- ITEM *item = items[i];
- if (qmenu_itemextra(extras, item)->user_id == id) {
- found = item;
- break;
- }
- }
- if (!found)
+ if (qm->current_item == id)
return;
- if (qm->ncurses_menu) {
- set_current_item(qm->ncurses_menu, found);
- } else {
- qm->initial_item = found;
- }
-}
-int qmenu_current_item(Qmenu *qm) {
- ITEM *item = NULL;
- if (qm->ncurses_menu)
- item = current_item(qm->ncurses_menu);
- if (!item)
- item = qm->initial_item;
- if (!item)
- return 0;
- struct Qmenu_item_extra *extras = qmenu_item_extras_ptr(qm);
- return qmenu_itemextra(extras, item)->user_id;
-}
-void qmenu_set_displayed_active(Qmenu *qm, bool active) {
- // Could add a flag in the Qmenu to avoid redundantly changing this stuff.
- set_menu_fore(qm->ncurses_menu, active ? A_BOLD : A_DIM);
- set_menu_back(qm->ncurses_menu, active ? A_NORMAL : A_DIM);
- set_menu_grey(qm->ncurses_menu, active ? A_DIM : A_DIM);
+ qm->current_item = id;
+ qm->needs_reprint = 1;
}
+int qmenu_current_item(Qmenu *qm) { return qm->current_item; }
void qmenu_push_to_nav(Qmenu *qm) {
- // new_menu() will get angry if there are no items in the menu. We'll get a
- // null pointer back, and our code will get angry. Instead, just add an empty
- // 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.
+ // Probably a programming error if there are no items. Make the menu visible
+ // so the programmer knows something went wrong.
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);
- set_menu_grey(qm->ncurses_menu, A_DIM);
- set_menu_format(qm->ncurses_menu, 30, 1); // temp to allow large Y
- int menu_min_h, menu_min_w;
- scale_menu(qm->ncurses_menu, &menu_min_h, &menu_min_w);
- if (!qm->has_submenu_item)
- menu_min_w += 1; // temp hack
+ Usz n = qm->items_count;
+ Qmenu_item *items = qm->items;
+ int menu_min_h = (int)n, menu_min_w = 0;
+ for (Usz i = 0; i < n; ++i) {
+ int item_w = (int)strlen(items[i].text);
+ if (item_w > menu_min_w)
+ menu_min_w = item_w;
+ }
+ menu_min_w += 3 + 1; // left " > " plus 1 empty space on right
if (qm->qblock.title) {
// Stupid lack of wcswidth() means we can't know how wide this string is
// actually displayed. Just fake it for now, until we have Unicode strings
@@ 529,58 467,50 @@ void qmenu_push_to_nav(Qmenu *qm) {
if (title_w > menu_min_w)
menu_min_w = title_w;
}
- if (qm->initial_item)
- set_current_item(qm->ncurses_menu, qm->initial_item);
qnav_stack_push(&qm->qblock, menu_min_h, menu_min_w);
- set_menu_win(qm->ncurses_menu, qm->qblock.outer_window);
- set_menu_sub(qm->ncurses_menu, qm->qblock.content_window);
- // TODO use this to set how "big" the menu is, visually, for scrolling.
- // (ncurses can't figure that out on its own, aparently...)
- // We'll need to split apart some work chunks so that we calculate the size
- // beforehand.
- // set_menu_format(qm->ncurses_menu, 5, 1);
- post_menu(qm->ncurses_menu);
}
void qmenu_free(Qmenu *qm) {
- unpost_menu(qm->ncurses_menu);
- free_menu(qm->ncurses_menu);
- struct Qmenu_item_extra *extras = qmenu_item_extras_ptr(qm);
- for (Usz i = 0; i < qm->items_count; ++i) {
- ITEM *item = qm->ncurses_items[i];
- struct Qmenu_item_extra *extra = qmenu_itemextra(extras, item);
- char const *freed_str = NULL;
- if (extra->owns_string)
- freed_str = item_name(item);
- free_item(qm->ncurses_items[i]);
- if (freed_str)
- free((void *)freed_str);
+ Qmenu_item *items = qm->items;
+ for (Usz i = 0, n = qm->items_count; i < n; ++i) {
+ if (items[i].owns_string)
+ free((void *)items[i].text);
}
- free(qm->ncurses_items);
+ free(qm->items);
free(qm);
}
-ORCA_NOINLINE
-static void qmenu_drive_upordown(Qmenu *qm, int req_up_or_down) {
- struct Qmenu_item_extra *extras = qmenu_item_extras_ptr(qm);
- ITEM *starting = current_item(qm->ncurses_menu);
- menu_driver(qm->ncurses_menu, req_up_or_down);
- ITEM *cur = current_item(qm->ncurses_menu);
+ORCA_NOINLINE static void qmenu_drive_upordown(Qmenu *qm, bool downwards) {
+ Qmenu_item *items = qm->items;
+ Usz n = qm->items_count;
+ if (n <= 1)
+ return;
+ int cur_id = qm->current_item;
+ Usz starting = 0;
+ for (; starting < n; ++starting) {
+ if (items[starting].id == cur_id)
+ goto found;
+ }
+ return;
+found:;
+ Usz current = starting;
for (;;) {
- if (!cur || cur == starting)
- break;
- if (!qmenu_itemextra(extras, cur)->is_spacer)
+ if (downwards && current < n - 1)
+ current++;
+ else if (!downwards && current > 0)
+ current--;
+ if (current == starting)
break;
- ITEM *prev = cur;
- menu_driver(qm->ncurses_menu, req_up_or_down);
- cur = current_item(qm->ncurses_menu);
- if (cur == prev)
+ if (!items[current].is_spacer)
break;
}
+ if (current != starting) {
+ qm->current_item = items[current].id;
+ qm->needs_reprint = 1;
+ }
}
bool qmenu_drive(Qmenu *qm, int key, Qmenu_action *out_action) {
- struct Qmenu_item_extra *extras = qmenu_item_extras_ptr(qm);
switch (key) {
case 27: {
out_action->any.type = Qmenu_action_type_canceled;
@@ 588,17 518,15 @@ bool qmenu_drive(Qmenu *qm, int key, Qmenu_action *out_action) {
}
case ' ':
case '\r':
- case KEY_ENTER: {
- ITEM *cur = current_item(qm->ncurses_menu);
+ case KEY_ENTER:
out_action->picked.type = Qmenu_action_type_picked;
- out_action->picked.id = cur ? qmenu_itemextra(extras, cur)->user_id : 0;
+ out_action->picked.id = qm->current_item;
return true;
- }
case KEY_UP:
- qmenu_drive_upordown(qm, REQ_UP_ITEM);
+ qmenu_drive_upordown(qm, false);
return false;
case KEY_DOWN:
- qmenu_drive_upordown(qm, REQ_DOWN_ITEM);
+ qmenu_drive_upordown(qm, true);
return false;
}
return false;