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