/* * Copyright (C) 2012-2014 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #ifdef EMSCRIPTEN #include #endif #include "sciteco.h" #include "cmdline.h" #include "qregisters.h" #include "ring.h" #include "interface.h" #include "interface-curses.h" namespace SciTECO { extern "C" { static void scintilla_notify(Scintilla *sci, int idFrom, void *notify, void *user_data); } #define UNNAMED_FILE "(Unnamed)" #define SCI_COLOR_ATTR(f, b) \ ((chtype)COLOR_PAIR(SCI_COLOR_PAIR(f, b))) void ViewCurses::initialize_impl(void) { WINDOW *window; /* NOTE: Scintilla initializes color pairs */ sci = scintilla_new(scintilla_notify); window = get_window(); /* * Window must have dimension before it can be * positioned. * Perhaps it's better to leave the window * unitialized and set the position in * InterfaceCurses::show_view(). */ wresize(window, 1, 1); /* Set up window position: never changes */ mvwin(window, 1, 0); setup(); } void InterfaceCurses::main_impl(int &argc, char **&argv) { init_screen(); cbreak(); noecho(); curs_set(0); /* Scintilla draws its own cursor */ setlocale(LC_CTYPE, ""); /* for displaying UTF-8 characters properly */ info_window = newwin(1, 0, 0, 0); info_current = g_strdup(PACKAGE_NAME); msg_window = newwin(1, 0, LINES - 2, 0); cmdline_window = newwin(0, 0, LINES - 1, 0); cmdline_current = NULL; draw_info(); /* scintilla will be refreshed in event loop */ msg_clear(); cmdline_update(""); #ifdef EMSCRIPTEN nodelay(cmdline_window, TRUE); #else #ifndef PDCURSES_WIN32A /* workaround: endwin() is somewhat broken in the win32a port */ endwin(); #endif #endif } #ifdef __PDCURSES__ void InterfaceCurses::init_screen(void) { #ifdef PDCURSES_WIN32A /* enables window resizing on Win32a port */ PDC_set_resize_limits(25, 0xFFFF, 80, 0xFFFF); #endif initscr(); screen_tty = NULL; screen = NULL; } #else void InterfaceCurses::init_screen(void) { /* * Prevent the initial redraw and any escape sequences that may * interfere with stdout, so we may use the terminal in * cooked mode, for commandline help and batch processing. * Scintilla must be initialized for batch processing to work. * (Frankly I have no idea why this works!) */ screen_tty = g_fopen("/dev/tty", "r+b"); screen = newterm(NULL, screen_tty, screen_tty); set_term(screen); } #endif /* !__PDCURSES__ */ void InterfaceCurses::resize_all_windows(void) { int lines, cols; /* screen dimensions */ getmaxyx(stdscr, lines, cols); wresize(info_window, 1, cols); wresize(current_view->get_window(), lines - 3, cols); wresize(msg_window, 1, cols); mvwin(msg_window, lines - 2, 0); wresize(cmdline_window, 1, cols); mvwin(cmdline_window, lines - 1, 0); draw_info(); /* scintilla will be refreshed in event loop */ msg_clear(); /* FIXME: use saved message */ cmdline_update(); } void InterfaceCurses::vmsg_impl(MessageType type, const gchar *fmt, va_list ap) { static const chtype type2attr[] = { SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE), /* MSG_USER */ SCI_COLOR_ATTR(COLOR_BLACK, COLOR_GREEN), /* MSG_INFO */ SCI_COLOR_ATTR(COLOR_BLACK, COLOR_YELLOW), /* MSG_WARNING */ SCI_COLOR_ATTR(COLOR_BLACK, COLOR_RED) /* MSG_ERROR */ }; #ifdef PDCURSES_WIN32A stdio_vmsg(type, fmt, ap); if (isendwin()) /* batch mode */ return; #else if (isendwin()) { /* batch mode */ stdio_vmsg(type, fmt, ap); return; } #endif wmove(msg_window, 0, 0); wbkgdset(msg_window, ' ' | type2attr[type]); vw_printw(msg_window, fmt, ap); wclrtoeol(msg_window); wrefresh(msg_window); } void InterfaceCurses::msg_clear(void) { if (isendwin()) /* batch mode */ return; wmove(msg_window, 0, 0); wbkgdset(msg_window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE)); wclrtoeol(msg_window); wrefresh(msg_window); } void InterfaceCurses::show_view_impl(ViewCurses *view) { int lines, cols; /* screen dimensions */ current_view = view; /* * screen size might have changed since * this view's WINDOW was last active */ getmaxyx(stdscr, lines, cols); wresize(current_view->get_window(), lines - 3, cols); } void InterfaceCurses::draw_info(void) { if (isendwin()) /* batch mode */ return; wmove(info_window, 0, 0); wbkgdset(info_window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_WHITE)); waddstr(info_window, info_current); wclrtoeol(info_window); wrefresh(info_window); } void InterfaceCurses::info_update_impl(QRegister *reg) { g_free(info_current); info_current = g_strdup_printf("%s - %s", PACKAGE_NAME, reg->name); draw_info(); } void InterfaceCurses::info_update_impl(Buffer *buffer) { g_free(info_current); info_current = g_strdup_printf("%s - %s%s", PACKAGE_NAME, buffer->filename ? : UNNAMED_FILE, buffer->dirty ? "*" : ""); draw_info(); } void InterfaceCurses::cmdline_update_impl(const gchar *cmdline) { size_t len; int half_line = (getmaxx(stdscr) - 2) / 2; const gchar *line; if (cmdline) { g_free(cmdline_current); cmdline_current = g_strdup(cmdline); } else { cmdline = cmdline_current; } len = strlen(cmdline); /* FIXME: optimize */ line = cmdline + len - MIN(len, half_line + len % half_line); mvwaddch(cmdline_window, 0, 0, '*'); waddstr(cmdline_window, line); waddch(cmdline_window, ' ' | A_REVERSE); wclrtoeol(cmdline_window); wrefresh(cmdline_window); } void InterfaceCurses::popup_add_impl(PopupEntryType type, const gchar *name, bool highlight) { gchar *entry; if (isendwin()) /* batch mode */ return; entry = g_strconcat(highlight ? "*" : " ", name, NIL); popup.longest = MAX(popup.longest, (gint)strlen(name)); popup.length++; popup.list = g_slist_prepend(popup.list, entry); } void InterfaceCurses::popup_show_impl(void) { int lines, cols; /* screen dimensions */ int popup_lines; gint popup_cols; gint cur_entry; if (isendwin()) /* batch mode */ goto cleanup; getmaxyx(stdscr, lines, cols); /* reserve 2 space characters between columns */ popup.longest += 2; popup.list = g_slist_reverse(popup.list); /* popup_cols = floor(cols / popup.longest) */ popup_cols = MAX(cols / popup.longest, 1); /* popup_lines = ceil(popup.length / popup_cols) */ popup_lines = (popup.length+popup_cols-1) / popup_cols; /* * Popup window can cover all but one screen row. * If it does not fit, the list of tokens is truncated * and "..." is displayed. */ popup_lines = MIN(popup_lines, lines - 1); /* window covers message, scintilla and info windows */ popup.window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0); wbkgd(popup.window, ' ' | SCI_COLOR_ATTR(COLOR_BLACK, COLOR_BLUE)); cur_entry = 0; for (GSList *cur = popup.list; cur; cur = g_slist_next(cur)) { gchar *entry = (gchar *)cur->data; gint cur_line = cur_entry/popup_cols + 1; wmove(popup.window, cur_line-1, (cur_entry % popup_cols)*popup.longest); cur_entry++; if (cur_line == popup_lines && !(cur_entry % popup_cols) && cur_entry < popup.length) { /* truncate entries in the popup's very last column */ (void)wattrset(popup.window, A_BOLD); waddstr(popup.window, "..."); break; } (void)wattrset(popup.window, *entry == '*' ? A_BOLD : A_NORMAL); waddstr(popup.window, entry + 1); } cleanup: g_slist_free_full(popup.list, g_free); popup.list = NULL; popup.longest = popup.length = 0; } void InterfaceCurses::popup_clear_impl(void) { if (!popup.window) return; redrawwin(info_window); wrefresh(info_window); redrawwin(current_view->get_window()); current_view->refresh(); redrawwin(msg_window); wrefresh(msg_window); delwin(popup.window); popup.window = NULL; } /** * One iteration of the event loop. * * This is a global function, so it may * be used as an Emscripten callback. * * @bug * Can probably be defined as a static method, * so we can avoid declaring it a fried function of * InterfaceCurses. */ void event_loop_iter() { int key; keypad(interface.cmdline_window, Flags::ed & Flags::ED_FNKEYS); /* no special handling */ raw(); key = wgetch(interface.cmdline_window); /* allow asynchronous interruptions on */ cbreak(); if (key == ERR) return; switch (key) { #ifdef KEY_RESIZE case KEY_RESIZE: #ifdef PDCURSES resize_term(0, 0); #endif interface.resize_all_windows(); break; #endif case 0x7F: /* DEL */ case KEY_BACKSPACE: cmdline_keypress('\b'); break; case KEY_ENTER: case '\r': case '\n': cmdline_keypress(get_eol()); break; /* * Function key macros */ #define FN(KEY) case KEY_##KEY: cmdline_fnmacro(#KEY); break #define FNS(KEY) FN(KEY); FN(S##KEY) FN(DOWN); FN(UP); FNS(LEFT); FNS(RIGHT); FNS(HOME); case KEY_F(0)...KEY_F(63): { gchar macro_name[3+1]; g_snprintf(macro_name, sizeof(macro_name), "F%d", key - KEY_F0); cmdline_fnmacro(macro_name); break; } FNS(DC); FNS(IC); FN(NPAGE); FN(PPAGE); FNS(PRINT); FN(A1); FN(A3); FN(B2); FN(C1); FN(C3); FNS(END); FNS(HELP); #undef FNS #undef FN /* * Control keys and keys with printable representation */ default: if (key <= 0xFF) cmdline_keypress((gchar)key); } sigint_occurred = FALSE; interface.current_view->refresh(); if (interface.popup.window) wrefresh(interface.popup.window); } void InterfaceCurses::event_loop_impl(void) { /* initial refresh: window might have been changed in batch mode */ current_view->refresh(); draw_info(); #ifdef EMSCRIPTEN PDC_emscripten_set_handler(event_loop_iter, TRUE); /* * We must not block emscripten's main loop, * instead event_loop_iter() is called asynchronously. * We also must not exit the event_loop() method, since * SciTECO would assume ordinary program termination. * We also must not call exit() since that would run * the global destructors. * The following exits the main() function immediately * while keeping the "runtime" alive. */ emscripten_exit_with_live_runtime(); #else try { for (;;) event_loop_iter(); } catch (Quit) { /* SciTECO termination (e.g. EX$$) */ } /* * Restore ordinary terminal behaviour */ endwin(); #endif } InterfaceCurses::Popup::~Popup() { if (window) delwin(window); if (list) g_slist_free_full(list, g_free); } InterfaceCurses::~InterfaceCurses() { if (info_window) delwin(info_window); g_free(info_current); if (cmdline_window) delwin(cmdline_window); g_free(cmdline_current); if (msg_window) delwin(msg_window); if (!isendwin()) endwin(); if (screen) delscreen(screen); if (screen_tty) fclose(screen_tty); } /* * Callbacks */ static void scintilla_notify(Scintilla *sci, int idFrom, void *notify, void *user_data) { interface.process_notify((SCNotification *)notify); } } /* namespace SciTECO */