fun-menu

ncurses learning thing
git clone https://git.pastanoggin.com/fun-menu.git
Log | Files | Refs | README | LICENSE

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 }