diff --git a/src/sdtui.c b/src/sdtui.c index e6cc058..39014c4 100644 --- a/src/sdtui.c +++ b/src/sdtui.c @@ -39,20 +39,15 @@ #define KEY_ESCAPE 27 /**< Curses doesn't define this. */ +#define KEY_VT 11 /**< Ctrl-K */ +#define KEY_NAK 21 /**< Ctrl-U */ +#define KEY_ETB 23 /**< Ctrl-W */ + +#define _(x) x /**< Fake gettext, for now. */ // --- Utilities --------------------------------------------------------------- -static void -display (const gchar *format, ...) -{ - va_list ap; - - va_start (ap, format); - vw_printw (stdscr, format, ap); - va_end (ap); - refresh (); -} - +/* TODO use iconv() wchar_t -> utf-8 */ static gchar * wchar_to_mb (wchar_t ch) { @@ -74,18 +69,6 @@ wchar_to_mb (wchar_t ch) return buffer; } -static const gchar * -wchar_to_mb_escaped (wchar_t ch) -{ - switch (ch) - { - case L'\r': return "\\r"; - case L'\n': return "\\n"; - case L'\t': return "\\t"; - default: return wchar_to_mb (ch); - } -} - static int poll_restart (struct pollfd *fds, nfds_t nfds, int timeout) { @@ -96,6 +79,22 @@ poll_restart (struct pollfd *fds, nfds_t nfds, int timeout) return ret; } +static gsize +utf8_offset (const gchar *s, gsize offset) +{ + return g_utf8_offset_to_pointer (s, offset) - s; +} + +/** Wrapper for curses event data. */ +typedef struct curses_event CursesEvent; + +struct curses_event +{ + wint_t code; + guint is_char : 1; + MEVENT mouse; +}; + // --- SIGWINCH ---------------------------------------------------------------- static int g_winch_pipe[2]; /**< SIGWINCH signalling pipe. */ @@ -128,50 +127,195 @@ install_winch_handler (void) g_old_winch_handler = oldact.sa_handler; } -// --- Event handlers ---------------------------------------------------------- +// --- Application ------------------------------------------------------------- -typedef struct +StardictDict *g_dict; //!< The current dictionary + +guint32 g_top_position; //!< Index of the topmost entry +guint g_selected; //!< Offset to the selected entry + +GString *g_input; //!< The current search input +guint g_input_pos; //!< Cursor position within input + +/** Initialize the application core. */ +static void +app_init (const gchar *filename) { - wint_t code; - guint is_char : 1; - MEVENT mouse; + GError *error; + g_dict = stardict_dict_new (filename, &error); + if (!g_dict) + { + g_printerr ("Error loading dictionary: %s\n", error->message); + exit (EXIT_FAILURE); + } + + g_top_position = 0; + g_selected = 0; + + g_input = g_string_new (NULL); + g_input_pos = 0; +} + +/** Render the top bar. */ +static void +app_redraw_top (void) +{ + mvprintw (0, 0, "%s: ", _("Search")); + + int y, x; + getyx (stdscr, y, x); + + gchar *input = g_locale_from_utf8 (g_input->str, -1, NULL, NULL, NULL); + g_return_if_fail (input != NULL); + + addstr (input); + clrtoeol (); + g_free (input); + + move (y, x + g_input_pos); + refresh (); +} + +/** Redraw the dictionary view. */ +static void +app_redraw_view (void) +{ + // TODO + refresh (); +} + +/** Redraw everything. */ +static void +app_redraw (void) +{ + app_redraw_view (); + app_redraw_top (); } -CursesEvent; static gboolean -process_curses_event (CursesEvent *event) +app_process_curses_event (CursesEvent *event) { + /* g_utf8_offset_to_pointer() is too dumb to detect this */ + g_assert (g_utf8_strlen (g_input->str, -1) >= g_input_pos); + if (!event->is_char) { switch (event->code) { case KEY_RESIZE: - display ("Screen has been resized to %u x %u\n", - COLS, LINES); + // TODO adapt to the new window size, COLS, LINES + app_redraw (); break; case KEY_MOUSE: - display ("Mouse event at (%d, %d), state %#lx\n", - event->mouse.x, event->mouse.y, event->mouse.bstate); + // TODO move the item cursor, event->mouse.{x,y,bstate} + break; + + case KEY_LEFT: + if (g_input_pos > 0) + { + g_input_pos--; + app_redraw_top (); + } + break; + case KEY_RIGHT: + if (g_input_pos < g_utf8_strlen (g_input->str, -1)) + { + g_input_pos++; + app_redraw_top (); + } + break; + case KEY_BACKSPACE: + if (g_input_pos > 0) + { + gchar *current = g_utf8_offset_to_pointer + (g_input->str, g_input_pos); + gchar *prev = g_utf8_prev_char (current); + g_string_erase (g_input, prev - g_input->str, current - prev); + g_input_pos--; + app_redraw_top (); + } + break; + case KEY_DC: + if (g_input_pos < g_utf8_strlen (g_input->str, -1)) + { + gchar *current = g_utf8_offset_to_pointer + (g_input->str, g_input_pos); + g_string_erase (g_input, current - g_input->str, + g_utf8_next_char (current) - current); + app_redraw_top (); + } break; - default: - display ("Keyboard event: non-character: %u\n", - event->code); } return TRUE; } - display ("Keyboard event: character: '%s'\n", - wchar_to_mb_escaped (event->code)); - - if (event->code == L'q' || event->code == KEY_ESCAPE) + switch (event->code) { - display ("Quitting...\n"); + case KEY_ESCAPE: return FALSE; + case KEY_VT: // Ctrl-K -- delete until the end of line + g_string_erase (g_input, utf8_offset (g_input->str, g_input_pos), -1); + app_redraw_top (); + return TRUE; + case KEY_ETB: // Ctrl-W -- delete word before cursor + { + if (!g_input_pos) + return TRUE; + + gchar *current = g_utf8_offset_to_pointer (g_input->str, g_input_pos); + gchar *space = g_utf8_strrchr (g_input->str, + g_utf8_prev_char (current) - g_input->str, ' '); + + if (space) + { + space = g_utf8_next_char (space); + g_string_erase (g_input, space - g_input->str, current - space); + g_input_pos = g_utf8_pointer_to_offset (g_input->str, space); + } + else + { + g_string_erase (g_input, 0, current - g_input->str); + g_input_pos = 0; + } + + app_redraw_top (); + return TRUE; } + case KEY_NAK: // Ctrl-U -- delete everything before the cursor + g_string_erase (g_input, 0, utf8_offset (g_input->str, g_input_pos)); + g_input_pos = 0; + app_redraw_top (); + return TRUE; + } + + /* What can you do... wchar_t, utf-8, locale encoding... */ + gchar *letter = g_locale_to_utf8 (wchar_to_mb (event->code), + -1, NULL, NULL, NULL); + g_return_val_if_fail (letter != NULL, FALSE); + + if (g_unichar_isprint (g_utf8_get_char (letter))) + { + g_string_insert (g_input, + utf8_offset (g_input->str, g_input_pos), letter); + g_input_pos += g_utf8_strlen (letter, -1); + + app_redraw_top (); + } + g_free (letter); return TRUE; } +/** Free any resources used by the application. */ +static void +app_destroy (void) +{ + g_string_free (g_input, TRUE); + g_object_unref (g_dict); +} + +// --- Event handlers ---------------------------------------------------------- + static gboolean process_stdin_input (void) { @@ -184,7 +328,7 @@ process_stdin_input (void) if (sta == KEY_CODE_YES && event.code == KEY_MOUSE && getmouse (&event.mouse) == ERR) abort (); - if (!process_curses_event (&event)) + if (!app_process_curses_event (&event)) return FALSE; } @@ -214,33 +358,48 @@ main (int argc, char *argv[]) abort (); GError *error = NULL; - GOptionContext *ctx = g_option_context_new ("- StarDict terminal UI"); + GOptionContext *ctx = g_option_context_new + ("dictionary.ifo - StarDict terminal UI"); g_option_context_add_main_entries (ctx, entries, NULL); if (!g_option_context_parse (ctx, &argc, &argv, &error)) { - g_printerr ("Error: option parsing failed: %s\n", error->message); + g_printerr ("%s: %s: %s\n", _("Error"), _("option parsing failed"), + error->message); exit (EXIT_FAILURE); } + if (argc != 2) + { + gchar *help = g_option_context_get_help (ctx, TRUE, FALSE); + g_printerr ("%s", help); + g_free (help); + exit (EXIT_FAILURE); + } + + g_option_context_free (ctx); + + app_init (argv[1]); + if (!initscr () || cbreak () == ERR - || noecho () == ERR) + || noecho () == ERR + || nonl () == ERR) abort (); keypad (stdscr, TRUE); /* Enable character processing. */ nodelay (stdscr, TRUE); /* Don't block on get_wch(). */ + scrollok (stdscr, TRUE); /* Also scrolling, pretty please. */ - mousemask (ALL_MOUSE_EVENTS, NULL); - - display ("Press Q, Escape or ^C to quit\n"); + setscrreg (1, LINES - 1); /* Create a scroll region. */ + mousemask (ALL_MOUSE_EVENTS, NULL); /* Register mouse events. */ if (pipe (g_winch_pipe) == -1) abort (); - install_winch_handler (); -// --- Message loop ------------------------------------------------------------ + app_redraw (); + /* Message loop. */ struct pollfd pollfd[2]; pollfd[0].fd = fileno (stdin); @@ -257,13 +416,12 @@ main (int argc, char *argv[]) && !process_stdin_input ()) break; if ((pollfd[1].revents & POLLIN) - && !process_winch_input (pollfd[2].fd)) + && !process_winch_input (pollfd[1].fd)) break; } -// --- Cleanup ----------------------------------------------------------------- - endwin (); + app_destroy (); if (close (g_winch_pipe[0]) == -1 || close (g_winch_pipe[1]) == -1)