Start working on sdtui; implement line editing

This commit is contained in:
Přemysl Eric Janouch 2013-05-12 01:27:25 +02:00
parent 8ab74e16d7
commit 6db153b1e2
1 changed files with 212 additions and 54 deletions

View File

@ -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)