mirror of
https://github.com/rkd77/elinks.git
synced 2024-12-04 14:46:47 -05:00
UTF-8: Fix scrolling of input fields.
form_state.state_cell is no longer used for FC_TEXT, FC_PASSWORD, nor FC_FILE. Instead, get_link_cursor_offset() computes the cell with utf8_ptr2chars (a new function) or utf8_ptr2cells. This shouldn't slow down ELinks too much, as it's done only for the selected link and only once per redraw. The left side of a scrolled input field is always aligned at a character boundary. The right side might not be.
This commit is contained in:
parent
92845d0b56
commit
8d77387f6f
@ -342,7 +342,8 @@ draw_form_entry(struct terminal *term, struct document_view *doc_view,
|
||||
switch (fc->type) {
|
||||
unsigned char *s;
|
||||
#ifdef CONFIG_UTF_8
|
||||
unsigned char *text, *end;
|
||||
unsigned char *text, *end, *last_in_view;
|
||||
int retried;
|
||||
#endif /* CONFIG_UTF_8 */
|
||||
int len;
|
||||
int i, x, y;
|
||||
@ -379,38 +380,118 @@ draw_form_entry(struct terminal *term, struct document_view *doc_view,
|
||||
break;
|
||||
#ifdef CONFIG_UTF_8
|
||||
utf_8:
|
||||
end = NULL; /* Shut up the compiler. */
|
||||
int_bounds(&fs->vpos, fs->state_cell - fc->size + 1, fs->state_cell);
|
||||
if (fc->type == FC_PASSWORD)
|
||||
len = utf8_ptr2chars(fs->value + fs->vpos, NULL);
|
||||
else
|
||||
len = utf8_ptr2cells(fs->value + fs->vpos, NULL);
|
||||
retried = 0;
|
||||
|
||||
retry_after_scroll:
|
||||
text = fs->value;
|
||||
end = strchr(text, '\0');
|
||||
if (!text) text = "";
|
||||
len = strlen(text);
|
||||
int_bounds(&fs->state, 0, len);
|
||||
int_bounds(&fs->vpos, 0, fs->state);
|
||||
end = text + len;
|
||||
text += fs->vpos;
|
||||
last_in_view = NULL;
|
||||
|
||||
|
||||
for (i = 0; i < fc->size; i++, x++) {
|
||||
for (i = 0; i < fc->size; ) {
|
||||
unicode_val_T data;
|
||||
int cells, cell;
|
||||
unsigned char *maybe_in_view = text;
|
||||
|
||||
if (!col_is_in_box(box, x)) continue;
|
||||
|
||||
if (fs->value && i >= -fs->vpos && i < len) {
|
||||
if (fc->type != FC_PASSWORD)
|
||||
data = utf_8_to_unicode(&text, end);
|
||||
else
|
||||
data = '*';
|
||||
} else
|
||||
data = utf_8_to_unicode(&text, end);
|
||||
if (data == UCS_NO_CHAR) /* end of string */
|
||||
data = '_';
|
||||
else if (fc->type == FC_PASSWORD)
|
||||
data = '*';
|
||||
|
||||
if (unicode_to_cell(data) == 2) {
|
||||
if (i + 1 < fc->size) {
|
||||
draw_char_data(term, x++, y, data);
|
||||
data = UCS_NO_CHAR;
|
||||
i++;
|
||||
} else
|
||||
data = ' ';
|
||||
cells = unicode_to_cell(data);
|
||||
if (i + cells <= fc->size) {
|
||||
last_in_view = maybe_in_view;
|
||||
if (colspan_is_in_box(box, x + i, cells)) {
|
||||
/* The character fits completely.
|
||||
* Draw the character, and mark any
|
||||
* further cells with UCS_NO_CHAR. */
|
||||
draw_char_data(term, x + i, y, data);
|
||||
for (cell = 1; cell < cells; cell++)
|
||||
draw_char_data(term, x + i + cell,
|
||||
y, UCS_NO_CHAR);
|
||||
goto drew_char;
|
||||
}
|
||||
}
|
||||
|
||||
/* The character does not fit completely.
|
||||
* Write spaces to the cells that do fit. */
|
||||
for (cell = 0; cell < cells; cell++) {
|
||||
if (col_is_in_box(box, x + i + cell)
|
||||
&& i + cell < fc->size)
|
||||
draw_char_data(term,
|
||||
x + i + cell,
|
||||
y, ' ');
|
||||
}
|
||||
|
||||
drew_char:
|
||||
i += cells;
|
||||
}
|
||||
|
||||
/* The int_bounds calls above ensured that the
|
||||
* insertion point cannot be at the left side
|
||||
* of the scrolled-visible part of the text.
|
||||
* However it can still be at the right side.
|
||||
* Check whether we need to change fs->vpos.
|
||||
*
|
||||
* This implementation attempts to follow
|
||||
* these rules:
|
||||
* - If the insertion point is at the end of
|
||||
* the string, leave at least one empty cell
|
||||
* so that there is a place for the cursor.
|
||||
* - If a character follows the insertion
|
||||
* point, make that character fully visible;
|
||||
* note the character may be double-width.
|
||||
* - If fc->size < 2, it is not possible to
|
||||
* make a double-width character fully
|
||||
* visible. In this case, it is OK if the
|
||||
* output is ugly, but ELinks must not fall
|
||||
* into an infinite loop or crash.
|
||||
* - The length of the string should not affect
|
||||
* how long this function takes. The width
|
||||
* of the widget naturally will.
|
||||
* - Optimize the case where fields are drawn
|
||||
* several times without being modified.
|
||||
*
|
||||
* It follows that:
|
||||
* - If the "for i" loop above hit UCS_NO_CHAR,
|
||||
* then there is no need to scroll.
|
||||
* - When the string ends with a double-width
|
||||
* character that fits in only partially,
|
||||
* then text==end, but the field may have
|
||||
* to be scrolled. */
|
||||
if (fs->value && last_in_view
|
||||
&& last_in_view < fs->value + fs->state) {
|
||||
unsigned char *ptr = fs->value + fs->state;
|
||||
int cells = fc->size;
|
||||
enum utf8_step how = (fc->type == FC_PASSWORD)
|
||||
? utf8_step_characters
|
||||
: utf8_step_cells_fewer;
|
||||
|
||||
/* The insertion point is at the right
|
||||
* side of the scrolled-visible part
|
||||
* of the text. Decide a new fs->vpos
|
||||
* by counting cells backwards from
|
||||
* @ptr. But first advance @ptr past
|
||||
* the character that follows the
|
||||
* insertion point, so that it will be
|
||||
* fully displayed. If there is no
|
||||
* such character, reserve one cell
|
||||
* for the cursor anyway. */
|
||||
if (utf_8_to_unicode(&ptr, end) == UCS_NO_CHAR)
|
||||
--cells;
|
||||
ptr = utf8_step_backward(ptr, fs->value,
|
||||
cells, how, NULL);
|
||||
|
||||
if (fs->vpos != ptr - fs->value) {
|
||||
fs->vpos = ptr - fs->value;
|
||||
retried = 1;
|
||||
goto retry_after_scroll;
|
||||
}
|
||||
draw_char_data(term, x, y, data);
|
||||
}
|
||||
break;
|
||||
#endif /* CONFIG_UTF_8 */
|
||||
|
@ -52,16 +52,14 @@ struct form_state {
|
||||
* in @form_control.labels. */
|
||||
int state;
|
||||
#ifdef CONFIG_UTF_8
|
||||
/* For FC_TEXT, FC_PASSWORD, and FC_FILE, @state_cell is the
|
||||
* number of cells needed for the text before the insertion
|
||||
* point. (For FC_PASSWORD, each character is one cell.)
|
||||
* When CONFIG_UTF_8 is not defined, @state_cell is assumed
|
||||
* to be the same as @state. */
|
||||
/* For FC_TEXT, FC_PASSWORD, and FC_FILE, @state_cell is not
|
||||
* used. There is still code that updates it; such code
|
||||
* should be removed. */
|
||||
int state_cell;
|
||||
#endif /* CONFIG_UTF_8 */
|
||||
/* For FC_TEXT, FC_PASSWORD, and FC_FILE, @vpos is the number
|
||||
* of cells that scrolling has hidden at the left. Thus, the
|
||||
* X offset of the cursor is @state_cell - @vpos. */
|
||||
/* For FC_TEXT, FC_PASSWORD, and FC_FILE, @vpos is the index
|
||||
* of the first displayed byte in @value. It should never be
|
||||
* in the middle of a character. */
|
||||
int vpos;
|
||||
int vypos;
|
||||
|
||||
|
@ -128,11 +128,17 @@ get_link_cursor_offset(struct document_view *doc_view, struct link *link)
|
||||
case LINK_FIELD:
|
||||
fc = get_link_form_control(link);
|
||||
fs = find_form_state(doc_view, fc);
|
||||
if (!fs)
|
||||
if (!fs || !fs->value)
|
||||
return 0;
|
||||
#ifdef CONFIG_UTF_8
|
||||
else if (utf8) {
|
||||
return fs->state_cell - fs->vpos;
|
||||
unsigned char *scroll = fs->value + fs->vpos;
|
||||
unsigned char *point = fs->value + fs->state;
|
||||
|
||||
if (fs->type == FC_PASSWORD)
|
||||
return utf8_ptr2chars(scroll, point);
|
||||
else
|
||||
return utf8_ptr2cells(scroll, point);
|
||||
}
|
||||
#endif /* CONFIG_UTF_8 */
|
||||
else
|
||||
|
Loading…
Reference in New Issue
Block a user