sdtui: handle input field overflows reasonably

This commit is contained in:
Přemysl Eric Janouch 2021-11-03 18:14:36 +01:00
parent 181df7fbae
commit 0d0ac40f96
Signed by: p
GPG Key ID: A0420B94F92B9493
2 changed files with 127 additions and 32 deletions

View File

@ -87,11 +87,12 @@ https://mega.co.nz/#!axtD0QRK!sbtBgizksyfkPqKvKEgr8GQ11rsWhtqyRgUUV0B7pwg[CZ <--
Further Development Further Development
------------------- -------------------
While I've been successfully using 'sdtui' for many years now, some work has to While I've been successfully using 'sdtui' for many years now, some issues
be done yet before the software can be considered fit for inclusion in regular should be addressed before including the software in regular Linux and/or
Linux and/or BSD distributions: BSD distributions:
- The tab bar and the text input field don't handle overflows well in the TUI. - The tab bar doesn't handle overflows well in the TUI.
- The GUI doesn't support text selection, and is awkward to configure.
- Lacking configuration, standard StarDict locations should be scanned. - Lacking configuration, standard StarDict locations should be scanned.
Given all issues with the file format, it might be better to start anew. Given all issues with the file format, it might be better to start anew.

View File

@ -167,6 +167,7 @@ struct application
gchar * search_label; ///< Text of the "Search" label gchar * search_label; ///< Text of the "Search" label
GArray * input; ///< The current search input GArray * input; ///< The current search input
guint input_pos; ///< Cursor position within input guint input_pos; ///< Cursor position within input
guint input_offset; ///< Render offset in codepoints
gboolean input_confirmed; ///< Input has been confirmed gboolean input_confirmed; ///< Input has been confirmed
gfloat division; ///< Position of the division column gfloat division; ///< Position of the division column
@ -533,7 +534,7 @@ app_init (Application *self, char **filenames)
self->search_label = g_strdup_printf ("%s: ", _("Search")); self->search_label = g_strdup_printf ("%s: ", _("Search"));
self->input = g_array_new (TRUE, FALSE, sizeof (gunichar)); self->input = g_array_new (TRUE, FALSE, sizeof (gunichar));
self->input_pos = 0; self->input_pos = self->input_offset = 0;
self->input_confirmed = FALSE; self->input_confirmed = FALSE;
self->division = 0.5; self->division = 0.5;
@ -795,6 +796,20 @@ row_buffer_ellipsis (RowBuffer *self, int target, chtype attrs)
} }
} }
static void
row_buffer_align (RowBuffer *self, int target, chtype attrs)
{
if (target >= 0 && self->total_width > target)
row_buffer_ellipsis (self, target, attrs);
while (self->total_width < target)
{
struct row_char rc = { ' ', attrs, 1 };
g_array_append_val (self->chars, rc);
self->total_width += rc.width;
}
}
static void static void
row_buffer_print (RowBuffer *self, gunichar *ucs4, size_t len, chtype attrs) row_buffer_print (RowBuffer *self, gunichar *ucs4, size_t len, chtype attrs)
{ {
@ -837,21 +852,82 @@ row_buffer_flush (RowBuffer *self)
static void static void
row_buffer_finish (RowBuffer *self, int width, chtype attrs) row_buffer_finish (RowBuffer *self, int width, chtype attrs)
{ {
if (width >= 0 && self->total_width > width) row_buffer_align (self, width, attrs);
row_buffer_ellipsis (self, width, attrs);
while (self->total_width < width)
{
struct row_char rc = { ' ', attrs, 1 };
g_array_append_val (self->chars, rc);
self->total_width += rc.width;
}
row_buffer_flush (self); row_buffer_flush (self);
row_buffer_free (self); row_buffer_free (self);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static gint
app_input_width (Application *self, guint begin, guint end)
{
gint width = 0;
for (guint i = begin; i < end; i++)
width += app_char_width (self,
g_array_index (self->input, gunichar, i));
return width;
}
static guint
app_scroll_back_input (Application *self, guint from, gint target)
{
guint last_spacing = from;
while (from--)
{
gint width = app_input_width (self, from, from + 1);
if (target < width)
break;
if (width)
{
last_spacing = from;
target -= width;
}
}
return last_spacing;
}
static guint
app_adjust_input_offset (Application *self, gint space)
{
gint to_cursor =
app_input_width (self, 0, self->input_pos);
gint at_cursor =
app_input_width (self, self->input_pos, self->input_pos + 1);
gint past_cursor =
app_input_width (self, self->input_pos + 1, self->input->len);
// 1. If everything fits, no scrolling is desired, and no arrows present
if (to_cursor + at_cursor + past_cursor <= space)
return 0;
// TODO: try to prevent 2. and 3. from fighting with each other
// 2. If everything up to and including the cursor, plus right arrow fits,
// start at the beginning
if (to_cursor + at_cursor + 1 /* right arrow */ <= space)
return 0;
// 3. If everything from the cursor to the right fits, fill the line,
// but keep one extra space for a trailing caret
gint reserved = self->input_pos != self->input->len;
gint from_cursor_with_trailing_caret = at_cursor + past_cursor + reserved;
if (1 /* left arrow */ + from_cursor_with_trailing_caret <= space)
return app_scroll_back_input (self, self->input->len, space - 1 - 1);
// At this point, we know there will be arrows on both sides
space -= 2;
// 4. If the cursor has moved too much to either side, follow it
if (self->input_pos < self->input_offset
|| app_input_width (self, self->input_offset, self->input_pos + 1) > space)
return app_scroll_back_input (self, self->input_pos, space / 2);
// 5. Otherwise, don't fiddle with the offset at all, it's not necessary
return self->input_offset;
}
/// Render the top bar. /// Render the top bar.
static void static void
app_redraw_top (Application *self) app_redraw_top (Application *self)
@ -871,26 +947,37 @@ app_redraw_top (Application *self)
buf = row_buffer_make (self); buf = row_buffer_make (self);
row_buffer_append (&buf, self->search_label, APP_ATTR (SEARCH)); row_buffer_append (&buf, self->search_label, APP_ATTR (SEARCH));
gsize indent = buf.total_width; gint indent = buf.total_width;
int word_attrs = APP_ATTR (SEARCH); int word_attrs = APP_ATTR (SEARCH);
if (self->input_confirmed) if (self->input_confirmed)
word_attrs |= A_BOLD; word_attrs |= A_BOLD;
gchar *input_utf8 = g_ucs4_to_utf8 self->input_offset = app_adjust_input_offset (self, COLS - indent);
((gunichar *) self->input->data, -1, NULL, NULL, NULL); if (self->input_offset)
{
row_buffer_append (&buf, "<", word_attrs ^ A_BOLD);
indent++;
}
gchar *input_utf8 = g_ucs4_to_utf8 ((gunichar *) self->input->data
+ self->input_offset, -1, NULL, NULL, NULL);
g_return_if_fail (input_utf8 != NULL); g_return_if_fail (input_utf8 != NULL);
row_buffer_append (&buf, input_utf8, word_attrs); row_buffer_append (&buf, input_utf8, word_attrs);
g_free (input_utf8); g_free (input_utf8);
gint overflow = buf.total_width - COLS;
if (overflow > 0)
{
row_buffer_pop_cells (&buf, overflow + 1 /* right arrow */);
row_buffer_align (&buf, COLS - 1 /* right arrow */, APP_ATTR (SEARCH));
row_buffer_append (&buf, ">", word_attrs ^ A_BOLD);
}
row_buffer_finish (&buf, COLS, APP_ATTR (SEARCH)); row_buffer_finish (&buf, COLS, APP_ATTR (SEARCH));
gint offset = app_input_width (self, self->input_offset, self->input_pos);
guint offset, i; move (1, MIN (indent + offset, COLS - 1));
for (offset = i = 0; i < self->input_pos; i++)
offset += app_char_width (self,
g_array_index (self->input, gunichar, i));
move (1, MIN ((gint) (indent + offset), COLS - 1));
refresh (); refresh ();
} }
@ -1270,7 +1357,7 @@ app_set_input (Application *self, const gchar *text, gsize text_len)
g_array_free (self->input, TRUE); g_array_free (self->input, TRUE);
self->input = g_array_new (TRUE, FALSE, sizeof (gunichar)); self->input = g_array_new (TRUE, FALSE, sizeof (gunichar));
self->input_pos = 0; self->input_pos = self->input_offset = 0;
gunichar *p = output; gunichar *p = output;
gboolean last_was_space = false; gboolean last_was_space = false;
@ -1782,7 +1869,7 @@ app_process_key (Application *self, termo_key_t *event)
{ {
if (self->input->len != 0) if (self->input->len != 0)
g_array_remove_range (self->input, 0, self->input->len); g_array_remove_range (self->input, 0, self->input->len);
self->input_pos = 0; self->input_pos = self->input_offset = 0;
self->input_confirmed = FALSE; self->input_confirmed = FALSE;
} }
@ -1815,16 +1902,23 @@ app_process_left_mouse_click (Application *self, int line, int column)
else if (line == 1) else if (line == 1)
{ {
// FIXME: this is only an approximation // FIXME: this is only an approximation
gsize label_len = g_utf8_strlen (self->search_label, -1); glong label_width = g_utf8_strlen (self->search_label, -1);
gint pos = column - label_len;
gint pos = column - label_width;
if (pos >= 0) if (pos >= 0)
{ {
guint offset, i; // On clicking the left arrow, go to that invisible character
for (offset = i = 0; i < self->input->len; i++) // behind the arrow (skiping over non-spacing suffixes)
guint i = self->input_offset;
if (i && !pos--)
{ {
size_t width = app_char_width while (i-- && !app_input_width (self, i, i + 1))
(self, g_array_index (self->input, gunichar, i)); ;
if ((offset += width) > (guint) pos) }
for (gint occupied = 0; i < self->input->len; i++)
{
size_t width = app_input_width (self, i, i + 1);
if ((occupied += width) > pos)
break; break;
} }