Start working on sdtui; implement line editing
This commit is contained in:
parent
8ab74e16d7
commit
6db153b1e2
266
src/sdtui.c
266
src/sdtui.c
|
@ -39,20 +39,15 @@
|
||||||
|
|
||||||
|
|
||||||
#define KEY_ESCAPE 27 /**< Curses doesn't define this. */
|
#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 ---------------------------------------------------------------
|
// --- Utilities ---------------------------------------------------------------
|
||||||
|
|
||||||
static void
|
/* TODO use iconv() wchar_t -> utf-8 */
|
||||||
display (const gchar *format, ...)
|
|
||||||
{
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
va_start (ap, format);
|
|
||||||
vw_printw (stdscr, format, ap);
|
|
||||||
va_end (ap);
|
|
||||||
refresh ();
|
|
||||||
}
|
|
||||||
|
|
||||||
static gchar *
|
static gchar *
|
||||||
wchar_to_mb (wchar_t ch)
|
wchar_to_mb (wchar_t ch)
|
||||||
{
|
{
|
||||||
|
@ -74,18 +69,6 @@ wchar_to_mb (wchar_t ch)
|
||||||
return buffer;
|
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
|
static int
|
||||||
poll_restart (struct pollfd *fds, nfds_t nfds, int timeout)
|
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;
|
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 ----------------------------------------------------------------
|
// --- SIGWINCH ----------------------------------------------------------------
|
||||||
|
|
||||||
static int g_winch_pipe[2]; /**< SIGWINCH signalling pipe. */
|
static int g_winch_pipe[2]; /**< SIGWINCH signalling pipe. */
|
||||||
|
@ -128,50 +127,195 @@ install_winch_handler (void)
|
||||||
g_old_winch_handler = oldact.sa_handler;
|
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;
|
GError *error;
|
||||||
guint is_char : 1;
|
g_dict = stardict_dict_new (filename, &error);
|
||||||
MEVENT mouse;
|
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
|
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)
|
if (!event->is_char)
|
||||||
{
|
{
|
||||||
switch (event->code)
|
switch (event->code)
|
||||||
{
|
{
|
||||||
case KEY_RESIZE:
|
case KEY_RESIZE:
|
||||||
display ("Screen has been resized to %u x %u\n",
|
// TODO adapt to the new window size, COLS, LINES
|
||||||
COLS, LINES);
|
app_redraw ();
|
||||||
break;
|
break;
|
||||||
case KEY_MOUSE:
|
case KEY_MOUSE:
|
||||||
display ("Mouse event at (%d, %d), state %#lx\n",
|
// TODO move the item cursor, event->mouse.{x,y,bstate}
|
||||||
event->mouse.x, event->mouse.y, event->mouse.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;
|
break;
|
||||||
default:
|
|
||||||
display ("Keyboard event: non-character: %u\n",
|
|
||||||
event->code);
|
|
||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
display ("Keyboard event: character: '%s'\n",
|
switch (event->code)
|
||||||
wchar_to_mb_escaped (event->code));
|
|
||||||
|
|
||||||
if (event->code == L'q' || event->code == KEY_ESCAPE)
|
|
||||||
{
|
{
|
||||||
display ("Quitting...\n");
|
case KEY_ESCAPE:
|
||||||
return FALSE;
|
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;
|
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
|
static gboolean
|
||||||
process_stdin_input (void)
|
process_stdin_input (void)
|
||||||
{
|
{
|
||||||
|
@ -184,7 +328,7 @@ process_stdin_input (void)
|
||||||
if (sta == KEY_CODE_YES && event.code == KEY_MOUSE
|
if (sta == KEY_CODE_YES && event.code == KEY_MOUSE
|
||||||
&& getmouse (&event.mouse) == ERR)
|
&& getmouse (&event.mouse) == ERR)
|
||||||
abort ();
|
abort ();
|
||||||
if (!process_curses_event (&event))
|
if (!app_process_curses_event (&event))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,33 +358,48 @@ main (int argc, char *argv[])
|
||||||
abort ();
|
abort ();
|
||||||
|
|
||||||
GError *error = NULL;
|
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);
|
g_option_context_add_main_entries (ctx, entries, NULL);
|
||||||
if (!g_option_context_parse (ctx, &argc, &argv, &error))
|
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);
|
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 ()
|
if (!initscr ()
|
||||||
|| cbreak () == ERR
|
|| cbreak () == ERR
|
||||||
|| noecho () == ERR)
|
|| noecho () == ERR
|
||||||
|
|| nonl () == ERR)
|
||||||
abort ();
|
abort ();
|
||||||
|
|
||||||
keypad (stdscr, TRUE); /* Enable character processing. */
|
keypad (stdscr, TRUE); /* Enable character processing. */
|
||||||
nodelay (stdscr, TRUE); /* Don't block on get_wch(). */
|
nodelay (stdscr, TRUE); /* Don't block on get_wch(). */
|
||||||
|
scrollok (stdscr, TRUE); /* Also scrolling, pretty please. */
|
||||||
|
|
||||||
mousemask (ALL_MOUSE_EVENTS, NULL);
|
setscrreg (1, LINES - 1); /* Create a scroll region. */
|
||||||
|
mousemask (ALL_MOUSE_EVENTS, NULL); /* Register mouse events. */
|
||||||
display ("Press Q, Escape or ^C to quit\n");
|
|
||||||
|
|
||||||
if (pipe (g_winch_pipe) == -1)
|
if (pipe (g_winch_pipe) == -1)
|
||||||
abort ();
|
abort ();
|
||||||
|
|
||||||
install_winch_handler ();
|
install_winch_handler ();
|
||||||
|
|
||||||
// --- Message loop ------------------------------------------------------------
|
app_redraw ();
|
||||||
|
|
||||||
|
/* Message loop. */
|
||||||
struct pollfd pollfd[2];
|
struct pollfd pollfd[2];
|
||||||
|
|
||||||
pollfd[0].fd = fileno (stdin);
|
pollfd[0].fd = fileno (stdin);
|
||||||
|
@ -257,13 +416,12 @@ main (int argc, char *argv[])
|
||||||
&& !process_stdin_input ())
|
&& !process_stdin_input ())
|
||||||
break;
|
break;
|
||||||
if ((pollfd[1].revents & POLLIN)
|
if ((pollfd[1].revents & POLLIN)
|
||||||
&& !process_winch_input (pollfd[2].fd))
|
&& !process_winch_input (pollfd[1].fd))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Cleanup -----------------------------------------------------------------
|
|
||||||
|
|
||||||
endwin ();
|
endwin ();
|
||||||
|
app_destroy ();
|
||||||
|
|
||||||
if (close (g_winch_pipe[0]) == -1
|
if (close (g_winch_pipe[0]) == -1
|
||||||
|| close (g_winch_pipe[1]) == -1)
|
|| close (g_winch_pipe[1]) == -1)
|
||||||
|
|
Loading…
Reference in New Issue