744 lines · 22.1 KB · C
RawDownload
#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.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t