#include "term_util.h"#include "oso.h"#include <ctype.h>#include <form.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;}; typedef struct Qmenu_item { char const *text; int id; U8 owns_string : 1, is_spacer : 1;} Qmenu_item; struct Qmenu { Qblock qblock; Qmenu_item *items; Usz items_count, items_cap; int current_item, id; U8 needs_reprint : 1, is_frontmost : 1;}; struct Qform { Qblock qblock; FORM *ncurses_form; FIELD *ncurses_fields[32]; Usz fields_count; int id;}; static void qmenu_free(Qmenu *qm);static void qform_free(Qform *qf);ORCA_NOINLINE static void qmenu_reprint(Qmenu *qm); Qnav_stack qnav_stack; void qnav_init() { qnav_stack = (Qnav_stack){0}; }void qnav_deinit() { while (qnav_stack.top) qnav_stack_pop();}// Set new y and x coordinates for the top and left of a Qblock based on the// position of the Qblock "below" it in the stack. (Below meaning its order in// the stack, not vertical position on a Y axis.) The target Qblock should// already be inserted into the stack somewhere, so don't call this before// you've finished doing the rest of the setup on the Qblock. The y and x// fields can be junk, though, since this function writes to them without// reading them.static ORCA_NOINLINE void qnav_reposition_block(Qblock *qb) { int top = 0, left = 0; Qblock *prev = qb->down; if (!prev) goto done; int total_h, total_w; getmaxyx(qb->outer_window, total_h, total_w); WINDOW *w = prev->outer_window; int prev_y = prev->y, prev_x = prev->x, prev_h, prev_w; 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; } }done: qb->y = top; qb->x = left;}static ORCA_NOINLINE void qnav_stack_push(Qblock *qb, int height, int width) {#ifndef NDEBUG for (Qblock *i = qnav_stack.top; i; i = i->down) { assert(i != qb); }#endif int total_h = height + 2, total_w = width + 2; if (qnav_stack.top) qnav_stack.top->up = qb; else qnav_stack.bottom = qb; qb->down = qnav_stack.top; qnav_stack.top = qb; qb->outer_window = newpad(total_h, total_w); qb->content_window = subpad(qb->outer_window, height, width, 1, 1); qnav_reposition_block(qb); qnav_stack.occlusion_dirty = true;} Qblock *qnav_top_block() { return qnav_stack.top; } void qblock_init(Qblock *qb, Qblock_type_tag tag) { *qb = (Qblock){0}; qb->tag = tag;} 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.top); if (!qnav_stack.top) return; Qblock *qb = qnav_stack.top; qnav_stack.top = qb->down; if (qnav_stack.top) qnav_stack.top->up = NULL; else qnav_stack.bottom = NULL; qnav_stack.occlusion_dirty = true; 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);} bool qnav_draw(void) { bool drew_any = false; if (!qnav_stack.bottom) goto done; int term_h, term_w; getmaxyx(stdscr, term_h, term_w); for (Qblock *qb = qnav_stack.bottom; qb; qb = qb->up) { bool is_frontmost = qb == qnav_stack.top; if (qnav_stack.occlusion_dirty) qblock_print_frame(qb, is_frontmost); 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) 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 qnav_adjust_term_size(void) { if (!qnav_stack.bottom) return; for (Qblock *qb = qnav_stack.bottom; qb; qb = qb->up) qnav_reposition_block(qb); qnav_stack.occlusion_dirty = true;} 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, ' '); waddstr(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->items = NULL; qm->items_count = 0; qm->items_cap = 0; qm->current_item = 0; qm->id = id; 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 Qmenu_item *qmenu_allocitems(Qmenu *qm, Usz count) { Usz old_count = qm->items_count; if (old_count > SIZE_MAX - count) // overflow exit(1); Usz new_count = old_count + count; Usz items_cap = qm->items_cap; Qmenu_item *items = qm->items; if (new_count > items_cap) { // todo overflow check, realloc fail check Usz new_cap = new_count < 32 ? 32 : orca_round_up_power2(new_count); 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; qm->items = new_items; qm->items_cap = new_cap; } 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); 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; 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? 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) { 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) { if (qm->current_item == id) return; 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) { // 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); 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 // in the UI. Then we get sad. int title_w = (int)strlen(qm->qblock.title) + 2; if (title_w > menu_min_w) menu_min_w = title_w; } qnav_stack_push(&qm->qblock, menu_min_h, menu_min_w);} static void qmenu_free(Qmenu *qm) { 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->items); free(qm);}Lines 1–500 of 697. Load more
#include "term_util.h" #include "oso.h" #include <ctype.h> #include <form.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; }; typedef struct Qmenu_item { char const *text; int id; U8 owns_string : 1, is_spacer : 1; } Qmenu_item; struct Qmenu { Qblock qblock; Qmenu_item *items; Usz items_count, items_cap; int current_item, id; U8 needs_reprint : 1, is_frontmost : 1; }; struct Qform { Qblock qblock; FORM *ncurses_form; FIELD *ncurses_fields[32]; Usz fields_count; int id; }; static void qmenu_free(Qmenu *qm); static void qform_free(Qform *qf); ORCA_NOINLINE static void qmenu_reprint(Qmenu *qm); Qnav_stack qnav_stack; void qnav_init() { qnav_stack = (Qnav_stack){0}; } void qnav_deinit() { while (qnav_stack.top) qnav_stack_pop(); } // Set new y and x coordinates for the top and left of a Qblock based on the // position of the Qblock "below" it in the stack. (Below meaning its order in // the stack, not vertical position on a Y axis.) The target Qblock should // already be inserted into the stack somewhere, so don't call this before // you've finished doing the rest of the setup on the Qblock. The y and x // fields can be junk, though, since this function writes to them without // reading them. static ORCA_NOINLINE void qnav_reposition_block(Qblock *qb) { int top = 0, left = 0; Qblock *prev = qb->down; if (!prev) goto done; int total_h, total_w; getmaxyx(qb->outer_window, total_h, total_w); WINDOW *w = prev->outer_window; int prev_y = prev->y, prev_x = prev->x, prev_h, prev_w; 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; } } done: qb->y = top; qb->x = left; } static ORCA_NOINLINE void qnav_stack_push(Qblock *qb, int height, int width) { #ifndef NDEBUG for (Qblock *i = qnav_stack.top; i; i = i->down) { assert(i != qb); } #endif int total_h = height + 2, total_w = width + 2; if (qnav_stack.top) qnav_stack.top->up = qb; else qnav_stack.bottom = qb; qb->down = qnav_stack.top; qnav_stack.top = qb; qb->outer_window = newpad(total_h, total_w); qb->content_window = subpad(qb->outer_window, height, width, 1, 1); qnav_reposition_block(qb); qnav_stack.occlusion_dirty = true; } Qblock *qnav_top_block() { return qnav_stack.top; } void qblock_init(Qblock *qb, Qblock_type_tag tag) { *qb = (Qblock){0}; qb->tag = tag; } 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.top); if (!qnav_stack.top) return; Qblock *qb = qnav_stack.top; qnav_stack.top = qb->down; if (qnav_stack.top) qnav_stack.top->up = NULL; else qnav_stack.bottom = NULL; qnav_stack.occlusion_dirty = true; 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); } bool qnav_draw(void) { bool drew_any = false; if (!qnav_stack.bottom) goto done; int term_h, term_w; getmaxyx(stdscr, term_h, term_w); for (Qblock *qb = qnav_stack.bottom; qb; qb = qb->up) { bool is_frontmost = qb == qnav_stack.top; if (qnav_stack.occlusion_dirty) qblock_print_frame(qb, is_frontmost); 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) 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 qnav_adjust_term_size(void) { if (!qnav_stack.bottom) return; for (Qblock *qb = qnav_stack.bottom; qb; qb = qb->up) qnav_reposition_block(qb); qnav_stack.occlusion_dirty = true; } 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, ' '); waddstr(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->items = NULL; qm->items_count = 0; qm->items_cap = 0; qm->current_item = 0; qm->id = id; 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 Qmenu_item *qmenu_allocitems(Qmenu *qm, Usz count) { Usz old_count = qm->items_count; if (old_count > SIZE_MAX - count) // overflow exit(1); Usz new_count = old_count + count; Usz items_cap = qm->items_cap; Qmenu_item *items = qm->items; if (new_count > items_cap) { // todo overflow check, realloc fail check Usz new_cap = new_count < 32 ? 32 : orca_round_up_power2(new_count); 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; qm->items = new_items; qm->items_cap = new_cap; } 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); 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; 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? 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) { 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) { if (qm->current_item == id) return; 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) { // 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); 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 // in the UI. Then we get sad. int title_w = (int)strlen(qm->qblock.title) + 2; if (title_w > menu_min_w) menu_min_w = title_w; } qnav_stack_push(&qm->qblock, menu_min_h, menu_min_w); } static void qmenu_free(Qmenu *qm) { 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->items); free(qm); }