struct result selection_add_item(struct selection *selection, char const *description, selection_action_fn *action) { if (!selection) return result_set_system_error(EINVAL); if (!description) return result_set_system_error(EINVAL); if (!description[0]) return result_set_system_error(EINVAL); int index = selection->items_count; int null_index = index + 1; ++selection->items_count; selection->items = reallocarray_or_die(selection->items, selection->items_count + 1, sizeof(ITEM *)); char name[] = "1"; name[0] += index; char *name_dup = strdup_or_die(name); char *description_dup = strdup_or_die(description); selection->items[index] = new_item(name_dup, description_dup); if (!selection->items[index]) { free_or_die(description_dup); free_or_die(name_dup); return result_system_error(); } struct selection_item *selection_item = calloc_or_die(1, sizeof(struct selection_item)); selection_item->action = action; set_item_userptr(selection->items[index], selection_item); selection->items[null_index] = NULL; return result_success(); }
struct result app_add_view(struct app *app, struct view *view) { int index = app->views_count; ++app->views_count; app->views = reallocarray_or_die(app->views, app->views_count, sizeof(struct view *)); app->views[index] = view; return result_success(); }
struct result app_activate_view(struct app *app, struct view *view) { struct result result = view_enable_keyboard(view); if (!result_is_success(result)) return result; app->active_view = view; return result_success(); }
struct result app_ring_bell(struct app *app) { int code = cbreak(); if (ERR == code) return result_ncurses_err(); code = beep(); if (ERR == code) return result_ncurses_err(); code = raw(); if (ERR == code) return result_ncurses_err(); return result_success(); }
static struct result get_selection(struct selection *selection) { while (true) { int ch = wgetch(selection->window); if (ERR == ch) return result_ncurses_err(); if (KEY_DOWN == ch) { int result = menu_driver(selection->menu, REQ_DOWN_ITEM); if (E_OK != result && E_REQUEST_DENIED != result) { return result_ncurses_error(result); } } if (KEY_UP == ch) { int result = menu_driver(selection->menu, REQ_UP_ITEM); if (E_OK != result && E_REQUEST_DENIED != result) { return result_ncurses_error(result); } } if ('\r' == ch) break; if (!isalnum(ch)) continue; for (int i = 0; i < selection->items_count; ++i) { char const *name = item_name(selection->items[i]); if (name && ch == name[0]) { set_current_item(selection->menu, selection->items[i]); return result_success(); } } } return result_success(); }
static struct result draw_window(struct selection *selection) { int result = wborder(selection->window, '|', '|', '-', '-', '+', '+', '+', '+'); if (OK != result) return result_ncurses_err(); // TODO: handle UTF-8 correctly size_t title_string_length = strlen(selection->title); if (title_string_length > (size_t)INT_MAX) { return result_set_system_error(ERANGE); } if (title_string_length) { int title_string_width = (int)title_string_length; int window_height; int window_width; getmaxyx(selection->window, window_height, window_width); char const left_frame[] = "- "; char const right_frame[] = " -"; int const left_frame_width = sizeof(left_frame) - 1; int const right_frame_width = sizeof(right_frame) - 1; int const left_border_width = 1; int const right_border_width = 1; int window_top_width = window_width - left_border_width - right_border_width; int title_width = left_frame_width + title_string_width + right_frame_width; if (window_top_width >= title_width) { int start = left_border_width + (window_top_width - title_width) / 2; result = mvwprintw(selection->window, 0, start, "%s%s%s", left_frame, selection->title, right_frame); if (ERR == result) return result_ncurses_err(); } else { // TODO: elide title } } result = wrefresh(selection->window); if (ERR == result) return result_ncurses_err(); return result_success(); }
struct result app_run(struct app *app) { sig_t previous_sigwinch_handler = signal(SIGWINCH, terminal_window_did_change); if (SIG_ERR == previous_sigwinch_handler) { return result_system_error(); } struct result result = result_success(); for (int i = 0; i < app->views_count; ++i) { result = app->views[i]->create(app->views[i], app); if (!result_is_success(result)) break; } if (result_is_success(result)) { for (int i = 0; i < app->views_count; ++i) { result = app->views[i]->draw(app->views[i], app); if (!result_is_success(result)) break; wnoutrefresh(app->views[i]->window); } } if (!app->active_view) { int i = app->views_count - 1; result = app_activate_view(app, app->views[i]); } doupdate(); if (result_is_success(result)) { result = dispatch_events(app); } for (int i = app->views_count - 1; i >= 0; --i) { app->views[i]->destroy(app->views[i], app); } signal(SIGWINCH, previous_sigwinch_handler); return result; }
static struct result dispatch_events(struct app *app) { struct result result = result_success(); app->is_running = true; do { int key = view_read_key(app->active_view); if (ERR == key) { result = result_ncurses_err(); app_quit(app); break; } result = app->active_view->on_key(app->active_view, app, key); if (!result_is_success(result)) { app_quit(app); break; } } while (app->is_running); return result; }
struct result selection_show(struct selection *selection, WINDOW *parent) { if (!selection) return result_set_system_error(EINVAL); selection->menu = new_menu(selection->items); if (!selection->menu) return result_ncurses_errno(); int menu_height; int menu_width; int code = scale_menu(selection->menu, &menu_height, &menu_width); if (E_OK != code) return result_ncurses_error(code); int title_width = (int)strlen(selection->title) + 1; if (title_width > menu_width) menu_width = title_width; int main_window_height; int main_window_width; getmaxyx(parent, main_window_height, main_window_width); int menu_window_height = menu_height + 4; int menu_window_width = menu_width + 5; int menu_window_y = (main_window_height - menu_window_height) / 2; int menu_window_x = (main_window_width - menu_window_width) / 2; selection->window = newwin(menu_window_height, menu_window_width, menu_window_y, menu_window_x); if (!selection->window) return result_ncurses_err(); code = keypad(selection->window, TRUE); if (ERR == code) return result_ncurses_err(); struct result result = draw_window(selection); if (!result_is_success(result)) return result; code = set_menu_win(selection->menu, selection->window); if (E_OK != code) return result_ncurses_error(code); int menu_sub_height = menu_height; int menu_sub_width = menu_width; int menu_sub_y = 2; int menu_sub_x = 2; selection->sub_window = derwin(selection->window, menu_sub_height, menu_sub_width, menu_sub_y, menu_sub_x); if (!selection->sub_window) return result_ncurses_err(); code = set_menu_sub(selection->menu, selection->sub_window); if (E_OK != code) return result_ncurses_error(code); selection->index = 0; code = post_menu(selection->menu); if (E_OK != code) return result_ncurses_error(code); code = wrefresh(selection->window); if (ERR == code) return result_ncurses_err(); result = get_selection(selection); if (!result_is_success(result)) return result; ITEM *selected_item = current_item(selection->menu); if (selected_item) selection->index = item_index(selected_item); code = unpost_menu(selection->menu); if (E_OK != code) return result_ncurses_error(code); code = wclear(selection->window); if (ERR == code) return result_ncurses_err(); code = wrefresh(selection->window); if (ERR == code) return result_ncurses_err(); code = free_menu(selection->menu); if (E_OK != code) return result_ncurses_error(code); selection->menu = NULL; code = delwin(selection->sub_window); if (E_OK != code) return result_ncurses_error(code); selection->sub_window = NULL; code = delwin(selection->window); if (E_OK != code) return result_ncurses_error(code); selection->window = NULL; return result_success(); }