You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

783 lines
29 KiB

// no transitive includes
#include "base.h"
#include "field.h"
#include "gbuffer.h"
#include "oso.h"
#include "sysmisc.h"
#include "term_util.h"
#include "midi.h"
// many transisitve includes
#include "ged.h"
#include "tui.h"
#include <getopt.h>
#include <locale.h>
#define SOKOL_IMPL
#include "sokol_time.h"
#undef SOKOL_IMPL
#define TIME_DEBUG 0
#if TIME_DEBUG
static int spin_track_timeout = 0;
#endif
staticni void usage(void)
{ // clang-format off
fprintf(stderr,
"Usage: orca [options] [file]\n\n"
"General options:\n"
" --undo-limit <number> Set the maximum number of undo steps.\n"
" If you plan to work with large files,\n"
" set this to a low number.\n"
" Default: 100\n"
" --initial-size <nxn> When creating a new grid file, use these\n"
" starting dimensions.\n"
" --bpm <number> Set the tempo (beats per minute).\n"
" Default: 120\n"
" --seed <number> Set the seed for the random function.\n"
" Default: 1\n"
" -h or --help Print this message and exit.\n"
"\n"
"OSC/MIDI options:\n"
" --strict-timing\n"
" Attempt to reduce timing jitter of outgoing MIDI and OSC\n"
" messages. Uses more CPU time. May have no effect.\n"
"\n"
" --osc-midi-bidule <path>\n"
" Set MIDI to be sent via OSC formatted for Plogue Bidule.\n"
" The path argument is the path of the Plogue OSC MIDI device.\n"
" Example: /OSC_MIDI_0/MIDI\n"
);} // clang-format on
typedef enum
{
Brackpaste_seq_none = 0,
Brackpaste_seq_begin,
Brackpaste_seq_end,
} Brackpaste_seq;
// Try to getch to complete the sequence of a start or end of brackpet paste
// escape sequence. If it doesn't turn out to be one, unwind it back into the
// event queue with ungetch. Yeah this is golfed let me have fun.
staticni Brackpaste_seq brackpaste_seq_getungetch(WINDOW *win)
{
int chs[5], n = 0, begorend; // clang-format off
if ((chs[n++] = wgetch(win)) != '[') goto unwind;
if ((chs[n++] = wgetch(win)) != '2') goto unwind;
if ((chs[n++] = wgetch(win)) != '0') goto unwind;
chs[n++] = begorend = wgetch(win);
if (begorend != '0' && begorend != '1') goto unwind;
if ((chs[n++] = wgetch(win)) != '~') goto unwind;
return begorend == '0' ? Brackpaste_seq_begin : Brackpaste_seq_end;
unwind:
while (n > 0) ungetch(chs[--n]);
return Brackpaste_seq_none; // clang-format on
}
staticni void try_send_to_gui_clipboard(Ged const *a, bool *io_use_gui_clipboard)
{
if (!*io_use_gui_clipboard)
return;
#if 0 // If we want to use grid directly
Usz curs_y, curs_x, curs_h, curs_w;
if (!ged_try_selection_clipped_to_field(a, &curs_y, &curs_x, &curs_h,
&curs_w))
return;
Cboard_error cberr =
cboard_copy(a->clipboard_field.buffer, a->clipboard_field.height,
a->clipboard_field.width, curs_y, curs_x, curs_h, curs_w);
#endif
Usz cb_h = a->clipboard_field.height;
Usz cb_w = a->clipboard_field.width;
if (cb_h < 1 || cb_w < 1)
return;
Cboard_error cberr = cboard_copy(a->clipboard_field.buffer, cb_h, cb_w, 0, 0, cb_h, cb_w);
if (cberr)
*io_use_gui_clipboard = false;
}
Ged ged;
WINDOW *window_main = NULL;
Tui tui;
void main_init(int argc, char **argv)
{
enum
{
Argopt_hardmargins = UCHAR_MAX + 1,
Argopt_undo_limit,
Argopt_init_grid_size,
Argopt_osc_midi_bidule,
Argopt_strict_timing,
Argopt_bpm,
Argopt_seed,
Argopt_portmidi_deprecated,
Argopt_osc_deprecated,
};
static struct option tui_options[] = {
{ "hard-margins", required_argument, 0, Argopt_hardmargins },
{ "undo-limit", required_argument, 0, Argopt_undo_limit },
{ "initial-size", required_argument, 0, Argopt_init_grid_size },
{ "help", no_argument, 0, 'h' },
{ "osc-midi-bidule", required_argument, 0, Argopt_osc_midi_bidule },
{ "strict-timing", no_argument, 0, Argopt_strict_timing },
{ "bpm", required_argument, 0, Argopt_bpm },
{ "seed", required_argument, 0, Argopt_seed },
{ "portmidi-list-devices", no_argument, 0, Argopt_portmidi_deprecated },
{ "portmidi-output-device", required_argument, 0, Argopt_portmidi_deprecated },
{ "osc-server", required_argument, 0, Argopt_osc_deprecated },
{ "osc-port", required_argument, 0, Argopt_osc_deprecated },
{ NULL, 0, NULL, 0 }
};
int init_bpm = 120;
int init_seed = 1;
int init_grid_dim_y = 25;
int init_grid_dim_x = 57;
bool explicit_initial_grid_size = false;
tui_init(&tui, &ged);
int longindex = 0;
#define OPTFAIL(...) \
{ \
fprintf(stderr, "Bad %s argument: %s\n", tui_options[longindex].name, optarg); \
fprintf(stderr, __VA_ARGS__); \
fputc('\n', stderr); \
exit(1); \
}
for (;;) {
int c = getopt_long(argc, argv, "h", tui_options, &longindex);
if (c == -1)
break;
switch (c) {
case 'h':
usage();
exit(0);
case '?':
usage();
exit(1);
case Argopt_hardmargins:
if (read_nxn_or_n(optarg, &tui.hardmargin_x, &tui.hardmargin_y) &&
tui.hardmargin_x >= 0 && tui.hardmargin_y >= 0)
break;
OPTFAIL("Must be 0 or positive integer.");
case Argopt_undo_limit:
if (str_to_int(optarg, &tui.undo_history_limit) && tui.undo_history_limit >= 0)
break;
OPTFAIL("Must be 0 or positive integer.");
case Argopt_bpm:
if (str_to_int(optarg, &init_bpm) && init_bpm >= 1)
break;
OPTFAIL("Must be positive integer.");
case Argopt_seed:
if (str_to_int(optarg, &init_seed) && init_seed >= 0)
break;
OPTFAIL("Must be 0 or positive integer.");
case Argopt_init_grid_size:
if (sscanf(optarg, "%dx%d", &init_grid_dim_x, &init_grid_dim_y) != 2)
OPTFAIL("Bad format or count. Expected something like: 40x30");
if (init_grid_dim_x <= 0 || init_grid_dim_x > ORCA_X_MAX)
OPTFAIL(
"X dimension for initial-size must be 1 <= n <= %d, was %d.",
ORCA_X_MAX,
init_grid_dim_x);
if (init_grid_dim_y <= 0 || init_grid_dim_y > ORCA_Y_MAX)
OPTFAIL(
"Y dimension for initial-size must be 1 <= n <= %d, was %d.",
ORCA_Y_MAX,
init_grid_dim_y);
explicit_initial_grid_size = true;
break;
case Argopt_osc_midi_bidule:
osoput(&tui.osc_midi_bidule_path, optarg);
break;
case Argopt_strict_timing:
tui.strict_timing = true;
break;
case Argopt_portmidi_deprecated:
fprintf(
stderr,
"Option \"--%s\" has been removed.\nInstead, choose "
"your MIDI output device from within the ORCA menu.\n"
"This new menu allows you to pick your MIDI device "
"interactively\n",
tui_options[longindex].name);
exit(1);
case Argopt_osc_deprecated:
fprintf(
stderr,
"Options --osc-server and --osc-port have been removed.\n"
"Instead, set the OSC server and port from within the ORCA menu.\n");
exit(1);
}
}
#undef OPTFAIL
if (optind == argc - 1) {
osoput(&tui.file_name, argv[optind]);
} else if (optind < argc - 1) {
fprintf(stderr, "Expected only 1 file argument.\n");
exit(1);
}
qnav_init(); // Initialize the menu/navigation global state
// Initialize the 'Grid EDitor' stuff. This sits underneath the TUI.
ged_init(&ged, (Usz)tui.undo_history_limit, (Usz)init_bpm, (Usz)init_seed);
// This will need to be changed to work with conf/menu
if (osolen(tui.osc_midi_bidule_path) > 0) {
midi_mode_deinit(&ged.midi_mode);
midi_mode_init_osc_bidule(&ged.midi_mode, osoc(tui.osc_midi_bidule_path));
}
stm_setup(); // Set up timer lib
// Enable UTF-8 by explicitly initializing our locale before initializing
// ncurses. Only needed (maybe?) if using libncursesw/wide-chars or UTF-8.
// Using it unguarded will mess up box drawing chars in Linux virtual
// consoles unless using libncursesw.
setlocale(LC_ALL, "");
initscr(); // Initialize ncurses
// Allow ncurses to control newline translation. Fine to use with any modern
// terminal, and will let ncurses run faster.
nonl();
// Set interrupt keys (interrupt, break, quit...) to not flush. Helps keep
// ncurses state consistent, at the cost of less responsive terminal
// interrupt. (This will rarely happen.)
intrflush(stdscr, FALSE);
// Receive keyboard input immediately without line buffering, and receive
// ctrl+z, ctrl+c etc. as input instead of having a signal generated. We need
// to do this even with wtimeout() if we don't want ctrl+z etc. to interrupt
// the program.
raw();
noecho(); // Don't echo keyboard input
keypad(stdscr, TRUE); // Also receive arrow keys, etc.
curs_set(0); // Hide the terminal cursor
set_escdelay(1); // Short delay before triggering escape
term_util_init_colors(); // Our color init routine
mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL);
if (has_mouse()) // no waiting for distinguishing click from press
mouseinterval(0);
printf("\033[?2004h\n"); // Ask terminal to use bracketed paste.
tui_load_conf(&tui); // load orca.conf (if it exists)
tui_restart_osc_udp_if_enabled(&tui); // start udp if conf enabled it
wtimeout(stdscr, 0);
tui_adjust_term_size(&tui, &window_main);
bool grid_initialized = false;
if (osolen(tui.file_name)) {
Field_load_error fle = field_load_file(osoc(tui.file_name), &ged.field);
switch (fle) {
case Field_load_error_ok:
if (ged.field.height < 1 || ged.field.width < 1) {
// Opening an empty file or attempting to open a directory can lead us
// here.
field_deinit(&ged.field);
qmsg_printf_push("Unusable File", "Not a usable file:\n%s", (osoc(tui.file_name)));
break;
}
grid_initialized = true;
break;
case Field_load_error_cant_open_file: {
// Probably a new file, though TODO we should add an explicit
// differentiation between "file exists and can't open it" and "file
// doesn't seem to exist."
Qmsg *qm = qmsg_printf_push(NULL, "New file: %s", osoc(tui.file_name));
qmsg_set_dismiss_mode(qm, Qmsg_dismiss_mode_passthrough);
break;
}
default:
qmsg_printf_push(
"File Load Error",
"File load error:\n%s.",
field_load_error_string(fle));
break;
}
}
// If we haven't yet initialized the grid, because we were waiting for the
// terminal size, do it now.
if (!grid_initialized) {
Usz new_field_h, new_field_w;
if (explicit_initial_grid_size ||
!tui_suggest_nice_grid_size(&tui, ged.win_h, ged.win_w, &new_field_h, &new_field_w)) {
new_field_h = (Usz)init_grid_dim_y;
new_field_w = (Usz)init_grid_dim_x;
}
field_init_fill(&ged.field, (Usz)new_field_h, (Usz)new_field_w, '.');
}
mbuf_reusable_ensure_size(&ged.mbuf_r, ged.field.height, ged.field.width);
ged_make_cursor_visible(&ged);
ged_send_osc_bpm(&ged, (I32)ged.bpm); // Send initial BPM
ged_set_playing(&ged, true); // Auto-play
}
int main(int argc, char **argv)
{
main_init(argc, argv);
int cur_timeout = 0;
bool is_in_brackpaste = false;
Usz brackpaste_starting_x = 0;
Usz brackpaste_y = 0;
Usz brackpaste_x = 0;
Usz brackpaste_max_y = 0;
Usz brackpaste_max_x = 0;
event_loop:;
int key = wgetch(stdscr);
if (cur_timeout != 0) {
wtimeout(stdscr, 0); // Until we run out, don't wait between events.
cur_timeout = 0;
}
switch (key) {
case ERR: { // ERR indicates no more events.
ged_do_stuff(&ged);
bool drew_any = false;
if (ged_is_draw_dirty(&ged) || qnav_stack.occlusion_dirty) {
werase(window_main);
ged_draw(
&ged,
window_main,
osoc(tui.file_name),
tui.fancy_grid_dots,
tui.fancy_grid_rulers);
wnoutrefresh(window_main);
drew_any = true;
}
drew_any |= qnav_draw(); // clears qnav_stack.occlusion_dirty
if (drew_any)
doupdate();
double secs_to_d = ged_secs_to_deadline(&ged);
// clang-format off
#define DEADTIME(_millisecs, _new_timeout) \
else if (secs_to_d < ms_to_sec(_millisecs)) new_timeout = _new_timeout;
int new_timeout;
// These values are tuned to work OK with the normal scheduling behavior
// on Linux, Mac, and Windows. All of the usual caveats of trying to
// guess what the scheduler will do apply.
//
// Of course, there's no guarantee about how the scheduler will work, so
// if you are using a modified kernel or something, or the measurements
// here are bad, or it's some OS that behaves differently than expected,
// this won't be very good. But there's not really much we can do about
// it, and it's better than doing nothing and burning up the CPU!
if (tui.strict_timing) {
if (false) {}
// "If there's less than 1.5 milliseconds to the deadline, use a curses
// timeout value of 0."
DEADTIME( 1.5, 0)
DEADTIME( 3.0, 1)
DEADTIME( 5.0, 2)
DEADTIME( 7.0, 3)
DEADTIME( 9.0, 4)
DEADTIME( 11.0, 5)
DEADTIME( 13.0, 6)
DEADTIME( 15.0, 7)
DEADTIME( 25.0, 12)
DEADTIME( 50.0, 20)
DEADTIME(100.0, 40)
else new_timeout = 50;
} else {
if (false) {}
DEADTIME( 1.0, 0)
DEADTIME( 2.0, 1)
DEADTIME( 7.0, 2)
DEADTIME( 15.0, 5)
DEADTIME( 25.0, 10)
DEADTIME( 50.0, 20)
DEADTIME(100.0, 40)
else new_timeout = 50;
}
#undef DEADTIME
if (new_timeout != cur_timeout) {
wtimeout(stdscr, new_timeout);
cur_timeout = new_timeout;
#if TIME_DEBUG
spin_track_timeout = cur_timeout;
#endif
}
goto event_loop;
}
// END Case: ERR
// clang-format on
case KEY_RESIZE:
tui_adjust_term_size(&tui, &window_main);
qnav_adjust_term_size();
goto event_loop;
#ifndef FEAT_NOMOUSE
case KEY_MOUSE: {
MEVENT mevent;
if (window_main && getmouse(&mevent) == OK) {
int win_y, win_x;
int win_h, win_w;
getbegyx(window_main, win_y, win_x);
getmaxyx(window_main, win_h, win_w);
int inwin_y = mevent.y - win_y;
int inwin_x = mevent.x - win_x;
if (inwin_y >= win_h)
inwin_y = win_h - 1;
if (inwin_y < 0)
inwin_y = 0;
if (inwin_x >= win_w)
inwin_x = win_w - 1;
if (inwin_x < 0)
inwin_x = 0;
ged_mouse_event(&ged, (Usz)inwin_y, (Usz)inwin_x, mevent.bstate);
}
goto event_loop;
}
#endif
}
// If we have the menus open, we'll let the menus do what they want with
// the input before the regular editor (which will be displayed
// underneath.) The menus may tell us to quit, that they didn't do anything
// with the input, or that they consumed the input and therefore we
// shouldn't pass the input key to the rest of the editing system.
switch (tui_drive_menus(&tui, key)) {
case Tui_menus_nothing:
break;
case Tui_menus_quit:
goto quit;
case Tui_menus_consumed_input:
goto event_loop;
}
// If this key input is intended to reach the grid, check to see if we're
// in bracketed paste and use alternate 'filtered input for characters'
// mode. We'll ignore most control sequences here.
if (is_in_brackpaste) {
if (key == 27 /* escape */) {
if (brackpaste_seq_getungetch(stdscr) == Brackpaste_seq_end) {
is_in_brackpaste = false;
if (brackpaste_max_y > ged.ged_cursor.y)
ged.ged_cursor.h = brackpaste_max_y - ged.ged_cursor.y + 1;
if (brackpaste_max_x > ged.ged_cursor.x)
ged.ged_cursor.w = brackpaste_max_x - ged.ged_cursor.x + 1;
ged.needs_remarking = true;
ged.is_draw_dirty = true;
}
goto event_loop;
}
if (key == KEY_ENTER)
key = '\r';
if (key >= CHAR_MIN && key <= CHAR_MAX) {
if ((char)key == '\r' || (char)key == '\n') {
brackpaste_x = brackpaste_starting_x;
++brackpaste_y;
goto event_loop;
}
if (key != ' ') {
char cleaned = (char)key;
if (!orca_is_valid_glyph((Glyph)key))
cleaned = '.';
if (brackpaste_y < ged.field.height && brackpaste_x < ged.field.width) {
gbuffer_poke(
ged.field.buffer,
ged.field.height,
ged.field.width,
brackpaste_y,
brackpaste_x,
cleaned);
// Could move this out one level if we wanted the final selection
// size to reflect even the pasted area which didn't fit on the
// grid.
if (brackpaste_y > brackpaste_max_y)
brackpaste_max_y = brackpaste_y;
if (brackpaste_x > brackpaste_max_x)
brackpaste_max_x = brackpaste_x;
}
}
++brackpaste_x;
}
goto event_loop;
}
// Regular inputs when we're not in a menu and not in bracketed paste.
switch (key) {
// Checking again for 'quit' here, because it's only listened for if we're
// in the menus or *not* in bracketed paste mode.
case CTRL_PLUS('q'):
goto quit;
case CTRL_PLUS('o'):
push_open_form(osoc(tui.file_name));
break;
case 127: // backspace in terminal.app, apparently
case KEY_BACKSPACE:
if (ged.input_mode == Ged_input_mode_append) {
ged_dir_input(&ged, Ged_dir_left, 1);
ged_input_character(&ged, '.');
ged_dir_input(&ged, Ged_dir_left, 1);
} else {
ged_input_character(&ged, '.');
}
break;
case CTRL_PLUS('z'):
case CTRL_PLUS('u'):
ged_input_cmd(&ged, Ged_input_cmd_undo);
break;
case CTRL_PLUS('r'):
ged.tick_num = 0;
ged.needs_remarking = true;
ged.is_draw_dirty = true;
break;
case '[':
ged_adjust_rulers_relative(&ged, 0, -1);
break;
case ']':
ged_adjust_rulers_relative(&ged, 0, 1);
break;
case '{':
ged_adjust_rulers_relative(&ged, -1, 0);
break;
case '}':
ged_adjust_rulers_relative(&ged, 1, 0);
break;
case '(':
ged_resize_grid_relative(&ged, 0, -1);
break;
case ')':
ged_resize_grid_relative(&ged, 0, 1);
break;
case '_':
ged_resize_grid_relative(&ged, -1, 0);
break;
case '+':
ged_resize_grid_relative(&ged, 1, 0);
break;
case '\r':
case KEY_ENTER:
break; // Currently unused.
case CTRL_PLUS('i'):
case KEY_IC:
ged_input_cmd(&ged, Ged_input_cmd_toggle_append_mode);
break;
case '/':
// Formerly 'piano'/trigger mode toggle. We're repurposing it here to
// input a '?' instead of a '/' because '?' opens the help guide, and it
// might be a bad idea to take that away, since orca will take over the
// TTY and may leave users confused. I know of at least 1 person who was
// saved by pressing '?' after they didn't know what to do. Hmm.
ged_input_character(&ged, '?');
break;
case '<':
ged_adjust_bpm(&ged, -1);
break;
case '>':
ged_adjust_bpm(&ged, 1);
break;
case CTRL_PLUS('f'):
ged_input_cmd(&ged, Ged_input_cmd_step_forward);
break;
case CTRL_PLUS('e'):
ged_input_cmd(&ged, Ged_input_cmd_toggle_show_event_list);
break;
case CTRL_PLUS('x'):
ged_input_cmd(&ged, Ged_input_cmd_cut);
try_send_to_gui_clipboard(&ged, &tui.use_gui_cboard);
break;
case CTRL_PLUS('c'):
ged_input_cmd(&ged, Ged_input_cmd_copy);
try_send_to_gui_clipboard(&ged, &tui.use_gui_cboard);
break;
case CTRL_PLUS('v'):
if (tui.use_gui_cboard) {
bool added_hist = undo_history_push(&ged.undo_hist, &ged.field, ged.tick_num);
Usz pasted_h, pasted_w;
Cboard_error cberr = cboard_paste(
ged.field.buffer,
ged.field.height,
ged.field.width,
ged.ged_cursor.y,
ged.ged_cursor.x,
&pasted_h,
&pasted_w);
if (cberr) {
if (added_hist)
undo_history_pop(&ged.undo_hist, &ged.field, &ged.tick_num);
tui.use_gui_cboard = false;
ged_input_cmd(&ged, Ged_input_cmd_paste);
} else {
if (pasted_h > 0 && pasted_w > 0) {
ged.ged_cursor.h = pasted_h;
ged.ged_cursor.w = pasted_w;
}
}
ged.needs_remarking = true;
ged.is_draw_dirty = true;
} else {
ged_input_cmd(&ged, Ged_input_cmd_paste);
}
break;
case '\'':
ged_input_cmd(&ged, Ged_input_cmd_toggle_selresize_mode);
break;
case '`':
case '~':
ged_input_cmd(&ged, Ged_input_cmd_toggle_slide_mode);
break;
case ' ':
if (ged.input_mode == Ged_input_mode_append)
ged_input_character(&ged, '.');
else
ged_input_cmd(&ged, Ged_input_cmd_toggle_play_pause);
break;
case 27: // Escape
// Check for escape sequences we're interested in that ncurses didn't
// handle. Such as bracketed paste.
if (brackpaste_seq_getungetch(stdscr) == Brackpaste_seq_begin) {
is_in_brackpaste = true;
undo_history_push(&ged.undo_hist, &ged.field, ged.tick_num);
brackpaste_y = ged.ged_cursor.y;
brackpaste_x = ged.ged_cursor.x;
brackpaste_starting_x = brackpaste_x;
brackpaste_max_y = brackpaste_y;
brackpaste_max_x = brackpaste_x;
break;
}
ged_input_cmd(&ged, Ged_input_cmd_escape);
break;
case 330: // delete?
ged_input_character(&ged, '.');
break;
// Cursor movement
case KEY_UP:
case CTRL_PLUS('k'):
ged_dir_input(&ged, Ged_dir_up, 1);
break;
case CTRL_PLUS('j'):
case KEY_DOWN:
ged_dir_input(&ged, Ged_dir_down, 1);
break;
case CTRL_PLUS('h'):
case KEY_LEFT:
ged_dir_input(&ged, Ged_dir_left, 1);
break;
case CTRL_PLUS('l'):
case KEY_RIGHT:
ged_dir_input(&ged, Ged_dir_right, 1);
break;
// Selection size modification. These may not work in all terminals. (Only
// tested in xterm so far.)
case 337: // shift-up
ged_modify_selection_size(&ged, -1, 0);
break;
case 336: // shift-down
ged_modify_selection_size(&ged, 1, 0);
break;
case 393: // shift-left
ged_modify_selection_size(&ged, 0, -1);
break;
case 402: // shift-right
ged_modify_selection_size(&ged, 0, 1);
break;
case 567: // shift-control-up
ged_modify_selection_size(&ged, -(int)ged.ruler_spacing_y, 0);
break;
case 526: // shift-control-down
ged_modify_selection_size(&ged, (int)ged.ruler_spacing_y, 0);
break;
case 546: // shift-control-left
ged_modify_selection_size(&ged, 0, -(int)ged.ruler_spacing_x);
break;
case 561: // shift-control-right
ged_modify_selection_size(&ged, 0, (int)ged.ruler_spacing_x);
break;
// Move cursor further if control is held
case 566: // control-up
ged_dir_input(&ged, Ged_dir_up, (int)ged.ruler_spacing_y);
break;
case 525: // control-down
ged_dir_input(&ged, Ged_dir_down, (int)ged.ruler_spacing_y);
break;
case 545: // control-left
ged_dir_input(&ged, Ged_dir_left, (int)ged.ruler_spacing_x);
break;
case 560: // control-right
ged_dir_input(&ged, Ged_dir_right, (int)ged.ruler_spacing_x);
break;
// Slide selection on alt-arrow
case 564: // alt-up
ged_slide_selection(&ged, -1, 0);
break;
case 523: // alt-down
ged_slide_selection(&ged, 1, 0);
break;
case 543: // alt-left
ged_slide_selection(&ged, 0, -1);
break;
case 558: // alt-right
ged_slide_selection(&ged, 0, 1);
break;
case CTRL_PLUS('d'):
case KEY_F(1):
push_main_menu();
break;
case '?':
push_controls_msg();
break;
case CTRL_PLUS('g'):
push_opers_guide_msg();
break;
case CTRL_PLUS('s'):
tui_try_save(&tui);
break;
default:
if (key >= CHAR_MIN && key <= CHAR_MAX && orca_is_valid_glyph((Glyph)key))
ged_input_character(&ged, (char)key);
#if 0
else
fprintf(stderr, "Unknown key number: %d\n", key);
#endif
break;
}
goto event_loop;
quit:
ged_stop_all_sustained_notes(&ged);
qnav_deinit();
if (window_main)
delwin(window_main);
#ifndef FEAT_NOMOUSE
printf("\033[?1003l\n"); // turn off console mouse events if they were active
#endif
printf("\033[?2004h\n"); // Tell terminal to not use bracketed paste
endwin();
ged_deinit(&ged);
osofree(tui.file_name);
osofree(tui.osc_address);
osofree(tui.osc_port);
osofree(tui.osc_midi_bidule_path);
#ifdef FEAT_PORTMIDI
// if (portmidi_is_initialized)
Pm_Terminate();
#endif
return 0;
}
#undef TOUCHFLAG
#undef staticni