diff options
Diffstat (limited to 'src/out_curses.c')
-rw-r--r-- | src/out_curses.c | 1277 |
1 files changed, 1277 insertions, 0 deletions
diff --git a/src/out_curses.c b/src/out_curses.c new file mode 100644 index 0000000..602998c --- /dev/null +++ b/src/out_curses.c @@ -0,0 +1,1277 @@ +/* + * out_curses.c Curses Output + * + * Copyright (c) 2001-2013 Thomas Graf <tgraf@suug.ch> + * Copyright (c) 2013 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <bmon/bmon.h> +#include <bmon/conf.h> +#include <bmon/attr.h> +#include <bmon/element.h> +#include <bmon/element_cfg.h> +#include <bmon/input.h> +#include <bmon/history.h> +#include <bmon/graph.h> +#include <bmon/output.h> +#include <bmon/utils.h> + +enum { + GRAPH_DISPLAY_SIDE_BY_SIDE = 1, + GRAPH_DISPLAY_STANDARD = 2, +}; + +enum { + KEY_TOGGLE_LIST = 'l', + KEY_TOGGLE_GRAPH = 'g', + KEY_TOGGLE_DETAILS = 'd', + KEY_TOGGLE_INFO = 'i', + KEY_COLLECT_HISTORY = 'h', +}; + +#define DETAILS_COLS 40 + +#define LIST_COL_1 31 +#define LIST_COL_2 55 + +/* Set to element_current() before drawing */ +static struct element *current_element; + +static struct attr *current_attr; + +/* Length of list to draw, updated in draw_content() */ +static int list_length; + +static int list_req; + +/* Number of graphs to draw (may be < c_ngraph) */ +static int ngraph; + +/* + * Offset in number of lines within the the element list of the currently + * selected element. Updated while summing up required lines. + */ +static unsigned int selection_offset; + +/* + * Offset in number of lines of the first element to be drawn. Updated + * in draw_content() + */ +static int offset; + +/* + * Offset to the first graph to draw in number of attributes with graphs. + */ +static unsigned int graph_offset; + +static int graph_display = GRAPH_DISPLAY_STANDARD; + +/* + * Number of detail columns + */ +static int detail_cols; +static int info_cols; + +static int initialized; +static int print_help; +static int quit_mode; +static int help_page; + +/* Current row */ +static int row; + +/* Number of rows */ +static int rows; + +/* Number of columns */ +static int cols; + +static int c_show_graph = 1; +static int c_ngraph = 1; +static int c_use_colors = 1; +static int c_show_details = 0; +static int c_show_list = 1; +static int c_show_info = 0; +static int c_list_min = 6; + +static struct graph_cfg c_graph_cfg = { + .gc_width = 60, + .gc_height = 6, + .gc_foreground = '|', + .gc_background = '.', + .gc_noise = ':', + .gc_unknown = '?', +}; + +#define NEXT_ROW() \ + do { \ + row++; \ + if (row >= rows - 1) \ + return; \ + move(row, 0); \ + } while(0) + +static void apply_layout(int layout) +{ + if (c_use_colors) + attrset(COLOR_PAIR(layout) | cfg_layout[layout].l_attr); + else + attrset(cfg_layout[layout].l_attr); +} + +char *float2str(double value, int width, int prec, char *buf, size_t len) +{ + snprintf(buf, len, "%'*.*f", width, value == 0.0f ? 0 : prec, value); + + return buf; +} + +static void put_line(const char *fmt, ...) +{ + va_list args; + char buf[2048]; + int x, y; + + memset(buf, 0, sizeof(buf)); + getyx(stdscr, y, x); + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + if (strlen(buf) > cols-x) + buf[cols - x] = '\0'; + else + memset(&buf[strlen(buf)], ' ', cols - strlen(buf)-x); + + addstr(buf); +} + +static void center_text(const char *fmt, ...) +{ + va_list args; + char *str; + unsigned int col; + + va_start(args, fmt); + vasprintf(&str, fmt, args); + va_end(args); + + col = (cols / 2) - (strlen(str) / 2); + if (col > cols - 1) + col = cols - 1; + + move(row, col); + addstr(str); + move(row, 0); + + free(str); +} + +static int curses_init(void) +{ + if (!initscr()) { + fprintf(stderr, "Unable to initialize curses screen\n"); + return -EOPNOTSUPP; + } + + initialized = 1; + + if (!has_colors()) + c_use_colors = 0; + + if (c_use_colors) { + int i; + + start_color(); + +#if defined HAVE_USE_DEFAULT_COLORS + use_default_colors(); +#endif + for (i = 1; i < LAYOUT_MAX+1; i++) + init_pair(i, cfg_layout[i].l_fg, cfg_layout[i].l_bg); + } + + keypad(stdscr, TRUE); + nonl(); + cbreak(); + noecho(); + nodelay(stdscr, TRUE); /* getch etc. must be non-blocking */ + clear(); + curs_set(0); + + return 0; +} + +static void curses_shutdown(void) +{ + if (initialized) + endwin(); +} + +struct detail_arg +{ + int nattr; +}; + +static void draw_attr_detail(struct element *e, struct attr *a, void *arg) +{ + char *rx_u, *tx_u, buf1[32], buf2[32]; + int rxprec, txprec, ncol; + struct detail_arg *da = arg; + + double rx = unit_value2str(a->a_rx_rate.r_total, + a->a_def->ad_unit, + &rx_u, &rxprec); + double tx = unit_value2str(a->a_tx_rate.r_total, + a->a_def->ad_unit, + &tx_u, &txprec); + + if (da->nattr >= detail_cols) { + NEXT_ROW(); + da->nattr = 0; + } + + ncol = (da->nattr * DETAILS_COLS) - 1; + move(row, ncol); + if (ncol > 0) + addch(ACS_VLINE); + + put_line(" %-14.14s %8s%-3s %8s%-3s\n", + a->a_def->ad_description, + (a->a_flags & ATTR_RX_ENABLED) ? + float2str(rx, 8, rxprec, buf1, sizeof(buf1)) : "-", rx_u, + (a->a_flags & ATTR_TX_ENABLED) ? + float2str(tx, 8, txprec, buf2, sizeof(buf2)) : "-", tx_u); + + da->nattr++; +} + +static void draw_details(void) +{ + int i; + struct detail_arg arg = { + .nattr = 0, + }; + + if (!current_element->e_nattrs) + return; + + for (i = 1; i < detail_cols; i++) + mvaddch(row, (i * DETAILS_COLS) - 1, ACS_TTEE); + + NEXT_ROW(); + put_line(""); + for (i = 0; i < detail_cols; i++) { + if (i > 0) + mvaddch(row, (i * DETAILS_COLS) - 1, ACS_VLINE); + move(row, (i * DETAILS_COLS) + 22); + put_line("RX TX"); + } + + NEXT_ROW(); + element_foreach_attr(current_element, draw_attr_detail, &arg); + + /* + * If the last row was incomplete, not all vlines have been drawn. + * draw them here + */ + for (i = 1; i < detail_cols; i++) + mvaddch(row, (i * DETAILS_COLS - 1), ACS_VLINE); +} + +static void print_message(const char *text) +{ + int i, y = (rows/2) - 2; + int len = strlen(text); + int x = (cols/2) - (len / 2); + + attrset(A_STANDOUT); + mvaddch(y - 2, x - 1, ACS_ULCORNER); + mvaddch(y + 2, x - 1, ACS_LLCORNER); + mvaddch(y - 2, x + len, ACS_URCORNER); + mvaddch(y + 2, x + len, ACS_LRCORNER); + + for (i = 0; i < 3; i++) { + mvaddch(y - 1 + i, x + len, ACS_VLINE); + mvaddch(y - 1 + i, x - 1 ,ACS_VLINE); + } + + for (i = 0; i < len; i++) { + mvaddch(y - 2, x + i, ACS_HLINE); + mvaddch(y - 1, x + i, ' '); + mvaddch(y + 1, x + i, ' '); + mvaddch(y + 2, x + i, ACS_HLINE); + } + + mvaddstr(y, x, text); + attroff(A_STANDOUT); + + row = y + 2; +} + +static void draw_help(void) +{ +#define HW 46 +#define HH 19 + int i, y = (rows/2) - (HH/2); + int x = (cols/2) - (HW/2); + char pad[HW+1]; + + memset(pad, ' ', sizeof(pad)); + pad[sizeof(pad) - 1] = '\0'; + + attron(A_STANDOUT); + + for (i = 0; i < HH; i++) + mvaddnstr(y + i, x, pad, -1); + + mvaddch(y - 1, x - 1, ACS_ULCORNER); + mvaddch(y + HH, x - 1, ACS_LLCORNER); + + mvaddch(y - 1, x + HW, ACS_URCORNER); + mvaddch(y + HH, x + HW, ACS_LRCORNER); + + for (i = 0; i < HH; i++) { + mvaddch(y + i, x - 1, ACS_VLINE); + mvaddch(y + i, x + HW, ACS_VLINE); + } + + for (i = 0; i < HW; i++) { + mvaddch(y - 1, x + i, ACS_HLINE); + mvaddch(y + HH, x + i, ACS_HLINE); + } + + attron(A_BOLD); + mvaddnstr(y- 1, x+15, "QUICK REFERENCE", -1); + attron(A_UNDERLINE); + mvaddnstr(y+ 0, x+1, "Navigation", -1); + attroff(A_BOLD | A_UNDERLINE); + + mvaddnstr(y+ 1, x+3, "Up, Down Previous/Next element", -1); + mvaddnstr(y+ 2, x+3, "PgUp, PgDown Scroll up/down entire page", -1); + mvaddnstr(y+ 3, x+3, "Left, Right Previous/Next attribute", -1); + mvaddnstr(y+ 4, x+3, "[, ] Previous/Next group", -1); + mvaddnstr(y+ 5, x+3, "? Toggle quick reference", -1); + mvaddnstr(y+ 6, x+3, "q Quit bmon", -1); + + attron(A_BOLD | A_UNDERLINE); + mvaddnstr(y+ 8, x+1, "Display Settings", -1); + attroff(A_BOLD | A_UNDERLINE); + + mvaddnstr(y+ 9, x+3, "d Toggle detailed statistics", -1); + mvaddnstr(y+10, x+3, "l Toggle element list", -1); + mvaddnstr(y+11, x+3, "i Toggle additional info", -1); + + attron(A_BOLD | A_UNDERLINE); + mvaddnstr(y+13, x+1, "Graph Settings", -1); + attroff(A_BOLD | A_UNDERLINE); + + mvaddnstr(y+14, x+3, "g Toggle graphical statistics", -1); + mvaddnstr(y+15, x+3, "H Start recording history data", -1); + mvaddnstr(y+16, x+3, "TAB Switch time unit of graph", -1); + mvaddnstr(y+17, x+3, "<, > Change number of graphs", -1); + + attroff(A_STANDOUT); + + row = y + HH; +} + +static int lines_required_for_header(void) +{ + return 1; +} + +static void draw_header(void) +{ + apply_layout(LAYOUT_STATUSBAR); + + if (current_element) + put_line(" %s %c%s%c", + current_element->e_name, + current_element->e_description ? '(' : ' ', + current_element->e_description ? : "", + current_element->e_description ? ')' : ' '); + else + put_line(""); + + move(row, COLS - strlen(PACKAGE_STRING) - 1); + put_line("%s", PACKAGE_STRING); + move(row, 0); +} + +static int lines_required_for_statusbar(void) +{ + return 1; +} + +static void draw_statusbar(void) +{ + static const char *help_text = "Press ? for help"; + char s[27]; + time_t t = time(0); + + apply_layout(LAYOUT_STATUSBAR); + + asctime_r(localtime(&t), s); + s[strlen(s) - 1] = '\0'; + + row = rows-1; + move(row, 0); + put_line(" %s", s); + + move(row, COLS - strlen(help_text) - 1); + put_line("%s", help_text); + + move(row, 0); +} + +static void count_attr_graph(struct element *g, struct attr *a, void *arg) +{ + if (a == current_attr) + graph_offset = ngraph; + + ngraph++; +} + +static int lines_required_for_graph(void) +{ + int lines = 0; + + ngraph = 0; + + if (c_show_graph && current_element) { + graph_display = GRAPH_DISPLAY_STANDARD; + + element_foreach_attr(current_element, count_attr_graph, NULL); + + if (ngraph > c_ngraph) + ngraph = c_ngraph; + + /* check if we have room to draw graphs on the same level */ + if (cols > (2 * (c_graph_cfg.gc_width + 10))) + graph_display = GRAPH_DISPLAY_SIDE_BY_SIDE; + + /* +2 = header + time axis */ + lines = ngraph * (graph_display * (c_graph_cfg.gc_height + 2)); + } + + return lines + 1; +} + +static int lines_required_for_details(void) +{ + int lines = 1; + + if (c_show_details && current_element) { + lines++; /* header */ + + detail_cols = cols / DETAILS_COLS; + + if (!detail_cols) + detail_cols = 1; + + lines += (current_element->e_nattrs / detail_cols); + if (current_element->e_nattrs % detail_cols) + lines++; + } + + return lines; +} + +static void count_element_lines(struct element_group *g, struct element *e, + void *arg) +{ + int *lines = arg; + + if (e == current_element) + selection_offset = *lines; + + (*lines)++; +} + +static void count_group_lines(struct element_group *g, void *arg) +{ + int *lines = arg; + + /* group title */ + (*lines)++; + + group_foreach_element(g, &count_element_lines, arg); +} + +static int lines_required_for_list(void) +{ + int lines = 0; + + if (c_show_list) + group_foreach(&count_group_lines, &lines); + else + lines = 1; + + return lines; +} + +static inline int line_visible(int line) +{ + return line >= offset && line < (offset + list_length); +} + +static void draw_attr(double rate1, int prec1, char *unit1, + double rate2, int prec2, char *unit2, + float usage, int ncol) +{ + char buf[32]; + + move(row, ncol); + addch(ACS_VLINE); + printw("%7s%-3s", + float2str(rate1, 7, prec1, buf, sizeof(buf)), unit1); + + printw("%7s%-3s", + float2str(rate2, 7, prec2, buf, sizeof(buf)), unit2); + + if (usage != FLT_MAX) + printw("%2.0f%%", usage); + else + printw("%3s", ""); +} + +static void draw_element(struct element_group *g, struct element *e, + void *arg) +{ + int *line = arg; + + apply_layout(LAYOUT_LIST); + + if (line_visible(*line)) { + char *rxu1 = "", *txu1 = "", *rxu2 = "", *txu2 = ""; + double rx1 = 0.0f, tx1 = 0.0f, rx2 = 0.0f, tx2 = 0.0f; + char pad[IFNAMSIZ + 32]; + int rx1prec = 0, tx1prec = 0, rx2prec = 0, tx2prec = 0; + struct attr *a; + + NEXT_ROW(); + + if (e->e_key_attr[GT_MAJOR] && + (a = attr_lookup(e, e->e_key_attr[GT_MAJOR]->ad_id))) + attr_rate2float(a, &rx1, &rxu1, &rx1prec, + &tx1, &txu1, &tx1prec); + + if (e->e_key_attr[GT_MINOR] && + (a = attr_lookup(e, e->e_key_attr[GT_MINOR]->ad_id))) + attr_rate2float(a, &rx2, &rxu2, &rx2prec, + &tx2, &txu2, &tx2prec); + + memset(pad, 0, sizeof(pad)); + memset(pad, ' ', e->e_level < 6 ? e->e_level * 2 : 12); + + strncat(pad, e->e_name, sizeof(pad) - strlen(pad) - 1); + + if (e->e_description) { + strncat(pad, " (", sizeof(pad) - strlen(pad) - 1); + strncat(pad, e->e_description, sizeof(pad) - strlen(pad) - 1); + strncat(pad, ")", sizeof(pad) - strlen(pad) - 1); + } + + if (*line == offset) { + attron(A_BOLD); + addch(ACS_UARROW); + attroff(A_BOLD); + addch(' '); + } else if (e == current_element) { + apply_layout(LAYOUT_SELECTED); + printw("->"); + apply_layout(LAYOUT_LIST); + } else if (*line == offset + list_length - 1 && + *line < (list_req - 1)) { + attron(A_BOLD); + addch(ACS_DARROW); + attroff(A_BOLD); + addch(' '); + } else + printw(" "); + + put_line("%-30.30s", pad); + + draw_attr(rx1, rx1prec, rxu1, rx2, rx2prec, rxu2, + e->e_rx_usage, LIST_COL_1); + + draw_attr(tx1, tx1prec, txu1, tx2, tx2prec, txu2, + e->e_tx_usage, LIST_COL_2); + + } + + (*line)++; +} + +static void draw_group(struct element_group *g, void *arg) +{ + int *line = arg; + + if (line_visible(*line)) { + NEXT_ROW(); + attron(A_BOLD); + put_line("%s", g->g_hdr->gh_title); + + attroff(A_BOLD); + mvaddch(row, LIST_COL_1, ACS_VLINE); + attron(A_BOLD); + put_line("%7s %7s %%", + g->g_hdr->gh_column[0], + g->g_hdr->gh_column[1]); + + attroff(A_BOLD); + mvaddch(row, LIST_COL_2, ACS_VLINE); + attron(A_BOLD); + put_line("%7s %7s %%", + g->g_hdr->gh_column[2], + g->g_hdr->gh_column[3]); + } + + (*line)++; + + group_foreach_element(g, draw_element, arg); +} + +static void draw_element_list(void) +{ + int line = 0; + + group_foreach(draw_group, &line); +} + +static inline int attr_visible(int nattr) +{ + return nattr >= graph_offset && nattr < (graph_offset + ngraph); +} + +static void draw_graph_centered(struct graph *g, int row, int ncol, + const char *text) +{ + int hcenter = (g->g_cfg.gc_width / 2) - (strlen(text) / 2) + 8; + + if (hcenter < 9) + hcenter = 9; + + mvprintw(row, ncol + hcenter, "%.*s", g->g_cfg.gc_width, text); +} + +static void draw_table(struct graph *g, struct graph_table *tbl, + struct attr *a, struct history *h, + const char *hdr, int ncol) +{ + int i, save_row; + char buf[32]; + + if (!tbl->gt_table) { + for (i = g->g_cfg.gc_height; i >= 0; i--) { + move(++row, ncol); + put_line(""); + } + return; + } + + move(++row, ncol); + put_line("%8s", tbl->gt_y_unit ? : ""); + + snprintf(buf, sizeof(buf), "(%s %s/%s)", + hdr, a->a_def->ad_description, + h ? h->h_definition->hd_name : "?"); + + draw_graph_centered(g, row, ncol, buf); + + //move(row, ncol + g->g_cfg.gc_width - 3); + //put_line("[err %.2f%%]", rtiming.rt_variance.v_error); + + for (i = (g->g_cfg.gc_height - 1); i >= 0; i--) { + move(++row, ncol); + put_line("%'8.2f %s", + tbl->gt_scale[i], + tbl->gt_table + (i * graph_row_size(&g->g_cfg))); + } + + move(++row, ncol); + put_line(" 1"); + + for (i = 1; i <= g->g_cfg.gc_width; i++) { + if (i % 5 == 0) { + move(row, ncol + i + 7); + printw("%2d", i); + } + } + + if (!h) { + const char *t1 = " No history data available. "; + const char *t2 = " Press h to start collecting history. "; + int vcenter = g->g_cfg.gc_height / 2; + + save_row = row; + draw_graph_centered(g, save_row - vcenter - 1, ncol, t1); + draw_graph_centered(g, save_row - vcenter, ncol, t2); + row = save_row; + } +} + +static void draw_history_graph(struct attr *a, struct history *h) +{ + struct graph *g; + int ncol = 0, save_row; + + g = graph_alloc(h, &c_graph_cfg); + graph_refill(g, h); + + save_row = row; + draw_table(g, &g->g_rx, a, h, "RX", ncol); + + if (graph_display == GRAPH_DISPLAY_SIDE_BY_SIDE) { + ncol = cols / 2; + row = save_row; + } + + draw_table(g, &g->g_tx, a, h, "TX", ncol); + + graph_free(g); +} + +static void draw_attr_graph(struct element *e, struct attr *a, void *arg) +{ + int *nattr = arg; + + if (attr_visible(*nattr)) { + struct history_def *sel; + struct history *h; + + sel = history_current(); + c_graph_cfg.gc_unit = a->a_def->ad_unit; + + list_for_each_entry(h, &a->a_history_list, h_list) { + if (h->h_definition != sel) + continue; + + draw_history_graph(a, h); + goto out; + } + + draw_history_graph(a, NULL); + } + +out: + (*nattr)++; +} + +static void draw_graph(void) +{ + int nattr = 0; + + element_foreach_attr(current_element, &draw_attr_graph, &nattr); +} + +static int lines_required_for_info(void) +{ + int lines = 1; + + if (c_show_info) { + info_cols = cols / DETAILS_COLS; + + if (!info_cols) + info_cols = 1; + + lines += (current_element->e_ninfo / info_cols); + if (current_element->e_ninfo % info_cols) + lines++; + } + + return lines; +} + +static void __draw_info(struct element *e, struct info *info, int *ninfo) +{ + int ncol; + + ncol = ((*ninfo) * DETAILS_COLS) - 1; + move(row, ncol); + if (ncol > 0) + addch(ACS_VLINE); + + put_line(" %-14.14s %22.22s", info->i_name, info->i_value); + + if (++(*ninfo) >= info_cols) { + NEXT_ROW(); + *ninfo = 0; + } +} + +static void draw_info(void) +{ + struct info *info; + int i, ninfo = 0; + + if (!current_element->e_ninfo) + return; + + for (i = 1; i < detail_cols; i++) + mvaddch(row, (i * DETAILS_COLS) - 1, + c_show_details ? ACS_PLUS : ACS_TTEE); + + NEXT_ROW(); + list_for_each_entry(info, ¤t_element->e_info_list, i_list) + __draw_info(current_element, info, &ninfo); + + /* + * If the last row was incomplete, not all vlines have been drawn. + * draw them here + */ + for (i = 1; i < info_cols; i++) + mvaddch(row, (i * DETAILS_COLS - 1), ACS_VLINE); +} + +static void draw_content(void) +{ + int graph_req, details_req, lines_available, total_req; + int info_req, empty_lines; + int disable_graph = 0, disable_details = 0, disable_info = 0; + + if (!current_element) + return; + + /* + * Reset selection offset. Will be set in lines_required_for_list(). + */ + selection_offset = 0; + offset = 0; + + /* Reset graph offset, will be set in lines_required_for_graph() */ + graph_offset = 0; + + lines_available = rows - lines_required_for_statusbar() + - lines_required_for_header(); + + list_req = lines_required_for_list(); + graph_req = lines_required_for_graph(); + details_req = lines_required_for_details(); + info_req = lines_required_for_info(); + + total_req = list_req + graph_req + details_req + info_req; + + if (total_req <= lines_available) { + /* + * Enough lines available for all data to displayed, all + * is good. Display the full list. + */ + list_length = list_req; + goto draw; + } + + /* + * Not enough lines available for full list and all details + * requested... + */ + + if (c_show_list) { + /* + * ... try shortening the list first. + */ + list_length = lines_available - (total_req - list_req); + if (list_length >= c_list_min) + goto draw; + } + + if (c_show_info) { + /* try disabling info */ + list_length = lines_available - (total_req - info_req + 1); + if (list_length >= c_list_min) { + disable_info = 1; + goto draw; + } + } + + if (c_show_details) { + /* ... try disabling details */ + list_length = lines_available - (total_req - details_req + 1); + if (list_length >= c_list_min) { + disable_details = 1; + goto draw; + } + } + + /* ... try disabling graph, details, and info */ + list_length = lines_available - 1 - 1 - 1; + if (list_length >= c_list_min) { + disable_graph = 1; + disable_details = 1; + disable_info = 1; + goto draw; + } + + NEXT_ROW(); + put_line("A minimum of %d lines is required to display content.\n", + (rows - lines_available) + c_list_min + 2); + return; + +draw: + if (selection_offset && list_length > 0) { + /* + * Vertically align the selected element in the middle + * of the list. + */ + offset = selection_offset - (list_length / 2); + + /* + * If element 0..(list_length/2) is selected, offset is + * negative here. Start drawing from first element. + */ + if (offset < 0) + offset = 0; + + /* + * Ensure the full list length is used if one of the + * last (list_length/2) elements is selected. + */ + if (offset > (list_req - list_length)) + offset = (list_req - list_length); + + if (offset >= list_req) + BUG(); + } + + if (c_show_list) { + draw_element_list(); + } else { + NEXT_ROW(); + hline(ACS_HLINE, cols); + center_text(" Press %c to enable list view ", + KEY_TOGGLE_LIST); + } + + /* + * Graphical statistics + */ + NEXT_ROW(); + hline(ACS_HLINE, cols); + mvaddch(row, LIST_COL_1, ACS_BTEE); + mvaddch(row, LIST_COL_2, ACS_BTEE); + + if (!c_show_graph) + center_text(" Press %c to enable graphical statistics ", + KEY_TOGGLE_GRAPH); + else { + if (disable_graph) + center_text(" Increase screen height to see graphical statistics "); + else + draw_graph(); + } + + empty_lines = rows - row - details_req - info_req + - lines_required_for_statusbar() - 1; + + while (empty_lines-- > 0) { + NEXT_ROW(); + put_line(""); + } + + /* + * Detailed statistics + */ + NEXT_ROW(); + hline(ACS_HLINE, cols); + + if (!c_show_details) + center_text(" Press %c to enable detailed statistics ", + KEY_TOGGLE_DETAILS); + else { + if (disable_details) + center_text(" Increase screen height to see detailed statistics "); + else + draw_details(); + } + + /* + * Additional information + */ + NEXT_ROW(); + hline(ACS_HLINE, cols); + + if (!c_show_info) + center_text(" Press %c to enable additional information ", + KEY_TOGGLE_INFO); + else { + if (disable_info) + center_text(" Increase screen height to see additional information "); + else + draw_info(); + } +} + + +static void curses_draw(void) +{ + row = 0; + move(0,0); + + getmaxyx(stdscr, rows, cols); + + if (rows < 4) { + clear(); + put_line("Screen must be at least 4 rows in height"); + goto out; + } + + if (cols < 48) { + clear(); + put_line("Screen must be at least 48 columns width"); + goto out; + } + + current_element = element_current(); + current_attr = attr_current(); + + draw_header(); + + apply_layout(LAYOUT_DEFAULT); + draw_content(); + + /* fill empty lines with blanks */ + while (row < (rows - 1 - lines_required_for_statusbar())) { + move(++row, 0); + put_line(""); + } + + draw_statusbar(); + + if (quit_mode) + print_message(" Really Quit? (y/n) "); + else if (print_help) { + if (help_page == 0) + draw_help(); +#if 0 + else + draw_help_2(); +#endif + } + +out: + attrset(0); + refresh(); +} + +static int handle_input(int ch) +{ + switch (ch) + { + case 'q': + if (print_help) + print_help = 0; + else + quit_mode = quit_mode ? 0 : 1; + return 1; + + case 0x1b: + quit_mode = 0; + print_help = 0; + return 1; + + case 'y': + if (quit_mode) + exit(0); + break; + + case 'n': + if (quit_mode) + quit_mode = 0; + return 1; + + case 12: + case KEY_CLEAR: +#ifdef HAVE_REDRAWWIN + redrawwin(stdscr); +#endif + clear(); + return 1; + + case '?': + clear(); + print_help = 1; + return 1; + + case KEY_TOGGLE_GRAPH: + c_show_graph = !c_show_graph; + if (c_show_graph && !c_ngraph) + c_ngraph = 1; + return 1; + + case KEY_TOGGLE_DETAILS: + c_show_details = !c_show_details; + return 1; + + case KEY_TOGGLE_LIST: + c_show_list = !c_show_list; + return 1; + + case KEY_TOGGLE_INFO: + c_show_info = !c_show_info; + return 1; + + case KEY_COLLECT_HISTORY: + if (current_attr) { + attr_start_collecting_history(current_attr); + return 1; + } + break; + + case KEY_PPAGE: + { + int i; + for (i = 1; i < list_length; i++) + element_select_prev(); + } + return 1; + + case KEY_NPAGE: + { + int i; + for (i = 1; i < list_length; i++) + element_select_next(); + } + return 1; + + case KEY_DOWN: + element_select_next(); + return 1; + + case KEY_UP: + element_select_prev(); + return 1; + + case KEY_LEFT: + attr_select_prev(); + return 1; + + case KEY_RIGHT: + attr_select_next(); + return 1; + + case ']': + group_select_next(); + return 1; + + case '[': + group_select_prev(); + return 1; + + case '<': + c_ngraph--; + if (c_ngraph <= 1) + c_ngraph = 1; + return 1; + + case '>': + c_ngraph++; + if (c_ngraph > 32) + c_ngraph = 32; + return 1; + + case '\t': + history_select_next(); + return 1; + } + + return 0; +} + +static void curses_pre(void) +{ + for (;;) { + int ch = getch(); + + if (ch == -1) + break; + + if (handle_input(ch)) + curses_draw(); + } +} + +static void print_module_help(void) +{ + printf( + "curses - Curses Output\n" \ + "\n" \ + " Interactive curses UI. Press '?' to see help.\n" \ + " Author: Thomas Graf <tgraf@suug.ch>\n" \ + "\n" \ + " Options:\n" \ + " fgchar=CHAR Foreground character (default: '*')\n" \ + " bgchar=CHAR Background character (default: '.')\n" \ + " nchar=CHAR Noise character (default: ':')\n" \ + " uchar=CHAR Unknown character (default: '?')\n" \ + " gheight=NUM Height of graph (default: 6)\n" \ + " gwidth=NUM Width of graph (default: 60)\n" \ + " ngraph=NUM Number of graphs (default: 1)\n" \ + " nocolors Do not use colors\n" \ + " graph Show graphical stats by default\n" \ + " details Show detailed stats by default\n" \ + " minlist=INT Minimum item list length\n"); +} + +static void curses_parse_opt(const char *type, const char *value) +{ + if (!strcasecmp(type, "fgchar") && value) + c_graph_cfg.gc_foreground = value[0]; + else if (!strcasecmp(type, "bgchar") && value) + c_graph_cfg.gc_background = value[0]; + else if (!strcasecmp(type, "nchar") && value) + c_graph_cfg.gc_noise = value[0]; + else if (!strcasecmp(type, "uchar") && value) + c_graph_cfg.gc_unknown = value[0]; + else if (!strcasecmp(type, "gheight") && value) + c_graph_cfg.gc_height = strtol(value, NULL, 0); + else if (!strcasecmp(type, "gwidth") && value) + c_graph_cfg.gc_width = strtol(value, NULL, 0); + else if (!strcasecmp(type, "ngraph")) { + c_ngraph = strtol(value, NULL, 0); + c_show_graph = !!c_ngraph; + } else if (!strcasecmp(type, "details")) + c_show_details = 1; + else if (!strcasecmp(type, "nocolors")) + c_use_colors = 0; + else if (!strcasecmp(type, "minlist") && value) + c_list_min = strtol(value, NULL, 0); + else if (!strcasecmp(type, "help")) { + print_module_help(); + exit(0); + } +} + +static struct bmon_module curses_ops = { + .m_name = "curses", + .m_flags = BMON_MODULE_DEFAULT, + .m_init = curses_init, + .m_shutdown = curses_shutdown, + .m_pre = curses_pre, + .m_do = curses_draw, + .m_parse_opt = curses_parse_opt, +}; + +static void __init do_curses_init(void) +{ + output_register(&curses_ops); +} |