diff --git a/gb.ncurses/src/Makefile.am b/gb.ncurses/src/Makefile.am index b8505e535..5efbc305d 100644 --- a/gb.ncurses/src/Makefile.am +++ b/gb.ncurses/src/Makefile.am @@ -9,9 +9,9 @@ gb_ncurses_la_CPPFLAGS = @NCURSES_INC@ gb_ncurses_la_SOURCES = \ main.h main.c \ - c_ncurses.h c_ncurses.c \ c_window.h c_window.c \ c_key.h c_key.c \ c_color.h c_color.c \ - c_screen.h c_screen.c + c_screen.h c_screen.c \ + input.h input.c diff --git a/gb.ncurses/src/c_color.c b/gb.ncurses/src/c_color.c index b1118a04d..b68e06984 100644 --- a/gb.ncurses/src/c_color.c +++ b/gb.ncurses/src/c_color.c @@ -27,6 +27,7 @@ #include "main.h" #include "c_color.h" +#include "c_screen.h" static int _index; static int _color; @@ -189,6 +190,7 @@ BEGIN_PROPERTY(ColorPair_Background) } b = VPROP(GB_INTEGER); COLOR_setpair(_index, f, b); + REAL_REFRESH(); END_PROPERTY @@ -208,6 +210,7 @@ BEGIN_PROPERTY(ColorPair_Foreground) } f = VPROP(GB_INTEGER); COLOR_setpair(_index, f, b); + REAL_REFRESH(); END_PROPERTY diff --git a/gb.ncurses/src/c_ncurses.c b/gb.ncurses/src/c_ncurses.c deleted file mode 100644 index d01514c34..000000000 --- a/gb.ncurses/src/c_ncurses.c +++ /dev/null @@ -1,84 +0,0 @@ -/* - * c_ncurses.c - gb.ncurses NCurses static class - * - * Copyright (C) 2012 Tobias Boege - * - * 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 2, 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ - -#define __C_NCURSES_C - -#include -#include - -#include - -#include "c_ncurses.h" -#include "c_color.h" -#include "c_screen.h" -#include "main.h" - -#define E_END "Could not end ncurses" - -static bool _init = FALSE; - -bool NCURSES_running() -{ - return _init && (!isendwin() || stdscr); -} - -void NCURSES_init(void) -{ - if (_init) - return; - - initscr(); - keypad(stdscr, TRUE); - - /* Color setup */ - COLOR_init(); /* Color._init() would be called before the main hook, apparently */ - /* Screen setup */ - SCREEN_init(); - - refresh(); - - _init = TRUE; -} - -void NCURSES_exit() -{ - if (_init) { - endwin(); - _init = FALSE; - } -} - -BEGIN_METHOD_VOID(NCurses_exit) - - NCURSES_exit(); - -END_METHOD - -GB_DESC CNCursesDesc[] = -{ - GB_DECLARE("NCurses", 0), - GB_NOT_CREATABLE(), - - - GB_STATIC_METHOD("_exit", NULL, NCurses_exit, NULL), - - GB_END_DECLARE -}; diff --git a/gb.ncurses/src/c_ncurses.h b/gb.ncurses/src/c_ncurses.h deleted file mode 100644 index bbb6d68de..000000000 --- a/gb.ncurses/src/c_ncurses.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * c_ncurses.h - gb.ncurses NCurses static class - * - * Copyright (C) 2012 Tobias Boege - * - * 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 2, 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ - -#ifndef __C_NCURSES_H -#define __C_NCURSES_H - -#include - -#include "gambas.h" - -#define NCURSES_RUNNING NCURSES_running() - -#ifndef __C_NCURSES_C -extern GB_DESC CNCursesDesc[]; -#endif - -void NCURSES_init(void); -void NCURSES_exit(void); -bool NCURSES_running(void); - -#endif /* __C_NCURSES_H */ diff --git a/gb.ncurses/src/c_screen.c b/gb.ncurses/src/c_screen.c index 8018f0874..e599f63b4 100644 --- a/gb.ncurses/src/c_screen.c +++ b/gb.ncurses/src/c_screen.c @@ -25,44 +25,81 @@ #include #include +#include #include "gambas.h" #include "c_screen.h" #include "main.h" +#include "input.h" + +#define THIS ((CSCREEN *) _object) +#define IS_BUFFERED (THIS->buffered) #define E_UNSUPP "Unsupported value" -#define CURSOR_HIDDEN 0 -#define CURSOR_VISIBLE 1 - -#define INPUT_COOKED 0 -#define INPUT_CBREAK 1 -#define INPUT_RAW 2 -#define INPUT_KEYBOARD 3 - static bool _cursor; -static int _input; static int _echo; +/* Currently active screen */ +static CSCREEN *active = NULL; + +GB_SIGNAL_CALLBACK *callback; + +DECLARE_EVENT(EVENT_Read); DECLARE_EVENT(EVENT_Resize); -#if 0 /* * Signal handler for the SIGWINCH signal. * @signum: signal number given * This routine dispatches the Resize Event */ -void nc_sigwinch_handler(int signum) +void SCREEN_sigwinch_handler(int signum, intptr_t data) { - /* TODO: I wonder if this works... */ - - /* BM: Of course not! :-) You can't raise an event from a signal handler - * and moreover you have no sender! */ - - if (signum == SIGWINCH) GB.Raise(NULL, EVENT_Resize, 0); + if (signum == SIGWINCH) + GB.Raise(active, EVENT_Resize, 0); +} + +/** + * Set mode of @_cursor + */ +static int SCREEN_cursor_mode(int mode) +{ + switch (mode) { + case CURSOR_HIDDEN: + curs_set(0); + break; + case CURSOR_VISIBLE: + curs_set(1); + break; + case CURSOR_VERY: + curs_set(2); + break; + default: + return -1; + } + _cursor = mode; + return 0; +} + +/** + * Set mode of @_echo + */ +static int SCREEN_echo_mode(int mode) +{ + switch (mode) { + case ECHO_NOECHO: + noecho(); + break; + case ECHO_ECHO: + echo(); + break; + default: + return -1; + } + _echo = mode; + return 0; } -#endif /** * Screen initialisation @@ -70,29 +107,92 @@ void nc_sigwinch_handler(int signum) int SCREEN_init() { /* Global variable default setup */ - _cursor = CURSOR_VISIBLE; - _input = INPUT_CBREAK; - _echo = 0; - /* accordingly... */ - curs_set(1); - cbreak(); - noecho(); + SCREEN_cursor_mode(CURSOR_VISIBLE); + SCREEN_echo_mode(ECHO_NOECHO); -#if 0 - struct sigaction sa; - - sa.sa_handler = nc_sigwinch_handler; - sigemptyset(&(sa.sa_mask)); - sa.sa_flags = 0; - if (sigaction(SIGWINCH, &sa, NULL) == -1) - { - fprintf(stderr, "gb.ncurses: Could not install SIGWINCH signal handler"); - } -#endif + INPUT_init(); +/* callback = GB.Signal.Register(SIGSEGV, + SCREEN_sigwinch_handler, (intptr_t) NULL); +*/ return 0; } +/** + * Screen cleanup + */ +void SCREEN_exit() +{ + INPUT_exit(); + +/* GB.Signal.Unregister(SIGWINCH, callback); +*/ +} + +/** + * Get the active Screen + * If @active is NULL, the default Screen will be made active and then returned + */ +CSCREEN *SCREEN_get_active() +{ + GB_CLASS screen_class; + + if (active) + return active; + screen_class = GB.FindClass("Screen"); + active = GB.AutoCreate(screen_class, 0); + return active; +} + +/** + * Redraw the screen no matter what the buffer settings are. + * Note that this function may not be used to return into ncurses mode once left. + */ +void SCREEN_real_refresh() +{ + if (!NCURSES_RUNNING) + return; + update_panels(); + doupdate(); +} + +/** + * Refresh the screen. This respects the currently active buffering wishes + */ +void SCREEN_refresh(void *_object) +{ + if (!NCURSES_RUNNING) + return; + + if (!_object) + _object = SCREEN_get_active(); + + if (!IS_BUFFERED) + SCREEN_real_refresh(); +} + +/** + * Let the specified screen raise its Read event. If the _object is NULL, + * the currently active screen will raise. + */ +void SCREEN_raise_read(void *_object) +{ + if (!_object) + GB.Raise(active, EVENT_Read, 0); + else + GB.Raise(_object, EVENT_Read, 0); +} + +BEGIN_PROPERTY(Screen_Buffered) + + if (READ_PROPERTY) { + GB.ReturnBoolean(IS_BUFFERED); + return; + } + THIS->buffered = VPROP(GB_BOOLEAN); + +END_PROPERTY + BEGIN_PROPERTY(Screen_Cursor) if (READ_PROPERTY) { @@ -100,47 +200,8 @@ BEGIN_PROPERTY(Screen_Cursor) return; } - switch (VPROP(GB_INTEGER)) { - case CURSOR_HIDDEN: - curs_set(0); - break; - case CURSOR_VISIBLE: - curs_set(1); - break; - default: - GB.Error(E_UNSUPP); - return; - } - _cursor = VPROP(GB_INTEGER); - -END_PROPERTY - -BEGIN_PROPERTY(Screen_Input) - - if (READ_PROPERTY) { - GB.ReturnInteger(_input); - return; - } - - switch (VPROP(GB_INTEGER)) { - case INPUT_COOKED: - noraw(); - nocbreak(); - break; - case INPUT_CBREAK: - cbreak(); - break; - case INPUT_RAW: - raw(); - break; - case INPUT_KEYBOARD: - /* TODO: implement! */ - break; - default: - GB.Error(E_UNSUPP); - return; - } - _input = VPROP(GB_INTEGER); + if (SCREEN_cursor_mode(VPROP(GB_INTEGER)) == -1) + GB.Error(E_UNSUPP); END_PROPERTY @@ -151,11 +212,46 @@ BEGIN_PROPERTY(Screen_Echo) return; } - _echo = VPROP(GB_BOOLEAN); - if (_echo) - echo(); - else - noecho(); + if (SCREEN_echo_mode(!!VPROP(GB_BOOLEAN)) == -1) + GB.Error(E_UNSUPP); + +END_PROPERTY + +BEGIN_PROPERTY(Screen_Input) + + if (READ_PROPERTY) { + GB.ReturnInteger(INPUT_mode(INPUT_RETURN)); + return; + } + + if (INPUT_mode(VPROP(GB_INTEGER)) == -1) + GB.Error(E_UNSUPP); + +END_PROPERTY + +BEGIN_PROPERTY(Screen_IsConsole) + + int fd = INPUT_consolefd(); + + if (fd == -1) { + GB.ReturnBoolean(FALSE); + return; + } + close(fd); + GB.ReturnBoolean(TRUE); + +END_PROPERTY + +BEGIN_PROPERTY(Screen_Repeater) + + if (READ_PROPERTY) { + GB.ReturnInteger(INPUT_repeater_delay(REPEATER_RETURN)); + return; + } + if (INPUT_repeater_delay(VPROP(GB_INTEGER)) == -1) { + GB.Error("Invalid value"); + return; + } END_PROPERTY @@ -173,30 +269,53 @@ END_PROPERTY BEGIN_METHOD_VOID(Screen_new) - SCREEN_init(); + active = THIS; + +END_METHOD + +BEGIN_METHOD_VOID(Screen_free) + + SCREEN_exit(); + +END_METHOD + +BEGIN_METHOD_VOID(Screen_Refresh) + + SCREEN_real_refresh(); END_METHOD GB_DESC CScreenDesc[] = { - GB_DECLARE("Screen", 0), //sizeof(struct nc_screen)), + GB_DECLARE("Screen", sizeof(CSCREEN)), GB_AUTO_CREATABLE(), + GB_EVENT("Read", NULL, NULL, &EVENT_Read), + GB_EVENT("Resize", NULL, NULL, &EVENT_Resize), + GB_CONSTANT("Hidden", "i", CURSOR_HIDDEN), GB_CONSTANT("Visible", "i", CURSOR_VISIBLE), GB_CONSTANT("Cooked", "i", INPUT_COOKED), GB_CONSTANT("CBreak", "i", INPUT_CBREAK), GB_CONSTANT("Raw", "i", INPUT_RAW), + GB_CONSTANT("NoDelay", "i", INPUT_NODELAY), + + GB_PROPERTY("Buffered", "b", Screen_Buffered), GB_STATIC_PROPERTY("Cursor", "i", Screen_Cursor), - GB_STATIC_PROPERTY("Input", "i", Screen_Input), GB_STATIC_PROPERTY("Echo", "b", Screen_Echo), + GB_STATIC_PROPERTY("Input", "i", Screen_Input), + GB_STATIC_PROPERTY_READ("IsConsole", "b", Screen_IsConsole), + GB_STATIC_PROPERTY("Repeater", "i", Screen_Repeater), GB_STATIC_PROPERTY_READ("Lines", "i", Screen_Lines), //GB_PROPERTY GB_STATIC_PROPERTY_READ("Cols", "i", Screen_Cols), GB_METHOD("_new", NULL, Screen_new, NULL), + GB_METHOD("_free", NULL, Screen_free, NULL), + + GB_METHOD("Refresh", NULL, Screen_Refresh, NULL), GB_END_DECLARE }; diff --git a/gb.ncurses/src/c_screen.h b/gb.ncurses/src/c_screen.h index c7b7b5a2d..7c1069d37 100644 --- a/gb.ncurses/src/c_screen.h +++ b/gb.ncurses/src/c_screen.h @@ -24,10 +24,45 @@ #include "gambas.h" -#ifndef __C_WINDOW_C +/* This will produce final output on terminal screen */ +#define REAL_REFRESH() SCREEN_real_refresh() +/* This macro is mostly called by Gambas implementation functions to request output on screen + (read: to check if the output is buffered and if not produce output by means of + REAL_REFRESH()) */ +#define REFRESH() SCREEN_refresh(NULL) + +/* + * Cursor modes + */ +enum { + CURSOR_HIDDEN, + CURSOR_VISIBLE, + CURSOR_VERY +}; + +/* + * Echo modes + */ +enum { + ECHO_NOECHO, + ECHO_ECHO +}; + +#ifndef __C_SCREEN_C extern GB_DESC CScreenDesc[]; #endif +typedef struct { + GB_BASE ob; + bool buffered; /* Whether output will be buffered, i.e. + only done via Screen.Refresh() */ +} CSCREEN; + int SCREEN_init(); +void SCREEN_exit(); +CSCREEN *SCREEN_get_active(); +void SCREEN_refresh(); +void SCREEN_real_refresh(); +void SCREEN_raise_read(void *); #endif /* __C_SCREEN_H */ diff --git a/gb.ncurses/src/c_window.c b/gb.ncurses/src/c_window.c index a9e844010..9b3456ff1 100644 --- a/gb.ncurses/src/c_window.c +++ b/gb.ncurses/src/c_window.c @@ -31,46 +31,96 @@ #include "gambas.h" #include "gb_common.h" -#include "main.h" #include "c_window.h" +#include "main.h" +#include "c_screen.h" #include "c_color.h" +#include "input.h" -static int Window_stream_open(GB_STREAM *stream, const char *path, int mode, void *data); -static int Window_stream_close(GB_STREAM *stream); -static int Window_stream_read(GB_STREAM *stream, char *buffer, int len); -static int Window_stream_getchar(GB_STREAM *stream, char *buffer); -static int Window_stream_write(GB_STREAM *stream, char *buffer, int len); -static int Window_stream_seek(GB_STREAM *stream, int64_t pos, int whence); -static int Window_stream_tell(GB_STREAM *stream, int64_t *pos); -static int Window_stream_flush(GB_STREAM *stream); -static int Window_stream_eof(GB_STREAM *stream); -static int Window_stream_lof(GB_STREAM *stream, int64_t *len); -static int Window_stream_handle(GB_STREAM *stream); +#define THIS ((CWINDOW *) _object) +#define HAS_BORDER (THIS->border != BORDER_NONE) +#define IS_WRAPPED (THIS->wrap) -/* The nc_window currently having the focus (raising Read events) */ -static struct nc_window *focused = NULL; +/* Windows have their own association to a particular Screen, they don't + * update just the active one */ +#ifdef REFRESH +#undef REFRESH +/* Only refresh if we are on the active screen */ +#define REFRESH() \ + do { \ + if (SCREEN_get_active() == THIS->parent) \ + SCREEN_refresh(THIS->parent); \ + } while (0) +#endif + +/* Translate linear (absolute) memory addresses and x,y coordinates into each other + most useful when wrapping is needed. */ +#define A2XY(win, a, x, y) do { \ + (x) = (a) % getmaxx(win); \ + (y) = (a) / getmaxx(win); \ + } while (0) +#define XY2A(win, x, y, a) do { \ + (a) = (y) * getmaxx(win) + (x); \ + } while (0) +/* Interpret the -1 values in coordinates as to insert the current cursor position */ +#define MAKE_COORDS(win, x, y) do { \ + if ((x) == -1) \ + x = getcurx(win); \ + if ((y) == -1) \ + y = getcury(win); \ + } while (0) +/* Check for out-of-range coordinates */ +#define BAD_COORDS(win, x, y) ((x) < 0 || (x) >= getmaxx(win) || \ + (y) < 0 || (y) >= getmaxy(win)) +#define BAD_DIMENSION(w, h) ((w) <= 0 || (w) > COLS || \ + (h) <= 0 || (h) > LINES) + +#define WIN_ATTR_METHOD(a) \ + do { \ + if (READ_PROPERTY) \ + GB.ReturnBoolean(WINDOW_attrs_driver( \ + THIS, (a), ATTR_DRV_RET) & (a)); \ + else \ + WINDOW_attrs_driver(THIS, (a), \ + VPROP(GB_BOOLEAN) ? ATTR_DRV_ON \ + : ATTR_DRV_OFF); \ + } while (0) +/* Notice the wtouchln() line in the following macro. It seems that a chgat() from + WINDOW_char_attrs_driver() doesn't mark anything dirty (no output on screen from + a REFRESH()). So to make the new attribute available immidiately, we touch the affected + line manually. A higher-callstack function may call REFRESH() to get output. */ +#define CHAR_ATTR_METHOD(a) \ + do { \ + if (READ_PROPERTY) \ + GB.ReturnBoolean(WINDOW_char_attrs_driver( \ + THIS, (a), THIS->pos.col, THIS->pos.line, \ + ATTR_DRV_RET) & (a)); \ + else \ + WINDOW_char_attrs_driver(THIS, (a), THIS->pos.col, \ + THIS->pos.line, \ + VPROP(GB_BOOLEAN) ? ATTR_DRV_ON \ + : ATTR_DRV_OFF); \ + wtouchln(THIS->main, THIS->pos.line \ + + (HAS_BORDER ? 1 : 0), 1, 1); \ + } while(0) + +#define WINDOW_main_to_content() WINDOW_copy_window(THIS->main, THIS->content, 0, 0, \ + getmaxx(THIS->content), \ + getmaxy(THIS->content), 0, 0) +#define WINDOW_content_to_main() WINDOW_copy_window(THIS->content, THIS->main, 0, 0, \ + getmaxx(THIS->content), \ + getmaxy(THIS->content), 0, 0) + +/* The window currently having the focus (raising Read events) */ +static CWINDOW *focused = NULL; DECLARE_EVENT(EVENT_Read); -GB_STREAM_DESC WindowStream = { - open: Window_stream_open, - close: Window_stream_close, - read: Window_stream_read, - getchar: Window_stream_getchar, - write: Window_stream_write, - seek: Window_stream_seek, - tell: Window_stream_tell, - flush: Window_stream_flush, - eof: Window_stream_eof, - lof: Window_stream_lof, - handle: Window_stream_handle -}; - /* * C Window interface * @_object: Reference to the struct nc_window representing the current object. * - * The theory: Each struct nc_window has a main and a content window pointer. The main window + * The theory: Each window has a main and a content window pointer. The main window * represents the window object outwards. It is linked to a panel structure which * is managed by the ncurses panel extension library to allow overlapping windows. * One aim of this design is to turn the ncurses-style inner-window border @@ -93,29 +143,9 @@ GB_STREAM_DESC WindowStream = { #define E_COORDS "Coordinates out of range" #define E_DIMENSION "Dimensions do not fit on screen" -/* - * Redraw the screen no matter what particular buffering settings the windows have. - * Note that this function may not be used to return into ncurses mode once left. - */ -void WINDOW_real_refresh() -{ - if (!NCURSES_running()) - return; - update_panels(); - doupdate(); -} -/** - * Refresh the screen. This respects THIS' buffering wishes - */ -void WINDOW_refresh(void *_object) -{ - if (!IS_BUFFERED) - WINDOW_real_refresh(); -} - /** * Copies text with attributes from a window to a newly malloced array as if the window - * was linear memory, too (line by line). + * was linear memory, too (line by line) - this is mainly used by wrapping code. * @src: source window * @arrp: pointer to store the newly malloced pointer containing the data in * @x: x position @@ -157,7 +187,7 @@ static int WINDOW_get_mem(WINDOW *src, chtype **arrp, int sx, int sy, int len) * @dst: destination window * @sx: starting x * @sy: starting y coordinate - * @len: length of data to copy from @arr (may not exceed its limits) + * @len: length of data to copy from @arr */ static int WINDOW_put_mem(chtype *arr, WINDOW *dst, int sx, int sy, unsigned int len) { @@ -176,8 +206,6 @@ static int WINDOW_put_mem(chtype *arr, WINDOW *dst, int sx, int sy, unsigned int wmove(dst, sy, sx); /* addch() ORs the current attributes but we want the chtypes drawn as they are in the buffer */ wattrset(dst, A_NORMAL); - /* advancing the cursor position is the good thing about addch() (at least, it is documented - behaviour...) */ for (i = 0; i < len; i++) waddch(dst, arr[i]); wattrset(dst, attrs); @@ -237,18 +265,27 @@ static int WINDOW_copy_window(WINDOW *src, WINDOW *dst, int sx, int sy, int nx, /** * Unlink and deallocate the main panel together with the window and, if present, the - * content window, refresh the screen to immediately remove leftovers of the window. + * content window and caption, refresh the screen to immediately remove leftovers of + * the window. */ static int WINDOW_remove(void *_object) { + int by, h; + + by = getbegy(THIS->main); + h = getmaxy(THIS->main); wclear(THIS->content); if (HAS_BORDER) { delwin(THIS->content); wclear(THIS->main); } - REFRESH(); + if (THIS->caption) { + GB.FreeString(&THIS->caption); + THIS->caption = NULL; + } del_panel(THIS->pan); delwin(THIS->main); + wtouchln(stdscr, by, h, 1); return 0; } @@ -293,22 +330,31 @@ static int WINDOW_remove_content(void *_object) } /** - * Draws an inner-window border to the main window which appears as outer-window for the content - * @b: 0: erase the border by overwriting it with all spaces - * !0: print a border + * Draws an inner-window border to the main window which appears as + * outer-window for the content. If a caption was set for the window and the + * border is to be drawn, also the caption is. + * @b: one of the BORDER_* constants */ static int WINDOW_draw_border(void *_object, bool b) { - /* TODO: how to check for the possibility of displaying ACS chars? - Terminfo exports the 'eacs' variable. Anyone to tell whether I understood the bare - information correctly? */ - if (b) { - if (tigetstr("enacs")) - box(THIS->main, 0, 0); - else - wborder(THIS->main, '|', '|', '-', '-', '+', '+', '+', '+'); - } else { + int width; + switch (b) { + case BORDER_NONE: wborder(THIS->main, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '); + break; + case BORDER_ASCII: + wborder(THIS->main, '|', '|', '-', '-', '+', '+', '+', '+'); + break; + case BORDER_ACS: + box(THIS->main, 0, 0); + break; + default: + return -1; + } + if (b && THIS->caption) { + width = MIN(getmaxx(THIS->main) - 1, + strlen(THIS->caption)); + mvwaddnstr(THIS->main, 0, 1, THIS->caption, width); } return 0; } @@ -424,21 +470,31 @@ static int WINDOW_print(void *_object, char *str, int x, int y) do { if (WINDOW_cursor_move(THIS, x, y) == -1) return -1; - width = getmaxx(THIS->content) - x; - width = MIN(width, strlen(p)); + width = strlen(p); + if (!IS_WRAPPED) + width = MIN(width, getmaxx(THIS->content) - x); + + /* waddnstr, as being subsequent calls to waddch, + * continuously overrides the last character in a window + * after reached that end. But we want a clear cut there */ + width = MIN(width, getmaxx(THIS->content) * + (getmaxy(THIS->content) - y) - x); if ((q = strchr(p, '\n'))) width = MIN(width, q - p); waddnstr(THIS->content, p, width); p += width; - if (*p == '\n') + x = getcurx(THIS->content); + y = getcury(THIS->content); + if (y == getmaxy(THIS->content) - 1) + break; + if (*p == '\n') { + y++; p++; - if (!THIS->wrap) - break; - x = 0; - y++; - if (y >= getmaxy(THIS->content)) - break; + } + if (*p) + x = 0; } while (*p); + WINDOW_cursor_move(THIS, x, y); return 0; } @@ -523,21 +579,7 @@ static int WINDOW_get_str(void *_object, int x, int y, unsigned int len, char ** */ static int WINDOW_key_timeout(void *_object, int timeout, int *ret) { - /* wtimeout() and wgetch() cause ncurses, for whatever reason, to mess up the panel - layout: the particular window gets risen to the top of everything. Consequently - I use the stdscr here which appears to not suffer from this. */ - if (timeout >= 0) - timeout(timeout); - *ret = getch(); - if (*ret == ERR) { - /* Had a timeout, the manual doesn't define any errors to happen for wgetch() - besides NULL pointer arguments. The only source of ERR is timeout expired. */ - if (timeout >= 0) - *ret = 0; - } - - if (timeout >= 0) - timeout(-1); + *ret = INPUT_get(timeout); return 0; } @@ -588,32 +630,35 @@ static int WINDOW_show(void *_object) } /** - * Callback for data on stdin - * @fd: stdin means 0 - * @type: having watched stdin for read means GB_WATCH_READ - * @param: NULL since we don't need it - * This function raises the Read event. Since only one callback appears to be able to - * be registered for an fd, there can only be one window to raise this event, i.e. - * the one being set to "have the focus" via Window.SetFocus() + * Raise the Window_Read event of the given window. If _object is NULL, + * the currently focused window is used. The event is, however, only risen, + * if the window can raise events. Otherwise let the parent screen raise the + * event. */ -static void WINDOW_read_callback(int fd, int type, void *param) +void WINDOW_raise_read(void *_object) { - GB.Raise(focused, EVENT_Read, 0); + if (!_object) + _object = focused; + + if (_object && GB.CanRaise(_object, EVENT_Read)) + GB.Raise(_object, EVENT_Read, 0); + else + SCREEN_raise_read(_object ? THIS->parent : NULL); } /** - * Bring the given nc_window to focus. That means that this window now raises the - * Read event if data arrives on stdin. The global variable @focussed is used to - * keep track of that very window + * Bring the given window to focus. That means that this window now raises the + * Read event whenever data arrives at the input queue _and_ the window can + * raise events. The global variable @focused is used to keep track of that + * very window. */ static int WINDOW_setfocus(void *_object) { - if (!focused) - GB.Watch(0, GB_WATCH_READ, WINDOW_read_callback, 0); - else + if (focused) GB.Unref((void **) &focused); focused = THIS; GB.Ref((void **) &focused); + /* We watch since SCREEN_init() */ return 0; } @@ -649,16 +694,19 @@ static int WINDOW_import(void *_object, WINDOW *imp) static int WINDOW_attrs_driver(void *_object, int attr, int req) { switch (req) { - case ATTR_DRV_RET: - return getattrs(THIS->content); - case ATTR_DRV_ON: + case ATTR_DRV_RET: + return getattrs(THIS->content); + case ATTR_DRV_ON: + if (attr == A_NORMAL) + wattrset(THIS->content, A_NORMAL); + else wattron(THIS->content, attr); - break; - case ATTR_DRV_OFF: - wattroff(THIS->content, attr); - break; - case ATTR_DRV_COL: - wbkgd(THIS->content, attr | ' '); + break; + case ATTR_DRV_OFF: + wattroff(THIS->content, attr); + break; + case ATTR_DRV_COL: + wbkgd(THIS->content, attr | ' '); } return 0; } @@ -685,112 +733,28 @@ static int WINDOW_char_attrs_driver(void *_object, int attr, int x, int y, int r WINDOW_cursor_move(THIS, x, y); ch = winch(THIS->content); switch (req) { - case ATTR_DRV_RET: - res = ch & A_ATTRIBUTES; - goto _cleanup; - case ATTR_DRV_ON: + case ATTR_DRV_RET: + res = ch & A_ATTRIBUTES; + goto cleanup; + case ATTR_DRV_ON: + if (attr == A_NORMAL) + wchgat(THIS->content, 1, A_NORMAL, PAIR_NUMBER(ch), NULL); + else wchgat(THIS->content, 1, (ch & A_ATTRIBUTES) | attr, PAIR_NUMBER(ch), NULL); - break; - case ATTR_DRV_OFF: - wchgat(THIS->content, 1, (ch & A_ATTRIBUTES) & ~attr, PAIR_NUMBER(ch), NULL); - break; - case ATTR_DRV_COL: - wchgat(THIS->content, 1, (ch & A_ATTRIBUTES), PAIR_NUMBER(attr), NULL); + break; + case ATTR_DRV_OFF: + wchgat(THIS->content, 1, (ch & A_ATTRIBUTES) & ~attr, PAIR_NUMBER(ch), NULL); + break; + case ATTR_DRV_COL: + wchgat(THIS->content, 1, (ch & A_ATTRIBUTES), PAIR_NUMBER(attr), NULL); } res = 0; - _cleanup: + cleanup: WINDOW_cursor_move(THIS, ox, oy); return res; } -/* - * Window stream functions - */ - -static int Window_stream_open(GB_STREAM *stream, const char *path, int mode, void *data) -{ - return -1; -} - -static int Window_stream_close(GB_STREAM *stream) -{ - return -1; -} - -static int Window_stream_read(GB_STREAM *stream, char *buffer, int len) -{ - STREAM_PROLOGUE(); - int key, res; - - /* We only support reading Byte or Integer. */ - switch (len) { - case sizeof(char): - return Window_stream_getchar(stream, buffer); - case sizeof(int): - res = WINDOW_key_timeout(THIS, -1, &key); - *((int *) buffer) = key; - return res; - default: - return -1; - } -} - -static int Window_stream_getchar(GB_STREAM *stream, char *buffer) -{ - STREAM_PROLOGUE(); - int key, res; - - res = WINDOW_key_timeout(THIS, -1, &key); - *buffer = (char) key; - return res; -} - -static int Window_stream_write(GB_STREAM *stream, char *buffer, int len) -{ - STREAM_PROLOGUE(); - - /* However, we terminate at the first NUL byte */ - return WINDOW_print(THIS, buffer, -1, -1); -} - -static int Window_stream_seek(GB_STREAM *steram, int64_t pos, int whence) -{ - return -1; -} - -static int Window_stream_tell(GB_STREAM *stream, int64_t *pos) -{ - return -1; -} - -static int Window_stream_flush(GB_STREAM *stream) -{ - REAL_REFRESH(); - return 0; -} - -static int Window_stream_eof(GB_STREAM *stream) -{ - int n; - - /* Safest method */ - if (ioctl(0, TIOCINQ, &n) == -1) - return -1; - return n ? 1 : 0; -} - -static int Window_stream_lof(GB_STREAM *stream, int64_t *len) -{ - return ioctl(0, TIOCINQ, len); -} - -static int Window_stream_handle(GB_STREAM *stream) -{ - /* There's no clear 'handle', we operate on stdin or stdout depending on context */ - return -1; -} - /* * Window class */ @@ -813,20 +777,21 @@ BEGIN_PROPERTY(Window_Background) } COLOR_setpair(pair, f, b); wbkgd(THIS->content, COLOR_PAIR(pair) | ' '); + REFRESH(); END_PROPERTY BEGIN_PROPERTY(Window_Border) - bool b; + int b; int x, y, w, h; if (READ_PROPERTY) { - GB.ReturnBoolean(THIS->border); + GB.ReturnInteger(THIS->border); return; } - b = VPROP(GB_BOOLEAN); + b = VPROP(GB_INTEGER); if (b == THIS->border) return; @@ -855,13 +820,21 @@ BEGIN_PROPERTY(Window_Border) END_PROPERTY -BEGIN_PROPERTY(Window_Buffered) +BEGIN_PROPERTY(Window_Caption) if (READ_PROPERTY) { - GB.ReturnBoolean(THIS->buffered); + if (!THIS->caption) + GB.ReturnNull(); + else + GB.ReturnString(THIS->caption); return; } - THIS->buffered = VPROP(GB_BOOLEAN); + + THIS->caption = GB.NewZeroString(PSTRING()); + if (HAS_BORDER) { + WINDOW_draw_border(THIS, 1); + REFRESH(); + } END_PROPERTY @@ -917,6 +890,7 @@ BEGIN_PROPERTY(Window_Foreground) } COLOR_setpair(pair, f, b); wbkgd(THIS->content, COLOR_PAIR(pair) | ' '); + REFRESH(); END_PROPERTY @@ -975,7 +949,7 @@ BEGIN_PROPERTY(Window_Y) END_PROPERTY -BEGIN_METHOD(Window_new, GB_INTEGER x; GB_INTEGER y; GB_INTEGER w; GB_INTEGER h) +BEGIN_METHOD(Window_new, GB_OBJECT parent; GB_INTEGER x; GB_INTEGER y; GB_INTEGER w; GB_INTEGER h) WINDOW *new; @@ -983,15 +957,11 @@ BEGIN_METHOD(Window_new, GB_INTEGER x; GB_INTEGER y; GB_INTEGER w; GB_INTEGER h) GB.Error("Not in NCurses mode"); return; } - new = newwin(MISSING(h) ? LINES : VARG(h), MISSING(w) ? COLS : VARG(w), - MISSING(y) ? 0 : VARG(y), MISSING(x) ? 0 : VARG(x)); + new = newwin(VARGOPT(h, LINES), VARGOPT(w, COLS), + VARGOPT(y, 0), VARGOPT(x, 0)); WINDOW_import(THIS, new); - if (!GB.Parent(_object)) - GB.Attach(THIS, (void *) GB.Application.StartupClass(), "Window"); - - THIS->stream.desc = &WindowStream; - THIS->stream.tag = THIS; + THIS->parent = VARGOPT(parent, SCREEN_get_active()); REFRESH(); @@ -1026,12 +996,8 @@ BEGIN_METHOD(Window_Ask, GB_STRING opts; GB_INTEGER tries) char *o, c[2]; miss = MISSING(tries); - if (!miss) - t = VARG(tries); - else - t = -1; /* to silence the compiler */ - - o = STRING(opts); + t = VARGOPT(tries, -1); + o = GB.ToZeroString(ARG(opts)); while (miss || t--) { ch = getch(); @@ -1049,7 +1015,7 @@ BEGIN_METHOD(Window_Ask, GB_STRING opts; GB_INTEGER tries) END_METHOD -BEGIN_METHOD_VOID(Window_Bottom) +BEGIN_METHOD_VOID(Window_Lower) WINDOW_bottom(THIS); REFRESH(); @@ -1071,6 +1037,54 @@ BEGIN_METHOD_VOID(Window_Drain) END_METHOD +BEGIN_METHOD(Window_DrawHLine, GB_INTEGER x; GB_INTEGER y; GB_INTEGER len; GB_STRING ch; GB_INTEGER thickness) + + int ox, oy, gx, gy; + char c; + int length, t; + int i; + + getyx(THIS->content, oy, ox); + c = *(STRING(ch)); + length = VARG(len); + t = VARGOPT(thickness, 1); + + gx = VARG(x); + gy = VARG(y); + for (i = 0; i < t; i++) { + WINDOW_cursor_move(THIS, gx, gy); + whline(THIS->content, c, length); + gy++; + } + WINDOW_cursor_move(THIS, ox, oy); + REFRESH(); + +END_METHOD + +BEGIN_METHOD(Window_DrawVLine, GB_INTEGER x; GB_INTEGER y; GB_INTEGER len; GB_STRING ch; GB_INTEGER thickness) + + int ox, oy, gx, gy; + char c; + int length, t; + int i; + + getyx(THIS->content, oy, ox); + c = *(STRING(ch)); + length = VARG(len); + t = VARGOPT(thickness, 1); + + gx = VARG(x); + gy = VARG(y); + for (i = 0; i < t; i++) { + WINDOW_cursor_move(THIS, gx, gy); + wvline(THIS->content, c, length); + gx++; + } + WINDOW_cursor_move(THIS, ox, oy); + REFRESH(); + +END_METHOD + BEGIN_METHOD_VOID(Window_Full) WINDOW_move(THIS, 0, 0); @@ -1088,10 +1102,7 @@ BEGIN_METHOD(Window_Get, GB_INTEGER x; GB_INTEGER y; GB_INTEGER len) char *ret; /* if no @len is given, we return until the end of the line */ - if (MISSING(len)) - l = -1; - else - l = VARG(len); + l = VARGOPT(len, -1); WINDOW_get_str(THIS, VARG(x), VARG(y), l, &ret); GB.ReturnNewZeroString(ret); GB.Free((void **) &ret); @@ -1105,36 +1116,9 @@ BEGIN_METHOD_VOID(Window_Hide) END_METHOD -BEGIN_METHOD(Window_DrawHLine, GB_INTEGER x; GB_INTEGER y; GB_INTEGER len; GB_STRING ch; GB_INTEGER thickness) - - int ox, oy, gx, gy; - char c; - int length, t; - int i; - - getyx(THIS->content, oy, ox); - c = *(STRING(ch)); - length = VARG(len); - if (MISSING(thickness)) - t = 1; - else - t = VARG(thickness); - - gx = VARG(x); - gy = VARG(y); - for (i = 0; i < t; i++) { - WINDOW_cursor_move(THIS, gx, gy); - whline(THIS->content, c, length); - gy++; - } - WINDOW_cursor_move(THIS, ox, oy); - REFRESH(); - -END_METHOD - BEGIN_METHOD(Window_Insert, GB_STRING text; GB_INTEGER x; GB_INTEGER y) - WINDOW_insert(THIS, STRING(text), MISSING(x) ? -1 : VARG(x), MISSING(y) ? -1 : VARG(y)); + WINDOW_insert(THIS, GB.ToZeroString(ARG(text)), VARGOPT(x, -1), VARGOPT(y, -1)); REFRESH(); END_METHOD @@ -1148,14 +1132,21 @@ END_METHOD BEGIN_METHOD(Window_Move, GB_INTEGER x; GB_INTEGER y) - WINDOW_move(THIS, MISSING(x) ? -1 : VARG(x), MISSING(y) ? -1 : VARG(y)); + WINDOW_move(THIS, VARGOPT(x, -1), VARGOPT(y, -1)); REFRESH(); END_METHOD BEGIN_METHOD(Window_Print, GB_STRING text; GB_INTEGER x; GB_INTEGER y) - WINDOW_print(THIS, STRING(text), MISSING(x) ? -1 : VARG(x), MISSING(y) ? -1 : VARG(y)); + WINDOW_print(THIS, GB.ToZeroString(ARG(text)), VARGOPT(x, -1), VARGOPT(y, -1)); + REFRESH(); + +END_METHOD + +BEGIN_METHOD_VOID(Window_Raise) + + WINDOW_top(THIS); REFRESH(); END_METHOD @@ -1166,13 +1157,13 @@ BEGIN_METHOD(Window_PrintCenter, GB_STRING text) int x, y; char *p, *q; - p = STRING(text); + p = GB.ToZeroString(ARG(text)); while ((q = strchr(p, '\n'))) { lines++; p = q + 1; } - p = STRING(text); + p = GB.ToZeroString(ARG(text)); y = (getmaxy(THIS->content) - lines) / 2; while (lines--) { if (!lines) { @@ -1192,16 +1183,30 @@ BEGIN_METHOD(Window_PrintCenter, GB_STRING text) END_METHOD -BEGIN_METHOD_VOID(Window_Refresh) +BEGIN_METHOD(Window_Resize, GB_INTEGER w; GB_INTEGER h) - REAL_REFRESH(); + WINDOW_resize(THIS, VARGOPT(w, -1), VARGOPT(h, -1)); + REFRESH(); END_METHOD -BEGIN_METHOD(Window_Resize, GB_INTEGER w; GB_INTEGER h) +BEGIN_METHOD(Window_Read, GB_INTEGER timeout) - WINDOW_resize(THIS, MISSING(w) ? -1 : VARG(w), MISSING(h) ? -1 : VARG(h)); - REFRESH(); + int t; + int ret; + + t = VARGOPT(timeout, -1); + + WINDOW_key_timeout(THIS, t, &ret); + GB.ReturnInteger(ret); + +END_METHOD + +BEGIN_METHOD_VOID(Window_ReadLine) + + /*abbruch-kondition? ein backspace mehr, als zeichen vorhanden? eine + bestimmte taste? NULL wird zurueckgegeben. \n und \x4 werden nicht + geliefert.*/ END_METHOD @@ -1218,55 +1223,6 @@ BEGIN_METHOD_VOID(Window_SetFocus) END_METHOD -BEGIN_METHOD_VOID(Window_Top) - - WINDOW_top(THIS); - REFRESH(); - -END_METHOD - -BEGIN_METHOD(Window_DrawVLine, GB_INTEGER x; GB_INTEGER y; GB_INTEGER len; GB_STRING ch; GB_INTEGER thickness) - - int ox, oy, gx, gy; - char c; - int length, t; - int i; - - getyx(THIS->content, oy, ox); - c = *(STRING(ch)); - length = VARG(len); - if (MISSING(thickness)) - t = 1; - else - t = VARG(thickness); - - gx = VARG(x); - gy = VARG(y); - for (i = 0; i < t; i++) { - WINDOW_cursor_move(THIS, gx, gy); - wvline(THIS->content, c, length); - gx++; - } - WINDOW_cursor_move(THIS, ox, oy); - REFRESH(); - -END_METHOD - -BEGIN_METHOD(Window_WaitKey, GB_INTEGER timeout) - - int t; - int ret; - - if (MISSING(timeout)) - t = -1; - else - t = VARG(timeout); - - WINDOW_key_timeout(THIS, t, &ret); - GB.ReturnInteger(ret); - -END_METHOD - /* * .Window.Attrs virtual class */ @@ -1278,30 +1234,35 @@ BEGIN_PROPERTY(WindowAttrs_Normal) GB.ReturnBoolean(getattrs(THIS->content) == A_NORMAL); if (VPROP(GB_BOOLEAN)) wattrset(THIS->content, A_NORMAL); + REFRESH(); END_PROPERTY BEGIN_PROPERTY(WindowAttrs_Underline) - WIN_ATTR_METHOD_BOOL(A_UNDERLINE); + WIN_ATTR_METHOD(A_UNDERLINE); + REFRESH(); END_PROPERTY BEGIN_PROPERTY(WindowAttrs_Reverse) - WIN_ATTR_METHOD_BOOL(A_REVERSE); + WIN_ATTR_METHOD(A_REVERSE); + REFRESH(); END_PROPERTY BEGIN_PROPERTY(WindowAttrs_Blink) - WIN_ATTR_METHOD_BOOL(A_BLINK); + WIN_ATTR_METHOD(A_BLINK); + REFRESH(); END_PROPERTY BEGIN_PROPERTY(WindowAttrs_Bold) - WIN_ATTR_METHOD_BOOL(A_BOLD); + WIN_ATTR_METHOD(A_BOLD); + REFRESH(); END_PROPERTY @@ -1319,6 +1280,7 @@ BEGIN_PROPERTY(WindowAttrs_Color) return; } WINDOW_attrs_driver(THIS, COLOR_PAIR(pair), ATTR_DRV_COL); + REFRESH(); END_PROPERTY @@ -1334,33 +1296,34 @@ BEGIN_PROPERTY(CharAttrs_Normal) return; } WINDOW_char_attrs_driver(THIS, A_NORMAL, THIS->pos.col, THIS->pos.line, ATTR_DRV_ON); + REFRESH(); END_PROPERTY BEGIN_PROPERTY(CharAttrs_Underline) - CHAR_ATTR_METHOD_BOOL(A_UNDERLINE); + CHAR_ATTR_METHOD(A_UNDERLINE); REFRESH(); END_PROPERTY BEGIN_PROPERTY(CharAttrs_Reverse) - CHAR_ATTR_METHOD_BOOL(A_REVERSE); + CHAR_ATTR_METHOD(A_REVERSE); REFRESH(); END_PROPERTY BEGIN_PROPERTY(CharAttrs_Blink) - CHAR_ATTR_METHOD_BOOL(A_BLINK); + CHAR_ATTR_METHOD(A_BLINK); REFRESH(); END_PROPERTY BEGIN_PROPERTY(CharAttrs_Bold) - CHAR_ATTR_METHOD_BOOL(A_BOLD); + CHAR_ATTR_METHOD(A_BOLD); REFRESH(); END_PROPERTY @@ -1386,18 +1349,18 @@ BEGIN_PROPERTY(CharAttrs_Color) END_PROPERTY -#define TIMEOUT_NOTIMEOUT -1 - GB_DESC CWindowDesc[] = { - GB_DECLARE("Window", sizeof(struct nc_window)), - GB_INHERITS("Stream"), + GB_DECLARE("Window", sizeof(CWINDOW)), GB_AUTO_CREATABLE(), GB_EVENT("Read", NULL, NULL, &EVENT_Read), /* Constants */ GB_CONSTANT("NoTimeout", "i", TIMEOUT_NOTIMEOUT), + GB_CONSTANT("None", "i", BORDER_NONE), + GB_CONSTANT("Ascii", "i", BORDER_ASCII), + GB_CONSTANT("ACS", "i", BORDER_ACS), /* Properties */ GB_PROPERTY_SELF("Attributes", ".Window.Attrs"), @@ -1405,9 +1368,9 @@ GB_DESC CWindowDesc[] = GB_PROPERTY("Background", "i", Window_Background), GB_PROPERTY("Paper", "i", Window_Background), - GB_PROPERTY("Border", "b", Window_Border), + GB_PROPERTY("Border", "i", Window_Border), - GB_PROPERTY("Buffered", "b", Window_Buffered), + GB_PROPERTY("Caption", "s", Window_Caption), GB_PROPERTY_READ("ContainerHeight", "i", Window_ContainerHeight), GB_PROPERTY_READ("ContainerH", "i", Window_ContainerHeight), @@ -1432,14 +1395,14 @@ GB_DESC CWindowDesc[] = GB_PROPERTY("Y", "i", Window_Y), /* Methods */ - GB_METHOD("_new", NULL, Window_new, "[(X)i(Y)i(W)i(H)i]"), + GB_METHOD("_new", NULL, Window_new, "[(Parent)Screen;(X)i(Y)i(W)i(H)i]"), GB_METHOD("_free", NULL, Window_free, NULL), GB_METHOD("_get", ".Char.Attrs", Window_get, "(Y)i(X)i"), GB_METHOD("Ask", "s", Window_Ask, "(Opts)s[(Tries)i]"), - GB_METHOD("Bottom", NULL, Window_Bottom, NULL), - GB_METHOD("Top", NULL, Window_Top, NULL), + GB_METHOD("Lower", NULL, Window_Lower, NULL), + GB_METHOD("Raise", NULL, Window_Raise, NULL), GB_METHOD("Cls", NULL, Window_Cls, NULL), @@ -1463,12 +1426,11 @@ GB_DESC CWindowDesc[] = GB_METHOD("Move", NULL, Window_Move, "[(X)i(Y)i]"), GB_METHOD("Resize", NULL, Window_Resize, "[(W)i(H)i]"), - GB_METHOD("Refresh", NULL, Window_Refresh, NULL), + GB_METHOD("Read", "i", Window_Read, "[(Timeout)i]"), + GB_METHOD("ReadLine", "s", Window_ReadLine, NULL), GB_METHOD("SetFocus", NULL, Window_SetFocus, NULL), - GB_METHOD("WaitKey", "i", Window_WaitKey, "[(Timeout)i]"), - GB_END_DECLARE }; diff --git a/gb.ncurses/src/c_window.h b/gb.ncurses/src/c_window.h index 9e348bff9..f8bf9f59a 100644 --- a/gb.ncurses/src/c_window.h +++ b/gb.ncurses/src/c_window.h @@ -28,40 +28,19 @@ #include "gambas.h" #include "gb_common.h" -#include "c_ncurses.h" +#include "c_screen.h" -#define THIS ((CWINDOW *) _object) -#define HAS_BORDER (THIS->border) -#define IS_WRAPPED (THIS->wrap) -#define IS_BUFFERED (THIS->buffered) -/* This will produce final output on terminal screen */ -#define REAL_REFRESH() WINDOW_real_refresh() -/* This macro is mostly called by Gambas implementation functions to request output on screen - (read: to check if the output is buffered and if not produce output by means of - REAL_REFRESH(). To check buffering, this needs to get an object parameter.) */ -#define REFRESH() WINDOW_refresh(THIS) +/* Border constants */ +enum { + BORDER_NONE = 0, + BORDER_ASCII, + BORDER_ACS +}; -/* Translate linear (absolute) memory addresses and x,y coordinates into each other - most useful when wrapping is needed. */ -#define A2XY(win, a, x, y) do { \ - (x) = (a) % getmaxx(win); \ - (y) = (a) / getmaxx(win); \ - } while (0) -#define XY2A(win, x, y, a) do { \ - (a) = (y) * getmaxx(win) + (x); \ - } while (0) -/* Interpret the -1 values in coordinates as to insert the current cursor position */ -#define MAKE_COORDS(win, x, y) do { \ - if ((x) == -1) \ - x = getcurx(win); \ - if ((y) == -1) \ - y = getcury(win); \ - } while (0) -/* Check for out-of-range coordinates */ -#define BAD_COORDS(win, x, y) ((x) < 0 || (x) >= getmaxx(win) || \ - (y) < 0 || (y) >= getmaxy(win)) -#define BAD_DIMENSION(w, h) ((w) <= 0 || (w) > COLS || \ - (h) <= 0 || (h) > LINES) +/* Timeout constants */ +enum { + TIMEOUT_NOTIMEOUT = -1 +}; /* @reqs for *_attrs_driver() */ enum @@ -76,49 +55,18 @@ enum ATTR_DRV_COL }; -#define WIN_ATTR_METHOD(b, a) do { \ - if (READ_PROPERTY) \ - GB.ReturnBoolean(WINDOW_attrs_driver( \ - THIS, (a), ATTR_DRV_RET) \ - & (a)); \ - else \ - WINDOW_attrs_driver(THIS, (a), \ - (b) ? ATTR_DRV_ON : ATTR_DRV_OFF); \ - } while (0) -#define WIN_ATTR_METHOD_BOOL(a) WIN_ATTR_METHOD(VPROP(GB_BOOLEAN), a) -/* Notice the wtouchln() line in the following macro. It seems that a chgat() from - nc_window_char_attrs_driver() doesn't mark anything dirty (no output on screen from - a REFRESH()). So to make the new attribute available immidiately, we touch the affected - line manually. A higher-callstack function may call REFRESH() to get output. */ -#define CHAR_ATTR_METHOD(b, a) do { \ - if (READ_PROPERTY) \ - GB.ReturnBoolean(WINDOW_char_attrs_driver( \ - THIS, (a), THIS->pos.col, THIS->pos.line, \ - ATTR_DRV_RET) & (a)); \ - else \ - WINDOW_char_attrs_driver(THIS, (a), THIS->pos.col, \ - THIS->pos.line, (b) ? ATTR_DRV_ON : ATTR_DRV_OFF); \ - wtouchln(THIS->main, THIS->pos.line + (HAS_BORDER ? 1 : 0), 1, 1); \ - } while(0) -#define CHAR_ATTR_METHOD_BOOL(a) CHAR_ATTR_METHOD(VPROP(GB_BOOLEAN), a) - -#define STREAM_PROLOGUE() void *_object = stream->tag - -//TODO: [-] Stream -// [-] background/foreground colors -typedef struct nc_window { +typedef struct { GB_BASE ob; - GB_STREAM stream; /* Gambas stream structure to enable stream-related syntaxes */ - WINDOW *main; /* The main window. */ + CSCREEN *parent; /* The parent Screen */ + WINDOW *main; /* The main window */ WINDOW *content; /* This window is used for all content-related operations. Its purpose is turning the ncurses window borders which are inner-window to outer-window ones thus separating border from content. If there is no border, this is the same as @main - otherwise a subwindow of it. */ + otherwise a subwindow of it */ PANEL *pan; /* Panel of the main window to provide overlapping windows */ - bool border; /* Whether there is a border */ + int border; /* What kind of border */ bool wrap; /* Whether text shall be truncated or wrapped on line ends */ - bool buffered; /* Whether the output via REFRESH() macro shall be buffered (only a call to - Window.Refresh() will then produce any output) */ + char *caption; /* Text to be displayed in the main window if there is a border */ struct { /* This structure is used to pass a line and a column number to virtual objects */ int line; int col; @@ -129,17 +77,8 @@ typedef struct nc_window { extern GB_DESC CWindowDesc[]; extern GB_DESC CWindowAttrsDesc[]; extern GB_DESC CCharAttrsDesc[]; -extern GB_STREAM_DESC WindowStream; #endif -#define WINDOW_main_to_content() WINDOW_copy_window(THIS->main, THIS->content, 0, 0, \ - getmaxx(THIS->content), \ - getmaxy(THIS->content), 0, 0) -#define WINDOW_content_to_main() WINDOW_copy_window(THIS->content, THIS->main, 0, 0, \ - getmaxx(THIS->content), \ - getmaxy(THIS->content), 0, 0) - -void WINDOW_real_refresh(); -void WINDOW_refresh(); +void WINDOW_raise_read(void *); #endif /* __C_WINDOW_C */ diff --git a/gb.ncurses/src/input.c b/gb.ncurses/src/input.c new file mode 100644 index 000000000..828c11789 --- /dev/null +++ b/gb.ncurses/src/input.c @@ -0,0 +1,588 @@ +/* + * input.c - gb.ncurses opaque input routines + * + * Copyright (C) 2012 Tobias Boege + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#define __INPUT_C + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gambas.h" +#include "gb_common.h" + +#include "main.h" +#include "input.h" +#include "c_window.h" + +#define E_UNSUPP "Unsupported value" +#define E_NO_NODELAY "Could not initialise NoDelay mode" + +static int _input = -1; +static char _watching = 0; + +/* Note that this is not safe for functions that are used to change the mode + * settings, in particular INPUT_init_nodelay() and INPUT_exit_nodelay(), + * because @_input is updated after this function */ +#define IN_NODELAY (_input == INPUT_NODELAY) + +static struct { + struct { + struct termios term; + int kbmode; + void (*error_hook)(); + } old; + int fd; + unsigned short pressed; + unsigned int delay; + GB_TIMER *timer; +} no_delay; +static char _exiting_nodelay = 0; + +/** + * Input initialisation + */ +int INPUT_init() +{ + INPUT_mode(INPUT_CBREAK); + INPUT_repeater_delay(100); + return 0; +} + +/** + * Input cleanup + */ +void INPUT_exit() +{ + /* If we are still in NoDelay mode, exit it */ + if (IN_NODELAY) { + INPUT_mode(INPUT_CBREAK); + } +} + +/** + * Begin or stop watching the input queue in question depending on @_input + */ +static int INPUT_watch(char start) +{ + int fd = IN_NODELAY ? no_delay.fd : 0; + + if (!start && !_watching) + return 0; + if (start && _watching) + INPUT_watch(!start); + GB.Watch(fd, start ? GB_WATCH_READ : GB_WATCH_NONE, + INPUT_callback, 0); + _watching = start; + return 0; +} + +/** + * Function to be called by Gambas when data arrives + */ +static void INPUT_callback(int fd, int flag, intptr_t arg) +{ + WINDOW_raise_read(NULL); +} + +/** + * Return if the given fd can be used with console_ioctls + * @fd: file descriptor to test + * The idea was derived from "kbd" package, getfd.c, is_a_console() + */ +static inline char is_cons(int fd) +{ + char type; + + if (fd != -1 && isatty(fd) && ioctl(fd, KDGKBTYPE, &type) != -1 + && (type == KB_101 || type == KB_84)) + return 1; + return 0; +} + +/** + * Returns an fd that can be used with console_ioctls or -1 if none + * available + */ +int INPUT_consolefd() +{ + int fd; + + if (is_cons(0)) + return 0; + + fd = open("/dev/tty", O_RDWR); + if (fd == -1) + return -1; + if (is_cons(fd)) + return fd; + + close(fd); + return -1; +} + +/** + * Init NoDelay mode + * We save old settings and prepare the TTY driver and Gambas + */ +static int INPUT_init_nodelay() +{ + int fd = INPUT_consolefd(); + struct termios term; + + if (fd == -1) + return -1; + + /* TODO: implement switching between vts, need available signals to + * be sent */ + + tcgetattr(fd, &no_delay.old.term); + ioctl(fd, KDGKBMODE, &no_delay.old.kbmode); + + memcpy(&term, &no_delay.old.term, sizeof(term)); + term.c_lflag &= ~(ICANON | ECHO | ISIG); + term.c_iflag &= ~(ISTRIP | IGNCR | ICRNL | INLCR | IXOFF | IXON); + /* Have no timeout per default */ + term.c_cc[VMIN] = 0; + term.c_cc[VTIME] = -1; + tcsetattr(fd, TCSAFLUSH, &term); + no_delay.old.error_hook = GB.Hook(GB_HOOK_ERROR, + INPUT_nodelay_error_hook); + + no_delay.fd = fd; + + no_delay.timer = NULL; + + /* Switch to K_MEDIUMRAW now. Could not have been done on-the-fly + * when reading from the console fd, because our key repeat code + * relies on data maybe present on that fd (to determine if we can + * safely inject new events for the currently pressed key or shall + * examine if there is another keypress) and there wouldn't be + * anything if we switch on-the-fly */ + ioctl(no_delay.fd, KDSKBMODE, K_MEDIUMRAW); + + return 0; +} + +/** + * Cleanup NoDelay mode + * Restore old settings + * This assumes that @_input reflects the current settings + */ +static int INPUT_exit_nodelay() +{ + /* @_input must be updated after this function, if even, after this + * function */ + if (!IN_NODELAY) + return 0; + if (_exiting_nodelay) + return 0; + + _exiting_nodelay = 1; + ioctl(no_delay.fd, KDSKBMODE, no_delay.old.kbmode); + tcsetattr(no_delay.fd, TCSANOW, &no_delay.old.term); + GB.Hook(GB_HOOK_ERROR, no_delay.old.error_hook); + if (no_delay.timer) + GB.Unref((void **) &no_delay.timer); + close(no_delay.fd); + _exiting_nodelay = 0; + return 0; +} + +/** + * The NoDelay mode error hook + * This calls the former error hook, saved by INPUT_init_nodelay() to not + * disturb any piece code + */ +static void INPUT_nodelay_error_hook() +{ + if (_exiting_nodelay) + return; + INPUT_exit_nodelay(); + no_delay.old.error_hook(); +} + +/** + * Return or set the repeater delay + * @val: value to set the delay to. This value must be at least 1 or an + * error is returned. If it is REPEATER_RETURN, the current value is + * returned to the caller. + * Note that this setting affects the repeater function itself, that gets + * called in this interval to generate events and the INPUT_get_nodelay() + * function which will wait to return the amount of milliseconds if it is to + * return the pressed key. + */ +int INPUT_repeater_delay(int val) +{ + if (val == REPEATER_RETURN) + return no_delay.delay; + if (val < 1) + return -1; + no_delay.delay = (unsigned int) val; + return 0; +} + +/** + * NoDelay mode event repeater + * Used to insert Window_Read events if there is a key pressed + */ +static int INPUT_nodelay_repeater() +{ + fprintf(stderr, "here\n"); + if (!no_delay.pressed) + return TRUE; + WINDOW_raise_read(NULL); + return FALSE; +} + +/** + * Return or set the current input mode + * @mode: one of INPUT_* enums + */ +int INPUT_mode(int mode) +{ + if (mode == INPUT_RETURN) + return _input; + + if (mode == _input) + return 0; + + INPUT_watch(0); + + if (_input == INPUT_NODELAY) + INPUT_exit_nodelay(); + + switch (mode) { + case INPUT_COOKED: + noraw(); + nocbreak(); + break; + case INPUT_CBREAK: + noraw(); + cbreak(); + break; + case INPUT_RAW: + nocbreak(); + raw(); + break; + case INPUT_NODELAY: + if (INPUT_init_nodelay() == -1) { + GB.Error(E_NO_NODELAY); + /* We return 0 to not override the previous + * error message with the one emitted by the + * caller if we return error */ + return 0; + } + break; + default: + GB.Error(E_UNSUPP); + return -1; + } + _input = mode; + + INPUT_watch(1); + + return 0; +} + +/** + * Retrieve input from ncurses + */ +static int INPUT_get_ncurses(int timeout) +{ + int ret; + + if (timeout >= 0) + timeout(timeout); + ret = getch(); + if (ret == ERR) { + /* Had a timeout, the manual doesn't define any errors to + happen for wgetch() besides NULL pointer arguments. The + only source of ERR is timeout expired. */ + if (timeout >= 0) + ret = 0; + } + + if (timeout >= 0) + timeout(-1); + return ret; +} + +/* + * Return codes from INPUT_trans_keycode() + */ +enum { + TRANS_NEED_MORE = -1, + TRANS_KEY_MIN +}; + +/* + * States of the modifier keys that we recognise + */ +enum { + MOD_NONE = 0, + MOD_SHIFT = 1, + MOD_CTRL = 2, + MOD_ALT = 4 +}; + +#define IS_BREAK(k) ((k) & 0x80) + +/** + * Translate a keycode (or a sequence) to an ncurses compatible int + * @kc: keycode to translate + * This function returns one of the above codes. TRANS_NEED_MORE means that + * the given @kc is considered part of a multi-keycode sequence (or is a + * Shift, Control, Alt), so we need more keys which are (in first case + * likely) available without waiting from the tty driver then; in the latter + * case (modifier keys), it does not count as a keypress anyway. + * Note that after a usual tty key sequence is assembled it is passed to the + * driver to try to get an escape sequence for it. If there is no escape + * seqeuence for that key, we can simply return the plain value. Otherwise + * we exploit ncurses key_defined() routine to translate the sequence to an + * int for us. This gives an int like ncurses getch() would do. + */ +static int INPUT_trans_keycode(unsigned char kc) +{ + /* Pause/Break has the largest scancode: e1 1d 45 e1 9d c5 */ + static unsigned char codes[8]; + static int num = 0; + static int modifiers = MOD_NONE; + + unsigned char seq[8]; + struct kbentry kbe; + struct kbsentry kbs; + register int mod; + +/* TODO: Hope they're fixed */ +#define KEYCODE_LCTRL 0x1d +#define KEYCODE_RCTRL 0x61 +#define KEYCODE_ALT 0x38 +#define KEYCODE_LSHIFT 0x2a +#define KEYCODE_RSHIFT 0x36 + /* Modifiers */ + switch (kc) { + case KEYCODE_LCTRL: + case KEYCODE_RCTRL: + mod = MOD_CTRL; + goto apply_mod; + case KEYCODE_ALT: + mod = MOD_ALT; + goto apply_mod; + case KEYCODE_LSHIFT: + case KEYCODE_RSHIFT: + mod = MOD_SHIFT; + goto apply_mod; + default: + goto account_key; + } +apply_mod: + if (IS_BREAK(kc)) + modifiers &= ~mod; + else + modifiers |= mod; + return TRANS_NEED_MORE; + +account_key: + codes[num++] = kc; + /* Break key, sends make and break code together */ + if (codes[0] == '\xe1') { + if (num == 6) + return KEY_BREAK; + else + return TRANS_NEED_MORE; + } + /* Keys with two keycodes, no matter, those correspond to + * single-keycode keys, we can safely use @kc */ + if (codes[0] == '\xe0' && num != 2) + return TRANS_NEED_MORE; + + /* TODO: what to do with ctrl- ? */ + + /* Set table and get action code */ + if (modifiers & MOD_ALT) { + if (modifiers & MOD_SHIFT) + kbe.kb_table = K_ALTSHIFTTAB; + else + kbe.kb_table = K_ALTTAB; + } else if (modifiers & MOD_SHIFT) { + kbe.kb_table = K_SHIFTTAB; + } else { + kbe.kb_table = K_NORMTAB; + } + kbe.kb_index = kc & 0x7f; + kbe.kb_value = 0; + ioctl(no_delay.fd, KDGKBENT, &kbe); + seq[0] = (unsigned char) kbe.kb_value; + /* Has an escape sequence? */ + kbs.kb_func = seq[0]; + ioctl(no_delay.fd, KDGKBSENT, &kbs); + if (kbs.kb_string[0]) { + strncpy((char *) seq, (char *) kbs.kb_string, 7); + seq[7] = '\0'; + return key_defined((char *) seq); + } else { + return (int) seq[0]; + } +} + +/** + * Change the currently pressed key + * @key: current key. 0 means that no key is pressed + * This install the repeater + */ +static void INPUT_change_pressed(int key) +{ + fprintf(stderr, "%d\n", no_delay.delay); + if (key == 0) { + GB.Unref((void **) no_delay.timer); + no_delay.timer = NULL; + } else { + no_delay.timer = GB.Every(no_delay.delay, + (GB_TIMER_CALLBACK) INPUT_nodelay_repeater, 0); + } + no_delay.pressed = key; +} + +/** + * Retrieve input in NoDelay mode, using console_ioctl(4). + * If there is a key pressed and no input available on the input queue, + * the pressed key is returned. + * @timeout: timeout in milliseconds. If that timeout expires, we return 0. + * Note that @timeout can only be in the scope of deciseconds and is + * silently cut down to those. + * The Repeater Delay applies to this function, too, in that we only return + * a pressed key when we waited for that delay. + * Note carefully, that there is an emergency exit: pressing ESC thrice + * during one second will immediately abort NoDelay mode and enter CBreak. + */ +static int INPUT_get_nodelay(int timeout) +{ + static char esc = 0; + struct termios old, new; + unsigned char b; + static time_t stamp; + int ret, res, num; + int key; /* This will already be ncurses compatible */ + struct timeval tv1, tv2; + + if (timeout > -1) { + gettimeofday(&tv1, NULL); + tcgetattr(no_delay.fd, &old); + memcpy(&new, &old, sizeof(new)); + } + +recalc_timeout: +#define USEC2MSEC(us) (us / 1000) +#define MSEC2USEC(ms) (ms * 1000) +#define MSEC2DSEC(ms) (ms / 100) +#define DSEC2MSEC(ds) (ds * 100) +#define SEC2MSEC(s) (s * 1000) +#define MSEC2SEC(ms) (ms / 1000) + + if (timeout > -1) { + gettimeofday(&tv2, NULL); + timeout -= USEC2MSEC(tv2.tv_usec - tv1.tv_usec); + timeout -= SEC2MSEC(tv2.tv_sec - tv1.tv_sec); + if (timeout < 0) { + ret = 0; + goto cleanup; + } + } + + /* Set timeout */ + ioctl(no_delay.fd, TIOCINQ, &num); + /* We don't need to set any timeout if there are bytes available */ + if (!num && timeout > -1) { + new.c_cc[VTIME] = MSEC2DSEC(timeout); + tcsetattr(no_delay.fd, TCSANOW, &new); + gettimeofday(&tv1, NULL); + } + + /* Begin reading */ + if (no_delay.pressed && !num) { + usleep(MSEC2USEC(no_delay.delay)); + ret = no_delay.pressed; + goto cleanup; + } + /* Try to stick to the user-supplied timeout */ + ioctl(no_delay.fd, TIOCINQ, &num); + if ((res = read(no_delay.fd, &b, 1)) == -1 && errno == EINTR) { + goto recalc_timeout; + } else if (res == 0) { /* Timeout expired */ + ret = 0; + goto cleanup; + } else { /* Got a key */ + /* Emergency exit from NoDelay mode */ +#define KEYCODE_ESC 0x01 + if (b == KEYCODE_ESC) { + if (time(NULL) - stamp > 0) + esc = 0; + if (++esc == 3) { + INPUT_exit_nodelay(); + INPUT_mode(INPUT_CBREAK); + return 0; + } + stamp = time(NULL); + } + /* We use ncurses keys for operations on our no_delay data */ + if ((key = INPUT_trans_keycode(b)) == TRANS_NEED_MORE) + goto recalc_timeout; + /* Ignore break codes, except when it is the currently + pressed key */ + if (IS_BREAK(b)) { + if (no_delay.pressed == key) + INPUT_change_pressed(0); + /* Key release isn't visible to the gambas programmer + * and thus not really an event to gb.ncurses. If + * time is left, we try again reading another key */ + goto recalc_timeout; + } else { + INPUT_change_pressed(key); + } + } + + ret = key; + +cleanup: + if (timeout > -1) + tcsetattr(no_delay.fd, TCSANOW, &old); + return ret; +} + +/** + * Get a keypress within the given timeout + * @timeout: number of milliseconds to wait. If no key is pressed during it, + * 0 will be returned. + */ +int INPUT_get(int timeout) +{ + if (_input == INPUT_NODELAY) + return INPUT_get_nodelay(timeout); + else + return INPUT_get_ncurses(timeout); +} diff --git a/gb.ncurses/src/input.h b/gb.ncurses/src/input.h new file mode 100644 index 000000000..1d3d95997 --- /dev/null +++ b/gb.ncurses/src/input.h @@ -0,0 +1,61 @@ +/* + * input.h - gb.ncurses opaque input routines for use of either ncurses or + * the terminal driver directly + * + * Copyright (C) 2012 Tobias Boege + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#ifndef __INPUT_H +#define __INPUT_H + +enum { + /* Return the current mode */ + INPUT_RETURN, + /* Line discipline, signal generation */ + INPUT_COOKED, + /* No line discipline, signal generation */ + INPUT_CBREAK, + /* No line discipline, no signal generation */ + INPUT_RAW, + /* Use terminal driver, enabled to use raw scancodes + (which are internally converted to ncurses keys but enable to + distinguish key press and release) */ + INPUT_NODELAY +}; + +enum { + /* Let INPUT_repeater_delay() return the current value of the + repeater delay */ + REPEATER_RETURN = -1 +}; + +int INPUT_init(); +void INPUT_exit(); +int INPUT_consolefd(); +int INPUT_mode(int mode); +int INPUT_get(int timeout); +int INPUT_repeater_delay(int val); +#ifdef __INPUT_C +static int INPUT_init_nodelay(); +static int INPUT_exit_nodelay(); +static void INPUT_nodelay_error_hook(); +static int INPUT_nodelay_repeater(); +static void INPUT_callback(int fd, int flag, intptr_t arg); +#endif + +#endif /* __INPUT_H */ diff --git a/gb.ncurses/src/main.c b/gb.ncurses/src/main.c index 55ca6a294..756fe23ad 100644 --- a/gb.ncurses/src/main.c +++ b/gb.ncurses/src/main.c @@ -21,7 +21,6 @@ #define __MAIN_C -#include "c_ncurses.h" #include "c_window.h" #include "c_key.h" #include "c_color.h" @@ -32,38 +31,86 @@ GB_INTERFACE GB EXPORT; GB_DESC *GB_CLASSES[] EXPORT = { - CNCursesDesc, - CWindowDesc, - CWindowAttrsDesc, - CCharAttrsDesc, - CKeyDesc, - CColorDesc, - CColorCapabilitiesDesc, - CColorPairDesc, - CColorContentDesc, - CScreenDesc, - NULL + CScreenDesc, + CWindowDesc, + CWindowAttrsDesc, + CCharAttrsDesc, + CKeyDesc, + CColorDesc, + CColorCapabilitiesDesc, + CColorPairDesc, + CColorContentDesc, + NULL }; -static void hook_error(int code, char *error, char *where) +static bool _init = FALSE; + +/** + * Returns if we are in ncurses mode + */ +bool MAIN_running() { - NCURSES_exit(); + return _init && (!isendwin() || stdscr); } -static void hook_main(int *argc, char **argv) +/** + * Component-global initialisation + * Start ncurses and prepare stdscr. Call other relevant initialisation + * routines. + */ +static void MAIN_init() { - NCURSES_init(); + if (_init) + return; + + initscr(); + keypad(stdscr, TRUE); + + SCREEN_init(); + COLOR_init(); + + refresh(); + + _init = TRUE; +} + +/** + * Cleanup and exit ncurses + */ +static void MAIN_exit() +{ + if (_init) { + SCREEN_exit(); + endwin(); + _init = FALSE; + } +} + +/** + * Error hook + */ +static void MAIN_hook_error(int code, char *error, char *where) +{ + MAIN_exit(); +} + +/** + * Main hook + */ +static void MAIN_hook_main(int *argc, char **argv) +{ + MAIN_init(); } int EXPORT GB_INIT() { - GB.Hook(GB_HOOK_ERROR, (void *) hook_error); - GB.Hook(GB_HOOK_MAIN, (void *) hook_main); + GB.Hook(GB_HOOK_ERROR, (void *) MAIN_hook_error); + GB.Hook(GB_HOOK_MAIN, (void *) MAIN_hook_main); return 0; } void EXPORT GB_EXIT() { - NCURSES_exit(); + MAIN_exit(); } diff --git a/gb.ncurses/src/main.h b/gb.ncurses/src/main.h index 9840a6dbb..46b639678 100644 --- a/gb.ncurses/src/main.h +++ b/gb.ncurses/src/main.h @@ -26,8 +26,12 @@ #include "gb_common.h" #include "gambas.h" +#define NCURSES_RUNNING MAIN_running() + #ifndef __MAIN_C extern GB_INTERFACE GB; #endif +bool MAIN_running(); + #endif /* __MAIN_H */