From 6db153b1e2713152d72eebd2b505f7e3b51a6895 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sun, 12 May 2013 01:27:25 +0200
Subject: Start working on sdtui; implement line editing
---
src/sdtui.c | 266 ++++++++++++++++++++++++++++++++++++++++++++++++------------
1 file changed, 212 insertions(+), 54 deletions(-)
(limited to 'src')
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 */
-// --- Utilities ---------------------------------------------------------------
-
-static void
-display (const gchar *format, ...)
-{
- va_list ap;
+#define _(x) x /**< Fake gettext, for now. */
- va_start (ap, format);
- vw_printw (stdscr, format, ap);
- va_end (ap);
- refresh ();
-}
+// --- Utilities ---------------------------------------------------------------
+/* 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 -------------------------------------------------------------
+
+StardictDict *g_dict; //!< The current dictionary
-typedef struct
+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)
--
cgit v1.2.3-70-g09d2