summaryrefslogtreecommitdiff
path: root/fun_menu.c
diff options
context:
space:
mode:
Diffstat (limited to 'fun_menu.c')
-rw-r--r--fun_menu.c1055
1 files changed, 1055 insertions, 0 deletions
diff --git a/fun_menu.c b/fun_menu.c
new file mode 100644
index 0000000..36fed4d
--- /dev/null
+++ b/fun_menu.c
@@ -0,0 +1,1055 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <locale.h> // for setlocale()
+#include <string.h> // for strncpy() and strlen()
+#include <ctype.h> // for isalnum()
+#include <stdlib.h> // for malloc() and free()
+
+#include <curses.h>
+#include "fun_menu.h"
+#include "utils.h"
+#include "richwin.h"
+#include "stack.h"
+#include "cycle.h"
+
+// macros
+#define ARRAY_LENGTH(arr) (sizeof (arr) / sizeof ((arr)[0]))
+#define MEMBER_SIZE(arr) (sizeof ((arr)[0]))
+
+// default strings/characters
+#define DEFAULT_WORD "hello"
+#define DEFAULT_PROMPT "Enter word to draw"
+#define INDICATOR_CHAR_LEFT '>'
+#define INDICATOR_CHAR_RIGHT '<'
+#define INPUT_PLACEHOLDER '_'
+#define TEXT_CONTINUATION '-'
+#define INPUT_CONTINUATION '+'
+
+// default numbers
+#define WORD_MAXLEN 20
+#define INDICATORS 2
+#define BORDERS 2
+#define PADDINGS 2
+#define INDICATOR INDICATORS/2
+#define BORDER BORDERS/2
+#define PADDING PADDINGS/2
+
+enum WindowType { BOARD_WIN, MENU_WIN, PROMPT_WIN };
+enum AppActionType { APP_DRAW, APP_CLEAN, APP_CHANGE_COLOR, APP_CHANGE_WORD,
+ APP_END};
+
+/* structures */
+
+// MainWin: pointer to one of the following windows and it's type. used for
+// converting to/from void *
+struct MainWin {
+ void *sub;
+ enum WindowType type;
+ struct RichWin *richwin;
+ struct RichWin *(*new_richwin)(void *sub);
+ void *(*draw_window)(void *sub);
+};
+// BoardWin: a resizable drawing board with stats of the word to draw,
+// background color, whether it's enables/disabled and whether drawing on it is
+// done
+struct BoardWin {
+ struct MainWin base;
+ char *word;
+ Cycle *colors;
+ bool on;
+ bool done;
+};
+// MenuWin: a resizable menu with a list of options, and the currently
+// choosen option from that list
+struct MenuWin {
+ struct MainWin base;
+ Cycle *options;
+};
+// PromptWin: a resizable user prompt with prompt text, prompt input buffer,
+// and it's size
+struct PromptWin {
+ struct MainWin base;
+ const char *prompt;
+ char *input;
+ const int input_maxlen;
+ const int content_minlen;
+};
+// MenuOption: text for a menu option used in a MenuWin and accociated function
+// to call on choosing that option
+struct MenuOption {
+ struct BoardWin *(*func)(struct BoardWin *boardwin);
+ enum AppActionType type;
+ const char *text;
+};
+
+/* function declarations */
+
+// misc functions
+static int prompt_user(const char *prompt, char *buf, size_t len);
+static int mvwaddcstr(WINDOW *win, int y, int x, const char *str, int len,
+ const char break_char);
+// cleanup functions
+static void cleanup_main(Stack *win_stack, struct BoardWin *boardwin,
+ struct MenuWin *menuwin);
+static void cleanup_prompt_user(struct PromptWin *promptwin, bool on_stack);
+// error handling functions
+static void write_error(enum ErrorCode code, int line, const char *func_name);
+static int print_error(void);
+// window methds:
+// MainWin methods
+static void *mainwin_resize(void *window);
+static void mainwin_cleanup(struct MainWin *mainwin);
+// BoardWin methods
+static struct RichWin *boardwin_new_richwin(void *window);
+static void *boardwin_draw(void *window);
+static void boardwin_cleanup(struct BoardWin *boardwin);
+// MenuWin methods
+static struct RichWin *menuwin_new_richwin(void *window);
+static void *menuwin_draw(void *window);
+static void menuwin_cleanup(struct MenuWin *menuwin);
+// PromptWin methods
+static struct RichWin *promptwin_new_richwin(void *window);
+static void *promptwin_draw(void *window);
+static void promptwin_cleanup(struct PromptWin *promptwin);
+
+// application actions
+static struct BoardWin *app_draw(struct BoardWin *boardwin);
+static struct BoardWin *app_clean(struct BoardWin *boardwin);
+static struct BoardWin *app_change_word(struct BoardWin *boardwin);
+static struct BoardWin *app_change_color(struct BoardWin *boardwin);
+static struct BoardWin *app_end(struct BoardWin *boardwin);
+
+
+/* globals */
+
+// holds curses errors
+static int err;
+// remembers whether the terminal supports color
+static bool colorize;
+// keeps track of every window for responding to a terminal resize
+static Stack *window_stack;
+
+/* constants */
+
+// holds error info
+static struct ErrorInfo {
+ enum ErrorCode code;
+ int line;
+ const char *func_name;
+} error_info = {
+ .code = ERROR_OK,
+ .line = 0,
+ .func_name = NULL,
+};
+// text representation of error codes
+static const char *error_texts[ERROR_MAX] = {
+ [ERROR_LOCALE_SETTING] = "Failed to set locale!",
+ [ERROR_COLOR_STARTING] = "Failed to start colors!",
+ [ERROR_CURSES_SETTING] = "Failed to set curses settings!",
+ [ERROR_MEMORY_ALLOCATION] = "Failed to allocate memory!",
+ [ERROR_CHARACTER_INPUT] = "Failed to recieve character from window!",
+ [ERROR_CHARACTER_OUTPUT] = "Failed to print character to window!",
+ [ERROR_WINDOW_CREATION] = "Failed to create a window!",
+ [ERROR_WINDOW_DRAWING] = "Failed to write to a window!",
+ [ERROR_WINDOW_RENDERING] = "Failed to render a window!",
+ [ERROR_COLOR_PAIR_DEFINITION] = "Failed to define a color pair!",
+ [ERROR_OPTION_SETTING] = "Failed to set an option for a window!",
+ [ERROR_UNKNOWN] = "Unknown error!",
+};
+// holds BoardWin's colors to cycle through
+static const short colors[] = {
+ COLOR_RED,
+ COLOR_GREEN,
+ COLOR_YELLOW,
+ COLOR_BLUE,
+ COLOR_MAGENTA,
+ COLOR_CYAN,
+};
+// holds an iterable vtable
+static const struct MenuOption menu_options[] = {
+ {
+ .func = app_draw,
+ .type = APP_DRAW,
+ .text = "draw",
+ },
+ {
+ .func = app_clean,
+ .type = APP_CLEAN,
+ .text = "clean",
+ },
+ {
+ .func = app_change_color,
+ .type = APP_CHANGE_COLOR,
+ .text = "change color",
+ },
+ {
+ .func = app_change_word,
+ .type = APP_CHANGE_WORD,
+ .text = "change word",
+ },
+ {
+ .func = app_end,
+ .type = APP_END,
+ .text = "exit",
+ },
+};
+
+/* a "little" arrow-driven/hjkl menu practice */
+int
+main(void)
+{
+ struct BoardWin *boardwin = NULL;
+ struct MenuWin *menuwin = NULL;
+
+ // set locale from environment
+ if (setlocale(LC_ALL, "") == NULL) {
+ write_error(ERROR_LOCALE_SETTING, __LINE__, __func__);
+ goto error;
+ }
+ // start curses
+ (void) initscr();
+ // start color
+ colorize = has_colors();
+ if (colorize) {
+ if (start_color() == ERR) {
+ write_error(ERROR_COLOR_STARTING, __LINE__, __func__);
+ goto error;
+ }
+ }
+ // set options
+ if (noecho() == ERR
+ || raw() == ERR
+ || nl() == ERR
+ || leaveok(stdscr, true) == ERR
+ || curs_set(0) == ERR)
+ {
+ write_error(ERROR_CURSES_SETTING, __LINE__, __func__);
+ goto error;
+ }
+
+ // set color pairs
+ if (colorize) {
+ if (init_pair(1, COLOR_BLACK, COLOR_WHITE) == ERR
+ ||init_pair(2, COLOR_BLACK, colors[0]) == ERR)
+ {
+ write_error(ERROR_COLOR_PAIR_DEFINITION, __LINE__, __func__);
+ goto error;
+ }
+ }
+
+ // create window stack
+ if ((window_stack = stack_new()) == NULL) {
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ goto error;
+ }
+
+ // create board window
+ struct BoardWin boardwin_struct = {
+ .word = malloc(WORD_MAXLEN),
+ .colors = cycle_new(colors, ARRAY_LENGTH(colors), MEMBER_SIZE(colors)),
+ .on = false,
+ .done = false,
+ };
+ boardwin = &boardwin_struct;
+ // verify allocations
+ if (boardwin->word == NULL || boardwin->colors == NULL) {
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ goto error;
+ }
+ // create base
+ struct MainWin boardwin_base = {
+ .sub = boardwin,
+ .type = BOARD_WIN,
+ .richwin = boardwin_new_richwin(NULL),
+ .new_richwin = boardwin_new_richwin,
+ .draw_window = boardwin_draw,
+ };
+ boardwin->base = boardwin_base;
+ // error checking
+ if (boardwin->base.richwin == NULL) goto error;
+ // push boardwin to window stack
+ if (stack_push(window_stack, boardwin) == NULL) {
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ goto error;
+ }
+ // write default word into it
+ strncpy(boardwin->word, DEFAULT_WORD, WORD_MAXLEN);
+ boardwin->word[WORD_MAXLEN-1] = '\0';
+
+ // create menu window
+ struct MenuWin menuwin_struct = {
+ .options = cycle_new(menu_options, ARRAY_LENGTH(menu_options),
+ MEMBER_SIZE(menu_options)),
+ };
+ menuwin = &menuwin_struct;
+ // verify allocations
+ if (menuwin->options == NULL) {
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ goto error;
+ }
+ // create base
+ struct MainWin menuwin_base = {
+ .sub = menuwin,
+ .type = MENU_WIN,
+ .richwin = menuwin_new_richwin(NULL),
+ .new_richwin = menuwin_new_richwin,
+ .draw_window = menuwin_draw,
+ };
+ menuwin->base = menuwin_base;
+ // error checking
+ if (menuwin->base.richwin == NULL) goto error;
+ // push it to window stack
+ if (stack_push(window_stack, menuwin) == NULL) {
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ goto error;
+ }
+
+ // main loop
+ if (menuwin_draw(menuwin) == NULL) goto error;
+ if (doupdate() == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ goto error;
+ }
+ int ch;
+ while (!boardwin->done) {
+ ch = wgetch(menuwin->base.richwin->win);
+ if (ch == ERR) {
+ write_error(ERROR_CHARACTER_INPUT, __LINE__, __func__);
+ goto error;
+ }
+ switch(ch) {
+ case KEY_RESIZE:
+ // resize all windows
+ if (stack_iter_reverse(window_stack, mainwin_resize) == NULL) {
+ goto error;
+ }
+ // refresh
+ if (doupdate() == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ goto error;
+ }
+ break;
+ case 'j':
+ case KEY_DOWN:
+ // go down
+ cycle_next(menuwin->options);
+ break;
+ case 'k':
+ case KEY_UP:
+ // go up
+ cycle_prev(menuwin->options);
+ break;
+ case '\n':
+ {
+ // execute an option
+ const struct MenuOption *menu_option = cycle_current(
+ menuwin->options);
+ if (menu_option->func(boardwin) == NULL) goto error;
+ break;
+ }
+ }
+ // render
+ if (menuwin_draw(menuwin) == NULL) goto error;
+ if (doupdate() == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ goto error;
+ }
+ }
+
+ // cleanup and exit
+ cleanup_main(window_stack, boardwin, menuwin);
+ return 0;
+error:
+ cleanup_main(window_stack, boardwin, menuwin);
+ print_error();
+ return 1;
+}
+
+/*== misc functions =========================================================*/
+
+/*
+ * prompt_user: open a prompt window with text 'prompt' and store the result
+ * in buffer 'buffer' of size 'buffer_size'. number of characters read from
+ * user is returned or -1 if prompt failed
+ */
+static int
+prompt_user(const char *prompt, char *buffer, size_t buffer_size)
+{
+ assert(prompt != NULL);
+ assert(buffer != NULL);
+ assert(buffer_size > 0);
+ struct PromptWin *promptwin = NULL;
+ bool on_stack = false;
+ const int input_maxlen = (int)buffer_size - 1,
+ content_minlen = (int) max_size_t(strlen(prompt), buffer_size);
+
+ // allocate buffer for user input
+ char *input = malloc(buffer_size);
+ if (input == NULL) {
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ goto error;
+ }
+ // initialize buffer to placholders
+ {
+ int i;
+ for (i = 0; i < input_maxlen; i++) {
+ input[i] = INPUT_PLACEHOLDER;
+ }
+ input[i] = '\0';
+ }
+
+ // create prompt window
+ struct PromptWin promptwin_struct = {
+ .prompt = prompt,
+ .input = input,
+ .input_maxlen = input_maxlen,
+ .content_minlen = content_minlen,
+ };
+ promptwin = &promptwin_struct;
+ // create base
+ struct MainWin promptwin_base = {
+ .sub = promptwin,
+ .type = PROMPT_WIN,
+ .richwin = promptwin_new_richwin(promptwin),
+ .new_richwin = promptwin_new_richwin,
+ .draw_window = promptwin_draw,
+ };
+ promptwin->base = promptwin_base;
+ // verify data allocation
+ if (promptwin->base.richwin == NULL) goto error;
+ if (stack_push(window_stack, promptwin) == NULL) {
+ cleanup_prompt_user(promptwin, on_stack);
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ return -1;
+ }
+ on_stack = true;
+
+ // prompt loop
+ // draw promptwin
+ if (promptwin_draw(promptwin) == NULL) goto error;
+ // refresh
+ if (doupdate() == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ goto error;
+ }
+ // keep reading into buffer until a newline is read
+ int ch, i = 0;
+ while ((ch = wgetch(promptwin->base.richwin->win)) != '\n') {
+ if (ch == ERR) {
+ // failed to read character
+ write_error(ERROR_CHARACTER_INPUT, __LINE__, __func__);
+ goto error;
+ } else if (ch == KEY_RESIZE) {
+ // resize all windows from first to last
+ if (stack_iter_reverse(window_stack, mainwin_resize) == NULL) {
+ goto error;
+ }
+ // refresh
+ if (doupdate() == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ goto error;
+ }
+ } else if ((ch == '\b' || ch == KEY_BACKSPACE) && i > 0) {
+ // remove current character and go back by one
+ input[--i] = INPUT_PLACEHOLDER;
+ } else if (i < input_maxlen && isalnum(ch)) {
+ // read character into input buffer
+ input[i++] = (char) ch;
+ }
+ // draw promptwin
+ if (promptwin_draw(promptwin) == NULL) goto error;
+ if (doupdate() == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ goto error;
+ }
+ }
+ if (i != 0) {
+ // copy visual input to the user's buffer
+ input[i] = '\0';
+ strncpy(buffer, input, buffer_size);
+ buffer[buffer_size-1] = '\0';
+ }
+
+ // remove promptwin from program
+ wclear(promptwin->base.richwin->win);
+ wrefresh(promptwin->base.richwin->win);
+
+ // cleanup
+ cleanup_prompt_user(promptwin, on_stack);
+ return i;
+error:
+ cleanup_prompt_user(promptwin, on_stack);
+ return -1;
+}
+
+/* mvwaddcstr: like mvwaddstr but also centers 'str' on area of length 'n' */
+static int
+mvwaddcstr(WINDOW *win, int y, int x, const char *str, int n, char break_char)
+{
+ assert(win != NULL);
+ assert(y >= 0);
+ assert(x >= 0);
+ assert(str != NULL);
+ const int text_len = (int) strlen(str);
+
+ if (text_len <= 0) {
+ // there's no text
+ return x;
+ }
+ if (n <= 0) {
+ // cant write on that area
+ return x;
+ }
+ if (text_len <= n) {
+ // text fits in area
+ n -= x;
+ x = x + (n-text_len) / 2;
+ // print text centered
+ err = mvwaddnstr(win, y, x, str, n);
+ if (err == ERR) return -1;
+ } else {
+ // text is longer than area:
+ // print text truncated to length of area
+ err = mvwaddnstr(win, y, x, str, n-1);
+ if (err == ERR) return -1;
+ // replace last character of text with specified text break character
+ err = waddch(win, (const chtype) break_char);
+ if (err == ERR) return -1;
+ }
+ return x;
+}
+
+/*
+ * highlight_str: change background color or write indicators on edges of area
+ * on screen starting at 'y' and 'x' of length 'n'
+ */
+static int
+highlight_str(WINDOW *win, int y, int x, int n, int pair)
+{
+ assert(win != NULL);
+ assert(y >= 0);
+ assert(x >= 0);
+ assert(n >= 1);
+ assert(pair >= 0);
+
+ if (colorize) {
+ // colorize area of length 'n'
+ err = mvwchgat(win, y, x, n, A_NORMAL, 1, NULL);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return -1;
+ }
+ } else {
+ // add indicators on edges of area of length 'n'
+ err = mvwaddch(win, y, x, INDICATOR_CHAR_LEFT);
+ if (err == ERR) {
+ write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__);
+ return -1;
+ }
+ err = mvwaddch(win, y, x+n-1, INDICATOR_CHAR_RIGHT);
+ if (err == ERR) {
+ write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+/*== cleanup functions ======================================================*/
+
+/* cleanup_main: free resources allocated by main() */
+static void
+cleanup_main(Stack *win_stack, struct BoardWin *boardwin,
+ struct MenuWin *menuwin)
+{
+ if (win_stack != NULL) stack_delete(window_stack);
+ if (boardwin != NULL) boardwin_cleanup(boardwin);
+ if (menuwin != NULL) menuwin_cleanup(menuwin);
+ endwin();
+}
+
+/* cleanup_prompt_user: cleanup resources allocated by prompt_user() */
+static void
+cleanup_prompt_user(struct PromptWin *promptwin, bool on_stack)
+{
+ assert(window_stack != NULL);
+ if (promptwin != NULL) promptwin_cleanup(promptwin);
+ if (on_stack == true) stack_pop(window_stack);
+}
+
+/*== error handling functions ===============================================*/
+
+/* error: write error information to global struct 'error_info' */
+static void
+write_error(enum ErrorCode code, int line, const char *func_name)
+{
+ error_info.code = code;
+ error_info.line = line;
+ error_info.func_name = func_name;
+}
+
+/* print_error: print error stored in global struct 'error_info' */
+static int
+print_error(void)
+{
+ assert(error_info.code >= 1 && error_info.code < ERROR_MAX);
+ return fprintf((stderr), "%s:%d:%s(): %s\n",
+ __FILE__,
+ error_info.line,
+ error_info.func_name,
+ (error_texts[error_info.code] == NULL)
+ ? error_texts[ERROR_UNKNOWN]
+ : error_texts[error_info.code]);
+}
+
+/* dump_core: crashes the program to generate debugging information */
+void
+dump_core(void)
+{
+ // suppress -Wunused-function
+ (void) dump_core;
+ int *crash = NULL;
+ *crash = 7;
+}
+
+
+/*== window methods =========================================================*/
+
+/*-- MainWin methods --------------------------------------------------------*/
+
+/* mainwin_resize: resize any main window pointed to by 'window' */
+static void *
+mainwin_resize(void *window)
+{
+ assert(window != NULL);
+ struct MainWin *mainwin = window;
+
+ // clear richwin
+ err = wclear(mainwin->richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+ // delete richwin
+ err = richwin_del(mainwin->richwin);
+ if (err == ERR) return NULL;
+ // create new richwin
+ mainwin->richwin = mainwin->new_richwin(mainwin->sub);
+ if (mainwin->richwin == NULL) return NULL;
+ // redraw window
+ if (mainwin->draw_window(mainwin->sub) == NULL) return NULL;
+ return mainwin;
+}
+
+/* mainwin_cleanup: free resources used by a MainWin window */
+static void
+mainwin_cleanup(struct MainWin *mainwin)
+{
+ assert(mainwin != NULL);
+ richwin_del(mainwin->richwin);
+ mainwin->richwin = NULL;
+}
+
+/*-- BoardWin methods -------------------------------------------------------*/
+
+/* boardwin_new_richwin: defaults for board rich window */
+static struct RichWin *
+boardwin_new_richwin(void *window)
+{
+ (void) window;
+ struct RichWin *richwin;
+
+ // create window
+ richwin = richwin_new(LINES, COLS, 0, 0, NULL);
+ if (richwin == NULL) {
+ write_error(richwin_error_code, __LINE__, __func__);
+ return NULL;
+ }
+ // set options
+ err = leaveok(richwin->win, true);
+ if (err == ERR) {
+ richwin_del(richwin);
+ write_error(ERROR_OPTION_SETTING, __LINE__, __func__);
+ return NULL;
+ }
+ return richwin;
+}
+
+/* boardwin_draw: draw cool stuff on 'boardwin' in the color choosen by user */
+static void *
+boardwin_draw(void *window)
+{
+ assert(window != NULL);
+ struct BoardWin *boardwin = window;
+ struct RichWin *richwin = boardwin->base.richwin;
+ const int colstep = (int) strlen(boardwin->word);
+
+ // clear previous stuff
+ err = wclear(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (!boardwin->on) {
+ // save current screen and exit if board is turned off
+ // write to virtual screen
+ err = wnoutrefresh(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ return NULL;
+ }
+ return boardwin;
+ }
+ if (colstep <= 0) {
+ // no appropriate word to draw
+ return boardwin;
+ }
+
+ // set color
+ if (colorize) {
+ err = wbkgd(richwin->win, COLOR_PAIR(2));
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+ }
+ // write 'word' all over 'boardwin'
+ const int left = (richwin->cols%colstep) / 2,
+ colmax = richwin->cols - colstep;
+ for (int line = 0; line < richwin->lines; line++) {
+ // print 'word' repeatedly until end of line
+ for (int col = left; col <= colmax; col+=colstep) {
+ err = mvwaddnstr(richwin->win, line, col, boardwin->word, colstep);
+ /* error is ignored when last character of word is written at the
+ * lower right margin of screen (addch succeeds but returns an
+ * error) */
+ if (err == ERR && richwin->cols-col != colstep) {
+ dump_core();
+ write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__);
+ return NULL;
+ }
+ }
+ }
+
+ // write to virtual screen
+ err = wnoutrefresh(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ return NULL;
+ }
+ return boardwin;
+}
+
+/* boardwin_cleanup: free resources used by a BoardWin window */
+static void
+boardwin_cleanup(struct BoardWin *boardwin)
+{
+ assert(boardwin != NULL);
+ free(boardwin->word);
+ boardwin->word = NULL;
+ cycle_free(boardwin->colors);
+ boardwin->colors = NULL;
+ mainwin_cleanup(&boardwin->base);
+}
+
+/*-- MenuWin methods --------------------------------------------------------*/
+
+/* menuwin_new_richwin: defaults for menu rich window */
+static struct RichWin *
+menuwin_new_richwin(void *window)
+{
+ (void) window;
+ int lines = min_int(ARRAY_LENGTH(menu_options)+BORDERS, LINES),
+ cols = COLS/2;
+ // ser borders
+ struct WinBorders winborders = {
+ .ls = '|', .rs = '|', .ts = '-', .bs = '-',
+ .tl = '+', .tr = '+', .bl = '+', .br = '+',
+ };
+ struct RichWin *richwin;
+
+ // create window
+ richwin = richwin_new_centered(lines, cols, &winborders);
+ if (richwin == NULL) {
+ write_error(richwin_error_code, __LINE__, __func__);
+ return NULL;
+ }
+ // set options
+ if (keypad(richwin->win, true) == ERR
+ ||leaveok(richwin->win, true) == ERR)
+ {
+ richwin_del(richwin);
+ write_error(ERROR_OPTION_SETTING, __LINE__, __func__);
+ return NULL;
+ }
+ return richwin;
+}
+
+/*
+ * menuwin_draw: draw 'menuwin' and it's options each centered on their own
+ * line, then highlight the selected option
+ */
+static void *
+menuwin_draw(void *window)
+{
+ assert(window != NULL);
+ static const int option_left = BORDER + INDICATOR,
+ highlight_left = BORDER;
+ struct MenuWin *menuwin = window;
+ struct RichWin *richwin = menuwin->base.richwin;
+ // calculate number of menu options to draw and width of each one
+ const int option_maxnum = richwin->lines - BORDERS,
+ option_num = min_int(option_maxnum, ARRAY_LENGTH(menu_options)),
+ option_width = richwin->cols - BORDERS - INDICATORS;
+
+ // clear menuwin
+ err = werase(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+
+ // border menuwin
+ err = richwin_border(richwin);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (option_width > 0 && option_num > 0) {
+ // draw menu options centered on window
+ for (int i = 0; i < option_num; i++) {
+ const int option_top = BORDER + i;
+ const char *option_text = menu_options[i].text;
+ // write centered menu option
+ err = mvwaddcstr(richwin->win, option_top, option_left,
+ option_text, option_width, TEXT_CONTINUATION);
+ if (err == ERR) {
+ write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__);
+ return NULL;
+ }
+ }
+
+ // highlight selected option
+ const int highlight_top = BORDER + (int) cycle_index(menuwin->options),
+ highlight_width = option_width + INDICATORS;
+ if (highlight_top < BORDER+option_maxnum) {
+ highlight_str(richwin->win, highlight_top, highlight_left,
+ highlight_width, 1);
+ }
+ }
+
+ // write to virtual screen
+ err = wnoutrefresh(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ return NULL;
+ }
+ return menuwin;
+}
+
+/* menuwin_cleanup: free resources used by a MenuWin window */
+static void
+menuwin_cleanup(struct MenuWin *menuwin)
+{
+ assert(menuwin != NULL);
+ cycle_free(menuwin->options);
+ menuwin->options = NULL;
+ mainwin_cleanup(&menuwin->base);
+}
+
+/*-- PromptWin methods ------------------------------------------------------*/
+
+/* promptwin_new_richwin: defaults for prompt rich window */
+static struct RichWin *
+promptwin_new_richwin(void *window)
+{
+ assert(window != NULL);
+ struct PromptWin *promptwin = window;
+ const int lines = min_int(BORDERS+2, LINES),
+ needed_cols = promptwin->content_minlen + PADDINGS + BORDERS,
+ cols = min_int(needed_cols, COLS/2);
+ // set borders
+ struct WinBorders winborders = {
+ .ls = '|', .rs = '|', .ts = '-', .bs = '-',
+ .tl = '+', .tr = '+', .bl = '+', .br = '+',
+ };
+ struct RichWin *richwin;
+
+ // create window
+ richwin = richwin_new_centered(lines, cols, &winborders);
+ if (richwin == NULL) {
+ write_error(richwin_error_code, __LINE__, __func__);
+ return NULL;
+ }
+ // set options
+ err = keypad(richwin->win, true);
+ if (err == ERR) {
+ richwin_del(richwin);
+ write_error(ERROR_OPTION_SETTING, __LINE__, __func__);
+ return NULL;
+ }
+ return richwin;
+}
+
+/* promptwin_draw: draw 'propmtwin', it's prompt, and input field */
+static void *
+promptwin_draw(void *window)
+{
+ assert(window != NULL);
+ struct PromptWin *promptwin = window;
+ struct RichWin *richwin = promptwin->base.richwin;
+ static const int content_left = BORDER + PADDING,
+ prompt_top = BORDER;
+ const int content_width = richwin->cols - BORDERS - PADDING,
+ input_top = prompt_top + 1;
+
+ // clear window
+ err = werase(richwin->win) == ERR;
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+
+ // border window
+ err = richwin_border(richwin) == ERR;
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (content_width > 0 && richwin->lines > input_top) {
+ // print prompt
+ err = mvwaddcstr(richwin->win, prompt_top, content_left,
+ promptwin->prompt, content_width, TEXT_CONTINUATION);
+ if (err == ERR) {
+ dump_core();
+ write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__);
+ return NULL;
+ }
+ // print input field
+ err = mvwaddcstr(richwin->win, input_top, content_left,
+ promptwin->input, content_width, INPUT_CONTINUATION);
+ if (err == ERR) {
+ write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__);
+ return NULL;
+ }
+ }
+
+ // write to virtual screen
+ err = wnoutrefresh(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ return NULL;
+ }
+ return promptwin;
+}
+
+/* promptwin_cleanup: free resources used by a PromptWin window */
+static void
+promptwin_cleanup(struct PromptWin *promptwin)
+{
+ assert(promptwin != NULL);
+ free(promptwin->input);
+ promptwin->input = NULL;
+ mainwin_cleanup(&promptwin->base);
+}
+
+
+/*== application actions ====================================================*/
+
+/* app_draw: enable drawing and draw board */
+static struct BoardWin *
+app_draw(struct BoardWin *boardwin)
+{
+ assert(boardwin != NULL);
+
+ // turn board on and draw board
+ boardwin->on = true;
+ if (boardwin_draw(boardwin) == NULL) return NULL;
+ return boardwin;
+}
+
+/* app_clean: erase stuff drwan on 'boardwin' */
+static struct BoardWin *
+app_clean(struct BoardWin *boardwin)
+{
+ assert(boardwin != NULL);
+ struct RichWin *richwin = boardwin->base.richwin;
+
+ // turn off board and clear it
+ boardwin->on = false;
+ err = wclear(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+ // restore background color to original
+ if (colorize) {
+ err = wbkgd(richwin->win, COLOR_PAIR(0));
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+ }
+
+ // write to virtual screen
+ err = wnoutrefresh(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ return NULL;
+ }
+ return boardwin;
+}
+
+/* app_change_word: replace word to draw with new one from user */
+static struct BoardWin *
+app_change_word(struct BoardWin *boardwin)
+{
+ assert(boardwin != NULL);
+
+ // prompt user for new word
+ if (prompt_user(DEFAULT_PROMPT, boardwin->word, WORD_MAXLEN) < 0) {
+ return NULL;
+ }
+ // draw board
+ if (boardwin->on) {
+ if (boardwin_draw(boardwin) == NULL) return NULL;
+ }
+ return boardwin;
+}
+
+/* app_change_color: change the color to use when drawing 'boardwin' */
+static struct BoardWin *
+app_change_color(struct BoardWin *boardwin)
+{
+ assert(boardwin != NULL);
+ if (!colorize) {
+ return boardwin;
+ }
+
+ // modify board drawing color pair
+ const short bg_color = *(const short *) cycle_next(boardwin->colors);
+ err = init_pair(2, COLOR_BLACK, bg_color);
+ if (err == ERR) {
+ write_error(ERROR_COLOR_PAIR_DEFINITION, __LINE__, __func__);
+ return NULL;
+ }
+ // draw board
+ if (boardwin->on) {
+ if (boardwin_draw(boardwin) == NULL) return NULL;
+ }
+ return boardwin;
+}
+
+/* app_end: mark 'boardwin' done being used so program can exit */
+static struct BoardWin *
+app_end(struct BoardWin *boardwin)
+{
+ assert(boardwin != NULL);
+ boardwin->done = true;
+ return boardwin;
+}