1/* 2 * Copyright (C) 2008 Nir Tzachar <nir.tzachar@gmail.com? 3 * Released under the terms of the GNU GPL v2.0. 4 * 5 * Derived from menuconfig. 6 * 7 */ 8#include "nconf.h" 9 10/* a list of all the different widgets we use */ 11attributes_t attributes[ATTR_MAX+1] = {0}; 12 13/* available colors: 14 COLOR_BLACK 0 15 COLOR_RED 1 16 COLOR_GREEN 2 17 COLOR_YELLOW 3 18 COLOR_BLUE 4 19 COLOR_MAGENTA 5 20 COLOR_CYAN 6 21 COLOR_WHITE 7 22 */ 23static void set_normal_colors(void) 24{ 25 init_pair(NORMAL, -1, -1); 26 init_pair(MAIN_HEADING, COLOR_MAGENTA, -1); 27 28 /* FORE is for the selected item */ 29 init_pair(MAIN_MENU_FORE, -1, -1); 30 /* BACK for all the rest */ 31 init_pair(MAIN_MENU_BACK, -1, -1); 32 init_pair(MAIN_MENU_GREY, -1, -1); 33 init_pair(MAIN_MENU_HEADING, COLOR_GREEN, -1); 34 init_pair(MAIN_MENU_BOX, COLOR_YELLOW, -1); 35 36 init_pair(SCROLLWIN_TEXT, -1, -1); 37 init_pair(SCROLLWIN_HEADING, COLOR_GREEN, -1); 38 init_pair(SCROLLWIN_BOX, COLOR_YELLOW, -1); 39 40 init_pair(DIALOG_TEXT, -1, -1); 41 init_pair(DIALOG_BOX, COLOR_YELLOW, -1); 42 init_pair(DIALOG_MENU_BACK, COLOR_YELLOW, -1); 43 init_pair(DIALOG_MENU_FORE, COLOR_RED, -1); 44 45 init_pair(INPUT_BOX, COLOR_YELLOW, -1); 46 init_pair(INPUT_HEADING, COLOR_GREEN, -1); 47 init_pair(INPUT_TEXT, -1, -1); 48 init_pair(INPUT_FIELD, -1, -1); 49 50 init_pair(FUNCTION_HIGHLIGHT, -1, -1); 51 init_pair(FUNCTION_TEXT, COLOR_YELLOW, -1); 52} 53 54/* available attributes: 55 A_NORMAL Normal display (no highlight) 56 A_STANDOUT Best highlighting mode of the terminal. 57 A_UNDERLINE Underlining 58 A_REVERSE Reverse video 59 A_BLINK Blinking 60 A_DIM Half bright 61 A_BOLD Extra bright or bold 62 A_PROTECT Protected mode 63 A_INVIS Invisible or blank mode 64 A_ALTCHARSET Alternate character set 65 A_CHARTEXT Bit-mask to extract a character 66 COLOR_PAIR(n) Color-pair number n 67 */ 68static void normal_color_theme(void) 69{ 70 /* automatically add color... */ 71#define mkattr(name, attr) do { \ 72attributes[name] = attr | COLOR_PAIR(name); } while (0) 73 mkattr(NORMAL, NORMAL); 74 mkattr(MAIN_HEADING, A_BOLD | A_UNDERLINE); 75 76 mkattr(MAIN_MENU_FORE, A_REVERSE); 77 mkattr(MAIN_MENU_BACK, A_NORMAL); 78 mkattr(MAIN_MENU_GREY, A_NORMAL); 79 mkattr(MAIN_MENU_HEADING, A_BOLD); 80 mkattr(MAIN_MENU_BOX, A_NORMAL); 81 82 mkattr(SCROLLWIN_TEXT, A_NORMAL); 83 mkattr(SCROLLWIN_HEADING, A_BOLD); 84 mkattr(SCROLLWIN_BOX, A_BOLD); 85 86 mkattr(DIALOG_TEXT, A_BOLD); 87 mkattr(DIALOG_BOX, A_BOLD); 88 mkattr(DIALOG_MENU_FORE, A_STANDOUT); 89 mkattr(DIALOG_MENU_BACK, A_NORMAL); 90 91 mkattr(INPUT_BOX, A_NORMAL); 92 mkattr(INPUT_HEADING, A_BOLD); 93 mkattr(INPUT_TEXT, A_NORMAL); 94 mkattr(INPUT_FIELD, A_UNDERLINE); 95 96 mkattr(FUNCTION_HIGHLIGHT, A_BOLD); 97 mkattr(FUNCTION_TEXT, A_REVERSE); 98} 99 100static void no_colors_theme(void) 101{ 102 /* automatically add highlight, no color */ 103#define mkattrn(name, attr) { attributes[name] = attr; } 104 105 mkattrn(NORMAL, NORMAL); 106 mkattrn(MAIN_HEADING, A_BOLD | A_UNDERLINE); 107 108 mkattrn(MAIN_MENU_FORE, A_STANDOUT); 109 mkattrn(MAIN_MENU_BACK, A_NORMAL); 110 mkattrn(MAIN_MENU_GREY, A_NORMAL); 111 mkattrn(MAIN_MENU_HEADING, A_BOLD); 112 mkattrn(MAIN_MENU_BOX, A_NORMAL); 113 114 mkattrn(SCROLLWIN_TEXT, A_NORMAL); 115 mkattrn(SCROLLWIN_HEADING, A_BOLD); 116 mkattrn(SCROLLWIN_BOX, A_BOLD); 117 118 mkattrn(DIALOG_TEXT, A_NORMAL); 119 mkattrn(DIALOG_BOX, A_BOLD); 120 mkattrn(DIALOG_MENU_FORE, A_STANDOUT); 121 mkattrn(DIALOG_MENU_BACK, A_NORMAL); 122 123 mkattrn(INPUT_BOX, A_BOLD); 124 mkattrn(INPUT_HEADING, A_BOLD); 125 mkattrn(INPUT_TEXT, A_NORMAL); 126 mkattrn(INPUT_FIELD, A_UNDERLINE); 127 128 mkattrn(FUNCTION_HIGHLIGHT, A_BOLD); 129 mkattrn(FUNCTION_TEXT, A_REVERSE); 130} 131 132void set_colors() 133{ 134 start_color(); 135 use_default_colors(); 136 set_normal_colors(); 137 if (has_colors()) { 138 normal_color_theme(); 139 } else { 140 /* give defaults */ 141 no_colors_theme(); 142 } 143} 144 145 146/* this changes the windows attributes !!! */ 147void print_in_middle(WINDOW *win, 148 int starty, 149 int startx, 150 int width, 151 const char *string, 152 chtype color) 153{ int length, x, y; 154 float temp; 155 156 157 if (win == NULL) 158 win = stdscr; 159 getyx(win, y, x); 160 if (startx != 0) 161 x = startx; 162 if (starty != 0) 163 y = starty; 164 if (width == 0) 165 width = 80; 166 167 length = strlen(string); 168 temp = (width - length) / 2; 169 x = startx + (int)temp; 170 (void) wattrset(win, color); 171 mvwprintw(win, y, x, "%s", string); 172 refresh(); 173} 174 175int get_line_no(const char *text) 176{ 177 int i; 178 int total = 1; 179 180 if (!text) 181 return 0; 182 183 for (i = 0; text[i] != '\0'; i++) 184 if (text[i] == '\n') 185 total++; 186 return total; 187} 188 189const char *get_line(const char *text, int line_no) 190{ 191 int i; 192 int lines = 0; 193 194 if (!text) 195 return 0; 196 197 for (i = 0; text[i] != '\0' && lines < line_no; i++) 198 if (text[i] == '\n') 199 lines++; 200 return text+i; 201} 202 203int get_line_length(const char *line) 204{ 205 int res = 0; 206 while (*line != '\0' && *line != '\n') { 207 line++; 208 res++; 209 } 210 return res; 211} 212 213/* print all lines to the window. */ 214void fill_window(WINDOW *win, const char *text) 215{ 216 int x, y; 217 int total_lines = get_line_no(text); 218 int i; 219 220 getmaxyx(win, y, x); 221 /* do not go over end of line */ 222 total_lines = min(total_lines, y); 223 for (i = 0; i < total_lines; i++) { 224 char tmp[x+10]; 225 const char *line = get_line(text, i); 226 int len = get_line_length(line); 227 strncpy(tmp, line, min(len, x)); 228 tmp[len] = '\0'; 229 mvwprintw(win, i, 0, "%s", tmp); 230 } 231} 232 233/* get the message, and buttons. 234 * each button must be a char* 235 * return the selected button 236 * 237 * this dialog is used for 2 different things: 238 * 1) show a text box, no buttons. 239 * 2) show a dialog, with horizontal buttons 240 */ 241int btn_dialog(WINDOW *main_window, const char *msg, int btn_num, ...) 242{ 243 va_list ap; 244 char *btn; 245 int btns_width = 0; 246 int msg_lines = 0; 247 int msg_width = 0; 248 int total_width; 249 int win_rows = 0; 250 WINDOW *win; 251 WINDOW *msg_win; 252 WINDOW *menu_win; 253 MENU *menu; 254 ITEM *btns[btn_num+1]; 255 int i, x, y; 256 int res = -1; 257 258 259 va_start(ap, btn_num); 260 for (i = 0; i < btn_num; i++) { 261 btn = va_arg(ap, char *); 262 btns[i] = new_item(btn, ""); 263 btns_width += strlen(btn)+1; 264 } 265 va_end(ap); 266 btns[btn_num] = NULL; 267 268 /* find the widest line of msg: */ 269 msg_lines = get_line_no(msg); 270 for (i = 0; i < msg_lines; i++) { 271 const char *line = get_line(msg, i); 272 int len = get_line_length(line); 273 if (msg_width < len) 274 msg_width = len; 275 } 276 277 total_width = max(msg_width, btns_width); 278 /* place dialog in middle of screen */ 279 y = (getmaxy(stdscr)-(msg_lines+4))/2; 280 x = (getmaxx(stdscr)-(total_width+4))/2; 281 282 283 /* create the windows */ 284 if (btn_num > 0) 285 win_rows = msg_lines+4; 286 else 287 win_rows = msg_lines+2; 288 289 win = newwin(win_rows, total_width+4, y, x); 290 keypad(win, TRUE); 291 menu_win = derwin(win, 1, btns_width, win_rows-2, 292 1+(total_width+2-btns_width)/2); 293 menu = new_menu(btns); 294 msg_win = derwin(win, win_rows-2, msg_width, 1, 295 1+(total_width+2-msg_width)/2); 296 297 set_menu_fore(menu, attributes[DIALOG_MENU_FORE]); 298 set_menu_back(menu, attributes[DIALOG_MENU_BACK]); 299 300 (void) wattrset(win, attributes[DIALOG_BOX]); 301 box(win, 0, 0); 302 303 /* print message */ 304 (void) wattrset(msg_win, attributes[DIALOG_TEXT]); 305 fill_window(msg_win, msg); 306 307 set_menu_win(menu, win); 308 set_menu_sub(menu, menu_win); 309 set_menu_format(menu, 1, btn_num); 310 menu_opts_off(menu, O_SHOWDESC); 311 menu_opts_off(menu, O_SHOWMATCH); 312 menu_opts_on(menu, O_ONEVALUE); 313 menu_opts_on(menu, O_NONCYCLIC); 314 set_menu_mark(menu, ""); 315 post_menu(menu); 316 317 318 touchwin(win); 319 refresh_all_windows(main_window); 320 while ((res = wgetch(win))) { 321 switch (res) { 322 case KEY_LEFT: 323 menu_driver(menu, REQ_LEFT_ITEM); 324 break; 325 case KEY_RIGHT: 326 menu_driver(menu, REQ_RIGHT_ITEM); 327 break; 328 case 10: /* ENTER */ 329 case 27: /* ESCAPE */ 330 case ' ': 331 case KEY_F(F_BACK): 332 case KEY_F(F_EXIT): 333 break; 334 } 335 touchwin(win); 336 refresh_all_windows(main_window); 337 338 if (res == 10 || res == ' ') { 339 res = item_index(current_item(menu)); 340 break; 341 } else if (res == 27 || res == KEY_F(F_BACK) || 342 res == KEY_F(F_EXIT)) { 343 res = KEY_EXIT; 344 break; 345 } 346 } 347 348 unpost_menu(menu); 349 free_menu(menu); 350 for (i = 0; i < btn_num; i++) 351 free_item(btns[i]); 352 353 delwin(win); 354 return res; 355} 356 357int dialog_inputbox(WINDOW *main_window, 358 const char *title, const char *prompt, 359 const char *init, char **resultp, int *result_len) 360{ 361 int prompt_lines = 0; 362 int prompt_width = 0; 363 WINDOW *win; 364 WINDOW *prompt_win; 365 WINDOW *form_win; 366 PANEL *panel; 367 int i, x, y; 368 int res = -1; 369 int cursor_position = strlen(init); 370 int cursor_form_win; 371 char *result = *resultp; 372 373 if (strlen(init)+1 > *result_len) { 374 *result_len = strlen(init)+1; 375 *resultp = result = realloc(result, *result_len); 376 } 377 378 /* find the widest line of msg: */ 379 prompt_lines = get_line_no(prompt); 380 for (i = 0; i < prompt_lines; i++) { 381 const char *line = get_line(prompt, i); 382 int len = get_line_length(line); 383 prompt_width = max(prompt_width, len); 384 } 385 386 if (title) 387 prompt_width = max(prompt_width, strlen(title)); 388 389 /* place dialog in middle of screen */ 390 y = (getmaxy(stdscr)-(prompt_lines+4))/2; 391 x = (getmaxx(stdscr)-(prompt_width+4))/2; 392 393 strncpy(result, init, *result_len); 394 395 /* create the windows */ 396 win = newwin(prompt_lines+6, prompt_width+7, y, x); 397 prompt_win = derwin(win, prompt_lines+1, prompt_width, 2, 2); 398 form_win = derwin(win, 1, prompt_width, prompt_lines+3, 2); 399 keypad(form_win, TRUE); 400 401 (void) wattrset(form_win, attributes[INPUT_FIELD]); 402 403 (void) wattrset(win, attributes[INPUT_BOX]); 404 box(win, 0, 0); 405 (void) wattrset(win, attributes[INPUT_HEADING]); 406 if (title) 407 mvwprintw(win, 0, 3, "%s", title); 408 409 /* print message */ 410 (void) wattrset(prompt_win, attributes[INPUT_TEXT]); 411 fill_window(prompt_win, prompt); 412 413 mvwprintw(form_win, 0, 0, "%*s", prompt_width, " "); 414 cursor_form_win = min(cursor_position, prompt_width-1); 415 mvwprintw(form_win, 0, 0, "%s", 416 result + cursor_position-cursor_form_win); 417 418 /* create panels */ 419 panel = new_panel(win); 420 421 /* show the cursor */ 422 curs_set(1); 423 424 touchwin(win); 425 refresh_all_windows(main_window); 426 while ((res = wgetch(form_win))) { 427 int len = strlen(result); 428 switch (res) { 429 case 10: /* ENTER */ 430 case 27: /* ESCAPE */ 431 case KEY_F(F_HELP): 432 case KEY_F(F_EXIT): 433 case KEY_F(F_BACK): 434 break; 435 case 127: 436 case KEY_BACKSPACE: 437 if (cursor_position > 0) { 438 memmove(&result[cursor_position-1], 439 &result[cursor_position], 440 len-cursor_position+1); 441 cursor_position--; 442 cursor_form_win--; 443 len--; 444 } 445 break; 446 case KEY_DC: 447 if (cursor_position >= 0 && cursor_position < len) { 448 memmove(&result[cursor_position], 449 &result[cursor_position+1], 450 len-cursor_position+1); 451 len--; 452 } 453 break; 454 case KEY_UP: 455 case KEY_RIGHT: 456 if (cursor_position < len) { 457 cursor_position++; 458 cursor_form_win++; 459 } 460 break; 461 case KEY_DOWN: 462 case KEY_LEFT: 463 if (cursor_position > 0) { 464 cursor_position--; 465 cursor_form_win--; 466 } 467 break; 468 case KEY_HOME: 469 cursor_position = 0; 470 cursor_form_win = 0; 471 break; 472 case KEY_END: 473 cursor_position = len; 474 cursor_form_win = min(cursor_position, prompt_width-1); 475 break; 476 default: 477 if ((isgraph(res) || isspace(res))) { 478 /* one for new char, one for '\0' */ 479 if (len+2 > *result_len) { 480 *result_len = len+2; 481 *resultp = result = realloc(result, 482 *result_len); 483 } 484 /* insert the char at the proper position */ 485 memmove(&result[cursor_position+1], 486 &result[cursor_position], 487 len-cursor_position+1); 488 result[cursor_position] = res; 489 cursor_position++; 490 cursor_form_win++; 491 len++; 492 } else { 493 mvprintw(0, 0, "unknown key: %d\n", res); 494 } 495 break; 496 } 497 if (cursor_form_win < 0) 498 cursor_form_win = 0; 499 else if (cursor_form_win > prompt_width-1) 500 cursor_form_win = prompt_width-1; 501 502 wmove(form_win, 0, 0); 503 wclrtoeol(form_win); 504 mvwprintw(form_win, 0, 0, "%*s", prompt_width, " "); 505 mvwprintw(form_win, 0, 0, "%s", 506 result + cursor_position-cursor_form_win); 507 wmove(form_win, 0, cursor_form_win); 508 touchwin(win); 509 refresh_all_windows(main_window); 510 511 if (res == 10) { 512 res = 0; 513 break; 514 } else if (res == 27 || res == KEY_F(F_BACK) || 515 res == KEY_F(F_EXIT)) { 516 res = KEY_EXIT; 517 break; 518 } else if (res == KEY_F(F_HELP)) { 519 res = 1; 520 break; 521 } 522 } 523 524 /* hide the cursor */ 525 curs_set(0); 526 del_panel(panel); 527 delwin(prompt_win); 528 delwin(form_win); 529 delwin(win); 530 return res; 531} 532 533/* refresh all windows in the correct order */ 534void refresh_all_windows(WINDOW *main_window) 535{ 536 update_panels(); 537 touchwin(main_window); 538 refresh(); 539} 540 541/* layman's scrollable window... */ 542void show_scroll_win(WINDOW *main_window, 543 const char *title, 544 const char *text) 545{ 546 int res; 547 int total_lines = get_line_no(text); 548 int x, y, lines, columns; 549 int start_x = 0, start_y = 0; 550 int text_lines = 0, text_cols = 0; 551 int total_cols = 0; 552 int win_cols = 0; 553 int win_lines = 0; 554 int i = 0; 555 WINDOW *win; 556 WINDOW *pad; 557 PANEL *panel; 558 559 getmaxyx(stdscr, lines, columns); 560 561 /* find the widest line of msg: */ 562 total_lines = get_line_no(text); 563 for (i = 0; i < total_lines; i++) { 564 const char *line = get_line(text, i); 565 int len = get_line_length(line); 566 total_cols = max(total_cols, len+2); 567 } 568 569 /* create the pad */ 570 pad = newpad(total_lines+10, total_cols+10); 571 (void) wattrset(pad, attributes[SCROLLWIN_TEXT]); 572 fill_window(pad, text); 573 574 win_lines = min(total_lines+4, lines-2); 575 win_cols = min(total_cols+2, columns-2); 576 text_lines = max(win_lines-4, 0); 577 text_cols = max(win_cols-2, 0); 578 579 /* place window in middle of screen */ 580 y = (lines-win_lines)/2; 581 x = (columns-win_cols)/2; 582 583 win = newwin(win_lines, win_cols, y, x); 584 keypad(win, TRUE); 585 /* show the help in the help window, and show the help panel */ 586 (void) wattrset(win, attributes[SCROLLWIN_BOX]); 587 box(win, 0, 0); 588 (void) wattrset(win, attributes[SCROLLWIN_HEADING]); 589 mvwprintw(win, 0, 3, " %s ", title); 590 panel = new_panel(win); 591 592 /* handle scrolling */ 593 do { 594 595 copywin(pad, win, start_y, start_x, 2, 2, text_lines, 596 text_cols, 0); 597 print_in_middle(win, 598 text_lines+2, 599 0, 600 text_cols, 601 "<OK>", 602 attributes[DIALOG_MENU_FORE]); 603 wrefresh(win); 604 605 res = wgetch(win); 606 switch (res) { 607 case KEY_NPAGE: 608 case ' ': 609 case 'd': 610 start_y += text_lines-2; 611 break; 612 case KEY_PPAGE: 613 case 'u': 614 start_y -= text_lines+2; 615 break; 616 case KEY_HOME: 617 start_y = 0; 618 break; 619 case KEY_END: 620 start_y = total_lines-text_lines; 621 break; 622 case KEY_DOWN: 623 case 'j': 624 start_y++; 625 break; 626 case KEY_UP: 627 case 'k': 628 start_y--; 629 break; 630 case KEY_LEFT: 631 case 'h': 632 start_x--; 633 break; 634 case KEY_RIGHT: 635 case 'l': 636 start_x++; 637 break; 638 } 639 if (res == 10 || res == 27 || res == 'q' || 640 res == KEY_F(F_HELP) || res == KEY_F(F_BACK) || 641 res == KEY_F(F_EXIT)) 642 break; 643 if (start_y < 0) 644 start_y = 0; 645 if (start_y >= total_lines-text_lines) 646 start_y = total_lines-text_lines; 647 if (start_x < 0) 648 start_x = 0; 649 if (start_x >= total_cols-text_cols) 650 start_x = total_cols-text_cols; 651 } while (res); 652 653 del_panel(panel); 654 delwin(win); 655 refresh_all_windows(main_window); 656} 657