1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) 2008 Nir Tzachar <nir.tzachar@gmail.com> 4 * 5 * Derived from menuconfig. 6 */ 7 #include "nconf.h" 8 #include "lkc.h" 9 10 /* a list of all the different widgets we use */ 11 attributes_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 */ 23 static 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 */ 68 static void normal_color_theme(void) 69 { 70 /* automatically add color... */ 71 #define mkattr(name, attr) do { \ 72 attributes[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 100 static 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 132 void set_colors(void) 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 !!! */ 147 void 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 175 int 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 189 const char *get_line(const char *text, int line_no) 190 { 191 int i; 192 int lines = 0; 193 194 if (!text) 195 return NULL; 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 203 int 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. */ 214 void 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 */ 241 int 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 357 int 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, lines, columns, win_lines, win_cols; 368 int res = -1; 369 int cursor_position = strlen(init); 370 int cursor_form_win; 371 char *result = *resultp; 372 373 getmaxyx(stdscr, lines, columns); 374 375 if (strlen(init)+1 > *result_len) { 376 *result_len = strlen(init)+1; 377 *resultp = result = xrealloc(result, *result_len); 378 } 379 380 /* find the widest line of msg: */ 381 prompt_lines = get_line_no(prompt); 382 for (i = 0; i < prompt_lines; i++) { 383 const char *line = get_line(prompt, i); 384 int len = get_line_length(line); 385 prompt_width = max(prompt_width, len); 386 } 387 388 if (title) 389 prompt_width = max(prompt_width, strlen(title)); 390 391 win_lines = min(prompt_lines+6, lines-2); 392 win_cols = min(prompt_width+7, columns-2); 393 prompt_lines = max(win_lines-6, 0); 394 prompt_width = max(win_cols-7, 0); 395 396 /* place dialog in middle of screen */ 397 y = (lines-win_lines)/2; 398 x = (columns-win_cols)/2; 399 400 strncpy(result, init, *result_len); 401 402 /* create the windows */ 403 win = newwin(win_lines, win_cols, y, x); 404 prompt_win = derwin(win, prompt_lines+1, prompt_width, 2, 2); 405 form_win = derwin(win, 1, prompt_width, prompt_lines+3, 2); 406 keypad(form_win, TRUE); 407 408 (void) wattrset(form_win, attributes[INPUT_FIELD]); 409 410 (void) wattrset(win, attributes[INPUT_BOX]); 411 box(win, 0, 0); 412 (void) wattrset(win, attributes[INPUT_HEADING]); 413 if (title) 414 mvwprintw(win, 0, 3, "%s", title); 415 416 /* print message */ 417 (void) wattrset(prompt_win, attributes[INPUT_TEXT]); 418 fill_window(prompt_win, prompt); 419 420 mvwprintw(form_win, 0, 0, "%*s", prompt_width, " "); 421 cursor_form_win = min(cursor_position, prompt_width-1); 422 mvwprintw(form_win, 0, 0, "%s", 423 result + cursor_position-cursor_form_win); 424 425 /* create panels */ 426 panel = new_panel(win); 427 428 /* show the cursor */ 429 curs_set(1); 430 431 touchwin(win); 432 refresh_all_windows(main_window); 433 while ((res = wgetch(form_win))) { 434 int len = strlen(result); 435 switch (res) { 436 case 10: /* ENTER */ 437 case 27: /* ESCAPE */ 438 case KEY_F(F_HELP): 439 case KEY_F(F_EXIT): 440 case KEY_F(F_BACK): 441 break; 442 case 8: /* ^H */ 443 case 127: /* ^? */ 444 case KEY_BACKSPACE: 445 if (cursor_position > 0) { 446 memmove(&result[cursor_position-1], 447 &result[cursor_position], 448 len-cursor_position+1); 449 cursor_position--; 450 cursor_form_win--; 451 len--; 452 } 453 break; 454 case KEY_DC: 455 if (cursor_position >= 0 && cursor_position < len) { 456 memmove(&result[cursor_position], 457 &result[cursor_position+1], 458 len-cursor_position+1); 459 len--; 460 } 461 break; 462 case KEY_UP: 463 case KEY_RIGHT: 464 if (cursor_position < len) { 465 cursor_position++; 466 cursor_form_win++; 467 } 468 break; 469 case KEY_DOWN: 470 case KEY_LEFT: 471 if (cursor_position > 0) { 472 cursor_position--; 473 cursor_form_win--; 474 } 475 break; 476 case KEY_HOME: 477 cursor_position = 0; 478 cursor_form_win = 0; 479 break; 480 case KEY_END: 481 cursor_position = len; 482 cursor_form_win = min(cursor_position, prompt_width-1); 483 break; 484 default: 485 if ((isgraph(res) || isspace(res))) { 486 /* one for new char, one for '\0' */ 487 if (len+2 > *result_len) { 488 *result_len = len+2; 489 *resultp = result = realloc(result, 490 *result_len); 491 } 492 /* insert the char at the proper position */ 493 memmove(&result[cursor_position+1], 494 &result[cursor_position], 495 len-cursor_position+1); 496 result[cursor_position] = res; 497 cursor_position++; 498 cursor_form_win++; 499 len++; 500 } else { 501 mvprintw(0, 0, "unknown key: %d\n", res); 502 } 503 break; 504 } 505 if (cursor_form_win < 0) 506 cursor_form_win = 0; 507 else if (cursor_form_win > prompt_width-1) 508 cursor_form_win = prompt_width-1; 509 510 wmove(form_win, 0, 0); 511 wclrtoeol(form_win); 512 mvwprintw(form_win, 0, 0, "%*s", prompt_width, " "); 513 mvwprintw(form_win, 0, 0, "%s", 514 result + cursor_position-cursor_form_win); 515 wmove(form_win, 0, cursor_form_win); 516 touchwin(win); 517 refresh_all_windows(main_window); 518 519 if (res == 10) { 520 res = 0; 521 break; 522 } else if (res == 27 || res == KEY_F(F_BACK) || 523 res == KEY_F(F_EXIT)) { 524 res = KEY_EXIT; 525 break; 526 } else if (res == KEY_F(F_HELP)) { 527 res = 1; 528 break; 529 } 530 } 531 532 /* hide the cursor */ 533 curs_set(0); 534 del_panel(panel); 535 delwin(prompt_win); 536 delwin(form_win); 537 delwin(win); 538 return res; 539 } 540 541 /* refresh all windows in the correct order */ 542 void refresh_all_windows(WINDOW *main_window) 543 { 544 update_panels(); 545 touchwin(main_window); 546 refresh(); 547 } 548 549 /* layman's scrollable window... */ 550 void show_scroll_win(WINDOW *main_window, 551 const char *title, 552 const char *text) 553 { 554 int res; 555 int total_lines = get_line_no(text); 556 int x, y, lines, columns; 557 int start_x = 0, start_y = 0; 558 int text_lines = 0, text_cols = 0; 559 int total_cols = 0; 560 int win_cols = 0; 561 int win_lines = 0; 562 int i = 0; 563 WINDOW *win; 564 WINDOW *pad; 565 PANEL *panel; 566 567 getmaxyx(stdscr, lines, columns); 568 569 /* find the widest line of msg: */ 570 total_lines = get_line_no(text); 571 for (i = 0; i < total_lines; i++) { 572 const char *line = get_line(text, i); 573 int len = get_line_length(line); 574 total_cols = max(total_cols, len+2); 575 } 576 577 /* create the pad */ 578 pad = newpad(total_lines+10, total_cols+10); 579 (void) wattrset(pad, attributes[SCROLLWIN_TEXT]); 580 fill_window(pad, text); 581 582 win_lines = min(total_lines+4, lines-2); 583 win_cols = min(total_cols+2, columns-2); 584 text_lines = max(win_lines-4, 0); 585 text_cols = max(win_cols-2, 0); 586 587 /* place window in middle of screen */ 588 y = (lines-win_lines)/2; 589 x = (columns-win_cols)/2; 590 591 win = newwin(win_lines, win_cols, y, x); 592 keypad(win, TRUE); 593 /* show the help in the help window, and show the help panel */ 594 (void) wattrset(win, attributes[SCROLLWIN_BOX]); 595 box(win, 0, 0); 596 (void) wattrset(win, attributes[SCROLLWIN_HEADING]); 597 mvwprintw(win, 0, 3, " %s ", title); 598 panel = new_panel(win); 599 600 /* handle scrolling */ 601 do { 602 603 copywin(pad, win, start_y, start_x, 2, 2, text_lines, 604 text_cols, 0); 605 print_in_middle(win, 606 text_lines+2, 607 0, 608 text_cols, 609 "<OK>", 610 attributes[DIALOG_MENU_FORE]); 611 wrefresh(win); 612 613 res = wgetch(win); 614 switch (res) { 615 case KEY_NPAGE: 616 case ' ': 617 case 'd': 618 start_y += text_lines-2; 619 break; 620 case KEY_PPAGE: 621 case 'u': 622 start_y -= text_lines+2; 623 break; 624 case KEY_HOME: 625 start_y = 0; 626 break; 627 case KEY_END: 628 start_y = total_lines-text_lines; 629 break; 630 case KEY_DOWN: 631 case 'j': 632 start_y++; 633 break; 634 case KEY_UP: 635 case 'k': 636 start_y--; 637 break; 638 case KEY_LEFT: 639 case 'h': 640 start_x--; 641 break; 642 case KEY_RIGHT: 643 case 'l': 644 start_x++; 645 break; 646 } 647 if (res == 10 || res == 27 || res == 'q' || 648 res == KEY_F(F_HELP) || res == KEY_F(F_BACK) || 649 res == KEY_F(F_EXIT)) 650 break; 651 if (start_y < 0) 652 start_y = 0; 653 if (start_y >= total_lines-text_lines) 654 start_y = total_lines-text_lines; 655 if (start_x < 0) 656 start_x = 0; 657 if (start_x >= total_cols-text_cols) 658 start_x = total_cols-text_cols; 659 } while (res); 660 661 del_panel(panel); 662 delwin(win); 663 refresh_all_windows(main_window); 664 } 665