diff --git a/src/fe_modes.c b/src/fe_modes.c index a8527e7..ce30cde 100644 --- a/src/fe_modes.c +++ b/src/fe_modes.c @@ -23,6 +23,7 @@ #include "fe_modes.h" +#include #include #include @@ -34,11 +35,15 @@ // #define LOG_KEY_EVENTS // `logd` new mouse and key events editor_mode_t modes[] = { + {"", "", NULL}, // empty (0) {"Switcher", "Switch to another mode", mode_picker}, + {"GOTO", "", mode_goto}, + {"", "", NULL}, // USER_MODES {"Insert", "Insert characters", mode_insert}, {"Pan", "Pan around the canvas", mode_pan}, {"Free-Line", "Draw a line with your arrow keys", mode_free_line}, {"Brush", "Paint with arrow keys and mouse", mode_brush}, + {"", "", NULL}, // END }; typedef struct { @@ -57,6 +62,14 @@ typedef struct { mode_insert_config_t mode_insert_config = {NULL}; +typedef struct { + enum { ENTER_FIRST, ENTER_SECOND } state; + char buffer[8]; + int xpos, ypos; +} mode_goto_config_t; + +mode_goto_config_t mode_goto_config = {ENTER_FIRST, "", 0, 0}; + /////////////////////// // GENERAL FUNCTIONS // /////////////////////// @@ -97,6 +110,9 @@ void cmd_trim_canvas(State *state) { int call_mode(Mode_ID mode, reason_t reason, State *state) { int res = modes[mode].mode_function(reason, state); update_info_win_state(state); + if (res & RESP_EXIT) { + switch_mode(state->last_canvas_mode, state); + } return res; } @@ -112,7 +128,7 @@ inline void update_info_win_state(State *state) { * * It sends END to the old mode and then START to the new mode */ -void switch_mode(Mode_ID new_mode, State *state) { +inline void switch_mode(Mode_ID new_mode, State *state) { logd("Switching to %s\n", modes[new_mode].name); call_mode(state->current_mode, END, state); state->last_canvas_mode = state->current_mode; @@ -122,6 +138,32 @@ void switch_mode(Mode_ID new_mode, State *state) { refresh_screen(); } +// switch from normal mode operation to an overridden mode +void enter_override_mode(Mode_ID override_mode, State *state) { + state->override_mode = override_mode; + call_mode(state->override_mode, START, state); +} + +// return control from override mode to normal mode +void exit_override_mode(State *state) { + call_mode(state->override_mode, END, state); + state->override_mode = 0; + // TODO: "redraw" old mode +} + +// TODO: better name +void handle_mode(reason_t reason, State *state) { + if (state->override_mode != 0) { + int res = call_mode(state->override_mode, reason, state); + if (res & RESP_EXIT) { + logd("Exiting override mode %s\n", modes[state->override_mode].name); + exit_override_mode(state); + } + } else { + call_mode(state->current_mode, reason, state); + } +} + Mode_ID add_mod_canvas_mode(Mode_ID mode, int n) { int mode_first = MODE_PICKER + 1; // beginning of selectable modes int mode_list_end = LAST; // length of total mode list @@ -162,31 +204,17 @@ int master_handler(State *state, WINDOW *canvas_win, WINDOW *status_win) { // https://invisible-island.net/ncurses/man/curs_mouse.3x.html state->ch_in = c; state->mevent_in = &event; - call_mode(state->current_mode, NEW_MOUSE, state); - } - } else if (c == KEY_TAB) { // switching modes - if (state->current_mode == MODE_PICKER && - state->last_canvas_mode != MODE_PICKER) { - state->last_canvas_mode = next_canvas_mode(state->last_canvas_mode); - - // update mode_picker() to redraw the highlight - state->ch_in = c; - call_mode(state->current_mode, NEW_KEY, state); - } else { - switch_mode(MODE_PICKER, state); + handle_mode(NEW_MOUSE, state); } return 0; - } else if (c == KEY_SHIFT_TAB) { - if (state->current_mode == MODE_PICKER && - state->last_canvas_mode != MODE_PICKER) { - state->last_canvas_mode = previous_canvas_mode(state->last_canvas_mode); - - // update mode_picker() to redraw the highlight - state->ch_in = c; - call_mode(state->current_mode, NEW_KEY, state); - } else { - switch_mode(MODE_PICKER, state); + } else if (c == KEY_TAB) { // switch modes + if (state->override_mode != MODE_PICKER) { + enter_override_mode(MODE_PICKER, state); + return 0; } + } else if (c == KEY_CTRL('g')) { // goto pos + enter_override_mode(MODE_GOTO, state); + return 0; } else if (c == KEY_NPAGE || c == KEY_PPAGE || c == KEY_SLEFT || c == KEY_SRIGHT) { // shift view down/up/left/right @@ -218,19 +246,22 @@ int master_handler(State *state, WINDOW *canvas_win, WINDOW *status_win) { state->view->x = new_vx; redraw_canvas_win(); update_info_win_state(state); + return 0; } else if (c == KEY_CTRL('r')) { cmd_read_from_file(state); print_msg_win("Read from file '%s'\n", state->filepath); + return 0; } else if (c == KEY_CTRL('s')) { cmd_write_to_file(state); print_msg_win("Saved to file '%s'\n", state->filepath); + return 0; } else if (c == KEY_CTRL('t')) { cmd_trim_canvas(state); - } else { - // pass character on to mode - state->ch_in = c; - call_mode(state->current_mode, NEW_KEY, state); + return 0; } + // pass character on to mode + state->ch_in = c; + handle_mode(NEW_KEY, state); // Move UI cursor to the right place wmove(canvas_win, cursor_y_to_canvas(state->cursor), @@ -296,8 +327,36 @@ int mode_picker(reason_t reason, State *state) { } // get bounds of switchable modes in enum array (DON'T include mode_picker) - int mode_first = MODE_PICKER + 1; // beginning of selectable modes - int mode_list_end = LAST; // length of total mode list + int mode_first = USER_MODES + 1; // beginning of selectable modes + int mode_list_end = LAST; // length of total mode list + + // INTERPRET KEYS + Mode_ID new_mode = 0; + if (reason == NEW_KEY) { + // only accept characters within the bounds of the list + if (state->ch_in >= '1' && + state->ch_in < '1' + mode_list_end - mode_first) { + new_mode = mode_first + state->ch_in - '1'; + } else if (state->ch_in == KEY_ENTER) { + new_mode = state->last_canvas_mode; + } else if (state->ch_in == KEY_TAB) { + state->last_canvas_mode = next_canvas_mode(state->last_canvas_mode); + } else if (state->ch_in == KEY_SHIFT_TAB) { + state->last_canvas_mode = previous_canvas_mode(state->last_canvas_mode); + } + } + + // exit early if switching modes + if (new_mode != 0) { + // this is a little funky. We can't just return from override mode, because + // the new mode isn't initialized, but we can't switch without turning off + // override_mode + exit_override_mode(state); + switch_mode(new_mode, state); + return 0; + } + + // DRAW INTERFACE // BUILD MODE INFO MESSAGE char msg[128] = ""; @@ -337,20 +396,6 @@ int mode_picker(reason_t reason, State *state) { if (selected_num_char != -1) { highlight_mode_text(selected_x, selected_num_char); } - - // INTERPRET KEYS - if (reason == NEW_KEY) { - // only accept characters within the bounds of the list - if (state->ch_in >= '1' && - state->ch_in < '1' + mode_list_end - mode_first) { - Mode_ID new_mode = mode_first + state->ch_in - '1'; - switch_mode(new_mode, state); - return 0; - } else if (state->ch_in == KEY_ENTER) { - switch_mode(state->last_canvas_mode, state); - return 0; - } - } return 0; } @@ -533,3 +578,86 @@ int mode_brush(reason_t reason, State *state) { return 0; } + +int update_pos_value(mode_goto_config_t *mode_cfg) { + int *pos; + switch (mode_cfg->state) { + case ENTER_FIRST: + pos = &(mode_cfg->xpos); + break; + case ENTER_SECOND: + pos = &(mode_cfg->ypos); + } + char *buff = mode_cfg->buffer; + // parse buffer value + errno = 0; + int val = (int)strtol(buff, NULL, 10); + if (errno != 0) { + perror("update_pos_value strtol"); + print_mode_win("Invalid number"); + return 1; + } + // move relative if sign is present + switch (buff[0]) { + case '-': + case '+': + *pos = *pos + val; + break; + default: + *pos = val; + break; + } + return 0; +} + +/* mode_goto + * + * Move the cursor to a position entered on the keyboard. + */ +int mode_goto(reason_t reason, State *state) { + mode_goto_config_t *mode_cfg = &mode_goto_config; + char *buff = mode_cfg->buffer; + if (reason == START) { + // reset mode state + mode_cfg->state = ENTER_FIRST; + mode_cfg->xpos = state->view->x + state->cursor->x; + mode_cfg->ypos = state->view->y + state->cursor->y; + memset(buff, 0, 8); + } else if (reason == NEW_KEY) { + switch (state->ch_in) { + case ',': + // parse, switch to second value + update_pos_value(mode_cfg); + memset(buff, 0, 8); // reset buffer + mode_cfg->state = ENTER_SECOND; + break; + case KEY_ENTER: + // parse, set cursor location and return to last mode + update_pos_value(mode_cfg); + memset(buff, 0, 8); // reset buffer + state->cursor->x = mode_cfg->xpos - state->view->x; + state->cursor->y = mode_cfg->ypos - state->view->y; + // TODO: return somehow + return RESP_EXIT; + case KEY_BACKSPACE: { + // remove last char in buffer + int l = strlen(buff); + if (l > 0) { + buff[l] = '\0'; + } + } + default: { + // check if valid and add to buffer + // TODO: check for validity + int l = strlen(buff); + if (l < 8 - 1) { + buff[l] = state->ch_in; + } + break; + } + } + } + + print_mode_win("Go to pos: (%i, %i)", mode_cfg->xpos, mode_cfg->ypos); + return 0; +} diff --git a/src/fe_modes.h b/src/fe_modes.h index 39f0b0e..6f80ad4 100644 --- a/src/fe_modes.h +++ b/src/fe_modes.h @@ -8,6 +8,11 @@ int mode_master(State *state, WINDOW *canvas_win, WINDOW *status_win); typedef enum { START, NEW_KEY, NEW_MOUSE, END } reason_t; +// mode responses +#define RESP_OK 0 +#define RESP_EXIT 1 +#define RESP_REDRAW 2 + // Prototype for mode functions. Note that this is NOT a function pointer type // (use `mode_function_t*` for that). https://stackoverflow.com/a/5195682 typedef int mode_function_t(reason_t, State *); @@ -18,6 +23,8 @@ mode_function_t mode_pan; mode_function_t mode_free_line; mode_function_t mode_brush; +mode_function_t mode_goto; + typedef struct { char *name; char *description; diff --git a/src/frontend.c b/src/frontend.c index fca3b29..3ca0199 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -304,6 +304,7 @@ void init_state(State *state, const arguments_t *const arguments) { .last_arrow_direction = KEY_RIGHT, .last_canvas_mode = MODE_INSERT, + .override_mode = 0, .view = view, .last_cursor = cursor_newyx(arguments->y, arguments->x), .filepath = arguments->filename, @@ -733,7 +734,7 @@ void update_info_win(const Mode_ID current_mode, const int x, const int y, /* Signal handler for exiting * - * Turns of ncurses, disables mouse moves commands, closes the logfile. + * Turns off ncurses, disables mouse moves commands, closes the logfile. * * If sig is 0 or SIGINT, exits normally, otherwise prints the signal * information to stderr and exits with sig. diff --git a/src/mode_id.h b/src/mode_id.h index 98d806c..310112b 100644 --- a/src/mode_id.h +++ b/src/mode_id.h @@ -7,7 +7,9 @@ * This enum is used to index into the modes array. */ typedef enum { - MODE_PICKER, + MODE_PICKER = 1, + MODE_GOTO, + USER_MODES, // empty start of user modes MODE_INSERT, MODE_PAN, MODE_FREE_LINE, diff --git a/src/state.h b/src/state.h index b4c9d25..ee47b67 100644 --- a/src/state.h +++ b/src/state.h @@ -19,6 +19,7 @@ typedef struct { View *view; // view of the canvas (also holds the canvas) Mode_ID current_mode; // the current mode of the interface Mode_ID last_canvas_mode; // the last mode of the interface + Mode_ID override_mode; // any special mode that should be used instead int last_arrow_direction; Cursor *last_cursor; char *filepath; // path of savefile