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_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)
|
||||
|
Loading…
Reference in New Issue
Block a user