diff options
author | noodle <shawtynoodle@gmail.com> | 2023-07-10 15:40:08 +0300 |
---|---|---|
committer | noodle <shawtynoodle@gmail.com> | 2023-07-10 15:40:08 +0300 |
commit | b7ac144cd2d242791938b51569effb7a1378a332 (patch) | |
tree | 0db39dc6d72a96697707c662c32f4dcdb99372b7 | |
parent | 35eacac40f265aad47bf25d10f3ecd3670b79b2f (diff) |
Add files
-rwxr-xr-x | build | 7 | ||||
-rw-r--r-- | cycle.c | 77 | ||||
-rw-r--r-- | cycle.h | 34 | ||||
-rw-r--r-- | fun_menu.c | 1055 | ||||
-rw-r--r-- | fun_menu.h | 28 | ||||
-rw-r--r-- | richwin.c | 85 | ||||
-rw-r--r-- | richwin.h | 68 | ||||
-rw-r--r-- | stack.c | 179 | ||||
-rw-r--r-- | stack.h | 68 | ||||
-rw-r--r-- | utils.c | 25 | ||||
-rw-r--r-- | utils.h | 11 |
11 files changed, 1637 insertions, 0 deletions
@@ -0,0 +1,7 @@ +#!/bin/sh -e +options="" +options="${options} -g3 -O0" +options="${options} -Weverything -Wno-declaration-after-statement -Wno-padded -Wno-disabled-macro-expansion" +options="${options} -std=c99 -Wpedantic -pedantic-errors" +options="${options} $(pkg-config --cflags --libs ncurses)" +clang $options -lm -o fun_menu fun_menu.c utils.c richwin.c stack.c cycle.c @@ -0,0 +1,77 @@ +#include <stddef.h> // for size_t +#include <stdlib.h> // for malloc() and free() +#include <assert.h> +#include "utils.h" +#include "cycle.h" + +/* cycling iterable */ +struct Cycle { + const void *base; + size_t i; + size_t memb_n; + size_t memb_size; +}; + +/* + * cycle_new: create a new cycle of array startinng at 'base' with memb_n + * elements each of size 'memb_size' + */ +struct Cycle * +cycle_new(const void *base, size_t memb_n, size_t memb_size) +{ + assert(base != NULL); + assert(memb_n > 0); + assert(memb_size > 0); + struct Cycle *cycle = malloc(sizeof *cycle); + + if (cycle == NULL) { + return NULL; + } + cycle->base = base; + cycle->i = 0; + cycle->memb_n = memb_n; + cycle->memb_size = memb_size; + return cycle; +} + +/* cycle_free: free memmory used by cycle */ +void +cycle_free(struct Cycle *cycle) +{ + assert(cycle != NULL); + free(cycle); +} + +/* cycle_index: get index of current element of cycle */ +size_t +cycle_index(struct Cycle *cycle) +{ + assert(cycle != NULL); + return cycle->i; +} + +/* cycle_current: get current element of cycle */ +const void * +cycle_current(struct Cycle *cycle) +{ + assert(cycle != NULL); + return (const char *)cycle->base + cycle->memb_size*cycle->i; +} + +/* cycle_next: point cycle to next element and return it */ +const void * +cycle_next(struct Cycle *cycle) +{ + assert(cycle != NULL); + cycle->i = (cycle->i >= cycle->memb_n-1) ? 0 : cycle->i+1; + return cycle_current(cycle); +} + +/* cycle_prev: point cycle to previous elemetn and return it */ +const void * +cycle_prev(struct Cycle *cycle) +{ + assert(cycle != NULL); + cycle->i = (cycle->i <= 0) ? cycle->memb_n-1 : cycle->i-1; + return cycle_current(cycle); +} @@ -0,0 +1,34 @@ +#ifndef CYCLE_H +#define CYCLE_H + +// cycle +typedef struct Cycle Cycle; + +/* + * cycle_new: create a new cycling iterator + * parameters: + * base (void *) -> pointer to start of array to cycle through + * memb_n (size_t) -> number of elements of array 'base' + * memb_size (size_t) -> size of an element of array 'base' + * returns: + * Cycle * -> the created cylce + * NULL -> failed to allocate memory for cycle + */ +Cycle *cycle_new(const void *base, size_t memb_n, size_t memb_size); + +/* cycle_free: free the cycle. doesn't free buffer pointed to by cycle->base */ +void cycle_free(Cycle *cycle); + +/* cycle_index: get index of current element of cycle */ +size_t cycle_index(Cycle *cycle); + +/* cycle_current: get current element of cycle */ +const void *cycle_current(Cycle *cycle); + +/* cycle_next: move cycle one element forward */ +const void *cycle_next(Cycle *cycle); + +/* cycle_free: move cycle one element backward*/ +const void *cycle_prev(Cycle *cycle); + +#endif /* CYCLE_H */ 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; +} diff --git a/fun_menu.h b/fun_menu.h new file mode 100644 index 0000000..27bce8b --- /dev/null +++ b/fun_menu.h @@ -0,0 +1,28 @@ +#ifndef FUN_MENU_H +#define FUN_MENU_H + +// dump core +void dump_core(void); + +// define error codes +enum ErrorCode { + ERROR_OK, + ERROR_LOCALE_SETTING, + ERROR_COLOR_STARTING, + ERROR_CURSES_SETTING, + ERROR_MEMORY_ALLOCATION, + ERROR_CHARACTER_INPUT, + ERROR_CHARACTER_OUTPUT, + ERROR_WINDOW_CREATION, + ERROR_WINDOW_DRAWING, + ERROR_WINDOW_RENDERING, + ERROR_COLOR_PAIR_DEFINITION, + ERROR_OPTION_SETTING, + ERROR_UNKNOWN, + ERROR_MAX, +}; + +// hold errors of richwin.c +static enum ErrorCode richwin_error_code = ERROR_OK; + +#endif /* FUN_MENU_H */ diff --git a/richwin.c b/richwin.c new file mode 100644 index 0000000..07af571 --- /dev/null +++ b/richwin.c @@ -0,0 +1,85 @@ +#include <stdlib.h> // for malloc() and free() +#include <string.h> // for memcpy() +#include <assert.h> + +#include <curses.h> +#include "fun_menu.h" +#include "richwin.h" + +/* richwin_new */ +struct RichWin * +richwin_new(int lines, int cols, int begy, int begx, + struct WinBorders *winborders) +{ + assert(lines >= 0); + assert(cols >= 0); + assert(begy >= 0); + assert(begx >= 0); + // create a temporary struct for initializing const members of 'richwin' + struct RichWin richwin_init = { + .lines = lines, .cols = cols, + .begy = begy, .begx = begx, + }; + + // ask curses for a window + richwin_init.win = newwin(lines, cols, begy, begx); + if (richwin_init.win == NULL) { + richwin_error_code = ERROR_WINDOW_CREATION; + return NULL; + } + // allocate window borders struct if user asked for it + if (winborders != NULL) { + richwin_init.winborders = malloc(sizeof *richwin_init.winborders); + if (richwin_init.winborders == NULL) { + richwin_error_code = ERROR_MEMORY_ALLOCATION; + return NULL; + } + *richwin_init.winborders = *winborders; + } + + // allocate rich window struct + struct RichWin *richwin = malloc(sizeof *richwin); + if (richwin == NULL) { + richwin_error_code = ERROR_MEMORY_ALLOCATION; + return NULL; + } + // initialize 'richwin' from 'richwin_init' + memcpy(richwin, &richwin_init, sizeof *richwin); + return richwin; +} + +/* richwin_new_centered */ +struct RichWin * +richwin_new_centered(int lines, int cols, struct WinBorders *winborders) +{ + assert(lines <= LINES); + assert(cols <= COLS); + // no need for asserting because this is a convenience wrapper + return richwin_new(lines, cols, (LINES-lines)/2, (COLS-cols)/2, + winborders); +} + +/* richwin_border */ +int +richwin_border(struct RichWin *richwin) +{ + assert(richwin != NULL); + assert(richwin->winborders != NULL); + struct WinBorders *winborders = richwin->winborders; + return wborder(richwin->win, + winborders->ls, winborders->rs, winborders->ts, winborders->bs, + winborders->tl, winborders->tr, winborders->bl, winborders->br); +} + +/* richwin_del */ +int +richwin_del(struct RichWin *richwin) +{ + assert(richwin != NULL); + if (delwin(richwin->win) == ERR) { + return ERR; + } + free(richwin->winborders); + free(richwin); + return OK; +} diff --git a/richwin.h b/richwin.h new file mode 100644 index 0000000..5a9bfb2 --- /dev/null +++ b/richwin.h @@ -0,0 +1,68 @@ +#ifndef RICHWIN_H +#define RICHWIN_H + +// a rich window: represents a ncurses window with stats +struct RichWin { + WINDOW *win; + struct WinBorders *winborders; + const int lines, cols; + const int begy, begx; +}; + +// window borders: holds each character for an ncurses window border +struct WinBorders { + chtype ls, rs, ts, bs, + tl, tr, bl, br; +}; + +/* + * richwin_new: create a new rich window + * parameters: + * lines (int) -> window line number + * colums (int) -> window column number + * begy (int) -> y-coordinate of upper-left corner of window + * begx (int) -> x-coordinate of upper-left corner of window + * winborders (struct WinBorders *) + * returns: + * struct RichWin * -> pointer to the new window + * NULL -> failure + */ +struct RichWin *richwin_new(int lines, int cols, int begy, int begx, + struct WinBorders *winborders); + +/* + * richwin_new_centered create a new centered rich window + * parameters: + * lines (int) -> window line number + * colums (int) -> window column number + * begy (int) -> y-coordinate of upper-left corner of window + * begx (int) -> x-coordinate of upper-left corner of window + * winborders (struct WinBorders *) + * returns: + * struct RichWin * -> pointer to the new window + * NULL -> failure + */ +struct RichWin *richwin_new_centered(int lines, int cols, + struct WinBorders *winborders); + +/* + * richwin_border: border a rich window + * parameters: + * winborders (struct WinBorders *) + * returns: + * ERR -> if wborder() returns ERR + * OK -> on success + */ +int richwin_border(struct RichWin *richwin); + +/* + * richwin_del: delete a rich window + * parameters: + * richwin (struct RichWin *) -> window to delete + * returns: + * ERR -> if delwin() returns ERR + * OK -> on success + */ +int richwin_del(struct RichWin *richwin); + +#endif /* RICHWIN_H */ @@ -0,0 +1,179 @@ +#include <assert.h> +#include <stdlib.h> // for malloc() and free() +#include "stack.h" + +/* node of a doubly-linked list */ +struct Node { + struct Node *next, *prev; + void *data; +}; + +/* + * stack implemented using a doubly-linked list to allow traversal from the top + * or the bottom + */ +struct Stack { + struct Node *top, *bottom; + size_t size; +}; + +/* node_new: create a new doubly-linked list node */ +static struct Node * +node_new(void) +{ + struct Node *node = malloc(sizeof *node); + if (node == NULL) { + return NULL; + } + node->next = node->prev = node->data = NULL; + return node; +} + +/* node_free: free a doubly-linked list node */ +static void +node_free(struct Node *node) +{ + assert(node != NULL); + free(node); +} + +/* stack_new: create a new empty stack */ +struct Stack * +stack_new(void) +{ + struct Stack *stack = malloc(sizeof *stack); + if (stack == NULL) { + return NULL; + } + stack->top = stack->bottom = NULL; + stack->size = 0; + return stack; +} + +/* + * stack_delete: free memory used by the stack and it's nodes. But don't free + * any data pointed to by the stack nodes + * good for stacks that don't store pointers to dynamically allocated buffers + */ +void +stack_delete(struct Stack *stack) +{ + assert(stack != NULL); + for (struct Node *node = stack->top, *next_node; + node != NULL; node = next_node) + { + next_node = node->next; + node_free(node); + } + free(stack); +} + +/* + * stack_free: free memory used by the stack, it's nodes, and the data stored + * inside each node + * good for stacks that store pointers to dynamically allocated buffers + */ +void +stack_free(struct Stack *stack, void (*free_data)(void *)) +{ + assert(stack != NULL); + assert(free_data != NULL); + + for (struct Node *node = stack->top, *next_node; + node != NULL; node = next_node) + { + next_node = node->next; + free_data(node->data); + node_free(node); + } + free(stack); +} + +/* push_stack: push a new node of value 'data' to top of 'stack' */ +void * +stack_push(struct Stack *stack, void *data) +{ + assert(stack != NULL); + assert(data != NULL); + + // create a new node with passed 'data' + struct Node *newtop; + if ((newtop = node_new()) == NULL) { + return NULL; + } + newtop->data = data; + + // push node to stack + if (stack->top == NULL) { + // first node in stack + stack->top = stack->bottom = newtop; + } else { + // link new top and old top + newtop->next = stack->top; + stack->top->prev = newtop; + // put new node at top of stack + stack->top = newtop; + } + stack->size++; + return newtop; +} + +/* stack_pop: pop data value of top node of 'stack' */ +void * +stack_pop(struct Stack *stack) +{ + assert(stack != NULL); + struct Node *oldtop = stack->top, + *newtop = stack->top->next; + void *data = oldtop->data; + + // remove reference to old top and free it + newtop->prev = NULL; + node_free(oldtop); + // set new stack top and size + stack->top = newtop; + stack->size--; + return data; +} + +/* stack_size: accessor for size of stack */ +size_t +stack_size(struct Stack *stack) +{ + assert(stack != NULL); + return stack->size; +} + +/* + * stack_iter: loop over 'stack' nodes from top to bottom, running 'apply' on + * each node + */ +void * +stack_iter(struct Stack *stack, void *(*apply)(void *)) +{ + assert(stack != NULL); + assert(apply != NULL); + for (struct Node *node = stack->top; node != NULL; node = node->next) { + if (apply(node->data) == NULL) { + return NULL; + } + } + return stack; +} + +/* + * stack_iter: loop over 'stack' nodes from bottom to top, running 'apply' on + * each node + */ +void * +stack_iter_reverse(struct Stack *stack, void *(*apply)(void *)) +{ + assert(stack != NULL); + assert(apply != NULL); + for (struct Node *node = stack->bottom; node != NULL; node = node->prev) { + if (apply(node->data) == NULL) { + return NULL; + } + } + return stack; +} @@ -0,0 +1,68 @@ +#ifndef STACK_H +#define STACK_H + +// stack +typedef struct Stack Stack; + +/* + * stack_new: create a new stack + * returns: + * Stack * -> the created stack + * NULL -> failed to allocate memory for stack + */ +Stack *stack_new(void); + +/* stack_delete: free the stack, but don't free any buffer in any of it's */ +void stack_delete(Stack *stack); + +/* + * stack_free: free the stack, each element is also freed accordingly using + * 'free_data' + * parameters: + * stack (Stack *) -> stack to delete + * free_data -> function that would free resources of each element in stack + */ +void stack_free(Stack *stack, void (*free_data)(void *data)); + +/* + * stack_push: push element to top of stack + * parameters: + * stack (Stack *) -> stack to push data into + * data (void *) -> data to push + * returns: + * void * -> the pushed data + * NULL -> failure of allocating memory for new stack element + */ +void *stack_push(Stack *stack, void *data); + +/* stack_pop: pop element from top of stack */ +void *stack_pop(Stack *stack); + +/* stack_get_size: get size of stack */ +size_t stack_size(Stack *stack); + +/* + * stack_get_size: iterate over a stack top-to-bottom, running 'apply' on each + * stack element + * parameters: + * stack (Stack *) -> stack to iterate over + * apply -> function that would be passed each element in stack + * returns: + * void * -> sucess (pointer to stack) + * NULL -> one of the executions of 'apply' returned NULL + */ +void *stack_iter(Stack *stack, void *(*apply)(void *data)); + +/* + * stack_iter_reverse: iterate over a stack bottom-to-top, running 'apply' on + * each stack element + * parameters: + * stack (Stack *) -> stack to iterate over + * apply -> function that would be passed each element in stack + * returns: + * void * -> sucess (pointer to stack) + * NULL -> one of the executions of 'apply' returned NULL + */ +void *stack_iter_reverse(Stack *stack, void *(*apply)(void *data)); + +#endif /* STACK_H */ @@ -0,0 +1,25 @@ +#include <stddef.h> // for int +#include <math.h> // for floorf() +#include "utils.h" + +int +min_int(int a, int b) +{ + return (a < b) ? a : b; +} +size_t +min_size_t(size_t a, size_t b) +{ + return (a < b) ? a : b; +} + +int +max_int(int a, int b) +{ + return (a > b) ? a : b; +} +size_t +max_size_t(size_t a, size_t b) +{ + return (a > b) ? a : b; +} @@ -0,0 +1,11 @@ +#ifndef UTILS_H +#define UTILS_H + +// min: get minimum of 'a' and 'b' +int min_int(int a, int b); +size_t min_size_t(size_t a, size_t b); +// max: get maximum of 'a' and 'b' +int max_int(int a, int b); +size_t max_size_t(size_t a, size_t b); + +#endif /* UTILS_H */ |