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 | |
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')
-rw-r--r-- | desktop/browser.c | 181 | ||||
-rw-r--r-- | desktop/browser.h | 3 | ||||
-rw-r--r-- | desktop/gui.h | 8 | ||||
-rw-r--r-- | desktop/selection.c | 166 | ||||
-rw-r--r-- | desktop/selection.h | 4 | ||||
-rw-r--r-- | desktop/textinput.c | 531 |
6 files changed, 707 insertions, 186 deletions
diff --git a/desktop/browser.c b/desktop/browser.c index 335dd9954..5324ae984 100644 --- a/desktop/browser.c +++ b/desktop/browser.c @@ -73,6 +73,9 @@ static struct box *browser_window_pick_text_box(struct browser_window *bw, browser_mouse_state mouse, int x, int y, int *dx, int *dy); static void browser_window_page_drag_start(struct browser_window *bw, int x, int y); +static void browser_window_scroll_box(struct browser_window *bw, struct box *box, + int scroll_x, int scroll_y); + /** * Create and open a new browser window with the given page. @@ -852,14 +855,31 @@ void browser_window_mouse_action_html(struct browser_window *bw, case GADGET_TEXTAREA: status = messages_get("FormTextarea"); pointer = GUI_POINTER_CARET; - if (mouse & BROWSER_MOUSE_CLICK_1) - browser_window_textarea_click(bw, - mouse, - gadget_box, - gadget_box_x, - gadget_box_y, - x - gadget_box_x, - y - gadget_box_y); + if (mouse & (BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_2)) { + if (text_box) { + selection_click(bw->sel, text_box, mouse, x - box_x, y - box_y); + if (selection_dragging(bw->sel)) + bw->drag_type = DRAGGING_SELECTION; + } + } else { + if (mouse & BROWSER_MOUSE_CLICK_1) { + browser_window_textarea_click(bw, + mouse, + gadget_box, + gadget_box_x, + gadget_box_y, + x - gadget_box_x, + y - gadget_box_y); + } + else if (text_box) { + if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) + selection_init(bw->sel, gadget_box); + + selection_click(bw->sel, text_box, mouse, x - box_x, y - box_y); + if (selection_dragging(bw->sel)) + bw->drag_type = DRAGGING_SELECTION; + } + } break; case GADGET_TEXTBOX: case GADGET_PASSWORD: @@ -874,9 +894,13 @@ void browser_window_mouse_action_html(struct browser_window *bw, x - gadget_box_x, y - gadget_box_y); } - else { - selection_init(bw->sel, gadget_box); - selection_click(bw->sel, gadget_box, mouse, x - box_x, y - box_y); + else if (text_box) { + if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) + selection_init(bw->sel, gadget_box); + + selection_click(bw->sel, text_box, mouse, x - box_x, y - box_y); + if (selection_dragging(bw->sel)) + bw->drag_type = DRAGGING_SELECTION; } break; case GADGET_HIDDEN: @@ -1051,43 +1075,47 @@ void browser_window_mouse_track_html(struct browser_window *bw, browser_mouse_state mouse, int x, int y) { switch (bw->drag_type) { - case DRAGGING_VSCROLL: { - int scroll_y; - struct box *box = bw->scrolling_box; - assert(box); - scroll_y = bw->scrolling_start_scroll_y + - (float) (y - bw->scrolling_start_y) / - (float) bw->scrolling_well_height * - (float) (box->descendant_y1 - - box->descendant_y0); - if (scroll_y < box->descendant_y0) - scroll_y = box->descendant_y0; - else if (box->descendant_y1 - box->height < scroll_y) - scroll_y = box->descendant_y1 - box->height; - if (scroll_y == box->scroll_y) - return; - box->scroll_y = scroll_y; - browser_redraw_box(bw->current_content, bw->scrolling_box); - } - break; - case DRAGGING_HSCROLL: { - int scroll_x; + case DRAGGING_HSCROLL: + case DRAGGING_VSCROLL: + case DRAGGING_2DSCROLL: { struct box *box = bw->scrolling_box; + int scroll_y; + int scroll_x; + assert(box); - scroll_x = bw->scrolling_start_scroll_x + - (float) (x - bw->scrolling_start_x) / - (float) bw->scrolling_well_width * - (float) (box->descendant_x1 - - box->descendant_x0); - if (scroll_x < box->descendant_x0) - scroll_x = box->descendant_x0; - else if (box->descendant_x1 - box->width < scroll_x) - scroll_x = box->descendant_x1 - box->width; - if (scroll_x == box->scroll_x) - return; - box->scroll_x = scroll_x; - browser_redraw_box(bw->current_content, bw->scrolling_box); + + if (bw->drag_type == DRAGGING_HSCROLL) { + scroll_y = box->scroll_y; + } else { + scroll_y = bw->scrolling_start_scroll_y + + (float) (y - bw->scrolling_start_y) / + (float) bw->scrolling_well_height * + (float) (box->descendant_y1 - + box->descendant_y0); + if (scroll_y < box->descendant_y0) + scroll_y = box->descendant_y0; + else if (box->descendant_y1 - box->height < scroll_y) + scroll_y = box->descendant_y1 - box->height; + if (scroll_y == box->scroll_y) + return; + } + + if (bw->drag_type == DRAGGING_VSCROLL) { + scroll_x = box->scroll_x; + } else { + scroll_x = bw->scrolling_start_scroll_x + + (float) (x - bw->scrolling_start_x) / + (float) bw->scrolling_well_width * + (float) (box->descendant_x1 - + box->descendant_x0); + if (scroll_x < box->descendant_x0) + scroll_x = box->descendant_x0; + else if (box->descendant_x1 - box->width < scroll_x) + scroll_x = box->descendant_x1 - box->width; + } + + browser_window_scroll_box(bw, box, scroll_x, scroll_y); } break; @@ -1129,6 +1157,7 @@ void browser_window_mouse_drag_end(struct browser_window *bw, } break; + case DRAGGING_2DSCROLL: case DRAGGING_PAGE_SCROLL: browser_window_set_pointer(GUI_POINTER_DEFAULT); break; @@ -1218,9 +1247,32 @@ const char *browser_window_scrollbar_click(struct browser_window *bw, scroll += page; } else if (z < w + bar_start + bar_size - w / 4) { status = messages_get(vert ? "ScrollV" : "ScrollH"); - if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) - bw->drag_type = vert ? DRAGGING_VSCROLL : - DRAGGING_HSCROLL; + + /* respond on the click rather than the drag because it gives + the scrollbars a more solid, RISC OS feel */ + if (mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2)) { + int x0 = 0, x1 = 0; + int y0 = 0, y1 = 0; + + if (mouse & BROWSER_MOUSE_CLICK_1) { + bw->drag_type = vert ? DRAGGING_VSCROLL : + DRAGGING_HSCROLL; + } else + bw->drag_type = DRAGGING_2DSCROLL; + + /* \todo - some proper numbers please! */ + if (bw->drag_type != DRAGGING_VSCROLL) { + x0 = -1024; + x1 = 1024; + } + if (bw->drag_type != DRAGGING_HSCROLL) { + y0 = -1024; + y1 = 1024; + } + gui_window_box_scroll_start(bw->window, x0, y0, x1, y1); + if (bw->drag_type == DRAGGING_2DSCROLL) + gui_window_hide_pointer(); + } } else if (z < w + well_size) { status = messages_get(vert ? "ScrollPDown" : "ScrollPRight"); if (mouse & BROWSER_MOUSE_CLICK_1) @@ -1241,19 +1293,16 @@ const char *browser_window_scrollbar_click(struct browser_window *bw, scroll = box->descendant_y0; else if (box->descendant_y1 - box->height < scroll) scroll = box->descendant_y1 - box->height; - if (scroll != box->scroll_y) { - box->scroll_y = scroll; - browser_redraw_box(bw->current_content, box); - } + if (scroll != box->scroll_y) + browser_window_scroll_box(bw, box, box->scroll_x, scroll); + } else { if (scroll < box->descendant_x0) scroll = box->descendant_x0; else if (box->descendant_x1 - box->width < scroll) scroll = box->descendant_x1 - box->width; - if (scroll != box->scroll_x) { - box->scroll_x = scroll; - browser_redraw_box(bw->current_content, box); - } + if (scroll != box->scroll_x) + browser_window_scroll_box(bw, box, scroll, box->scroll_y); } return status; @@ -1369,6 +1418,26 @@ void browser_redraw_box(struct content *c, struct box *box) /** + * Update the scroll offsets of a box within a browser window + * (In future, copying where possible, rather than redrawing the entire box) + * + * \param bw browser window + * \param box box to be updated + * \param scroll_x new horizontal scroll offset + * \param scroll_y new vertical scroll offset + */ + +void browser_window_scroll_box(struct browser_window *bw, struct box *box, int scroll_x, int scroll_y) +{ + box->scroll_x = scroll_x; + box->scroll_y = scroll_y; + + /* fall back to redrawing the whole box */ + browser_redraw_box(bw->current_content, box); +} + + +/** * Process a selection from a form select menu. * * \param bw browser window with menu diff --git a/desktop/browser.h b/desktop/browser.h index f59974a39..1ff74a97d 100644 --- a/desktop/browser.h +++ b/desktop/browser.h @@ -73,7 +73,8 @@ struct browser_window { DRAGGING_VSCROLL, DRAGGING_HSCROLL, DRAGGING_SELECTION, - DRAGGING_PAGE_SCROLL + DRAGGING_PAGE_SCROLL, + DRAGGING_2DSCROLL } drag_type; /** Box currently being scrolled, or 0. */ diff --git a/desktop/gui.h b/desktop/gui.h index 59d7e6933..d5708852d 100644 --- a/desktop/gui.h +++ b/desktop/gui.h @@ -64,6 +64,7 @@ int gui_window_get_height(struct gui_window *g); void gui_window_set_extent(struct gui_window *g, int width, int height); void gui_window_set_status(struct gui_window *g, const char *text); void gui_window_set_pointer(gui_pointer_shape shape); +void gui_window_hide_pointer(void); void gui_window_set_url(struct gui_window *g, const char *url); void gui_window_start_throbber(struct gui_window *g); void gui_window_stop_throbber(struct gui_window *g); @@ -71,6 +72,8 @@ void gui_window_place_caret(struct gui_window *g, int x, int y, int height); void gui_window_remove_caret(struct gui_window *g); void gui_window_new_content(struct gui_window *g); bool gui_window_scroll_start(struct gui_window *g); +bool gui_window_box_scroll_start(struct gui_window *g, + int x0, int y0, int x1, int y1); struct gui_download_window *gui_download_window_create(const char *url, const char *mime_type, struct fetch *fetch, @@ -87,6 +90,9 @@ void gui_drag_save_selection(struct selection *s, struct gui_window *g); void gui_start_selection(struct gui_window *g); void gui_paste_from_clipboard(struct gui_window *g, int x, int y); +bool gui_empty_clipboard(void); +bool gui_add_to_clipboard(const char *text, size_t length, bool space); +bool gui_commit_clipboard(void); bool gui_copy_to_clipboard(struct selection *s); void gui_create_form_select_menu(struct browser_window *bw, @@ -97,5 +103,5 @@ void gui_launch_url(const char *url); bool gui_search_term_highlighted(struct gui_window *g, struct box *box, unsigned *start_idx, unsigned *end_idx); - #endif + diff --git a/desktop/selection.c b/desktop/selection.c index 2942b895b..e72807d80 100644 --- a/desktop/selection.c +++ b/desktop/selection.c @@ -20,6 +20,7 @@ #include "netsurf/render/box.h" #include "netsurf/render/font.h" #include "netsurf/utils/log.h" +#include "netsurf/utils/utils.h" #define IS_TEXT(box) ((box)->text && !(box)->object) @@ -43,8 +44,12 @@ static unsigned selection_label_subtree(struct selection *s, struct box *node, u static bool save_handler(struct box *box, int offset, size_t length, void *handle); static bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, seln_traverse_handler handler, void *handle); +static struct box *get_box(struct box *b, unsigned offset, int *pidx); +void set_start(struct selection *s, unsigned offset); +void set_end(struct selection *s, unsigned offset); + /** * Decides whether the char at byte offset 'a_idx' in the box 'a' lies after * position 'b' within the textual representation of the content. @@ -120,8 +125,12 @@ void selection_reinit(struct selection *s, struct box *root) assert(s); s->root = root; - if (root) - s->max_idx = selection_label_subtree(s, root, 0); + if (root) { + int root_idx = 0; + if (root->gadget) root_idx = 0x10000000; + + s->max_idx = selection_label_subtree(s, root, root_idx); + } else s->max_idx = 0; @@ -143,6 +152,9 @@ void selection_reinit(struct selection *s, struct box *root) void selection_init(struct selection *s, struct box *root) { + if (s->defined) + selection_clear(s, true); + s->defined = false; s->start_idx = 0; s->end_idx = 0; @@ -169,7 +181,6 @@ unsigned selection_label_subtree(struct selection *s, struct box *node, unsigned node->byte_offset = idx; - if (node->text && !node->object) { idx += node->length; if (node->space) idx++; @@ -319,7 +330,9 @@ void selection_track(struct selection *s, struct box *box, case DRAG_START: if (after(box, idx, s->end_idx)) { + unsigned old_end = s->end_idx; selection_set_end(s, box, idx); + set_start(s, old_end); s->drag_state = DRAG_END; } else @@ -328,7 +341,9 @@ void selection_track(struct selection *s, struct box *box, case DRAG_END: if (before(box, idx, s->start_idx)) { + unsigned old_start = s->start_idx; selection_set_start(s, box, idx); + set_end(s, old_start); s->drag_state = DRAG_START; } else @@ -386,6 +401,9 @@ bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, if (box->byte_offset >= end_idx) return true; + /* read before calling the handler in case it modifies the tree */ + child = box->children; + if (IS_TEXT(box) && box->length > 0) { if (box->byte_offset >= start_idx && @@ -426,7 +444,6 @@ bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, this is important at the top-levels of the tree for pruning subtrees that lie entirely before the selection */ - child = box->children; if (child) { struct box *next = child->next; @@ -436,9 +453,13 @@ bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, } while (child) { + /* read before calling the handler in case it modifies the tree */ + struct box *next = child->next; + if (!traverse_tree(child, start_idx, end_idx, handler, handle)) return false; - child = child->next; + + child = next; } } @@ -563,7 +584,7 @@ void selection_clear(struct selection *s, bool redraw) void selection_select_all(struct selection *s) { - int old_start, old_end; + unsigned old_start, old_end; bool was_defined; assert(s); @@ -584,25 +605,17 @@ void selection_select_all(struct selection *s) } -/** - * Set the start position of the current selection, updating the screen. - * - * \param s selection object - * \param box box object containing start point - * \param idx byte offset of starting point within box - */ - -void selection_set_start(struct selection *s, struct box *box, int idx) +void set_start(struct selection *s, unsigned offset) { - int old_start = s->start_idx; bool was_defined = selection_defined(s); + unsigned old_start = s->start_idx; - s->start_idx = box->byte_offset + idx; + s->start_idx = offset; s->last_was_end = false; s->defined = (s->start_idx < s->end_idx); if (was_defined) { - if (before(box, idx, old_start)) + if (offset < old_start) selection_redraw(s, s->start_idx, old_start); else selection_redraw(s, old_start, s->start_idx); @@ -612,25 +625,17 @@ void selection_set_start(struct selection *s, struct box *box, int idx) } -/** - * Set the end position of the current selection, updating the screen. - * - * \param s selection object - * \param box box object containing end point - * \param idx byte offset of end point within box - */ - -void selection_set_end(struct selection *s, struct box *box, int idx) +void set_end(struct selection *s, unsigned offset) { - int old_end = s->end_idx; bool was_defined = selection_defined(s); + unsigned old_end = s->end_idx; - s->end_idx = box->byte_offset + idx; + s->end_idx = offset; s->last_was_end = true; s->defined = (s->start_idx < s->end_idx); if (was_defined) { - if (before(box, idx, old_end)) + if (offset < old_end) selection_redraw(s, s->end_idx, old_end); else selection_redraw(s, old_end, s->end_idx); @@ -641,6 +646,101 @@ void selection_set_end(struct selection *s, struct box *box, int idx) /** + * Set the start position of the current selection, updating the screen. + * + * \param s selection object + * \param box box object containing start point + * \param idx byte offset of starting point within box + */ + +void selection_set_start(struct selection *s, struct box *box, int idx) +{ + set_start(s, box->byte_offset + idx); +} + + +/** + * Set the end position of the current selection, updating the screen. + * + * \param s selection object + * \param box box object containing end point + * \param idx byte offset of end point within box + */ + +void selection_set_end(struct selection *s, struct box *box, int idx) +{ + set_end(s, box->byte_offset + idx); +} + + +/** + * Get the box and index of the specified byte offset within the + * textual representation. + * + * \param b root node of search + * \param offset byte offset within textual representation + * \param pidx receives byte index of selection start point within box + * \return ptr to box, or NULL if no selection defined + */ + +struct box *get_box(struct box *b, unsigned offset, int *pidx) +{ + struct box *child = b->children; + + if (b->text && !b->object) { + + if (offset >= b->byte_offset && + offset < b->byte_offset + b->length + b->space) { + + /* it's in this box */ + *pidx = offset - b->byte_offset; + return b; + } + } + + /* find the first child that could contain this offset */ + if (child) { + struct box *next = child->next; + while (next && next->byte_offset < offset) { + child = next; + next = child->next; + } + return get_box(child, offset, pidx); + } + + return NULL; +} + + +/** + * Get the box and index of the selection start, if defined. + * + * \param s selection object + * \param pidx receives byte index of selection start point within box + * \return ptr to box, or NULL if no selection defined + */ + +struct box *selection_get_start(struct selection *s, int *pidx) +{ + return (s->defined ? get_box(s->root, s->start_idx, pidx) : NULL); +} + + +/** + * Get the box and index of the selection end, if defined. + * + * \param s selection object + * \param pidx receives byte index of selection end point within box + * \return ptr to box, or NULL if no selection defined. + */ + +struct box *selection_get_end(struct selection *s, int *pidx) +{ + return (s->defined ? get_box(s->root, s->end_idx, pidx) : NULL); +} + + +/** * Tests whether a text box lies partially within the selection, if there is * a selection defined, returning the start and end indexes of the bytes * that should be selected. @@ -703,10 +803,12 @@ bool save_handler(struct box *box, int offset, size_t length, void *handle) assert(out); if (box) { - if (fwrite(box->text + offset, 1, length, out) < length) + size_t len = min(length, box->length - offset); + + if (fwrite(box->text + offset, 1, len, out) < len) return false; - if (box->space) + if (box->space && length > len) return (EOF != fputc(' ', out)); return true; diff --git a/desktop/selection.h b/desktop/selection.h index be916913d..a5221ed9c 100644 --- a/desktop/selection.h +++ b/desktop/selection.h @@ -62,9 +62,13 @@ void selection_reinit(struct selection *s, struct box *root); void selection_clear(struct selection *s, bool redraw); void selection_select_all(struct selection *s); + void selection_set_start(struct selection *s, struct box *box, int idx); void selection_set_end(struct selection *s, struct box *box, int idx); +struct box *selection_get_start(struct selection *s, int *pidx); +struct box *selection_get_end(struct selection *s, int *pidx); + bool selection_click(struct selection *s, struct box *box, browser_mouse_state mouse, int dx, int dy); void selection_track(struct selection *s, struct box *box, browser_mouse_state mouse, int dx, int dy); 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; +} + |