#include "term_util.h"#include "oso.h"#include <ctype.h>#include <form.h>#include <menu.h> void term_util_init_colors() { if (has_colors()) { // Enable color start_color(); use_default_colors(); for (int ifg = 0; ifg < Colors_count; ++ifg) { for (int ibg = 0; ibg < Colors_count; ++ibg) { int res = init_pair((short int)(1 + ifg * Colors_count + ibg), (short int)(ifg - 1), (short int)(ibg - 1)); (void)res; // Might fail on Linux virtual console/terminal for a couple of colors. // Just ignore.#if 0 if (res == ERR) { endwin(); fprintf(stderr, "Error initializing color pair: %d %d\n", ifg - 1, ibg - 1); exit(1); }#endif } } }} #define ORCA_CONTAINER_OF(ptr, type, member) \ ((type *)((char *)(1 ? (ptr) : &((type *)0)->member) - \ offsetof(type, member))) struct Qmsg { Qblock qblock; Qmsg_dismiss_mode dismiss_mode;}; struct Qmenu_item_extra { int user_id; U8 owns_string : 1, is_spacer : 1;}; struct Qmenu { Qblock qblock; MENU *ncurses_menu; ITEM **ncurses_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;}; struct Qform { Qblock qblock; FORM *ncurses_form; FIELD *ncurses_fields[32]; Usz fields_count; int id;}; Qnav_stack qnav_stack; void qnav_init() { qnav_stack = (Qnav_stack){.blocks = {0}}; }void qnav_deinit() { while (qnav_stack.count != 0) qnav_stack_pop();}static ORCA_NOINLINE void qnav_stack_push(Qblock *qb, int height, int width) {#ifndef NDEBUG for (Usz i = 0; i < qnav_stack.count; ++i) { assert(qnav_stack.blocks[i] != qb); }#endif int top = 0, left = 0; int total_h = height + 2, total_w = width + 2; if (qnav_stack.count > 0) { WINDOW *w = qnav_stack.blocks[qnav_stack.count - 1]->outer_window; int prev_y, prev_x, prev_h, prev_w; getbegyx(w, prev_y, prev_x); getmaxyx(w, prev_h, prev_w); // Start by trying to position the item to the right of the previous item. left = prev_x + prev_w + 0; int term_h, term_w; getmaxyx(stdscr, term_h, term_w); // Check if we'll run out of room if we position the new item to the right // of the existing item (with the same Y position.) if (left + total_w > term_w) { // If we have enough room if we position just below the previous item in // the stack, do that instead of positioning to the right of it. if (prev_x + total_w <= term_w && total_h < term_h - (prev_y + prev_h)) { top = prev_y + prev_h; left = prev_x; } // If the item doesn't fit there, but it's less wide than the terminal, // right-align it to the edge of the terminal. else if (total_w < term_w) { left = term_w - total_w; } // Otherwise, just start the layout over at Y=0,X=0 else { left = 0; } } } qnav_stack.blocks[qnav_stack.count] = qb; ++qnav_stack.count; qb->outer_window = newpad(total_h, total_w); // This used to be derwin when when used newwin instead of newpad -- not sure // if we should use derwin or subpad now. subpad is probably more compatible. // ncurses docs state that it handles it correctly, unlike some others? qb->content_window = subpad(qb->outer_window, height, width, 1, 1); qb->y = top; qb->x = left; qnav_stack.occlusion_dirty = true;} Qblock *qnav_top_block() { if (qnav_stack.count == 0) return NULL; return qnav_stack.blocks[qnav_stack.count - 1];} void qblock_init(Qblock *qb, Qblock_type_tag tag) { *qb = (Qblock){0}; qb->tag = tag;} void qmenu_free(Qmenu *qm);void qform_free(Qform *qf); void qnav_free_block(Qblock *qb) { switch (qb->tag) { case Qblock_type_qmsg: { Qmsg *qm = qmsg_of(qb); free(qm); break; } case Qblock_type_qmenu: qmenu_free(qmenu_of(qb)); break; case Qblock_type_qform: qform_free(qform_of(qb)); break; }} void qnav_stack_pop(void) { assert(qnav_stack.count > 0); if (qnav_stack.count == 0) return; Qblock *qb = qnav_stack.blocks[qnav_stack.count - 1]; WINDOW *content_window = qb->content_window; WINDOW *outer_window = qb->outer_window; // erase any stuff underneath where this window is, in case it's outside of // the grid in an area that isn't actively redraw werase(outer_window); wnoutrefresh(outer_window); qnav_free_block(qb); delwin(content_window); delwin(outer_window); --qnav_stack.count; qnav_stack.blocks[qnav_stack.count] = NULL; qnav_stack.occlusion_dirty = true;} bool qnav_draw(void) { bool drew_any = false; if (qnav_stack.count < 1) goto done; int term_h, term_w; 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; 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; } } touchwin(qb->outer_window); // here? or after continue? if (term_h < 1 || term_w < 1) continue; int qbwin_h, qbwin_w; getmaxyx(qb->outer_window, qbwin_h, qbwin_w); int qbwin_endy = qb->y + qbwin_h; int qbwin_endx = qb->x + qbwin_w; if (qbwin_endy >= term_h) qbwin_endy = term_h - 1; if (qbwin_endx >= term_w) qbwin_endx = term_w - 1; if (qb->y >= qbwin_endy || qb->x >= qbwin_endx) continue; pnoutrefresh(qb->outer_window, 0, 0, qb->y, qb->x, qbwin_endy, qbwin_endx); drew_any = true; }done: qnav_stack.occlusion_dirty = false; return drew_any;} void qblock_print_border(Qblock *qb, unsigned int attr) { wborder(qb->outer_window, ACS_VLINE | attr, ACS_VLINE | attr, ACS_HLINE | attr, ACS_HLINE | attr, ACS_ULCORNER | attr, ACS_URCORNER | attr, ACS_LLCORNER | attr, ACS_LRCORNER | attr);} void qblock_print_title(Qblock *qb, char const *title, int attr) { wmove(qb->outer_window, 0, 1); attr_t attrs = A_NORMAL; short pair = 0; wattr_get(qb->outer_window, &attrs, &pair, NULL); wattrset(qb->outer_window, attr); waddch(qb->outer_window, ' '); wprintw(qb->outer_window, title); waddch(qb->outer_window, ' '); wattr_set(qb->outer_window, attrs, pair, NULL);} void qblock_set_title(Qblock *qb, char const *title) { qb->title = title; } void qblock_print_frame(Qblock *qb, bool active) { qblock_print_border(qb, active ? A_NORMAL : A_DIM); if (qb->title) { qblock_print_title(qb, qb->title, active ? A_NORMAL : A_DIM); } if (qb->tag == Qblock_type_qform) { Qform *qf = qform_of(qb); if (qf->ncurses_form) { pos_form_cursor(qf->ncurses_form); } }} WINDOW *qmsg_window(Qmsg *qm) { return qm->qblock.content_window; } void qmsg_set_title(Qmsg *qm, char const *title) { qblock_set_title(&qm->qblock, title);} void qmsg_set_dismiss_mode(Qmsg *qm, Qmsg_dismiss_mode mode) { if (qm->dismiss_mode == mode) return; qm->dismiss_mode = mode;} Qmsg *qmsg_push(int height, int width) { Qmsg *qm = malloc(sizeof(Qmsg)); qblock_init(&qm->qblock, Qblock_type_qmsg); qm->dismiss_mode = Qmsg_dismiss_mode_explicitly; qnav_stack_push(&qm->qblock, height, width); return qm;} Qmsg *qmsg_printf_push(char const *title, char const *fmt, ...) { int titlewidth = title ? (int)strlen(title) : 0; va_list ap; va_start(ap, fmt); int msgbytes = vsnprintf(NULL, 0, fmt, ap); va_end(ap); char *buffer = malloc((Usz)msgbytes + 1); if (!buffer) exit(1); va_start(ap, fmt); int printedbytes = vsnprintf(buffer, (Usz)msgbytes + 1, fmt, ap); va_end(ap); if (printedbytes != msgbytes) exit(1); // todo better handling? int lines = 1; int curlinewidth = 0; int maxlinewidth = 0; for (int i = 0; i < msgbytes; i++) { if (buffer[i] == '\n') { buffer[i] = '\0'; // This is terrifying :) lines++; if (curlinewidth > maxlinewidth) maxlinewidth = curlinewidth; curlinewidth = 0; } else { curlinewidth++; } } if (curlinewidth > maxlinewidth) maxlinewidth = curlinewidth; int width = titlewidth > maxlinewidth ? titlewidth : maxlinewidth; width += 2; // 1 padding on left and right each Qmsg *msg = qmsg_push(lines, width); // no wrapping yet, no real wcwidth, etc WINDOW *msgw = qmsg_window(msg); int i = 0; int offset = 0; for (;;) { if (offset == msgbytes + 1) break; int numbytes = (int)strlen(buffer + offset); wmove(msgw, i, 1); waddstr(msgw, buffer + offset); offset += numbytes + 1; i++; } free(buffer); if (title) qmsg_set_title(msg, title); return msg;} bool qmsg_drive(Qmsg *qm, int key, Qmsg_action *out_action) { *out_action = (Qmsg_action){0}; Qmsg_dismiss_mode dm = qm->dismiss_mode; switch (dm) { case Qmsg_dismiss_mode_explicitly: break; case Qmsg_dismiss_mode_easily: out_action->dismiss = true; return true; case Qmsg_dismiss_mode_passthrough: out_action->dismiss = true; out_action->passthrough = true; return true; } switch (key) { case ' ': case 27: case '\r': case KEY_ENTER: out_action->dismiss = true; return true; } return false;} 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_count = 0; qm->items_cap = 0; qm->initial_item = NULL; qm->id = id; qm->has_submenu_item = 0; return qm;}void qmenu_destroy(Qmenu *qm) { qmenu_free(qm); }int qmenu_id(Qmenu const *qm) { return qm->id; }static ORCA_NOINLINE voidqmenu_allocitems(Qmenu *qm, Usz count, Usz *out_idx, 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 = 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); 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_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));}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;}void qmenu_add_printf(Qmenu *qm, int id, char const *fmt, ...) { va_list ap; va_start(ap, fmt); int textsize = vsnprintf(NULL, 0, fmt, ap); va_end(ap); char *buffer = malloc((Usz)textsize + 1); if (!buffer) exit(1); va_start(ap, fmt); int printedsize = vsnprintf(buffer, (Usz)textsize + 1, fmt, ap); 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;}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;}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) 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);Lines 1–500 of 744. Load more
#include "term_util.h" #include "oso.h" #include <ctype.h> #include <form.h> #include <menu.h> void term_util_init_colors() { if (has_colors()) { // Enable color start_color(); use_default_colors(); for (int ifg = 0; ifg < Colors_count; ++ifg) { for (int ibg = 0; ibg < Colors_count; ++ibg) { int res = init_pair((short int)(1 + ifg * Colors_count + ibg), (short int)(ifg - 1), (short int)(ibg - 1)); (void)res; // Might fail on Linux virtual console/terminal for a couple of colors. // Just ignore. #if 0 if (res == ERR) { endwin(); fprintf(stderr, "Error initializing color pair: %d %d\n", ifg - 1, ibg - 1); exit(1); } #endif } } } } #define ORCA_CONTAINER_OF(ptr, type, member) \ ((type *)((char *)(1 ? (ptr) : &((type *)0)->member) - \ offsetof(type, member))) struct Qmsg { Qblock qblock; Qmsg_dismiss_mode dismiss_mode; }; struct Qmenu_item_extra { int user_id; U8 owns_string : 1, is_spacer : 1; }; struct Qmenu { Qblock qblock; MENU *ncurses_menu; ITEM **ncurses_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; }; struct Qform { Qblock qblock; FORM *ncurses_form; FIELD *ncurses_fields[32]; Usz fields_count; int id; }; Qnav_stack qnav_stack; void qnav_init() { qnav_stack = (Qnav_stack){.blocks = {0}}; } void qnav_deinit() { while (qnav_stack.count != 0) qnav_stack_pop(); } static ORCA_NOINLINE void qnav_stack_push(Qblock *qb, int height, int width) { #ifndef NDEBUG for (Usz i = 0; i < qnav_stack.count; ++i) { assert(qnav_stack.blocks[i] != qb); } #endif int top = 0, left = 0; int total_h = height + 2, total_w = width + 2; if (qnav_stack.count > 0) { WINDOW *w = qnav_stack.blocks[qnav_stack.count - 1]->outer_window; int prev_y, prev_x, prev_h, prev_w; getbegyx(w, prev_y, prev_x); getmaxyx(w, prev_h, prev_w); // Start by trying to position the item to the right of the previous item. left = prev_x + prev_w + 0; int term_h, term_w; getmaxyx(stdscr, term_h, term_w); // Check if we'll run out of room if we position the new item to the right // of the existing item (with the same Y position.) if (left + total_w > term_w) { // If we have enough room if we position just below the previous item in // the stack, do that instead of positioning to the right of it. if (prev_x + total_w <= term_w && total_h < term_h - (prev_y + prev_h)) { top = prev_y + prev_h; left = prev_x; } // If the item doesn't fit there, but it's less wide than the terminal, // right-align it to the edge of the terminal. else if (total_w < term_w) { left = term_w - total_w; } // Otherwise, just start the layout over at Y=0,X=0 else { left = 0; } } } qnav_stack.blocks[qnav_stack.count] = qb; ++qnav_stack.count; qb->outer_window = newpad(total_h, total_w); // This used to be derwin when when used newwin instead of newpad -- not sure // if we should use derwin or subpad now. subpad is probably more compatible. // ncurses docs state that it handles it correctly, unlike some others? qb->content_window = subpad(qb->outer_window, height, width, 1, 1); qb->y = top; qb->x = left; qnav_stack.occlusion_dirty = true; } Qblock *qnav_top_block() { if (qnav_stack.count == 0) return NULL; return qnav_stack.blocks[qnav_stack.count - 1]; } void qblock_init(Qblock *qb, Qblock_type_tag tag) { *qb = (Qblock){0}; qb->tag = tag; } void qmenu_free(Qmenu *qm); void qform_free(Qform *qf); void qnav_free_block(Qblock *qb) { switch (qb->tag) { case Qblock_type_qmsg: { Qmsg *qm = qmsg_of(qb); free(qm); break; } case Qblock_type_qmenu: qmenu_free(qmenu_of(qb)); break; case Qblock_type_qform: qform_free(qform_of(qb)); break; } } void qnav_stack_pop(void) { assert(qnav_stack.count > 0); if (qnav_stack.count == 0) return; Qblock *qb = qnav_stack.blocks[qnav_stack.count - 1]; WINDOW *content_window = qb->content_window; WINDOW *outer_window = qb->outer_window; // erase any stuff underneath where this window is, in case it's outside of // the grid in an area that isn't actively redraw werase(outer_window); wnoutrefresh(outer_window); qnav_free_block(qb); delwin(content_window); delwin(outer_window); --qnav_stack.count; qnav_stack.blocks[qnav_stack.count] = NULL; qnav_stack.occlusion_dirty = true; } bool qnav_draw(void) { bool drew_any = false; if (qnav_stack.count < 1) goto done; int term_h, term_w; 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; 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; } } touchwin(qb->outer_window); // here? or after continue? if (term_h < 1 || term_w < 1) continue; int qbwin_h, qbwin_w; getmaxyx(qb->outer_window, qbwin_h, qbwin_w); int qbwin_endy = qb->y + qbwin_h; int qbwin_endx = qb->x + qbwin_w; if (qbwin_endy >= term_h) qbwin_endy = term_h - 1; if (qbwin_endx >= term_w) qbwin_endx = term_w - 1; if (qb->y >= qbwin_endy || qb->x >= qbwin_endx) continue; pnoutrefresh(qb->outer_window, 0, 0, qb->y, qb->x, qbwin_endy, qbwin_endx); drew_any = true; } done: qnav_stack.occlusion_dirty = false; return drew_any; } void qblock_print_border(Qblock *qb, unsigned int attr) { wborder(qb->outer_window, ACS_VLINE | attr, ACS_VLINE | attr, ACS_HLINE | attr, ACS_HLINE | attr, ACS_ULCORNER | attr, ACS_URCORNER | attr, ACS_LLCORNER | attr, ACS_LRCORNER | attr); } void qblock_print_title(Qblock *qb, char const *title, int attr) { wmove(qb->outer_window, 0, 1); attr_t attrs = A_NORMAL; short pair = 0; wattr_get(qb->outer_window, &attrs, &pair, NULL); wattrset(qb->outer_window, attr); waddch(qb->outer_window, ' '); wprintw(qb->outer_window, title); waddch(qb->outer_window, ' '); wattr_set(qb->outer_window, attrs, pair, NULL); } void qblock_set_title(Qblock *qb, char const *title) { qb->title = title; } void qblock_print_frame(Qblock *qb, bool active) { qblock_print_border(qb, active ? A_NORMAL : A_DIM); if (qb->title) { qblock_print_title(qb, qb->title, active ? A_NORMAL : A_DIM); } if (qb->tag == Qblock_type_qform) { Qform *qf = qform_of(qb); if (qf->ncurses_form) { pos_form_cursor(qf->ncurses_form); } } } WINDOW *qmsg_window(Qmsg *qm) { return qm->qblock.content_window; } void qmsg_set_title(Qmsg *qm, char const *title) { qblock_set_title(&qm->qblock, title); } void qmsg_set_dismiss_mode(Qmsg *qm, Qmsg_dismiss_mode mode) { if (qm->dismiss_mode == mode) return; qm->dismiss_mode = mode; } Qmsg *qmsg_push(int height, int width) { Qmsg *qm = malloc(sizeof(Qmsg)); qblock_init(&qm->qblock, Qblock_type_qmsg); qm->dismiss_mode = Qmsg_dismiss_mode_explicitly; qnav_stack_push(&qm->qblock, height, width); return qm; } Qmsg *qmsg_printf_push(char const *title, char const *fmt, ...) { int titlewidth = title ? (int)strlen(title) : 0; va_list ap; va_start(ap, fmt); int msgbytes = vsnprintf(NULL, 0, fmt, ap); va_end(ap); char *buffer = malloc((Usz)msgbytes + 1); if (!buffer) exit(1); va_start(ap, fmt); int printedbytes = vsnprintf(buffer, (Usz)msgbytes + 1, fmt, ap); va_end(ap); if (printedbytes != msgbytes) exit(1); // todo better handling? int lines = 1; int curlinewidth = 0; int maxlinewidth = 0; for (int i = 0; i < msgbytes; i++) { if (buffer[i] == '\n') { buffer[i] = '\0'; // This is terrifying :) lines++; if (curlinewidth > maxlinewidth) maxlinewidth = curlinewidth; curlinewidth = 0; } else { curlinewidth++; } } if (curlinewidth > maxlinewidth) maxlinewidth = curlinewidth; int width = titlewidth > maxlinewidth ? titlewidth : maxlinewidth; width += 2; // 1 padding on left and right each Qmsg *msg = qmsg_push(lines, width); // no wrapping yet, no real wcwidth, etc WINDOW *msgw = qmsg_window(msg); int i = 0; int offset = 0; for (;;) { if (offset == msgbytes + 1) break; int numbytes = (int)strlen(buffer + offset); wmove(msgw, i, 1); waddstr(msgw, buffer + offset); offset += numbytes + 1; i++; } free(buffer); if (title) qmsg_set_title(msg, title); return msg; } bool qmsg_drive(Qmsg *qm, int key, Qmsg_action *out_action) { *out_action = (Qmsg_action){0}; Qmsg_dismiss_mode dm = qm->dismiss_mode; switch (dm) { case Qmsg_dismiss_mode_explicitly: break; case Qmsg_dismiss_mode_easily: out_action->dismiss = true; return true; case Qmsg_dismiss_mode_passthrough: out_action->dismiss = true; out_action->passthrough = true; return true; } switch (key) { case ' ': case 27: case '\r': case KEY_ENTER: out_action->dismiss = true; return true; } return false; } 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_count = 0; qm->items_cap = 0; qm->initial_item = NULL; qm->id = id; qm->has_submenu_item = 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) { 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 = 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); 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_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)); } 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; } void qmenu_add_printf(Qmenu *qm, int id, char const *fmt, ...) { va_list ap; va_start(ap, fmt); int textsize = vsnprintf(NULL, 0, fmt, ap); va_end(ap); char *buffer = malloc((Usz)textsize + 1); if (!buffer) exit(1); va_start(ap, fmt); int printedsize = vsnprintf(buffer, (Usz)textsize + 1, fmt, ap); 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; } 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; } 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) 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);