fun_menu.c (27867B)
1 #include <stdio.h> 2 #include <stdbool.h> 3 #include <assert.h> 4 #include <locale.h> // for setlocale() 5 #include <string.h> // for strncpy() and strlen() 6 #include <ctype.h> // for isalnum() 7 #include <stdlib.h> // for malloc() and free() 8 9 #include <curses.h> 10 #include "fun_menu.h" 11 #include "utils.h" 12 #include "richwin.h" 13 #include "stack.h" 14 #include "cycle.h" 15 16 // macros 17 #define ARRAY_LENGTH(arr) (sizeof (arr) / sizeof ((arr)[0])) 18 #define MEMBER_SIZE(arr) (sizeof ((arr)[0])) 19 20 // default strings/characters 21 #define DEFAULT_WORD "hello" 22 #define DEFAULT_PROMPT "Enter word to draw" 23 #define INDICATOR_CHAR_LEFT '>' 24 #define INDICATOR_CHAR_RIGHT '<' 25 #define INPUT_PLACEHOLDER '_' 26 #define TEXT_CONTINUATION '-' 27 #define INPUT_CONTINUATION '+' 28 29 // default numbers 30 #define WORD_MAXLEN 20 31 #define INDICATORS 2 32 #define BORDERS 2 33 #define PADDINGS 2 34 #define INDICATOR INDICATORS/2 35 #define BORDER BORDERS/2 36 #define PADDING PADDINGS/2 37 38 enum WindowType { BOARD_WIN, MENU_WIN, PROMPT_WIN }; 39 enum AppActionType { APP_DRAW, APP_CLEAN, APP_CHANGE_COLOR, APP_CHANGE_WORD, 40 APP_END}; 41 42 /* structures */ 43 44 // MainWin: pointer to one of the following windows and it's type. used for 45 // converting to/from void * 46 struct MainWin { 47 void *sub; 48 enum WindowType type; 49 struct RichWin *richwin; 50 struct RichWin *(*new_richwin)(void *sub); 51 void *(*draw_window)(void *sub); 52 }; 53 // BoardWin: a resizable drawing board with stats of the word to draw, 54 // background color, whether it's enables/disabled and whether drawing on it is 55 // done 56 struct BoardWin { 57 struct MainWin base; 58 char *word; 59 Cycle *colors; 60 bool on; 61 bool done; 62 }; 63 // MenuWin: a resizable menu with a list of options, and the currently 64 // choosen option from that list 65 struct MenuWin { 66 struct MainWin base; 67 Cycle *options; 68 }; 69 // PromptWin: a resizable user prompt with prompt text, prompt input buffer, 70 // and it's size 71 struct PromptWin { 72 struct MainWin base; 73 const char *prompt; 74 char *input; 75 const int input_maxlen; 76 const int content_minlen; 77 }; 78 // MenuOption: text for a menu option used in a MenuWin and accociated function 79 // to call on choosing that option 80 struct MenuOption { 81 struct BoardWin *(*func)(struct BoardWin *boardwin); 82 enum AppActionType type; 83 const char *text; 84 }; 85 86 /* function declarations */ 87 88 // misc functions 89 static int prompt_user(const char *prompt, char *buf, size_t len); 90 static int mvwaddcstr(WINDOW *win, int y, int x, const char *str, int len, 91 const char break_char); 92 // cleanup functions 93 static void cleanup_main(Stack *win_stack, struct BoardWin *boardwin, 94 struct MenuWin *menuwin); 95 static void cleanup_prompt_user(struct PromptWin *promptwin, bool on_stack); 96 // error handling functions 97 static void write_error(enum ErrorCode code, int line, const char *func_name); 98 static int print_error(void); 99 // window methds: 100 // MainWin methods 101 static void *mainwin_resize(void *window); 102 static void mainwin_cleanup(struct MainWin *mainwin); 103 // BoardWin methods 104 static struct RichWin *boardwin_new_richwin(void *window); 105 static void *boardwin_draw(void *window); 106 static void boardwin_cleanup(struct BoardWin *boardwin); 107 // MenuWin methods 108 static struct RichWin *menuwin_new_richwin(void *window); 109 static void *menuwin_draw(void *window); 110 static void menuwin_cleanup(struct MenuWin *menuwin); 111 // PromptWin methods 112 static struct RichWin *promptwin_new_richwin(void *window); 113 static void *promptwin_draw(void *window); 114 static void promptwin_cleanup(struct PromptWin *promptwin); 115 116 // application actions 117 static struct BoardWin *app_draw(struct BoardWin *boardwin); 118 static struct BoardWin *app_clean(struct BoardWin *boardwin); 119 static struct BoardWin *app_change_word(struct BoardWin *boardwin); 120 static struct BoardWin *app_change_color(struct BoardWin *boardwin); 121 static struct BoardWin *app_end(struct BoardWin *boardwin); 122 123 124 /* globals */ 125 126 // holds curses errors 127 static int err; 128 // remembers whether the terminal supports color 129 static bool colorize; 130 // keeps track of every window for responding to a terminal resize 131 static Stack *window_stack; 132 133 /* constants */ 134 135 // holds error info 136 static struct ErrorInfo { 137 enum ErrorCode code; 138 int line; 139 const char *func_name; 140 } error_info = { 141 .code = ERROR_OK, 142 .line = 0, 143 .func_name = NULL, 144 }; 145 // text representation of error codes 146 static const char *error_texts[ERROR_MAX] = { 147 [ERROR_LOCALE_SETTING] = "Failed to set locale!", 148 [ERROR_COLOR_STARTING] = "Failed to start colors!", 149 [ERROR_CURSES_SETTING] = "Failed to set curses settings!", 150 [ERROR_MEMORY_ALLOCATION] = "Failed to allocate memory!", 151 [ERROR_CHARACTER_INPUT] = "Failed to recieve character from window!", 152 [ERROR_CHARACTER_OUTPUT] = "Failed to print character to window!", 153 [ERROR_WINDOW_CREATION] = "Failed to create a window!", 154 [ERROR_WINDOW_DRAWING] = "Failed to write to a window!", 155 [ERROR_WINDOW_RENDERING] = "Failed to render a window!", 156 [ERROR_COLOR_PAIR_DEFINITION] = "Failed to define a color pair!", 157 [ERROR_OPTION_SETTING] = "Failed to set an option for a window!", 158 [ERROR_UNKNOWN] = "Unknown error!", 159 }; 160 // holds BoardWin's colors to cycle through 161 static const short colors[] = { 162 COLOR_RED, 163 COLOR_GREEN, 164 COLOR_YELLOW, 165 COLOR_BLUE, 166 COLOR_MAGENTA, 167 COLOR_CYAN, 168 }; 169 // holds an iterable vtable 170 static const struct MenuOption menu_options[] = { 171 { 172 .func = app_draw, 173 .type = APP_DRAW, 174 .text = "draw", 175 }, 176 { 177 .func = app_clean, 178 .type = APP_CLEAN, 179 .text = "clean", 180 }, 181 { 182 .func = app_change_color, 183 .type = APP_CHANGE_COLOR, 184 .text = "change color", 185 }, 186 { 187 .func = app_change_word, 188 .type = APP_CHANGE_WORD, 189 .text = "change word", 190 }, 191 { 192 .func = app_end, 193 .type = APP_END, 194 .text = "exit", 195 }, 196 }; 197 198 /* a "little" arrow-driven/hjkl menu practice */ 199 int 200 main(void) 201 { 202 struct BoardWin *boardwin = NULL; 203 struct MenuWin *menuwin = NULL; 204 205 // set locale from environment 206 if (setlocale(LC_ALL, "") == NULL) { 207 write_error(ERROR_LOCALE_SETTING, __LINE__, __func__); 208 goto error; 209 } 210 // start curses 211 (void) initscr(); 212 // start color 213 colorize = has_colors(); 214 if (colorize) { 215 if (start_color() == ERR) { 216 write_error(ERROR_COLOR_STARTING, __LINE__, __func__); 217 goto error; 218 } 219 } 220 // set options 221 if (noecho() == ERR 222 || raw() == ERR 223 || nl() == ERR 224 || leaveok(stdscr, true) == ERR 225 || curs_set(0) == ERR) 226 { 227 write_error(ERROR_CURSES_SETTING, __LINE__, __func__); 228 goto error; 229 } 230 231 // set color pairs 232 if (colorize) { 233 if (init_pair(1, COLOR_BLACK, COLOR_WHITE) == ERR 234 ||init_pair(2, COLOR_BLACK, colors[0]) == ERR) 235 { 236 write_error(ERROR_COLOR_PAIR_DEFINITION, __LINE__, __func__); 237 goto error; 238 } 239 } 240 241 // create window stack 242 if ((window_stack = stack_new()) == NULL) { 243 write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__); 244 goto error; 245 } 246 247 // create board window 248 struct BoardWin boardwin_struct = { 249 .word = malloc(WORD_MAXLEN), 250 .colors = cycle_new(colors, ARRAY_LENGTH(colors), MEMBER_SIZE(colors)), 251 .on = false, 252 .done = false, 253 }; 254 boardwin = &boardwin_struct; 255 // verify allocations 256 if (boardwin->word == NULL || boardwin->colors == NULL) { 257 write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__); 258 goto error; 259 } 260 // create base 261 struct MainWin boardwin_base = { 262 .sub = boardwin, 263 .type = BOARD_WIN, 264 .richwin = boardwin_new_richwin(NULL), 265 .new_richwin = boardwin_new_richwin, 266 .draw_window = boardwin_draw, 267 }; 268 boardwin->base = boardwin_base; 269 // error checking 270 if (boardwin->base.richwin == NULL) goto error; 271 // push boardwin to window stack 272 if (stack_push(window_stack, boardwin) == NULL) { 273 write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__); 274 goto error; 275 } 276 // write default word into it 277 strncpy(boardwin->word, DEFAULT_WORD, WORD_MAXLEN); 278 boardwin->word[WORD_MAXLEN-1] = '\0'; 279 280 // create menu window 281 struct MenuWin menuwin_struct = { 282 .options = cycle_new(menu_options, ARRAY_LENGTH(menu_options), 283 MEMBER_SIZE(menu_options)), 284 }; 285 menuwin = &menuwin_struct; 286 // verify allocations 287 if (menuwin->options == NULL) { 288 write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__); 289 goto error; 290 } 291 // create base 292 struct MainWin menuwin_base = { 293 .sub = menuwin, 294 .type = MENU_WIN, 295 .richwin = menuwin_new_richwin(NULL), 296 .new_richwin = menuwin_new_richwin, 297 .draw_window = menuwin_draw, 298 }; 299 menuwin->base = menuwin_base; 300 // error checking 301 if (menuwin->base.richwin == NULL) goto error; 302 // push it to window stack 303 if (stack_push(window_stack, menuwin) == NULL) { 304 write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__); 305 goto error; 306 } 307 308 // main loop 309 if (menuwin_draw(menuwin) == NULL) goto error; 310 if (doupdate() == ERR) { 311 write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__); 312 goto error; 313 } 314 int ch; 315 while (!boardwin->done) { 316 ch = wgetch(menuwin->base.richwin->win); 317 if (ch == ERR) { 318 write_error(ERROR_CHARACTER_INPUT, __LINE__, __func__); 319 goto error; 320 } 321 switch(ch) { 322 case KEY_RESIZE: 323 // resize all windows 324 if (stack_iter_reverse(window_stack, mainwin_resize) == NULL) { 325 goto error; 326 } 327 // refresh 328 if (doupdate() == ERR) { 329 write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__); 330 goto error; 331 } 332 break; 333 case 'j': 334 case KEY_DOWN: 335 // go down 336 cycle_next(menuwin->options); 337 break; 338 case 'k': 339 case KEY_UP: 340 // go up 341 cycle_prev(menuwin->options); 342 break; 343 case '\n': 344 { 345 // execute an option 346 const struct MenuOption *menu_option = cycle_current( 347 menuwin->options); 348 if (menu_option->func(boardwin) == NULL) goto error; 349 break; 350 } 351 } 352 // render 353 if (menuwin_draw(menuwin) == NULL) goto error; 354 if (doupdate() == ERR) { 355 write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__); 356 goto error; 357 } 358 } 359 360 // cleanup and exit 361 cleanup_main(window_stack, boardwin, menuwin); 362 return 0; 363 error: 364 cleanup_main(window_stack, boardwin, menuwin); 365 print_error(); 366 return 1; 367 } 368 369 /*== misc functions =========================================================*/ 370 371 /* 372 * prompt_user: open a prompt window with text 'prompt' and store the result 373 * in buffer 'buffer' of size 'buffer_size'. number of characters read from 374 * user is returned or -1 if prompt failed 375 */ 376 static int 377 prompt_user(const char *prompt, char *buffer, size_t buffer_size) 378 { 379 assert(prompt != NULL); 380 assert(buffer != NULL); 381 assert(buffer_size > 0); 382 struct PromptWin *promptwin = NULL; 383 bool on_stack = false; 384 const int input_maxlen = (int)buffer_size - 1, 385 content_minlen = (int) max_size_t(strlen(prompt), buffer_size); 386 387 // allocate buffer for user input 388 char *input = malloc(buffer_size); 389 if (input == NULL) { 390 write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__); 391 goto error; 392 } 393 // initialize buffer to placholders 394 { 395 int i; 396 for (i = 0; i < input_maxlen; i++) { 397 input[i] = INPUT_PLACEHOLDER; 398 } 399 input[i] = '\0'; 400 } 401 402 // create prompt window 403 struct PromptWin promptwin_struct = { 404 .prompt = prompt, 405 .input = input, 406 .input_maxlen = input_maxlen, 407 .content_minlen = content_minlen, 408 }; 409 promptwin = &promptwin_struct; 410 // create base 411 struct MainWin promptwin_base = { 412 .sub = promptwin, 413 .type = PROMPT_WIN, 414 .richwin = promptwin_new_richwin(promptwin), 415 .new_richwin = promptwin_new_richwin, 416 .draw_window = promptwin_draw, 417 }; 418 promptwin->base = promptwin_base; 419 // verify data allocation 420 if (promptwin->base.richwin == NULL) goto error; 421 if (stack_push(window_stack, promptwin) == NULL) { 422 cleanup_prompt_user(promptwin, on_stack); 423 write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__); 424 return -1; 425 } 426 on_stack = true; 427 428 // prompt loop 429 // draw promptwin 430 if (promptwin_draw(promptwin) == NULL) goto error; 431 // refresh 432 if (doupdate() == ERR) { 433 write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__); 434 goto error; 435 } 436 // keep reading into buffer until a newline is read 437 int ch, i = 0; 438 while ((ch = wgetch(promptwin->base.richwin->win)) != '\n') { 439 if (ch == ERR) { 440 // failed to read character 441 write_error(ERROR_CHARACTER_INPUT, __LINE__, __func__); 442 goto error; 443 } else if (ch == KEY_RESIZE) { 444 // resize all windows from first to last 445 if (stack_iter_reverse(window_stack, mainwin_resize) == NULL) { 446 goto error; 447 } 448 // refresh 449 if (doupdate() == ERR) { 450 write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__); 451 goto error; 452 } 453 } else if ((ch == '\b' || ch == KEY_BACKSPACE) && i > 0) { 454 // remove current character and go back by one 455 input[--i] = INPUT_PLACEHOLDER; 456 } else if (i < input_maxlen && isalnum(ch)) { 457 // read character into input buffer 458 input[i++] = (char) ch; 459 } 460 // draw promptwin 461 if (promptwin_draw(promptwin) == NULL) goto error; 462 if (doupdate() == ERR) { 463 write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__); 464 goto error; 465 } 466 } 467 if (i != 0) { 468 // copy visual input to the user's buffer 469 input[i] = '\0'; 470 strncpy(buffer, input, buffer_size); 471 buffer[buffer_size-1] = '\0'; 472 } 473 474 // remove promptwin from program 475 wclear(promptwin->base.richwin->win); 476 wrefresh(promptwin->base.richwin->win); 477 478 // cleanup 479 cleanup_prompt_user(promptwin, on_stack); 480 return i; 481 error: 482 cleanup_prompt_user(promptwin, on_stack); 483 return -1; 484 } 485 486 /* mvwaddcstr: like mvwaddstr but also centers 'str' on area of length 'n' */ 487 static int 488 mvwaddcstr(WINDOW *win, int y, int x, const char *str, int n, char break_char) 489 { 490 assert(win != NULL); 491 assert(y >= 0); 492 assert(x >= 0); 493 assert(str != NULL); 494 const int text_len = (int) strlen(str); 495 496 if (text_len <= 0) { 497 // there's no text 498 return x; 499 } 500 if (n <= 0) { 501 // cant write on that area 502 return x; 503 } 504 if (text_len <= n) { 505 // text fits in area 506 n -= x; 507 x = x + (n-text_len) / 2; 508 // print text centered 509 err = mvwaddnstr(win, y, x, str, n); 510 if (err == ERR) return -1; 511 } else { 512 // text is longer than area: 513 // print text truncated to length of area 514 err = mvwaddnstr(win, y, x, str, n-1); 515 if (err == ERR) return -1; 516 // replace last character of text with specified text break character 517 err = waddch(win, (const chtype) break_char); 518 if (err == ERR) return -1; 519 } 520 return x; 521 } 522 523 /* 524 * highlight_str: change background color or write indicators on edges of area 525 * on screen starting at 'y' and 'x' of length 'n' 526 */ 527 static int 528 highlight_str(WINDOW *win, int y, int x, int n, int pair) 529 { 530 assert(win != NULL); 531 assert(y >= 0); 532 assert(x >= 0); 533 assert(n >= 1); 534 assert(pair >= 0); 535 536 if (colorize) { 537 // colorize area of length 'n' 538 err = mvwchgat(win, y, x, n, A_NORMAL, 1, NULL); 539 if (err == ERR) { 540 write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__); 541 return -1; 542 } 543 } else { 544 // add indicators on edges of area of length 'n' 545 err = mvwaddch(win, y, x, INDICATOR_CHAR_LEFT); 546 if (err == ERR) { 547 write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__); 548 return -1; 549 } 550 err = mvwaddch(win, y, x+n-1, INDICATOR_CHAR_RIGHT); 551 if (err == ERR) { 552 write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__); 553 return -1; 554 } 555 } 556 return 0; 557 } 558 559 560 /*== cleanup functions ======================================================*/ 561 562 /* cleanup_main: free resources allocated by main() */ 563 static void 564 cleanup_main(Stack *win_stack, struct BoardWin *boardwin, 565 struct MenuWin *menuwin) 566 { 567 if (win_stack != NULL) stack_delete(window_stack); 568 if (boardwin != NULL) boardwin_cleanup(boardwin); 569 if (menuwin != NULL) menuwin_cleanup(menuwin); 570 endwin(); 571 } 572 573 /* cleanup_prompt_user: cleanup resources allocated by prompt_user() */ 574 static void 575 cleanup_prompt_user(struct PromptWin *promptwin, bool on_stack) 576 { 577 assert(window_stack != NULL); 578 if (promptwin != NULL) promptwin_cleanup(promptwin); 579 if (on_stack == true) stack_pop(window_stack); 580 } 581 582 /*== error handling functions ===============================================*/ 583 584 /* error: write error information to global struct 'error_info' */ 585 static void 586 write_error(enum ErrorCode code, int line, const char *func_name) 587 { 588 error_info.code = code; 589 error_info.line = line; 590 error_info.func_name = func_name; 591 } 592 593 /* print_error: print error stored in global struct 'error_info' */ 594 static int 595 print_error(void) 596 { 597 assert(error_info.code >= 1 && error_info.code < ERROR_MAX); 598 return fprintf((stderr), "%s:%d:%s(): %s\n", 599 __FILE__, 600 error_info.line, 601 error_info.func_name, 602 (error_texts[error_info.code] == NULL) 603 ? error_texts[ERROR_UNKNOWN] 604 : error_texts[error_info.code]); 605 } 606 607 /* dump_core: crashes the program to generate debugging information */ 608 void 609 dump_core(void) 610 { 611 // suppress -Wunused-function 612 (void) dump_core; 613 int *crash = NULL; 614 *crash = 7; 615 } 616 617 618 /*== window methods =========================================================*/ 619 620 /*-- MainWin methods --------------------------------------------------------*/ 621 622 /* mainwin_resize: resize any main window pointed to by 'window' */ 623 static void * 624 mainwin_resize(void *window) 625 { 626 assert(window != NULL); 627 struct MainWin *mainwin = window; 628 629 // clear richwin 630 err = wclear(mainwin->richwin->win); 631 if (err == ERR) { 632 write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__); 633 return NULL; 634 } 635 // delete richwin 636 err = richwin_del(mainwin->richwin); 637 if (err == ERR) return NULL; 638 // create new richwin 639 mainwin->richwin = mainwin->new_richwin(mainwin->sub); 640 if (mainwin->richwin == NULL) return NULL; 641 // redraw window 642 if (mainwin->draw_window(mainwin->sub) == NULL) return NULL; 643 return mainwin; 644 } 645 646 /* mainwin_cleanup: free resources used by a MainWin window */ 647 static void 648 mainwin_cleanup(struct MainWin *mainwin) 649 { 650 assert(mainwin != NULL); 651 richwin_del(mainwin->richwin); 652 mainwin->richwin = NULL; 653 } 654 655 /*-- BoardWin methods -------------------------------------------------------*/ 656 657 /* boardwin_new_richwin: defaults for board rich window */ 658 static struct RichWin * 659 boardwin_new_richwin(void *window) 660 { 661 (void) window; 662 struct RichWin *richwin; 663 664 // create window 665 richwin = richwin_new(LINES, COLS, 0, 0, NULL); 666 if (richwin == NULL) { 667 write_error(richwin_error_code, __LINE__, __func__); 668 return NULL; 669 } 670 // set options 671 err = leaveok(richwin->win, true); 672 if (err == ERR) { 673 richwin_del(richwin); 674 write_error(ERROR_OPTION_SETTING, __LINE__, __func__); 675 return NULL; 676 } 677 return richwin; 678 } 679 680 /* boardwin_draw: draw cool stuff on 'boardwin' in the color choosen by user */ 681 static void * 682 boardwin_draw(void *window) 683 { 684 assert(window != NULL); 685 struct BoardWin *boardwin = window; 686 struct RichWin *richwin = boardwin->base.richwin; 687 const int colstep = (int) strlen(boardwin->word); 688 689 // clear previous stuff 690 err = wclear(richwin->win); 691 if (err == ERR) { 692 write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__); 693 return NULL; 694 } 695 696 if (!boardwin->on) { 697 // save current screen and exit if board is turned off 698 // write to virtual screen 699 err = wnoutrefresh(richwin->win); 700 if (err == ERR) { 701 write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__); 702 return NULL; 703 } 704 return boardwin; 705 } 706 if (colstep <= 0) { 707 // no appropriate word to draw 708 return boardwin; 709 } 710 711 // set color 712 if (colorize) { 713 err = wbkgd(richwin->win, COLOR_PAIR(2)); 714 if (err == ERR) { 715 write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__); 716 return NULL; 717 } 718 } 719 // write 'word' all over 'boardwin' 720 const int left = (richwin->cols%colstep) / 2, 721 colmax = richwin->cols - colstep; 722 for (int line = 0; line < richwin->lines; line++) { 723 // print 'word' repeatedly until end of line 724 for (int col = left; col <= colmax; col+=colstep) { 725 err = mvwaddnstr(richwin->win, line, col, boardwin->word, colstep); 726 /* error is ignored when last character of word is written at the 727 * lower right margin of screen (addch succeeds but returns an 728 * error) */ 729 if (err == ERR && richwin->cols-col != colstep) { 730 dump_core(); 731 write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__); 732 return NULL; 733 } 734 } 735 } 736 737 // write to virtual screen 738 err = wnoutrefresh(richwin->win); 739 if (err == ERR) { 740 write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__); 741 return NULL; 742 } 743 return boardwin; 744 } 745 746 /* boardwin_cleanup: free resources used by a BoardWin window */ 747 static void 748 boardwin_cleanup(struct BoardWin *boardwin) 749 { 750 assert(boardwin != NULL); 751 free(boardwin->word); 752 boardwin->word = NULL; 753 cycle_free(boardwin->colors); 754 boardwin->colors = NULL; 755 mainwin_cleanup(&boardwin->base); 756 } 757 758 /*-- MenuWin methods --------------------------------------------------------*/ 759 760 /* menuwin_new_richwin: defaults for menu rich window */ 761 static struct RichWin * 762 menuwin_new_richwin(void *window) 763 { 764 (void) window; 765 int lines = min_int(ARRAY_LENGTH(menu_options)+BORDERS, LINES), 766 cols = COLS/2; 767 // ser borders 768 struct WinBorders winborders = { 769 .ls = '|', .rs = '|', .ts = '-', .bs = '-', 770 .tl = '+', .tr = '+', .bl = '+', .br = '+', 771 }; 772 struct RichWin *richwin; 773 774 // create window 775 richwin = richwin_new_centered(lines, cols, &winborders); 776 if (richwin == NULL) { 777 write_error(richwin_error_code, __LINE__, __func__); 778 return NULL; 779 } 780 // set options 781 if (keypad(richwin->win, true) == ERR 782 ||leaveok(richwin->win, true) == ERR) 783 { 784 richwin_del(richwin); 785 write_error(ERROR_OPTION_SETTING, __LINE__, __func__); 786 return NULL; 787 } 788 return richwin; 789 } 790 791 /* 792 * menuwin_draw: draw 'menuwin' and it's options each centered on their own 793 * line, then highlight the selected option 794 */ 795 static void * 796 menuwin_draw(void *window) 797 { 798 assert(window != NULL); 799 static const int option_left = BORDER + INDICATOR, 800 highlight_left = BORDER; 801 struct MenuWin *menuwin = window; 802 struct RichWin *richwin = menuwin->base.richwin; 803 // calculate number of menu options to draw and width of each one 804 const int option_maxnum = richwin->lines - BORDERS, 805 option_num = min_int(option_maxnum, ARRAY_LENGTH(menu_options)), 806 option_width = richwin->cols - BORDERS - INDICATORS; 807 808 // clear menuwin 809 err = werase(richwin->win); 810 if (err == ERR) { 811 write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__); 812 return NULL; 813 } 814 815 // border menuwin 816 err = richwin_border(richwin); 817 if (err == ERR) { 818 write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__); 819 return NULL; 820 } 821 822 if (option_width > 0 && option_num > 0) { 823 // draw menu options centered on window 824 for (int i = 0; i < option_num; i++) { 825 const int option_top = BORDER + i; 826 const char *option_text = menu_options[i].text; 827 // write centered menu option 828 err = mvwaddcstr(richwin->win, option_top, option_left, 829 option_text, option_width, TEXT_CONTINUATION); 830 if (err == ERR) { 831 write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__); 832 return NULL; 833 } 834 } 835 836 // highlight selected option 837 const int highlight_top = BORDER + (int) cycle_index(menuwin->options), 838 highlight_width = option_width + INDICATORS; 839 if (highlight_top < BORDER+option_maxnum) { 840 highlight_str(richwin->win, highlight_top, highlight_left, 841 highlight_width, 1); 842 } 843 } 844 845 // write to virtual screen 846 err = wnoutrefresh(richwin->win); 847 if (err == ERR) { 848 write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__); 849 return NULL; 850 } 851 return menuwin; 852 } 853 854 /* menuwin_cleanup: free resources used by a MenuWin window */ 855 static void 856 menuwin_cleanup(struct MenuWin *menuwin) 857 { 858 assert(menuwin != NULL); 859 cycle_free(menuwin->options); 860 menuwin->options = NULL; 861 mainwin_cleanup(&menuwin->base); 862 } 863 864 /*-- PromptWin methods ------------------------------------------------------*/ 865 866 /* promptwin_new_richwin: defaults for prompt rich window */ 867 static struct RichWin * 868 promptwin_new_richwin(void *window) 869 { 870 assert(window != NULL); 871 struct PromptWin *promptwin = window; 872 const int lines = min_int(BORDERS+2, LINES), 873 needed_cols = promptwin->content_minlen + PADDINGS + BORDERS, 874 cols = min_int(needed_cols, COLS/2); 875 // set borders 876 struct WinBorders winborders = { 877 .ls = '|', .rs = '|', .ts = '-', .bs = '-', 878 .tl = '+', .tr = '+', .bl = '+', .br = '+', 879 }; 880 struct RichWin *richwin; 881 882 // create window 883 richwin = richwin_new_centered(lines, cols, &winborders); 884 if (richwin == NULL) { 885 write_error(richwin_error_code, __LINE__, __func__); 886 return NULL; 887 } 888 // set options 889 err = keypad(richwin->win, true); 890 if (err == ERR) { 891 richwin_del(richwin); 892 write_error(ERROR_OPTION_SETTING, __LINE__, __func__); 893 return NULL; 894 } 895 return richwin; 896 } 897 898 /* promptwin_draw: draw 'propmtwin', it's prompt, and input field */ 899 static void * 900 promptwin_draw(void *window) 901 { 902 assert(window != NULL); 903 struct PromptWin *promptwin = window; 904 struct RichWin *richwin = promptwin->base.richwin; 905 static const int content_left = BORDER + PADDING, 906 prompt_top = BORDER; 907 const int content_width = richwin->cols - BORDERS - PADDING, 908 input_top = prompt_top + 1; 909 910 // clear window 911 err = werase(richwin->win) == ERR; 912 if (err == ERR) { 913 write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__); 914 return NULL; 915 } 916 917 // border window 918 err = richwin_border(richwin) == ERR; 919 if (err == ERR) { 920 write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__); 921 return NULL; 922 } 923 924 if (content_width > 0 && richwin->lines > input_top) { 925 // print prompt 926 err = mvwaddcstr(richwin->win, prompt_top, content_left, 927 promptwin->prompt, content_width, TEXT_CONTINUATION); 928 if (err == ERR) { 929 dump_core(); 930 write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__); 931 return NULL; 932 } 933 // print input field 934 err = mvwaddcstr(richwin->win, input_top, content_left, 935 promptwin->input, content_width, INPUT_CONTINUATION); 936 if (err == ERR) { 937 write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__); 938 return NULL; 939 } 940 } 941 942 // write to virtual screen 943 err = wnoutrefresh(richwin->win); 944 if (err == ERR) { 945 write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__); 946 return NULL; 947 } 948 return promptwin; 949 } 950 951 /* promptwin_cleanup: free resources used by a PromptWin window */ 952 static void 953 promptwin_cleanup(struct PromptWin *promptwin) 954 { 955 assert(promptwin != NULL); 956 free(promptwin->input); 957 promptwin->input = NULL; 958 mainwin_cleanup(&promptwin->base); 959 } 960 961 962 /*== application actions ====================================================*/ 963 964 /* app_draw: enable drawing and draw board */ 965 static struct BoardWin * 966 app_draw(struct BoardWin *boardwin) 967 { 968 assert(boardwin != NULL); 969 970 // turn board on and draw board 971 boardwin->on = true; 972 if (boardwin_draw(boardwin) == NULL) return NULL; 973 return boardwin; 974 } 975 976 /* app_clean: erase stuff drwan on 'boardwin' */ 977 static struct BoardWin * 978 app_clean(struct BoardWin *boardwin) 979 { 980 assert(boardwin != NULL); 981 struct RichWin *richwin = boardwin->base.richwin; 982 983 // turn off board and clear it 984 boardwin->on = false; 985 err = wclear(richwin->win); 986 if (err == ERR) { 987 write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__); 988 return NULL; 989 } 990 // restore background color to original 991 if (colorize) { 992 err = wbkgd(richwin->win, COLOR_PAIR(0)); 993 if (err == ERR) { 994 write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__); 995 return NULL; 996 } 997 } 998 999 // write to virtual screen 1000 err = wnoutrefresh(richwin->win); 1001 if (err == ERR) { 1002 write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__); 1003 return NULL; 1004 } 1005 return boardwin; 1006 } 1007 1008 /* app_change_word: replace word to draw with new one from user */ 1009 static struct BoardWin * 1010 app_change_word(struct BoardWin *boardwin) 1011 { 1012 assert(boardwin != NULL); 1013 1014 // prompt user for new word 1015 if (prompt_user(DEFAULT_PROMPT, boardwin->word, WORD_MAXLEN) < 0) { 1016 return NULL; 1017 } 1018 // draw board 1019 if (boardwin->on) { 1020 if (boardwin_draw(boardwin) == NULL) return NULL; 1021 } 1022 return boardwin; 1023 } 1024 1025 /* app_change_color: change the color to use when drawing 'boardwin' */ 1026 static struct BoardWin * 1027 app_change_color(struct BoardWin *boardwin) 1028 { 1029 assert(boardwin != NULL); 1030 if (!colorize) { 1031 return boardwin; 1032 } 1033 1034 // modify board drawing color pair 1035 const short bg_color = *(const short *) cycle_next(boardwin->colors); 1036 err = init_pair(2, COLOR_BLACK, bg_color); 1037 if (err == ERR) { 1038 write_error(ERROR_COLOR_PAIR_DEFINITION, __LINE__, __func__); 1039 return NULL; 1040 } 1041 // draw board 1042 if (boardwin->on) { 1043 if (boardwin_draw(boardwin) == NULL) return NULL; 1044 } 1045 return boardwin; 1046 } 1047 1048 /* app_end: mark 'boardwin' done being used so program can exit */ 1049 static struct BoardWin * 1050 app_end(struct BoardWin *boardwin) 1051 { 1052 assert(boardwin != NULL); 1053 boardwin->done = true; 1054 return boardwin; 1055 }