diff options
author | Adrian Lees <adrian@aemulor.com> | 2005-07-20 23:27:28 +0000 |
---|---|---|
committer | Adrian Lees <adrian@aemulor.com> | 2005-07-20 23:27:28 +0000 |
commit | 1a1901d19b07ea265840962877b34b1205f6b092 (patch) | |
tree | f839d3b9687660d1b26556ba1944496aff10d8f5 /desktop/textinput.c | |
parent | 5e148741154019d69338c0f8781ed8a084cdd53d (diff) | |
download | netsurf-1a1901d19b07ea265840962877b34b1205f6b092.tar.gz netsurf-1a1901d19b07ea265840962877b34b1205f6b092.tar.bz2 |
[project @ 2005-07-20 23:27:27 by adrianl]
2D scrolling of text areas/frames; First cut at selection in textareas; Further text editing actions (Word left/right; Page up/down; Cut block; Delete line start/end)
svn path=/import/netsurf/; revision=1812
Diffstat (limited to 'desktop/textinput.c')
-rw-r--r-- | desktop/textinput.c | 531 |
1 files changed, 435 insertions, 96 deletions
diff --git a/desktop/textinput.c b/desktop/textinput.c index e4017b435..9b8b94929 100644 --- a/desktop/textinput.c +++ b/desktop/textinput.c @@ -14,6 +14,7 @@ */ #include <assert.h> +#include <ctype.h> #include <string.h> #include "netsurf/desktop/browser.h" @@ -48,14 +49,22 @@ static void input_update_display(struct browser_window *bw, struct box *input, bool redraw); static bool textbox_insert(struct browser_window *bw, struct box *text_box, unsigned char_offset, const char *utf8, unsigned utf8_len); -static bool textbox_delete(struct browser_window *bw, struct box *text_box, - unsigned char_offset, unsigned utf8_len); +static bool textbox_delete(struct box *text_box, unsigned char_offset, + unsigned utf8_len); static struct box *textarea_insert_break(struct browser_window *bw, struct box *text_box, size_t char_offset); -static bool delete_handler(struct box *b, int offset, size_t length, - void *handle); +static bool delete_handler(struct box *b, int offset, size_t length); +static struct box *line_start(struct box *text_box); +static struct box *line_end(struct box *text_box); +static struct box *line_above(struct box *text_box); +static struct box *line_below(struct box *text_box); +static bool textarea_cut(struct browser_window *bw, + struct box *start_box, unsigned start_idx, + struct box *end_box, unsigned end_idx); static void textarea_reflow(struct browser_window *bw, struct box *textarea, struct box *inline_container); +static bool word_left(const char *text, int *poffset, int *pchars); +static bool word_right(const char *text, int len, int *poffset, int *pchars); /** @@ -249,7 +258,7 @@ void browser_window_textarea_callback(struct browser_window *bw, int prev_offset = char_offset; char_offset = utf8_prev(text_box->text, char_offset); - textbox_delete(bw, text_box, char_offset, prev_offset - char_offset); + textbox_delete(text_box, char_offset, prev_offset - char_offset); } reflow = true; break; @@ -287,8 +296,7 @@ void browser_window_textarea_callback(struct browser_window *bw, /* delete a character */ int next_offset = utf8_next(text_box->text, text_box->length, char_offset); - textbox_delete(bw, text_box, char_offset, - next_offset - char_offset); + textbox_delete(text_box, char_offset, next_offset - char_offset); } reflow = true; break; @@ -306,48 +314,39 @@ void browser_window_textarea_callback(struct browser_window *bw, break; case 21: { /* Ctrl + U */ - struct box *next; - - /* run back to start of line */ - while (text_box->prev && text_box->prev->type == BOX_TEXT) - text_box = text_box->prev; - - /* run forwards to end */ - next = text_box->next; - while (next && next->type == BOX_TEXT) { - box_unlink_and_free(text_box); - text_box = next; - next = text_box->next; - } - - /* can we take out this last inline and and the following BR? */ - if (next && next->type == BOX_BR && next->next) { - box_unlink_and_free(text_box); + struct box *start_box = line_start(text_box); + struct box *end_box = line_end(text_box); - /* place caret at start of next box */ - text_box = next->next; - box_unlink_and_free(next); - } - else - textbox_delete(bw, text_box, 0, text_box->length); + textarea_cut(bw, start_box, 0, end_box, end_box->length); - char_offset = 0; - reflow = true; - } - break; + text_box = start_box; + char_offset = 0; + reflow = true; + } + break; case 22: /* Ctrl + V */ gui_paste_from_clipboard(bw->window, box_x + inline_container->x + text_box->x + pixel_offset, box_y + inline_container->y + text_box->y); - break; - case 24: /* Ctrl + X */ - if (gui_copy_to_clipboard(bw->sel)) { - selection_traverse(bw->sel, delete_handler, bw); + /* screen updated and caret repositioned already */ + return; + + case 24: { /* Ctrl + X */ + int start_idx, end_idx; + struct box *start_box = selection_get_start(bw->sel, &start_idx); + struct box *end_box = selection_get_end(bw->sel, &end_idx); + if (start_box && end_box) { + selection_clear(bw->sel, false); + textarea_cut(bw, start_box, start_idx, end_box, end_idx); + + text_box = start_box; + char_offset = start_idx; reflow = true; } - break; + } + break; case KEY_RIGHT: if ((unsigned int) char_offset < text_box->length) { @@ -398,14 +397,12 @@ void browser_window_textarea_callback(struct browser_window *bw, return; case KEY_LINE_START: - while (text_box->prev && text_box->prev->type == BOX_TEXT) - text_box = text_box->prev; + text_box = line_start(text_box); char_offset = 0; break; case KEY_LINE_END: - while (text_box->next && text_box->next->type == BOX_TEXT) - text_box = text_box->next; + text_box = line_end(text_box); char_offset = text_box->length; break; @@ -425,41 +422,101 @@ void browser_window_textarea_callback(struct browser_window *bw, char_offset = text_box->length; break; - case KEY_WORD_LEFT: -// while () { -// } - break; + case KEY_WORD_LEFT: { + bool start_of_word = (char_offset <= 0 || + isspace(text_box->text[char_offset - 1])); - case KEY_WORD_RIGHT: -// while () { -// } - break; + while (!word_left(text_box->text, &char_offset, NULL)) { + struct box *prev = NULL; + + assert(char_offset == 0); + + if (start_of_word) { + /* find the preceding non-BR box */ + prev = text_box->prev; + while (prev && prev->type == BOX_BR) + prev = prev->prev; + } + + if (!prev) { + /* just stay at the start of this box */ + break; + } + + text_box = prev; + char_offset = prev->length; + } + } + break; + + case KEY_WORD_RIGHT: { + bool in_word = (char_offset < text_box->length && + !isspace(text_box->text[char_offset])); + + while (!word_right(text_box->text, text_box->length, + &char_offset, NULL)) { + struct box *next = text_box->next; + + /* find the next non-BR box */ + while (next && next->type == BOX_BR) + next = next->next; + + if (!next) { + /* just stay at the end of this box */ + char_offset = text_box->length; + break; + } + + text_box = next; + char_offset = 0; + + if (in_word && + text_box->length > 0 && + !isspace(text_box->text[0])) { + /* just stay at the start of this box */ + break; + } + } + } + break; + + case KEY_PAGE_UP: { + int nlines = (textarea->height / text_box->height) - 1; + + while (nlines-- > 0) + text_box = line_above(text_box); - case KEY_PAGE_UP: -// while () { -// } if (char_offset > text_box->length) char_offset = text_box->length; - break; + } + break; + + case KEY_PAGE_DOWN: { + int nlines = (textarea->height / text_box->height) - 1; + while (nlines-- > 0) + text_box = line_below(text_box); - case KEY_PAGE_DOWN: + /* vague attempt to keep the caret at the same horizontal position, + given that the code currently cannot support it being beyond the + end of a line */ -// while () { -// } if (char_offset > text_box->length) char_offset = text_box->length; - break; + } + break; case KEY_DELETE_LINE_START: - textbox_delete(bw, text_box, 0, char_offset); + textarea_cut(bw, line_start(text_box), 0, text_box, char_offset); + char_offset = 0; reflow = true; break; - case KEY_DELETE_LINE_END: - textbox_delete(bw, text_box, char_offset, - text_box->length - char_offset); + case KEY_DELETE_LINE_END: { + struct box *end_box = line_end(text_box); + textarea_cut(bw, text_box, char_offset, end_box, end_box->length); reflow = true; - break; + } + break; default: return; @@ -673,17 +730,13 @@ void browser_window_input_callback(struct browser_window *bw, return; box_offset += utf8_len; - - nsfont_width(text_box->style, text_box->text, - text_box->length, &text_box->width); changed = true; } else switch (key) { case KEY_DELETE_LEFT: { int prev_offset; - if (box_offset == 0) - return; + if (box_offset <= 0) return; /* Gadget */ prev_offset = form_offset; @@ -702,12 +755,8 @@ void browser_window_input_callback(struct browser_window *bw, /* Go to the previous valid UTF-8 character */ box_offset = utf8_prev(text_box->text, box_offset); - textbox_delete(bw, text_box, box_offset, - prev_offset - box_offset); - - nsfont_width(text_box->style, text_box->text, - text_box->length, &text_box->width); - + textbox_delete(text_box, box_offset, + prev_offset - box_offset); changed = true; } break; @@ -735,12 +784,8 @@ void browser_window_input_callback(struct browser_window *bw, next_offset = utf8_next(text_box->text, text_box->length, box_offset); - textbox_delete(bw, text_box, box_offset, + textbox_delete(text_box, box_offset, next_offset - box_offset); - - nsfont_width(text_box->style, text_box->text, - text_box->length, &text_box->width); - changed = true; } break; @@ -797,8 +842,6 @@ void browser_window_input_callback(struct browser_window *bw, input->gadget->value[0] = 0; input->gadget->length = 0; form_offset = 0; - - text_box->width = 0; changed = true; break; @@ -806,7 +849,10 @@ void browser_window_input_callback(struct browser_window *bw, gui_paste_from_clipboard(bw->window, box_x + input->children->x + text_box->x + pixel_offset, box_y + input->children->y + text_box->y); - break; + + /* screen updated and caret repositioned already */ + return; + case KEY_RIGHT: /* Text box */ @@ -837,6 +883,67 @@ void browser_window_input_callback(struct browser_window *bw, form_offset = input->gadget->length; break; + case KEY_WORD_LEFT: { + int nchars; + /* Text box */ + if (word_left(input->gadget->value, &form_offset, &nchars)) { + /* Gadget */ + while (box_offset > 0 && nchars-- > 0) + box_offset = utf8_prev(text_box->text, box_offset); + } else { + box_offset = 0; + form_offset = 0; + } + } + break; + + case KEY_WORD_RIGHT: { + int nchars; + /* Text box */ + if (word_right(input->gadget->value, input->gadget->length, + &form_offset, &nchars)) { + /* Gadget */ + const char *text = text_box->text; + unsigned len = text_box->length; + while (box_offset < len && nchars-- > 0) + box_offset = utf8_next(text, len, box_offset); + } else { + box_offset = text_box->length; + form_offset = input->gadget->length; + } + } + break; + + case KEY_DELETE_LINE_START: + + if (box_offset <= 0) return; + + /* Text box */ + textbox_delete(text_box, 0, box_offset); + box_offset = 0; + + /* Gadget */ + memmove(input->gadget->value, + input->gadget->value + form_offset, + (input->gadget->length - form_offset) + 1); /* inc NUL */ + input->gadget->length -= form_offset; + form_offset = 0; + changed = true; + break; + + case KEY_DELETE_LINE_END: + + if (box_offset >= text_box->length) + return; + + /* Text box */ + textbox_delete(text_box, box_offset, text_box->length - box_offset); + /* Gadget */ + input->gadget->length = form_offset; + input->gadget->value[form_offset] = 0; + changed = true; + break; + default: return; } @@ -1133,13 +1240,8 @@ bool browser_window_input_paste_text(struct browser_window *bw, utf8 = p; } - if (update) { - - nsfont_width(text_box->style, text_box->text, text_box->length, - &text_box->width); - + if (update) input_update_display(bw, input, form_offset, box_offset, false, true); - } return success; } @@ -1165,6 +1267,10 @@ void input_update_display(struct browser_window *bw, struct box *input, int box_x, box_y; int dx; + if (redraw) + nsfont_width(text_box->style, text_box->text, text_box->length, + &text_box->width); + box_coords(input, &box_x, &box_y); nsfont_width(text_box->style, text_box->text, box_offset, @@ -1172,7 +1278,7 @@ void input_update_display(struct browser_window *bw, struct box *input, dx = text_box->x; text_box->x = 0; if (input->width < text_box->width && - input->width / 2 < pixel_offset) { + input->width / 2 < (int)pixel_offset) { text_box->x = input->width / 2 - pixel_offset; if (text_box->x < input->width - text_box->width) text_box->x = input->width - text_box->width; @@ -1245,14 +1351,12 @@ bool textbox_insert(struct browser_window *bw, struct box *text_box, /** * Delete a number of chars from a text box * - * \param bw browser window * \param text_box text box * \param char_offset offset within text box (bytes) of first char to delete * \param utf8_len length (bytes) of chars to be deleted */ -bool textbox_delete(struct browser_window *bw, struct box *text_box, - unsigned char_offset, unsigned utf8_len) +bool textbox_delete(struct box *text_box, unsigned char_offset, unsigned utf8_len) { unsigned prev_offset = char_offset + utf8_len; if (prev_offset <= text_box->length) { @@ -1271,10 +1375,89 @@ bool textbox_delete(struct browser_window *bw, struct box *text_box, } -bool delete_handler(struct box *b, int offset, size_t length, void *handle) +/** + * Delete some text from a box, or delete the box in its entirety + * + * \param b box + * \param offset start offset of text to be deleted (in bytes) + * \param length length of text to be deleted + * \return true iff successful + */ + +bool delete_handler(struct box *b, int offset, size_t length) +{ + if (offset <= 0 && length >= b->length) { + /* remove the entire box */ + box_unlink_and_free(b); + return true; + } + else { + return textbox_delete(b, offset, length); + } +} + + +/** + * Locate the first inline box at the start of this line + * + * \param text_box text box from which to start searching + */ + +struct box *line_start(struct box *text_box) +{ + while (text_box->prev && text_box->prev->type == BOX_TEXT) + text_box = text_box->prev; + return text_box; +} + + +/** + * Locate the last inline box in this line + * + * \param text_box text box from which to start searching + */ + +struct box *line_end(struct box *text_box) { - struct browser_window *bw = handle; - return textbox_delete(bw, b, offset, length); + while (text_box->next && text_box->next->type == BOX_TEXT) + text_box = text_box->next; + return text_box; +} + + +/** + * Backtrack to the start of the previous line, if there is one. + */ + +struct box *line_above(struct box *text_box) +{ + struct box *prev; + + text_box = line_start(text_box); + + prev = text_box->prev; + while (prev && prev->type == BOX_BR) + prev = prev->prev; + + return prev ? line_start(prev) : text_box; +} + + +/** + * Advance to the start of the next line, if there is one. + */ + +struct box *line_below(struct box *text_box) +{ + struct box *next; + + text_box = line_end(text_box); + + next = text_box->next; + while (next && next->type == BOX_BR) + next = next->next; + + return next ? next : text_box; } @@ -1323,6 +1506,86 @@ struct box *textarea_insert_break(struct browser_window *bw, struct box *text_bo /** + * Cut a range of text to the global clipboard. + * + * \param bw browser window + * \param start_box text box at start of range + * \param start_idx index (bytes) within start box + * \param end_box text box at end of range + * \param end_idx index (bytes) within end box + */ + +bool textarea_cut(struct browser_window *bw, + struct box *start_box, unsigned start_idx, + struct box *end_box, unsigned end_idx) +{ + struct box *box = start_box; + bool success = true; + bool del = true; + + if (!gui_empty_clipboard()) + return false; + + if (!start_idx && (!start_box->prev || start_box->prev->type == BOX_BR)) { + /* deletion would leave two adjacent BRs, so just collapse + the start box to an empty TEXT rather than deleting it */ + del = false; + } + + while (box && box != end_box) { + /* read before deletion, in case the whole box goes */ + struct box *next = box->next; + + if (box->type == BOX_BR) { + if (!gui_add_to_clipboard("\n", 1, false)) { + gui_commit_clipboard(); + return false; + } + box_unlink_and_free(box); + } + else { + /* append box text to clipboard and then delete it */ + if (!gui_add_to_clipboard(box->text + start_idx, + box->length - start_idx, box->space)) { + gui_commit_clipboard(); + return false; + } + + if (del) { + if (!delete_handler(box, start_idx, + box->length - start_idx)) { + gui_commit_clipboard(); + return false; + } + } + else + textbox_delete(box, start_idx, box->length - start_idx); + } + + del = true; + start_idx = 0; + box = next; + } + + /* and the last box */ + if (box) { + if (gui_add_to_clipboard(box->text + start_idx, end_idx, box->space)) { + if (del) { + if (!delete_handler(box, start_idx, end_idx - start_idx)) + success = false; + } + else + textbox_delete(box, start_idx, end_idx - start_idx); + } + else + success = false; + } + + return gui_commit_clipboard() ? success : false; +} + + +/** * Reflow textarea preserving width and height * * \param bw browser window @@ -1344,3 +1607,79 @@ void textarea_reflow(struct browser_window *bw, struct box *textarea, layout_calculate_descendant_bboxes(textarea); } + +/** + * Move to the start of the word containing the given character position, + * or the start of the preceding word if already at the start of this one. + * + * \param text UTF-8 text string + * \param poffset offset of caret within string (updated on exit) + * \param pchars receives the number of characters skipped + * \return true iff the start of a word was found before/at the string start + */ + +bool word_left(const char *text, int *poffset, int *pchars) +{ + int offset = *poffset; + bool success = false; + int nchars = 0; + + while (offset > 0) { + offset = utf8_prev(text, offset); + nchars++; + if (!isspace(text[offset])) break; + } + + while (offset > 0) { + int prev = utf8_prev(text, offset); + success = true; + if (isspace(text[prev])) + break; + offset = prev; + nchars++; + } + + *poffset = offset; + if (pchars) *pchars = nchars; + + return success; +} + + +/** + * Move to the start of the first word following the given character position. + * + * \param text UTF-8 text string + * \param len length of string in bytes + * \param poffset offset of caret within string (updated on exit) + * \param pchars receives the number of characters skipped + * \return true iff the start of a word was found before the string end + */ + +bool word_right(const char *text, int len, int *poffset, int *pchars) +{ + int offset = *poffset; + bool success = false; + int nchars = 0; + + while (offset < len) { + if (isspace(text[offset])) break; + offset = utf8_next(text, len, offset); + nchars++; + } + + while (offset < len) { + offset = utf8_next(text, len, offset); + nchars++; + if (offset < len && !isspace(text[offset])) { + success = true; + break; + } + } + + *poffset = offset; + if (pchars) *pchars = nchars; + + return success; +} + |