diff options
-rw-r--r-- | Makefile.sources | 4 | ||||
-rw-r--r-- | atari/toolbar.c | 5 | ||||
-rw-r--r-- | content/content.h | 18 | ||||
-rw-r--r-- | desktop/browser.c | 21 | ||||
-rw-r--r-- | desktop/selection.c | 64 | ||||
-rw-r--r-- | desktop/textarea.c | 595 | ||||
-rw-r--r-- | desktop/textarea.h | 50 | ||||
-rw-r--r-- | desktop/textinput.c | 25 | ||||
-rw-r--r-- | desktop/tree.c | 8 | ||||
-rw-r--r-- | render/box_construct.c | 164 | ||||
-rw-r--r-- | render/box_textarea.c | 293 | ||||
-rw-r--r-- | render/box_textarea.h (renamed from render/textinput.h) | 31 | ||||
-rw-r--r-- | render/form.c | 87 | ||||
-rw-r--r-- | render/form.h | 12 | ||||
-rw-r--r-- | render/html.c | 53 | ||||
-rw-r--r-- | render/html.h | 1 | ||||
-rw-r--r-- | render/html_forms.c | 3 | ||||
-rw-r--r-- | render/html_interaction.c | 295 | ||||
-rw-r--r-- | render/html_internal.h | 37 | ||||
-rw-r--r-- | render/html_redraw.c | 20 | ||||
-rw-r--r-- | render/layout.c | 21 | ||||
-rw-r--r-- | render/textinput.c | 2213 |
22 files changed, 1165 insertions, 2855 deletions
diff --git a/Makefile.sources b/Makefile.sources index a38290d14..550c563bc 100644 --- a/Makefile.sources +++ b/Makefile.sources @@ -11,11 +11,11 @@ S_FETCHERS := curl.c data.c file.c about.c resource.c S_CSS := css.c dump.c internal.c select.c utils.c -S_RENDER := box.c box_construct.c box_normalise.c \ +S_RENDER := box.c box_construct.c box_normalise.c box_textarea.c \ font.c form.c \ html.c html_script.c html_interaction.c html_redraw.c \ html_forms.c imagemap.c layout.c list.c search.c table.c \ - textinput.c textplain.c + textplain.c S_UTILS := base64.c corestrings.c filename.c filepath.c hashtable.c \ libdom.c locale.c log.c messages.c nsurl.c talloc.c url.c \ diff --git a/atari/toolbar.c b/atari/toolbar.c index 480b69904..c472942de 100644 --- a/atari/toolbar.c +++ b/atari/toolbar.c @@ -322,8 +322,8 @@ struct s_toolbar *toolbar_create(struct s_gui_win_root *owner) toolbar_get_grect(t, TOOLBAR_AREA_URL, &url_area); url_area.g_h -= (TOOLBAR_URL_MARGIN_TOP + TOOLBAR_URL_MARGIN_BOTTOM); + textarea_flags ta_flags = TEXTAREA_INTERNAL_CARET; textarea_setup ta_setup; - ta_setup.flags = TEXTAREA_INTERNAL_CARET; ta_setup.width = 300; ta_setup.height = url_area.g_h; ta_setup.pad_top = 0; @@ -337,7 +337,8 @@ struct s_toolbar *toolbar_create(struct s_gui_win_root *owner) ta_setup.text = font_style_url; ta_setup.text.foreground = 0x000000; ta_setup.text.background = 0xffffff;
- t->url.textarea = textarea_create(&ta_setup, tb_txt_callback, t);
+ t->url.textarea = textarea_create(ta_flags, &ta_setup,
+ tb_txt_callback, t);
/* create the throbber widget: */
t->throbber.index = THROBBER_INACTIVE_INDEX;
diff --git a/content/content.h b/content/content.h index 7781ba9b8..2ae0b38be 100644 --- a/content/content.h +++ b/content/content.h @@ -78,7 +78,8 @@ typedef enum { CONTENT_MSG_SCROLL, /**< Request to scroll content */ CONTENT_MSG_DRAGSAVE, /**< Allow drag saving of content */ CONTENT_MSG_SAVELINK, /**< Allow URL to be saved */ - CONTENT_MSG_POINTER /**< Wants a specific mouse pointer set */ + CONTENT_MSG_POINTER, /**< Wants a specific mouse pointer set */ + CONTENT_MSG_DRAG /**< A drag started or ended */ } content_msg; /** RFC5988 metadata link */ @@ -152,14 +153,15 @@ union content_msg_data { } savelink; /** CONTENT_MSG_POINTER - Mouse pointer to set */ browser_pointer_shape pointer; - /** CONTENT_MSG_PASTE - Content requests that clipboard is pasted */ + /** CONTENT_MSG_DRAG - Drag start or end */ struct { - /* TODO: Get rid of these coords. - * browser_window_paste_text doesn't take coords, but - * RISC OS front end is doing something different. */ - int x; - int y; - } paste; + enum { + CONTENT_DRAG_NONE, + CONTENT_DRAG_SCROLL, + CONTENT_DRAG_SELECTION + } type; + const struct rect *rect; + } drag; }; /** parameters to content redraw */ diff --git a/desktop/browser.c b/desktop/browser.c index 6a1688192..f9353afef 100644 --- a/desktop/browser.c +++ b/desktop/browser.c @@ -396,6 +396,8 @@ void browser_window_set_drag_type(struct browser_window *bw, top_bw->drag_window = bw; switch (type) { + case DRAGGING_SELECTION: + return; case DRAGGING_SCR_X: case DRAGGING_SCR_Y: case DRAGGING_CONTENT_SCROLLBAR: @@ -1545,6 +1547,25 @@ nserror browser_window_callback(hlcache_handle *c, browser_window_set_pointer(bw, event->data.pointer); break; + case CONTENT_MSG_DRAG: + { + browser_drag_type bdt = DRAGGING_NONE; + + switch (event->data.drag.type) { + case CONTENT_DRAG_NONE: + bdt = DRAGGING_NONE; + break; + case CONTENT_DRAG_SCROLL: + bdt = DRAGGING_CONTENT_SCROLLBAR; + break; + case CONTENT_DRAG_SELECTION: + bdt = DRAGGING_SELECTION; + break; + } + browser_window_set_drag_type(bw, bdt, event->data.drag.rect); + } + break; + default: assert(0); } diff --git a/desktop/selection.c b/desktop/selection.c index ae9df5ec6..fa82ad027 100644 --- a/desktop/selection.c +++ b/desktop/selection.c @@ -47,24 +47,8 @@ /** * Text selection works by labelling each node in the box tree with its * start index in the textual representation of the tree's content. - * - * Text input fields and text areas have their own number spaces so that - * they can be relabelled more efficiently when editing (rather than relabel - * the entire box tree) and so that selections are either wholly within - * or wholly without the textarea/input box. */ -#define IS_INPUT(box) ((box) && (box)->gadget && \ - ((box)->gadget->type == GADGET_TEXTAREA || \ - (box)->gadget->type == GADGET_TEXTBOX || \ - (box)->gadget->type == GADGET_PASSWORD)) - -/** check whether the given text box is in the same number space as the - current selection; number spaces are identified by their uppermost nybble */ - -#define NUMBER_SPACE(x) ((x) & 0xF0000000U) -#define SAME_SPACE(s, offset) (NUMBER_SPACE((s)->max_idx) == NUMBER_SPACE(offset)) - #define SPACE_LEN(b) ((b->space == 0) ? 0 : 1) @@ -99,7 +83,7 @@ static bool save_handler(const char *text, size_t length, struct box *box, static bool selected_part(struct box *box, unsigned start_idx, unsigned end_idx, unsigned *start_offset, unsigned *end_offset); static bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, - unsigned int num_space, seln_traverse_handler handler, + seln_traverse_handler handler, void *handle, save_text_whitespace *before, bool *first, bool do_marker); static struct box *get_box(struct box *b, unsigned offset, size_t *pidx); @@ -188,13 +172,7 @@ void selection_reinit(struct selection *s, struct box *root) assert(s); - if (IS_INPUT(root)) { - static int next_idx = 0; - if (!++next_idx) next_idx = 1; - root_idx = next_idx << 28; - } - else - root_idx = 0; + root_idx = 0; // if (s->root == root) { // /* keep the same number space as before, because we want @@ -255,8 +233,7 @@ void selection_init(struct selection *s, struct box *root) bool selection_read_only(struct selection *s) { - return !s->root || !NUMBER_SPACE(s->root->byte_offset); - + return true; } @@ -280,13 +257,10 @@ unsigned selection_label_subtree(struct box *box, unsigned idx) idx += box->length + SPACE_LEN(box); while (child) { - if (!IS_INPUT(child)) { - if (child->list_marker) - idx = selection_label_subtree( - child->list_marker, idx); + if (child->list_marker) + idx = selection_label_subtree(child->list_marker, idx); - idx = selection_label_subtree(child, idx); - } + idx = selection_label_subtree(child, idx); child = child->next; } @@ -317,9 +291,6 @@ bool selection_click(struct selection *s, browser_mouse_state mouse, top = browser_window_get_root(top); - if (!SAME_SPACE(s, idx)) - return false; /* not our problem */ - if (selection_defined(s)) { if (idx > s->start_idx) { if (idx <= s->end_idx) @@ -421,9 +392,6 @@ bool selection_click(struct selection *s, browser_mouse_state mouse, void selection_track(struct selection *s, browser_mouse_state mouse, unsigned idx) { - if (!SAME_SPACE(s, idx)) - return; - if (!mouse) { s->drag_state = DRAG_NONE; } @@ -516,7 +484,6 @@ bool selected_part(struct box *box, unsigned start_idx, unsigned end_idx, * \param box box subtree * \param start_idx start of range within textual representation (bytes) * \param end_idx end of range - * \param num_space number space of the selection * \param handler handler function to call * \param handle handle to pass * \param before type of whitespace to place before next encountered text @@ -526,7 +493,7 @@ bool selected_part(struct box *box, unsigned start_idx, unsigned end_idx, */ bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, - unsigned int num_space, seln_traverse_handler handler, + seln_traverse_handler handler, void *handle, save_text_whitespace *before, bool *first, bool do_marker) { @@ -547,7 +514,7 @@ bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, /* do the marker box before continuing with the rest of the * list element */ if (!traverse_tree(box->list_marker, start_idx, end_idx, - num_space, handler, handle, before, first, + handler, handle, before, first, true)) return false; } @@ -568,8 +535,7 @@ bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, else { whitespace_text = NULL; } - if (num_space == NUMBER_SPACE(box->byte_offset) && - box->type != BOX_BR && + if (box->type != BOX_BR && !((box->type == BOX_FLOAT_LEFT || box->type == BOX_FLOAT_RIGHT) && !box->text)) { @@ -607,7 +573,7 @@ bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, * the tree */ struct box *next = child->next; - if (!traverse_tree(child, start_idx, end_idx, num_space, + if (!traverse_tree(child, start_idx, end_idx, handler, handle, before, first, false)) return false; @@ -642,8 +608,7 @@ static bool selection_traverse(struct selection *s, if (s->root) { /* HTML */ return traverse_tree(s->root, s->start_idx, s->end_idx, - NUMBER_SPACE(s->max_idx), handler, handle, - &before, &first, false); + handler, handle, &before, &first, false); } /* Text */ @@ -727,7 +692,7 @@ void selection_redraw(struct selection *s, unsigned start_idx, unsigned end_idx) if (s->root) { if (!traverse_tree(s->root, start_idx, end_idx, - NUMBER_SPACE(s->max_idx), redraw_handler, &rdw, + redraw_handler, &rdw, NULL, NULL, false)) return; } @@ -977,10 +942,7 @@ void selection_select_all(struct selection *s) assert(s); s->defined = true; - if (IS_INPUT(s->root)) - selection_set_start(s, s->root->children->children->byte_offset); - else - selection_set_start(s, 0); + selection_set_start(s, 0); selection_set_end(s, s->max_idx); } diff --git a/desktop/textarea.c b/desktop/textarea.c index a048058f5..9dd3ace2d 100644 --- a/desktop/textarea.c +++ b/desktop/textarea.c @@ -82,6 +82,7 @@ struct textarea { plot_font_style_t fstyle; /**< Text style, inc. textarea bg col */ plot_font_style_t sel_fstyle; /**< Selected text style */ + int line_height; /**< Line height obtained from style */ struct textarea_utf8 text; /**< Textarea text content */ #define PASSWORD_REPLACEMENT "\xe2\x80\xa2" @@ -102,10 +103,12 @@ struct textarea { int h_extent; /**< Width of content in px */ int v_extent; /**< Height of content in px */ + int line_count; /**< Count of lines */ + #define LINE_CHUNK_SIZE 16 struct line_info *lines; /**< Line info array */ - int line_height; /**< Line height obtained from style */ + unsigned int lines_alloc_size; /**< Number of LINE_CHUNK_SIZEs */ /** Callback function for messages to client */ textarea_client_callback callback; @@ -260,67 +263,80 @@ static bool textarea_select_fragment(struct textarea * ta) static bool textarea_scroll_visible(struct textarea *ta) { int x0, x1, y0, y1; /* area we want caret inside */ - int xc, yc; /* area centre */ int x, y; /* caret pos */ int xs = ta->scroll_x; int ys = ta->scroll_y; + int vis; + int scrollbar_width; bool scrolled = false; if (ta->caret_pos.char_off == -1) return false; + scrollbar_width = (ta->bar_y == NULL) ? 0 : SCROLLBAR_WIDTH; x0 = ta->border_width + ta->pad_left; x1 = ta->vis_width - (ta->border_width + ta->pad_right); - y0 = 0; - y1 = ta->vis_height - 2 * ta->border_width - - ta->pad_top - ta->pad_bottom; - xc = (x1 - x0) / 2 + x0; - yc = (y1 - y0) / 2 + y0; + /* Adjust scroll pos for reduced extents */ + vis = ta->vis_width - 2 * ta->border_width - scrollbar_width; + if (ta->h_extent - xs < vis) + xs -= vis - (ta->h_extent - xs); + + /* Get caret pos on screen */ x = ta->caret_x - xs; - y = ta->caret_y + ta->line_height / 2 - ys; - - /* horizontal scroll; centre caret */ - xs += x - xc; - - /* force back into range */ - if (xs < 0) - xs = 0; - else if (xs > ta->h_extent - (x1 - x0)) - xs = ta->h_extent - (x1 - x0); - - /* If scrolled, set new pos. */ - if (xs != ta->scroll_x && ta->bar_x != NULL) { - scrollbar_set(ta->bar_x, xs, false); - xs = scrollbar_get_offset(ta->bar_x); - if (xs != ta->scroll_x) { - ta->scroll_x = xs; - scrolled = true; - } - } else if (ta->flags & TEXTAREA_MULTILINE && ta->bar_x == NULL && - ta->scroll_x != 0) { + /* scroll as required */ + if (x < x0) + xs += (x - x0); + else if (x > x1) + xs += (x - x1); + + if (ta->bar_x == NULL && ta->scroll_x != 0 && + ta->flags & TEXTAREA_MULTILINE) { + /* Scrollbar removed, set to zero */ ta->scroll_x = 0; scrolled = true; - } else if (xs != ta->scroll_x && !(ta->flags & TEXTAREA_MULTILINE)) { - ta->scroll_x = xs; - scrolled = true; + } else if (xs != ta->scroll_x) { + /* Scrolled, set new pos. */ + if (ta->bar_x != NULL) { + scrollbar_set(ta->bar_x, xs, false); + xs = scrollbar_get_offset(ta->bar_x); + if (xs != ta->scroll_x) { + ta->scroll_x = xs; + scrolled = true; + } + + } else if (!(ta->flags & TEXTAREA_MULTILINE)) { + ta->scroll_x = xs; + scrolled = true; + + } } /* check and change vertical scroll */ if (ta->flags & TEXTAREA_MULTILINE) { - /* vertical scroll; centre caret */ - ys += y - yc; + scrollbar_width = (ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH; + y0 = 0; + y1 = ta->vis_height - 2 * ta->border_width - + ta->pad_top - ta->pad_bottom; - /* force back into range */ - if (ys < 0) - ys = 0; - else if (ys > ta->v_extent - (y1 - y0)) - ys = ta->v_extent - (y1 - y0); + /* Adjust scroll pos for reduced extents */ + vis = ta->vis_height - 2 * ta->border_width - scrollbar_width; + if (ta->v_extent - ys < vis) + ys -= vis - (ta->v_extent - ys); + + /* Get caret pos on screen */ + y = ta->caret_y - ys; + + /* scroll as required */ + if (y < y0) + ys += (y - y0); + else if (y + ta->line_height > y1) + ys += (y + ta->line_height - y1); - /* If scrolled, set new pos. */ if (ys != ta->scroll_y && ta->bar_y != NULL) { + /* Scrolled, set new pos. */ scrollbar_set(ta->bar_y, ys, false); ys = scrollbar_get_offset(ta->bar_y); if (ys != ta->scroll_y) { @@ -329,6 +345,7 @@ static bool textarea_scroll_visible(struct textarea *ta) } } else if (ta->bar_y == NULL && ta->scroll_y != 0) { + /* Scrollbar removed, set to zero */ ta->scroll_y = 0; scrolled = true; } @@ -361,6 +378,18 @@ static void textarea_scrollbar_callback(void *client_data, msg.data.redraw.y1 = ta->vis_height; ta->callback(ta->data, &msg); + + if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) { + /* Tell client where caret should be placed */ + msg.ta = ta; + msg.type = TEXTAREA_MSG_MOVED_CARET; + msg.data.caret.hidden = false; + msg.data.caret.x = ta->caret_x - ta->scroll_x; + msg.data.caret.y = ta->caret_y - ta->scroll_y; + msg.data.caret.height = ta->line_height; + + ta->callback(ta->data, &msg); + } break; case SCROLLBAR_MSG_SCROLL_START: @@ -421,6 +450,7 @@ static bool textarea_reflow(struct textarea *ta, unsigned int start) LOG(("malloc failed")); return false; } + ta->lines_alloc_size = LINE_CHUNK_SIZE; } if (!(ta->flags & TEXTAREA_MULTILINE)) { @@ -471,7 +501,7 @@ static bool textarea_reflow(struct textarea *ta, unsigned int start) if (x > w) w = x; - ta->h_extent = w + ta->pad_left - ta->pad_right; + ta->h_extent = w + ta->pad_left + ta->pad_right; ta->line_count = 1; return true; @@ -489,7 +519,10 @@ static bool textarea_reflow(struct textarea *ta, unsigned int start) do { /* Set line count to start point */ - line = start; + if (restart) + line = 0; + else + line = start; /* Find available width */ avail_width = ta->vis_width - 2 * ta->border_width - @@ -498,6 +531,14 @@ static bool textarea_reflow(struct textarea *ta, unsigned int start) avail_width = 0; h_extent = avail_width; + if (ta->text.len == 1) { + /* Handle empty textarea */ + assert(ta->text.data[0] == '\0'); + ta->lines[line].b_start = 0; + ta->lines[line++].b_length = 0; + ta->line_count = 1; + } + restart = false; for (len = ta->text.len - 1, text = ta->text.data; len > 0; len -= b_off, text += b_off) { @@ -536,9 +577,11 @@ static bool textarea_reflow(struct textarea *ta, unsigned int start) ta->line_height; } - if (line > 0 && line % LINE_CHUNK_SIZE == 0) { + /* Ensure enough storage for lines data */ + if (line > ta->lines_alloc_size - 2) { + /* Up to two lines my be added in a pass */ struct line_info *temp = realloc(ta->lines, - (line + LINE_CHUNK_SIZE) * + (line + 2 + LINE_CHUNK_SIZE) * sizeof(struct line_info)); if (temp == NULL) { LOG(("realloc failed")); @@ -546,6 +589,8 @@ static bool textarea_reflow(struct textarea *ta, unsigned int start) } ta->lines = temp; + ta->lines_alloc_size = line + 2 + + LINE_CHUNK_SIZE; } if (para_end == text + b_off && *para_end == '\n') { @@ -658,7 +703,6 @@ static bool textarea_reflow(struct textarea *ta, unsigned int start) * \param ta Text area * \param x X coordinate * \param y Y coordinate - * \param b_off Updated to byte offset * \param c_off Updated to character offset */ static void textarea_get_xy_offset(struct textarea *ta, int x, int y, @@ -699,9 +743,11 @@ static void textarea_get_xy_offset(struct textarea *ta, int x, int y, * following line, which is undesirable. */ if (ta->flags & TEXTAREA_MULTILINE && + ta->show->data[ta->lines[line].b_start + + ta->lines[line].b_length] > 0 && bpos == (unsigned)ta->lines[line].b_length && ta->show->data[ta->lines[line].b_start + - ta->lines[line].b_length - 1] == ' ') + ta->lines[line].b_length - 1] == ' ') bpos--; /* Get character position */ @@ -948,14 +994,15 @@ static bool textarea_drag_end(struct textarea *ta, browser_mouse_state mouse, /* exported interface, documented in textarea.h */ -struct textarea *textarea_create(const textarea_setup *setup, +struct textarea *textarea_create(const textarea_flags flags, + const textarea_setup *setup, textarea_client_callback callback, void *data) { struct textarea *ret; /* Sanity check flags */ - assert(!(setup->flags & TEXTAREA_MULTILINE && - setup->flags & TEXTAREA_PASSWORD)); + assert(!(flags & TEXTAREA_MULTILINE && + flags & TEXTAREA_PASSWORD)); if (callback == NULL) { LOG(("no callback provided")); @@ -971,7 +1018,7 @@ struct textarea *textarea_create(const textarea_setup *setup, ret->callback = callback; ret->data = data; - ret->flags = setup->flags; + ret->flags = flags; ret->vis_width = setup->width; ret->vis_height = setup->height; @@ -1008,7 +1055,7 @@ struct textarea *textarea_create(const textarea_setup *setup, ret->text.len = 1; ret->text.utf8_len = 0; - if (setup->flags & TEXTAREA_PASSWORD) { + if (flags & TEXTAREA_PASSWORD) { ret->password.data = malloc(64); if (ret->password.data == NULL) { LOG(("malloc failed")); @@ -1032,10 +1079,9 @@ struct textarea *textarea_create(const textarea_setup *setup, ret->show = &ret->text; } - ret->line_height = FIXTOINT(FDIV((FMUL(FLTTOFIX(1.2), - FMUL(nscss_screen_dpi, - INTTOFIX((setup->text.size / - FONT_SIZE_SCALE))))), F_72)); + ret->line_height = FIXTOINT(FDIV((FMUL(FLTTOFIX(1.3), + FMUL(nscss_screen_dpi, INTTOFIX((setup->text.size))))), + FONT_SIZE_SCALE * F_72)); ret->caret_pos.line = ret->caret_pos.char_off = -1; ret->caret_x = 0; @@ -1045,6 +1091,9 @@ struct textarea *textarea_create(const textarea_setup *setup, ret->line_count = 0; ret->lines = NULL; + ret->lines_alloc_size = 0; + + textarea_reflow(ret, 0); return ret; } @@ -1093,6 +1142,52 @@ bool textarea_set_text(struct textarea *ta, const char *text) /* exported interface, documented in textarea.h */ +bool textarea_drop_text(struct textarea *ta, const char *text, + size_t text_length) +{ + struct textarea_msg msg; + unsigned int caret_pos; + size_t text_chars; + + if (ta->flags & TEXTAREA_READONLY) + return false; + + if (text == NULL) + return false; + + text_chars = utf8_bounded_length(text, text_length); + caret_pos = textarea_get_caret(ta); + + if (ta->sel_start != -1) { + if (!textarea_replace_text(ta, ta->sel_start, ta->sel_end, + text, text_length, false)) + return false; + + caret_pos = ta->sel_start + text_chars; + ta->sel_start = ta->sel_end = -1; + } else { + if (!textarea_replace_text(ta, caret_pos, caret_pos, + text, text_length, false)) + return false; + caret_pos += text_chars; + } + + textarea_set_caret(ta, caret_pos); + + msg.ta = ta; + msg.type = TEXTAREA_MSG_REDRAW_REQUEST; + msg.data.redraw.x0 = 0; + msg.data.redraw.y0 = 0; + msg.data.redraw.x1 = ta->vis_width; + msg.data.redraw.y1 = ta->vis_height; + + ta->callback(ta->data, &msg); + + return true; +} + + +/* exported interface, documented in textarea.h */ int textarea_get_text(struct textarea *ta, char *buf, unsigned int len) { if (buf == NULL && len == 0) { @@ -1202,17 +1297,19 @@ bool textarea_set_caret(struct textarea *ta, int caret) x += ta->border_width + ta->pad_left; ta->caret_x = x; - y = ta->line_height * ta->caret_pos.line; + y = ta->line_height * ta->caret_pos.line + text_y_offset; ta->caret_y = y; - if (!textarea_scroll_visible(ta)) { - /* No scroll, just caret moved, redraw it */ + if (!textarea_scroll_visible(ta) && + ta->flags & TEXTAREA_INTERNAL_CARET) { + /* Didn't scroll, just moved caret. + * Caret is internal caret, redraw it */ x -= ta->scroll_x; y -= ta->scroll_y; x0 = max(x - 1, ta->border_width); - y0 = max(y + text_y_offset, 0); + y0 = max(y, 0); x1 = min(x + 1, ta->vis_width - ta->border_width); - y1 = min(y + ta->line_height + text_y_offset, + y1 = min(y + ta->line_height, ta->vis_height); width = x1 - x0; @@ -1229,6 +1326,26 @@ bool textarea_set_caret(struct textarea *ta, int caret) ta->callback(ta->data, &msg); } } + + if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) { + /* Tell client where caret should be placed */ + msg.ta = ta; + msg.type = TEXTAREA_MSG_MOVED_CARET; + msg.data.caret.hidden = false; + msg.data.caret.x = x - ta->scroll_x; + msg.data.caret.y = y - ta->scroll_y; + msg.data.caret.height = ta->line_height; + + ta->callback(ta->data, &msg); + } + + } else if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) { + /* Caret hidden, and client is responsible: tell client */ + msg.ta = ta; + msg.type = TEXTAREA_MSG_MOVED_CARET; + msg.data.caret.hidden = true; + + ta->callback(ta->data, &msg); } return true; @@ -1248,6 +1365,9 @@ int textarea_get_caret(struct textarea *ta) if (ta->text.utf8_len == 0) return 0; + if (ta->caret_pos.line >= ta->line_count) + return ta->text.utf8_len; + /* Calculate character offset of this line's start */ for (b_off = 0; b_off < ta->lines[ta->caret_pos.line].b_start; b_off = utf8_next(ta->text.data, ta->text.len, b_off)) @@ -1258,19 +1378,20 @@ int textarea_get_caret(struct textarea *ta) /* exported interface, documented in textarea.h */ -void textarea_redraw(struct textarea *ta, int x, int y, colour bg, +void textarea_redraw(struct textarea *ta, int x, int y, colour bg, float scale, const struct rect *clip, const struct redraw_context *ctx) { - struct textarea_msg msg; const struct plotter_table *plot = ctx->plot; - int line0, line1, line, left, right; + int line0, line1, line, left, right, line_y; int chars, text_y_offset, text_y_offset_baseline; unsigned int c_pos, c_len, c_len_part, b_start, b_end, line_len; unsigned int sel_start, sel_end; char *line_text; struct rect r, s; bool selected = false; - plot_font_style_t *fstyle; + plot_font_style_t fstyle; + int fsize = ta->fstyle.size; + int line_height = ta->line_height; plot_style_t plot_style_fill_bg = { .stroke_type = PLOT_OP_TYPE_NONE, .stroke_width = 0, @@ -1308,10 +1429,17 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, r.x0 = x; if (r.y0 < y) r.y0 = y; - if (r.x1 > x + ta->vis_width) - r.x1 = x + ta->vis_width; - if (r.y1 > y + ta->vis_height) - r.y1 = y + ta->vis_height; + if (scale == 1.0) { + if (r.x1 > x + ta->vis_width) + r.x1 = x + ta->vis_width; + if (r.y1 > y + ta->vis_height) + r.y1 = y + ta->vis_height; + } else { + if (r.x1 > x + ta->vis_width * scale) + r.x1 = x + ta->vis_width * scale; + if (r.y1 > y + ta->vis_height * scale) + r.y1 = y + ta->vis_height * scale; + } plot->clip(&r); if (ta->border_col != NS_TRANSPARENT && @@ -1329,16 +1457,32 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, &plot_style_fill_bg); } - if (r.x0 < x + ta->border_width) - r.x0 = x + ta->border_width; - if (r.x1 > x + ta->vis_width - ta->border_width) - r.x1 = x + ta->vis_width - ta->border_width; - if (r.y0 < y + ta->border_width) - r.y0 = y + ta->border_width; - if (r.y1 > y + ta->vis_height - ta->border_width - - (ta->bar_x != NULL ? SCROLLBAR_WIDTH : 0)) - r.y1 = y + ta->vis_height - ta->border_width - - (ta->bar_x != NULL ? SCROLLBAR_WIDTH : 0); + if (scale == 1.0) { + if (r.x0 < x + ta->border_width) + r.x0 = x + ta->border_width; + if (r.x1 > x + ta->vis_width - ta->border_width) + r.x1 = x + ta->vis_width - ta->border_width; + if (r.y0 < y + ta->border_width) + r.y0 = y + ta->border_width; + if (r.y1 > y + ta->vis_height - ta->border_width - + (ta->bar_x != NULL ? SCROLLBAR_WIDTH : 0)) + r.y1 = y + ta->vis_height - ta->border_width - + (ta->bar_x != NULL ? SCROLLBAR_WIDTH : + 0); + } else { + if (r.x0 < x + ta->border_width * scale) + r.x0 = x + ta->border_width * scale; + if (r.x1 > x + (ta->vis_width - ta->border_width) * scale) + r.x1 = x + (ta->vis_width - ta->border_width) * scale; + if (r.y0 < y + ta->border_width * scale) + r.y0 = y + ta->border_width * scale; + if (r.y1 > y + (ta->vis_height - ta->border_width - + (ta->bar_x != NULL ? SCROLLBAR_WIDTH : 0)) * + scale) + r.y1 = y + (ta->vis_height - ta->border_width - + (ta->bar_x != NULL ? SCROLLBAR_WIDTH : + 0) * scale); + } if (line0 > 0) c_pos = utf8_bounded_length(ta->show->data, @@ -1359,13 +1503,23 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, text_y_offset_baseline += (vis_height * 3 + 2) / 4; } + if (scale != 1.0) { + text_y_offset *= scale; + text_y_offset_baseline *= scale; + + fsize *= scale; + line_height *= scale; + } + plot_style_fill_bg.fill_colour = ta->sel_fstyle.background; for (line = line0; (line <= line1) && (y + line * ta->line_height <= r.y1 + ta->scroll_y); line++) { - if (ta->lines[line].b_length == 0) + if (ta->lines[line].b_length == 0) { + c_pos++; continue; + } /* reset clip rectangle */ plot->clip(&r); @@ -1387,30 +1541,31 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, /* rest of line unselected */ selected = false; c_len_part = c_len; - fstyle = &ta->fstyle; + fstyle = ta->fstyle; } else if (sel_start <= c_pos && sel_end > c_pos + c_len) { /* rest of line selected */ selected = true; c_len_part = c_len; - fstyle = &ta->sel_fstyle; + fstyle = ta->sel_fstyle; } else if (sel_start > c_pos) { /* next part of line unselected */ selected = false; c_len_part = sel_start - c_pos; - fstyle = &ta->fstyle; + fstyle = ta->fstyle; } else if (sel_end > c_pos) { /* next part of line selected */ selected = true; c_len_part = sel_end - c_pos; - fstyle = &ta->sel_fstyle; + fstyle = ta->sel_fstyle; } else { assert(0); } + fstyle.size = fsize; line_text = &(ta->show->data[ta->lines[line].b_start]); line_len = ta->lines[line].b_length; @@ -1424,7 +1579,7 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, /* find clip left/right for this part of line */ left = right; - nsfont.font_width(&ta->fstyle, line_text, + nsfont.font_width(&fstyle, line_text, b_end, &right); right += x + ta->border_width + ta->pad_left - ta->scroll_x; @@ -1435,27 +1590,31 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, s.x0 = left; if (s.x1 > right) s.x1 = right; + plot->clip(&s); + line_y = line * ta->line_height - ta->scroll_y; + + if (scale != 1.0) { + line_y *= scale; + } + if (selected) { /* draw selection fill */ - plot->rectangle(s.x0, - y + line * ta->line_height + 1 - - ta->scroll_y + text_y_offset, - s.x1, - y + (line + 1) * ta->line_height + 1 - - ta->scroll_y + text_y_offset, + plot->rectangle(s.x0, y + line_y + + text_y_offset, + s.x1, y + line_y + line_height + + text_y_offset, &plot_style_fill_bg); } /* draw text */ plot->text(x + ta->border_width + ta->pad_left - ta->scroll_x, - y + line * ta->line_height + - text_y_offset_baseline - ta->scroll_y, + y + line_y + text_y_offset_baseline, ta->show->data + ta->lines[line].b_start, - ta->lines[line].b_length, fstyle); + ta->lines[line].b_length, &fstyle); c_pos += c_len_part; c_len -= c_len_part; @@ -1475,7 +1634,7 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, if ((ta->sel_end == -1 || ta->sel_start == ta->sel_end) && ta->caret_pos.char_off >= 0) { /* There is no selection, and caret visible: show caret */ - int caret_y = y - ta->scroll_y + ta->caret_y + text_y_offset; + int caret_y = y - ta->scroll_y + ta->caret_y; if (ta->flags & TEXTAREA_INTERNAL_CARET) { /* Render our own caret */ @@ -1483,39 +1642,22 @@ void textarea_redraw(struct textarea *ta, int x, int y, colour bg, x - ta->scroll_x + ta->caret_x, caret_y + ta->line_height, &pstyle_stroke_caret); - } else { - /* Tell client where caret should be placed */ - msg.ta = ta; - msg.type = TEXTAREA_MSG_MOVED_CARET; - msg.data.caret.hidden = false; - msg.data.caret.x = x - ta->scroll_x + ta->caret_x; - msg.data.caret.y = caret_y; - msg.data.caret.height = ta->line_height; - - ta->callback(ta->data, &msg); } - } else if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) { - /* Caret hidden, and client is responsible: tell client */ - msg.ta = ta; - msg.type = TEXTAREA_MSG_MOVED_CARET; - msg.data.caret.hidden = true; - - ta->callback(ta->data, &msg); } if (ta->bar_x != NULL) scrollbar_redraw(ta->bar_x, - x + ta->border_width, - y + ta->vis_height - ta->border_width - + x / scale + ta->border_width, + y / scale + ta->vis_height - ta->border_width - SCROLLBAR_WIDTH, - clip, 1.0, ctx); + clip, scale, ctx); if (ta->bar_y != NULL) scrollbar_redraw(ta->bar_y, - x + ta->vis_width - ta->border_width - + x / scale + ta->vis_width - ta->border_width - SCROLLBAR_WIDTH, - y + ta->border_width, - clip, 1.0, ctx); + y / scale + ta->border_width, + clip, scale, ctx); } @@ -1584,26 +1726,69 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) caret = ta->sel_start; ta->sel_start = ta->sel_end = -1; - redraw = true; } else if (caret > 0) { if (!textarea_replace_text(ta, caret - 1, caret, "", 0, false)) return false; caret--; - redraw = true; } + redraw = true; break; + case KEY_CR: case KEY_NL: if (readonly) break; - if(!textarea_insert_text(ta, caret, "\n", 1)) - return false; - caret++; - ta->sel_start = ta->sel_end = -1; + + if (ta->sel_start != -1) { + if (!textarea_replace_text(ta, + ta->sel_start, ta->sel_end, + "\n", 1, false)) + return false; + + caret = ta->sel_start + 1; + ta->sel_start = ta->sel_end = -1; + } else { + if (!textarea_replace_text(ta, + caret, caret, + "\n", 1, false)) + return false; + caret++; + } redraw = true; break; case KEY_CUT_LINE: + /* Not actually CUT to clipboard, just delete */ + if (readonly) + break; + if (ta->sel_start != -1) { + if (!textarea_replace_text(ta, + ta->sel_start, + ta->sel_end, "", 0, false)) + return false; + ta->sel_start = ta->sel_end = -1; + } else { + if (ta->lines[line].b_length != 0) { + /* Delete line */ + b_off = ta->lines[line].b_start; + b_len = ta->lines[line].b_length; + l_len = utf8_bounded_length( + &(ta->text.data[b_off]), + b_len); + caret -= ta->caret_pos.char_off; + if (!textarea_replace_text(ta, caret, + caret + l_len, "", 0, + false)) + return false; + } else if (caret < ta->text.utf8_len) { + /* Delete blank line */ + if (!textarea_replace_text(ta, + caret, caret + 1, "", 0, + false)) + return false; + } + } + redraw = true; break; case KEY_PASTE: { @@ -1629,7 +1814,6 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) caret = ta->sel_start + clipboard_chars; ta->sel_start = ta->sel_end = -1; - redraw = true; } else { if (!textarea_replace_text(ta, caret, caret, @@ -1637,8 +1821,8 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) false)) return false; caret += clipboard_chars; - redraw = true; } + redraw = true; free(clipboard); } @@ -1702,38 +1886,37 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) ta->sel_start = ta->sel_end = -1; redraw = true; } - if (ta->flags & TEXTAREA_MULTILINE) { - line--; - if (line < 0) - line = 0; - if (line == ta->caret_pos.line) - break; + if (!(ta->flags & TEXTAREA_MULTILINE)) + break; - b_off = ta->lines[line].b_start; - b_len = ta->lines[line].b_length; + line--; + if (line < 0) + line = 0; + if (line == ta->caret_pos.line) + break; - c_line = ta->caret_pos.line; - c_chars = ta->caret_pos.char_off; + b_off = ta->lines[line].b_start; + b_len = ta->lines[line].b_length; - if (ta->text.data[b_off + b_len - 1] == ' ' - && line < ta->line_count - 1) - b_len--; + c_line = ta->caret_pos.line; + c_chars = ta->caret_pos.char_off; - l_len = utf8_bounded_length( - &(ta->text.data[b_off]), - b_len); + if (b_len > 0 && ta->text.data[b_off + b_len - 1] == ' ' + && line < ta->line_count - 1) + b_len--; + l_len = utf8_bounded_length(&(ta->text.data[b_off]), + b_len); - ta->caret_pos.line = line; - ta->caret_pos.char_off = min(l_len, - (unsigned) - ta->caret_pos.char_off); + ta->caret_pos.line = line; + ta->caret_pos.char_off = min(l_len, + (unsigned)ta->caret_pos.char_off); - caret = textarea_get_caret(ta); + caret = textarea_get_caret(ta); + + ta->caret_pos.line = c_line; + ta->caret_pos.char_off = c_chars; - ta->caret_pos.line = c_line; - ta->caret_pos.char_off = c_chars; - } break; case KEY_PAGE_DOWN: if (readonly) @@ -1753,38 +1936,37 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) ta->sel_start = ta->sel_end = -1; redraw = true; } - if (ta->flags & TEXTAREA_MULTILINE) { - line++; - if (line > ta->line_count - 1) - line = ta->line_count - 1; - if (line == ta->caret_pos.line) - break; + if (!(ta->flags & TEXTAREA_MULTILINE)) + break; - b_off = ta->lines[line].b_start; - b_len = ta->lines[line].b_length; + line++; + if (line > ta->line_count - 1) + line = ta->line_count - 1; + if (line == ta->caret_pos.line) + break; - c_line = ta->caret_pos.line; - c_chars = ta->caret_pos.char_off; + b_off = ta->lines[line].b_start; + b_len = ta->lines[line].b_length; - if (ta->text.data[b_off + b_len - 1] == ' ' - && line < ta->line_count - 1) - b_len--; + c_line = ta->caret_pos.line; + c_chars = ta->caret_pos.char_off; - l_len = utf8_bounded_length( - &(ta->text.data[b_off]), - b_len); + if (ta->text.data[b_off + b_len - 1] == ' ' && + line < ta->line_count - 1) + b_len--; + l_len = utf8_bounded_length(&(ta->text.data[b_off]), + b_len); - ta->caret_pos.line = line; - ta->caret_pos.char_off = min(l_len, - (unsigned) - ta->caret_pos.char_off); + ta->caret_pos.line = line; + ta->caret_pos.char_off = min(l_len, + (unsigned)ta->caret_pos.char_off); - caret = textarea_get_caret(ta); + caret = textarea_get_caret(ta); + + ta->caret_pos.line = c_line; + ta->caret_pos.char_off = c_chars; - ta->caret_pos.line = c_line; - ta->caret_pos.char_off = c_chars; - } break; case KEY_DELETE_RIGHT: if (readonly) @@ -1865,8 +2047,8 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) return false; ta->sel_start = ta->sel_end = -1; } else { - b_off = ta->lines[ta->caret_pos.line].b_start; - b_len = ta->lines[ta->caret_pos.line].b_length; + b_off = ta->lines[line].b_start; + b_len = ta->lines[line].b_length; l_len = utf8_bounded_length( &(ta->text.data[b_off]), b_len); @@ -1899,8 +2081,9 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) } - if (caret != caret_init) + if (caret != caret_init || redraw) textarea_set_caret(ta, caret); + //TODO:redraw only the important part if (redraw) { msg.ta = ta; @@ -2006,7 +2189,8 @@ bool textarea_mouse_action(struct textarea *ta, browser_mouse_state mouse, return textarea_select_fragment(ta); } - } else if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_HOLDING_1)) { + } else if (mouse & BROWSER_MOUSE_DRAG_1) { + /* Selection start */ textarea_get_xy_offset(ta, x, y, &c_off); c_start = ta->drag_start_char; c_end = c_off; @@ -2019,6 +2203,30 @@ bool textarea_mouse_action(struct textarea *ta, browser_mouse_state mouse, ta->callback(ta->data, &msg); return textarea_select(ta, c_start, c_end); + } else if (mouse & BROWSER_MOUSE_HOLDING_1 && + ta->drag_info.type == TEXTAREA_DRAG_SELECTION) { + /* Selection track */ + int scrx = 0; + int scry = 0; + + textarea_get_xy_offset(ta, x, y, &c_off); + c_start = ta->drag_start_char; + c_end = c_off; + + /* selection auto-scroll */ + if (x < 0) + scrx = x / 4; + else if (x > ta->vis_width) + scrx = (x - ta->vis_width) / 4; + + if (y < 0) + scry = y / 4; + else if (y > ta->vis_height) + scry = (y - ta->vis_height) / 4; + + textarea_scroll(ta, scrx, scry); + + return textarea_select(ta, c_start, c_end); } return true; @@ -2038,18 +2246,37 @@ void textarea_get_dimensions(struct textarea *ta, int *width, int *height) /* exported interface, documented in textarea.h */ void textarea_set_dimensions(struct textarea *ta, int width, int height) { - struct textarea_msg msg; + ta->vis_width = width; + ta->vis_height = height; + textarea_reflow(ta, 0); +} + +/* exported interface, documented in textarea.h */ +void textarea_set_layout(struct textarea *ta, int width, int height, + int top, int right, int bottom, int left) +{ ta->vis_width = width; ta->vis_height = height; + ta->pad_top = top; + ta->pad_right = right + ((ta->bar_y == NULL) ? 0 : SCROLLBAR_WIDTH); + ta->pad_bottom = bottom + ((ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH); + ta->pad_left = left; textarea_reflow(ta, 0); +} - msg.ta = ta; - msg.type = TEXTAREA_MSG_REDRAW_REQUEST; - msg.data.redraw.x0 = 0; - msg.data.redraw.y0 = 0; - msg.data.redraw.x1 = ta->vis_width; - msg.data.redraw.y1 = ta->vis_height; - ta->callback(ta->data, &msg); +/* exported interface, documented in textarea.h */ +bool textarea_scroll(struct textarea *ta, int scrx, int scry) +{ + bool handled_scroll = false; + + if (ta->bar_x != NULL && scrx != 0 && + scrollbar_scroll(ta->bar_x, scrx)) + handled_scroll = true; + if (ta->bar_y != NULL && scry != 0 && + scrollbar_scroll(ta->bar_y, scry)) + handled_scroll = true; + + return handled_scroll; } diff --git a/desktop/textarea.h b/desktop/textarea.h index d8e720bae..d01cd12c6 100644 --- a/desktop/textarea.h +++ b/desktop/textarea.h @@ -70,8 +70,6 @@ struct textarea_msg { }; typedef struct textarea_setup { - textarea_flags flags; /**< Setup flags */ - int width; /**< Textarea width */ int height; /**< Textarea height */ @@ -105,7 +103,8 @@ typedef void(*textarea_client_callback)(void *data, struct textarea_msg *msg); * \param data user specified data which will be passed to callbacks * \return Opaque handle for textarea or 0 on error */ -struct textarea *textarea_create(const textarea_setup *setup, +struct textarea *textarea_create(const textarea_flags flags, + const textarea_setup *setup, textarea_client_callback callback, void *data); /** @@ -125,11 +124,21 @@ void textarea_destroy(struct textarea *ta); bool textarea_set_text(struct textarea *ta, const char *text); /** + * Insert the text in a text area at the caret, replacing any selection. + * + * \param ta Text area + * \param text UTF-8 text to set text area's contents to + * \return true on success, false on memory exhaustion or if ta lacks caret + */ +bool textarea_drop_text(struct textarea *ta, const char *text, + size_t text_length); + +/** * Extract the text from a text area * * \param ta Text area * \param buf Pointer to buffer to receive data, or NULL - * to read length required + * to read length required (includes trailing '\0') * \param len Length (bytes) of buffer pointed to by buf, or 0 to read length * \return Length (bytes) written/required or -1 on error */ @@ -160,10 +169,11 @@ int textarea_get_caret(struct textarea *ta); * \param x x coordinate of textarea top * \param y y coordinate of textarea left * \param bg background colour under textarea + * \param scale scale to render at * \param clip clip rectangle * \param ctx current redraw context */ -void textarea_redraw(struct textarea *ta, int x, int y, colour bg, +void textarea_redraw(struct textarea *ta, int x, int y, colour bg, float scale, const struct rect *clip, const struct redraw_context *ctx); /** @@ -190,6 +200,7 @@ bool textarea_mouse_action(struct textarea *ta, browser_mouse_state mouse, /** * Gets the dimensions of a textarea * + * \param ta textarea widget * \param width if not NULL, gets updated to the width of the textarea * \param height if not NULL, gets updated to the height of the textarea */ @@ -197,11 +208,38 @@ void textarea_get_dimensions(struct textarea *ta, int *width, int *height); /** * Set the dimensions of a textarea, causing a reflow and - * emitting a redraw request. + * Does not emit a redraw request. Up to client to call textarea_redraw. * + * \param ta textarea widget * \param width the new width of the textarea * \param height the new height of the textarea */ void textarea_set_dimensions(struct textarea *ta, int width, int height); + +/** + * Set the dimensions and padding of a textarea, causing a reflow. + * Does not emit a redraw request. Up to client to call textarea_redraw. + * + * \param ta textarea widget + * \param width the new width of the textarea + * \param height the new height of the textarea + * \param top the new top padding of the textarea + * \param right the new right padding of the textarea + * \param bottom the new bottom padding of the textarea + * \param left the new left padding of the textarea + */ +void textarea_set_layout(struct textarea *ta, int width, int height, + int top, int right, int bottom, int left); + +/** + * Scroll a textarea by an amount. Only does anything if multi-line textarea + * has scrollbars. If it scrolls, it will emit a redraw request. + * + * \param ta textarea widget + * \param scrx number of px try to scroll in x direction + * \param scry number of px try to scroll in y direction + * \return true iff the textarea was scrolled + */ +bool textarea_scroll(struct textarea *ta, int scrx, int scry); #endif diff --git a/desktop/textinput.c b/desktop/textinput.c index b4fda5eef..660708932 100644 --- a/desktop/textinput.c +++ b/desktop/textinput.c @@ -127,7 +127,13 @@ bool browser_window_key_press(struct browser_window *bw, uint32_t key) assert(bw->window != NULL); - /* safe keys that can be handled whether input claimed or not */ + if (focus->caret_callback) { + /* Pass keypress onto anything that has claimed input focus */ + return focus->caret_callback(focus, key, + focus->caret_p1, focus->caret_p2); + } + + /* TODO: pass these to content to deal with */ switch (key) { case KEY_COPY_SELECTION: selection_copy_to_clipboard(bw->cur_sel); @@ -137,6 +143,10 @@ bool browser_window_key_press(struct browser_window *bw, uint32_t key) selection_clear(bw->cur_sel, true); return true; + case KEY_SELECT_ALL: + selection_select_all(bw->cur_sel); + return true; + case KEY_ESCAPE: if (bw->cur_sel && selection_defined(bw->cur_sel)) { selection_clear(bw->cur_sel, true); @@ -147,19 +157,6 @@ bool browser_window_key_press(struct browser_window *bw, uint32_t key) return false; } - if (focus->caret_callback) { - /* Pass keypress onto anything that has claimed input focus */ - return focus->caret_callback(focus, key, - focus->caret_p1, focus->caret_p2); - } - - /* keys we can't handle here if cursor is in form */ - switch (key) { - case KEY_SELECT_ALL: - selection_select_all(bw->cur_sel); - return true; - } - return false; } diff --git a/desktop/tree.c b/desktop/tree.c index 959b9870a..af64be83b 100644 --- a/desktop/tree.c +++ b/desktop/tree.c @@ -2079,7 +2079,7 @@ void tree_draw(struct tree *tree, int x, int y, textarea_redraw(tree->textarea, x, y, plot_style_fill_tree_background. fill_colour, - &clip, &new_ctx); + 1.0, &clip, &new_ctx); } } @@ -2946,6 +2946,7 @@ void tree_start_edit(struct tree *tree, struct node_element *element) struct node *parent; int width, height; textarea_setup ta_setup; + textarea_flags ta_flags; assert(tree != NULL); assert(element != NULL); @@ -2972,7 +2973,8 @@ void tree_start_edit(struct tree *tree, struct node_element *element) tree->ta_height = height; - ta_setup.flags = TEXTAREA_INTERNAL_CARET; + ta_flags = TEXTAREA_INTERNAL_CARET; + ta_setup.width = width; ta_setup.height = tree->ta_height; ta_setup.pad_top = 0; @@ -2987,7 +2989,7 @@ void tree_start_edit(struct tree *tree, struct node_element *element) ta_setup.text.foreground = 0x000000; ta_setup.text.background = 0xffffff; - tree->textarea = textarea_create(&ta_setup, + tree->textarea = textarea_create(ta_flags, &ta_setup, tree_textarea_callback, tree); if (tree->textarea == NULL) { tree_stop_edit(tree, false); diff --git a/render/box_construct.c b/render/box_construct.c index 52c8cfee5..e5800edf8 100644 --- a/render/box_construct.c +++ b/render/box_construct.c @@ -38,6 +38,7 @@ #include "css/select.h" #include "desktop/options.h" #include "render/box.h" +#include "render/box_textarea.h" #include "render/form.h" #include "render/html_internal.h" #include "utils/corestrings.h" @@ -111,7 +112,6 @@ static bool box_image(BOX_SPECIAL_PARAMS); static bool box_textarea(BOX_SPECIAL_PARAMS); static bool box_select(BOX_SPECIAL_PARAMS); static bool box_input(BOX_SPECIAL_PARAMS); -static bool box_input_text(BOX_SPECIAL_PARAMS, bool password); static bool box_button(BOX_SPECIAL_PARAMS); static bool box_frameset(BOX_SPECIAL_PARAMS); static bool box_create_frameset(struct content_html_frames *f, dom_node *n, @@ -2497,6 +2497,38 @@ bool box_iframe(BOX_SPECIAL_PARAMS) /** + * Helper function for adding textarea widget to box. + * + * This is a load of hacks to ensure boxes replaced with textareas + * can be handled by the layout code. + */ + +static bool box_input_text(html_content *html, struct box *box, + struct dom_node *node) +{ + struct box *inline_container, *inline_box; + + box->type = BOX_INLINE_BLOCK; + + inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, html->bctx); + if (!inline_container) + return false; + inline_container->type = BOX_INLINE_CONTAINER; + inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, + html->bctx); + if (!inline_box) + return false; + inline_box->type = BOX_TEXT; + inline_box->text = talloc_strdup(html->bctx, ""); + + box_add_child(inline_container, inline_box); + box_add_child(box, inline_container); + + return box_textarea_create_textarea(html, box, node); +} + + +/** * Form control [17.4]. */ @@ -2518,7 +2550,7 @@ bool box_input(BOX_SPECIAL_PARAMS) if (type && dom_string_caseless_lwc_isequal(type, corestring_lwc_password)) { - if (box_input_text(n, content, box, 0, true) == false) + if (box_input_text(content, box, n) == false) goto no_memory; } else if (type && dom_string_caseless_lwc_isequal(type, @@ -2620,7 +2652,7 @@ bool box_input(BOX_SPECIAL_PARAMS) } } else { /* the default type is "text" */ - if (box_input_text(n, content, box, 0, false) == false) + if (box_input_text(content, box, n) == false) goto no_memory; } @@ -2639,52 +2671,6 @@ no_memory: /** - * Helper function for box_input(). - */ - -bool box_input_text(BOX_SPECIAL_PARAMS, bool password) -{ - struct box *inline_container, *inline_box; - - box->type = BOX_INLINE_BLOCK; - - inline_container = box_create(NULL, 0, false, 0, 0, 0, 0, content->bctx); - if (!inline_container) - return false; - inline_container->type = BOX_INLINE_CONTAINER; - inline_box = box_create(NULL, box->style, false, 0, 0, box->title, 0, - content->bctx); - if (!inline_box) - return false; - inline_box->type = BOX_TEXT; - if (password) { - inline_box->length = strlen(box->gadget->value); - inline_box->text = talloc_array(content->bctx, char, - inline_box->length + 1); - if (!inline_box->text) - return false; - memset(inline_box->text, '*', inline_box->length); - inline_box->text[inline_box->length] = '\0'; - } else { - /* replace spaces/TABs with hard spaces to prevent line - * wrapping */ - char *text = cnv_space2nbsp(box->gadget->value); - if (!text) - return false; - inline_box->text = talloc_strdup(content->bctx, text); - free(text); - if (!inline_box->text) - return false; - inline_box->length = strlen(inline_box->text); - } - box_add_child(inline_container, inline_box); - box_add_child(box, inline_container); - - return true; -} - - -/** * Push button [17.5]. */ @@ -2924,90 +2910,16 @@ no_memory: bool box_textarea(BOX_SPECIAL_PARAMS) { - /* A textarea is an INLINE_BLOCK containing a single INLINE_CONTAINER, - * which contains the text as runs of TEXT separated by BR. There is - * at least one TEXT. The first and last boxes are TEXT. - * Consecutive BR may not be present. These constraints are satisfied - * by using a 0-length TEXT for blank lines. */ - - const char *current; - dom_string *area_data = NULL; - dom_exception err; - struct box *inline_container, *inline_box, *br_box; - char *s; - size_t len; - - box->type = BOX_INLINE_BLOCK; + /* Get the form_control for the DOM node */ box->gadget = html_forms_get_control_for_node(content->forms, n); if (box->gadget == NULL) return false; - box->gadget->box = box; - inline_container = box_create(NULL, 0, false, 0, 0, box->title, 0, - content->bctx); - if (inline_container == NULL) - return false; - inline_container->type = BOX_INLINE_CONTAINER; - box_add_child(box, inline_container); + box->gadget->box = box; - err = dom_node_get_text_content(n, &area_data); - if (err != DOM_NO_ERR) + if (!box_input_text(content, box, n)) return false; - if (area_data != NULL) { - current = dom_string_data(area_data); - } else { - /* No content, or failed reading it: use a blank string */ - current = ""; - } - - while (true) { - /* BOX_TEXT */ - len = strcspn(current, "\r\n"); - s = talloc_strndup(content->bctx, current, len); - if (s == NULL) { - if (area_data != NULL) - dom_string_unref(area_data); - return false; - } - - inline_box = box_create(NULL, box->style, false, 0, 0, - box->title, 0, content->bctx); - if (inline_box == NULL) { - if (area_data != NULL) - dom_string_unref(area_data); - return false; - } - inline_box->type = BOX_TEXT; - inline_box->text = s; - inline_box->length = len; - box_add_child(inline_container, inline_box); - - current += len; - if (current[0] == 0) - /* finished */ - break; - - /* BOX_BR */ - br_box = box_create(NULL, box->style, false, 0, 0, box->title, - 0, content->bctx); - if (br_box == NULL) { - if (area_data != NULL) - dom_string_unref(area_data); - return false; - } - br_box->type = BOX_BR; - box_add_child(inline_container, br_box); - - if (current[0] == '\r' && current[1] == '\n') - current += 2; - else - current++; - } - - if (area_data != NULL) - dom_string_unref(area_data); - *convert_children = false; return true; } diff --git a/render/box_textarea.c b/render/box_textarea.c new file mode 100644 index 000000000..7f38ade5e --- /dev/null +++ b/render/box_textarea.c @@ -0,0 +1,293 @@ +/* + * Copyright 2013 Michael Drake <tlsa@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** \file + * Box tree treeview box replacement (implementation). + */ + +#include <dom/dom.h> + +#include "desktop/browser.h" +#include "desktop/textarea.h" +#include "desktop/textinput.h" +#include "render/box_textarea.h" +#include "render/font.h" +#include "render/form.h" +#include "utils/log.h" + + +static bool box_textarea_browser_caret_callback(struct browser_window *bw, + uint32_t key, void *p1, void *p2) +{ + struct box *box = p1; + struct form_control *gadget = box->gadget; + struct textarea *ta = gadget->data.text.ta; + struct form* form = box->gadget->form; + html_content *html = p2; + struct content *c = (struct content *) html; + + assert(ta != NULL); + + if (gadget->type != GADGET_TEXTAREA) { + switch (key) { + case KEY_NL: + case KEY_CR: + if (form) + form_submit(content_get_url(c), html->bw, + form, 0); + return true; + + case KEY_TAB: + { + struct form_control *next_input; + /* Find next text entry field that is actually + * displayed (i.e. has an associated box) */ + for (next_input = gadget->next; + next_input && + ((next_input->type != GADGET_TEXTBOX && + next_input->type != GADGET_TEXTAREA && + next_input->type != GADGET_PASSWORD) || + !next_input->box); + next_input = next_input->next) + ; + if (!next_input) + return true; + + textarea_set_caret(ta, -1); + textarea_set_caret(next_input->data.text.ta, 0); + } + return true; + + case KEY_SHIFT_TAB: + { + struct form_control *prev_input; + /* Find previous text entry field that is actually + * displayed (i.e. has an associated box) */ + for (prev_input = gadget->prev; + prev_input && + ((prev_input->type != GADGET_TEXTBOX && + prev_input->type != GADGET_TEXTAREA && + prev_input->type != GADGET_PASSWORD) || + !prev_input->box); + prev_input = prev_input->prev) + ; + if (!prev_input) + return true; + + textarea_set_caret(ta, -1); + textarea_set_caret(prev_input->data.text.ta, 0); + } + return true; + + default: + /* Pass to textarea widget */ + break; + } + } + + return textarea_keypress(ta, key); +} + + +static void box_textarea_browser_move_callback(struct browser_window *bw, + void *p1, void *p2) +{ +} + + +static bool box_textarea_browser_paste_callback(struct browser_window *bw, + const char *utf8, unsigned utf8_len, bool last, + void *p1, void *p2) +{ + printf("AWWOOOOOGA!\n"); + return true; +} + + +/** + * Callback for html form textareas. + */ +static void box_textarea_callback(void *data, struct textarea_msg *msg) +{ + struct form_textarea_data *d = data; + struct html_content *html = d->html; + struct form_control *gadget = d->gadget; + struct box *box = gadget->box; + + switch (msg->type) { + case TEXTAREA_MSG_DRAG_REPORT: + if (msg->data.drag == TEXTAREA_DRAG_NONE) { + /* Textarea drag finished */ + html_drag_type drag_type = HTML_DRAG_NONE; + union html_drag_owner drag_owner; + drag_owner.no_owner = true; + + html_set_drag_type(d->html, drag_type, drag_owner, + NULL); + } else { + /* Textarea drag started */ + struct rect rect = { + .x0 = INT_MIN, + .y0 = INT_MIN, + .x1 = INT_MAX, + .y1 = INT_MAX + }; + html_drag_type drag_type; + union html_drag_owner drag_owner; + drag_owner.textarea = box; + + switch (msg->data.drag) { + case TEXTAREA_DRAG_SCROLLBAR: + drag_type = HTML_DRAG_TEXTAREA_SCROLLBAR; + break; + case TEXTAREA_DRAG_SELECTION: + drag_type = HTML_DRAG_TEXTAREA_SELECTION; + break; + default: + LOG(("Drag type not handled.")); + assert(0); + break; + } + + html_set_drag_type(d->html, drag_type, drag_owner, + &rect); + } + break; + + case TEXTAREA_MSG_REDRAW_REQUEST: + /* Redraw the textarea */ + /* TODO: don't redraw whole box, just the part asked for */ + html__redraw_a_box(html, box); + break; + + case TEXTAREA_MSG_MOVED_CARET: + if (html->bw == NULL) + break; + + if (msg->data.caret.hidden) { + browser_window_remove_caret(html->bw); + } else { + int x, y; + box_coords(box, &x, &y); + browser_window_place_caret(html->bw, + x + msg->data.caret.x, + y + msg->data.caret.y, + msg->data.caret.height, + box_textarea_browser_caret_callback, + box_textarea_browser_paste_callback, + box_textarea_browser_move_callback, + box, html); + } + break; + } +} + + +/* Exported interface, documented in box_textarea.h */ +bool box_textarea_create_textarea(html_content *html, + struct box *box, struct dom_node *node) +{ + dom_string *dom_text = NULL; + dom_exception err; + textarea_setup ta_setup; + textarea_flags ta_flags; + plot_font_style_t fstyle; + struct form_control *gadget = box->gadget; + const char *text; + + /** TODO: Read only textarea */ + + assert(gadget != NULL); + assert(gadget->type == GADGET_TEXTAREA || + gadget->type == GADGET_TEXTBOX || + gadget->type == GADGET_PASSWORD); + + if (gadget->type == GADGET_TEXTAREA) { + ta_flags = TEXTAREA_MULTILINE; + + /* Get the textarea's initial content */ + err = dom_node_get_text_content(node, &dom_text); + if (err != DOM_NO_ERR) + return false; + + } else { + dom_html_input_element *input = (dom_html_input_element *) node; + + if (gadget->type == GADGET_PASSWORD) + ta_flags = TEXTAREA_PASSWORD; + else + ta_flags = TEXTAREA_DEFAULT; + + /* Get initial text */ + err = dom_html_input_element_get_value(input, &dom_text); + if (err != DOM_NO_ERR) + return false; + } + + if (dom_text != NULL) { + text = dom_string_data(dom_text); + } else { + /* No initial text, or failed reading it; + * use a blank string */ + text = ""; + } + + gadget->data.text.data.html = html; + gadget->data.text.data.gadget = gadget; + + font_plot_style_from_css(gadget->box->style, &fstyle); + + /* Reset to correct values by layout */ + ta_setup.width = 200; + ta_setup.height = 20; + ta_setup.pad_top = 4; + ta_setup.pad_right = 4; + ta_setup.pad_bottom = 4; + ta_setup.pad_left = 4; + + /* Set remaining data */ + ta_setup.border_width = 0; + ta_setup.border_col = 0x000000; + ta_setup.text = fstyle; + ta_setup.text.background = NS_TRANSPARENT; + /* Make selected text either black or white, as gives greatest contrast + * with background colour. (Calc lightness of background colour and + * choose the one the lightness is furthest from.) */ + ta_setup.selected_text = + (((((fstyle.foreground & 0x0000ff) ) * 19) / 64 + + (((fstyle.foreground & 0x00ff00) >> 8) * 38) / 64 + + (((fstyle.foreground & 0xff0000) >> 16) * 7) / 64) > + (0xff / 2)) ? 0x000000 : 0xffffff; + ta_setup.selected_bg = fstyle.foreground; + + /* Hand reference to dom text over to gadget */ + gadget->data.text.initial = dom_text; + + gadget->data.text.ta = textarea_create(ta_flags, &ta_setup, + box_textarea_callback, &gadget->data.text.data); + + if (gadget->data.text.ta == NULL) { + return false; + } + + if (!textarea_set_text(gadget->data.text.ta, text)) + return false; + + return true; +} + diff --git a/render/textinput.h b/render/box_textarea.h index 5aa747f75..30414e816 100644 --- a/render/textinput.h +++ b/render/box_textarea.h @@ -1,8 +1,5 @@ /* - * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> - * Copyright 2004 James Bursa <bursa@users.sourceforge.net> - * Copyright 2004 Andrew Timmins <atimmins@blueyonder.co.uk> - * Copyright 2004 John Tytgat <joty@netsurf-browser.org> + * Copyright 2013 Michael Drake <tlsa@netsurf-browser.org> * * This file is part of NetSurf, http://www.netsurf-browser.org/ * @@ -20,22 +17,28 @@ */ /** \file - * HTML form text input handling (interface) + * Box tree treeview box replacement (interface). */ -#ifndef _NETSURF_RENDER_TEXTINPUT_H_ -#define _NETSURF_RENDER_TEXTINPUT_H_ -#include <stdbool.h> -struct box; -struct content; +#ifndef _NETSURF_RENDER_BOX_TEXTAREA_H_ +#define _NETSURF_RENDER_BOX_TEXTAREA_H_ -void textinput_textarea_click(struct content *c, browser_mouse_state mouse, - struct box *textarea, int box_x, int box_y, int x, int y); +#include "render/box.h" +#include "render/html_internal.h" -void textinput_input_click(struct content *c, struct box *input, - int box_x, int box_y, int x, int y); +struct dom_node; + +/** + * Create textarea widget for a form element + * + * \param html html content object + * \param box box with gadget to be given textarea widget + * \param node DOM node for form element + */ +bool box_textarea_create_textarea(html_content *html, + struct box *box, struct dom_node *node); #endif diff --git a/render/form.c b/render/form.c index 42e76e1f2..c2819b479 100644 --- a/render/form.c +++ b/render/form.c @@ -43,6 +43,7 @@ #include "desktop/plot_style.h" #include "desktop/plotters.h" #include "desktop/scrollbar.h" +#include "desktop/textarea.h" #include "render/box.h" #include "render/font.h" #include "render/form.h" @@ -85,7 +86,6 @@ static plot_font_style_t plot_fstyle_entry = { .foreground = 0x000000, }; -static char *form_textarea_value(struct form_control *textarea); static char *form_acceptable_charset(struct form *form); static char *form_encode_item(const char *item, const char *charset, const char *fallback); @@ -252,6 +252,17 @@ void form_free_control(struct form_control *control) form_free_select_menu(control); } + if (control->type == GADGET_TEXTAREA || + control->type == GADGET_TEXTBOX || + control->type == GADGET_PASSWORD) { + + if (control->data.text.initial != NULL) + dom_string_unref(control->data.text.initial); + + if (control->data.text.ta != NULL) + textarea_destroy(control->data.text.ta); + } + free(control); } @@ -350,8 +361,6 @@ bool form_successful_controls(struct form *form, switch (control->type) { case GADGET_HIDDEN: - case GADGET_TEXTBOX: - case GADGET_PASSWORD: if (control->value) value = ENCODE_ITEM(control->value); else @@ -416,17 +425,26 @@ bool form_successful_controls(struct form *form, continue; break; + case GADGET_TEXTBOX: + case GADGET_PASSWORD: case GADGET_TEXTAREA: - { + { char *v2; + int ta_len = textarea_get_text( + control->data.text.ta, + NULL, 0); - /* textarea */ - value = form_textarea_value(control); + value = malloc(ta_len); if (!value) { LOG(("failed handling textarea")); goto no_memory; } - if (value[0] == 0) { + textarea_get_text(control->data.text.ta, + value, ta_len); + + if (control->type == GADGET_TEXTAREA && + value[0] == '\0') { + /* Textarea not submitted if empty */ free(value); continue; } @@ -440,7 +458,7 @@ bool form_successful_controls(struct form *form, free(value); value = v2; - } + } break; case GADGET_IMAGE: { @@ -617,59 +635,6 @@ no_memory: /** - * Find the value for a textarea control. - * - * \param textarea control of type GADGET_TEXTAREA - * \return the value as a UTF-8 string on heap, or 0 on memory exhaustion - */ -char *form_textarea_value(struct form_control *textarea) -{ - unsigned int len = 0; - char *value, *s; - struct box *text_box; - - /* Textarea may have no associated box if styled with display: none */ - if (textarea->box == NULL) { - /* Return the empty string: caller treats this as a - * non-successful control. */ - return strdup(""); - } - - /* find required length */ - for (text_box = textarea->box->children->children; text_box; - text_box = text_box->next) { - if (text_box->type == BOX_TEXT) - len += text_box->length + 1; - else /* BOX_BR */ - len += 2; - } - - /* construct value */ - s = value = malloc(len + 1); - if (!s) - return NULL; - - for (text_box = textarea->box->children->children; text_box; - text_box = text_box->next) { - if (text_box->type == BOX_TEXT) { - strncpy(s, text_box->text, text_box->length); - s += text_box->length; - if (text_box->next && text_box->next->type != BOX_BR) - /* only add space if this isn't - * the last box on a line (or in the area) */ - *s++ = ' '; - } else { /* BOX_BR */ - *s++ = '\r'; - *s++ = '\n'; - } - } - *s = 0; - - return value; -} - - -/** * Encode controls using application/x-www-form-urlencoded. * * \param form form to which successful controls relate diff --git a/render/form.h b/render/form.h index 67372d5d5..b5f6a7e2c 100644 --- a/render/form.h +++ b/render/form.h @@ -34,6 +34,7 @@ struct form_control; struct form_option; struct form_select_menu; struct html_content; +struct dom_string; /** Form submit method. */ typedef enum { @@ -73,6 +74,12 @@ typedef enum { GADGET_BUTTON } form_control_type; +/** Data for textarea */ +struct form_textarea_data { + struct html_content *html; + struct form_control *gadget; +}; + /** Form control. */ struct form_control { void *node; /**< Corresponding DOM node */ @@ -111,6 +118,11 @@ struct form_control { struct form_option *current; struct form_select_menu *menu; } select; + struct { + struct textarea *ta; + struct dom_string *initial; + struct form_textarea_data data; + } text; /**< input type=text or textarea */ } data; struct form_control *prev; /**< Previous control in this form */ diff --git a/render/html.c b/render/html.c index 3e26928fd..4fc152a84 100644 --- a/render/html.c +++ b/render/html.c @@ -35,6 +35,7 @@ #include "desktop/options.h" #include "desktop/selection.h" #include "desktop/scrollbar.h" +#include "desktop/textarea.h" #include "image/bitmap.h" #include "render/box.h" #include "render/font.h" @@ -343,7 +344,8 @@ html_create_html_data(html_content *c, const http_parameter *params) c->iframe = NULL; c->page = NULL; c->font_func = &nsfont; - c->scrollbar = NULL; + c->drag_type = HTML_DRAG_NONE; + c->drag_owner.no_owner = true; c->scripts_count = 0; c->scripts = NULL; c->jscontext = NULL; @@ -1307,6 +1309,29 @@ html_object_callback(hlcache_handle *object, content_broadcast(&c->base, event->type, event->data); break; + case CONTENT_MSG_DRAG: + { + html_drag_type drag_type = HTML_DRAG_NONE; + union html_drag_owner drag_owner; + drag_owner.content = box; + + switch (event->data.drag.type) { + case CONTENT_DRAG_NONE: + drag_type = HTML_DRAG_NONE; + drag_owner.no_owner = true; + break; + case CONTENT_DRAG_SCROLL: + drag_type = HTML_DRAG_CONTENT_SCROLL; + break; + case CONTENT_DRAG_SELECTION: + drag_type = HTML_DRAG_CONTENT_SELECTION; + break; + } + html_set_drag_type(c, drag_type, drag_owner, + event->data.drag.rect); + } + break; + default: assert(0); } @@ -2629,8 +2654,8 @@ html_get_contextual_content(struct content *c, * \param c html content to look inside * \param x x-coordinate of point of interest * \param y y-coordinate of point of interest - * \param scrx x-coordinate of point of interest - * \param scry y-coordinate of point of interest + * \param scrx number of px try to scroll something in x direction + * \param scry number of px try to scroll something in y direction * \return true iff scroll was consumed by something in the content */ static bool @@ -2657,6 +2682,14 @@ html_scroll_at_point(struct content *c, int x, int y, int scrx, int scry) x - box_x, y - box_y, scrx, scry) == true) return true; + /* Pass into textarea widget */ + if (box->gadget && (box->gadget->type == GADGET_TEXTAREA || + box->gadget->type == GADGET_PASSWORD || + box->gadget->type == GADGET_TEXTBOX) && + textarea_scroll(box->gadget->data.text.ta, + scrx, scry) == true) + return true; + /* Pass into object */ if (box->object != NULL && content_scroll_at_point( box->object, x - box_x, y - box_y, @@ -2763,7 +2796,7 @@ static bool html_drop_file_at_point(struct content *c, int x, int y, char *file) /* Redraw box. */ html__redraw_a_box(html, file_box); - } else if (html->bw != NULL) { + } else { /* File dropped on text input */ size_t file_len; @@ -2772,7 +2805,7 @@ static bool html_drop_file_at_point(struct content *c, int x, int y, char *file) char *utf8_buff; utf8_convert_ret ret; unsigned int size; - struct browser_window *bw; + int bx, by; /* Open file */ fp = fopen(file, "rb"); @@ -2828,13 +2861,13 @@ static bool html_drop_file_at_point(struct content *c, int x, int y, char *file) size = strlen(utf8_buff); /* Simulate a click over the input box, to place caret */ - browser_window_mouse_click(html->bw, - BROWSER_MOUSE_PRESS_1, x, y); - - bw = browser_window_get_root(html->bw); + box_coords(text_box, &bx, &by); + textarea_mouse_action(text_box->gadget->data.text.ta, + BROWSER_MOUSE_PRESS_1, x - bx, y - by); /* Paste the file as text */ - browser_window_paste_text(bw, utf8_buff, size, true); + textarea_drop_text(text_box->gadget->data.text.ta, + utf8_buff, size); free(utf8_buff); } diff --git a/render/html.h b/render/html.h index e11fc76ac..a9f7967f6 100644 --- a/render/html.h +++ b/render/html.h @@ -46,6 +46,7 @@ struct http_parameter; struct imagemap; struct object_params; struct plotters; +struct textarea; struct scrollbar; struct scrollbar_msg_data; struct search_context; diff --git a/render/html_forms.c b/render/html_forms.c index d1223819a..c80edc6bb 100644 --- a/render/html_forms.c +++ b/render/html_forms.c @@ -523,7 +523,8 @@ invent_fake_gadget(dom_node *node) } /* documented in html_internal.h */ -struct form_control *html_forms_get_control_for_node(struct form *forms, dom_node *node) +struct form_control *html_forms_get_control_for_node(struct form *forms, + dom_node *node) { struct form *f; struct form_control *ctl = NULL; diff --git a/render/html_interaction.c b/render/html_interaction.c index d22869edc..dfebc2577 100644 --- a/render/html_interaction.c +++ b/render/html_interaction.c @@ -35,13 +35,13 @@ #include "desktop/options.h" #include "desktop/scrollbar.h" #include "desktop/selection.h" +#include "desktop/textarea.h" #include "desktop/textinput.h" #include "render/box.h" #include "render/font.h" #include "render/form.h" #include "render/html_internal.h" #include "render/imagemap.h" -#include "render/textinput.h" #include "javascript/js.h" #include "utils/messages.h" #include "utils/utils.h" @@ -224,9 +224,10 @@ void html_mouse_track(struct content *c, struct browser_window *bw, browser_mouse_state mouse, int x, int y) { html_content *html = (html_content*) c; - browser_drag_type drag_type = browser_window_get_drag_type(bw); + union html_drag_owner drag_owner; - if (drag_type == DRAGGING_SELECTION && !mouse) { + if (html->drag_type == HTML_DRAG_SELECTION && !mouse) { + /* End of selection drag */ int dir = -1; size_t idx; @@ -238,40 +239,37 @@ void html_mouse_track(struct content *c, struct browser_window *bw, if (idx != 0) selection_track(&html->sel, mouse, idx); - browser_window_set_drag_type(bw, DRAGGING_NONE, NULL); + drag_owner.no_owner = true; + html_set_drag_type(html, HTML_DRAG_NONE, drag_owner, NULL); } - switch (drag_type) { - case DRAGGING_SELECTION: { - struct box *box; - int dir = -1; - int dx, dy; + if (html->drag_type == HTML_DRAG_SELECTION) { + /* Selection drag */ + struct box *box; + int dir = -1; + int dx, dy; - if (selection_dragging_start(&html->sel)) - dir = 1; + if (selection_dragging_start(&html->sel)) + dir = 1; - box = box_pick_text_box(html, x, y, dir, &dx, &dy); + box = box_pick_text_box(html, x, y, dir, &dx, &dy); - if (box) { - int pixel_offset; - size_t idx; - plot_font_style_t fstyle; + if (box != NULL) { + int pixel_offset; + size_t idx; + plot_font_style_t fstyle; - font_plot_style_from_css(box->style, &fstyle); + font_plot_style_from_css(box->style, &fstyle); - nsfont.font_position_in_string(&fstyle, - box->text, box->length, - dx, &idx, &pixel_offset); + nsfont.font_position_in_string(&fstyle, + box->text, box->length, + dx, &idx, &pixel_offset); - selection_track(&html->sel, mouse, - box->byte_offset + idx); - } + selection_track(&html->sel, mouse, + box->byte_offset + idx); } - break; - - default: - html_mouse_action(c, bw, mouse, x, y); - break; + } else { + html_mouse_action(c, bw, mouse, x, y); } } @@ -356,29 +354,30 @@ void html_mouse_action(struct content *c, struct browser_window *bw, return; } - if (!mouse && html->scrollbar != NULL) { - /* drag end: scrollbar */ - html_overflow_scroll_drag_end(html->scrollbar, mouse, x, y); - } + if (html->drag_type == HTML_DRAG_SCROLLBAR) { + struct scrollbar *scr = html->drag_owner.scrollbar; + struct html_scrollbar_data *data = scrollbar_get_data(scr); + + if (!mouse) { + /* drag end: scrollbar */ + html_overflow_scroll_drag_end(scr, mouse, x, y); + } - if (html->scrollbar != NULL) { - struct html_scrollbar_data *data = - scrollbar_get_data(html->scrollbar); box = data->box; box_coords(box, &box_x, &box_y); - if (scrollbar_is_horizontal(html->scrollbar)) { + if (scrollbar_is_horizontal(scr)) { scroll_mouse_x = x - box_x ; scroll_mouse_y = y - (box_y + box->padding[TOP] + box->height + box->padding[BOTTOM] - SCROLLBAR_WIDTH); - status = scrollbar_mouse_action(html->scrollbar, mouse, + status = scrollbar_mouse_action(scr, mouse, scroll_mouse_x, scroll_mouse_y); } else { scroll_mouse_x = x - (box_x + box->padding[LEFT] + box->width + box->padding[RIGHT] - SCROLLBAR_WIDTH); scroll_mouse_y = y - box_y; - status = scrollbar_mouse_action(html->scrollbar, mouse, + status = scrollbar_mouse_action(scr, mouse, scroll_mouse_x, scroll_mouse_y); } @@ -387,8 +386,35 @@ void html_mouse_action(struct content *c, struct browser_window *bw, return; } + if (html->drag_type == HTML_DRAG_TEXTAREA_SELECTION || + html->drag_type == HTML_DRAG_TEXTAREA_SCROLLBAR) { + box = html->drag_owner.textarea; + assert(box->gadget != NULL); + assert(box->gadget->type == GADGET_TEXTAREA || + box->gadget->type == GADGET_PASSWORD || + box->gadget->type == GADGET_TEXTBOX); + + box_coords(box, &box_x, &box_y); + textarea_mouse_action(box->gadget->data.text.ta, mouse, + x - box_x, y - box_y); + + /* TODO: Set appropriate statusbar message */ + return; + } + + if (html->drag_type == HTML_DRAG_CONTENT_SELECTION || + html->drag_type == HTML_DRAG_CONTENT_SCROLL) { + box = html->drag_owner.content; + assert(box->object != NULL); + + box_coords(box, &box_x, &box_y); + content_mouse_track(box->object, bw, mouse, + x - box_x, y - box_y); + return; + } + /* Content related drags handled by now */ - browser_window_set_drag_type(bw, DRAGGING_NONE, NULL); + assert(html->drag_type == HTML_DRAG_NONE); /* search the box tree for a link, imagemap, form control, or * box with scrollbars @@ -592,94 +618,18 @@ void html_mouse_action(struct content *c, struct browser_window *bw, status = messages_get("FormBadSubmit"); } break; - case GADGET_TEXTAREA: - status = messages_get("FormTextarea"); - pointer = get_pointer_shape(gadget_box, false); - - if (mouse & (BROWSER_MOUSE_PRESS_1 | - BROWSER_MOUSE_PRESS_2)) { - if (text_box && selection_root(&html->sel) != - gadget_box) - selection_init(&html->sel, gadget_box); - - textinput_textarea_click(c, mouse, - gadget_box, - gadget_box_x, - gadget_box_y, - x - gadget_box_x, - y - gadget_box_y); - } - - if (text_box) { - int pixel_offset; - size_t idx; - - font_plot_style_from_css(text_box->style, - &fstyle); - - nsfont.font_position_in_string(&fstyle, - text_box->text, - text_box->length, - x - gadget_box_x - text_box->x, - &idx, - &pixel_offset); - - selection_click(&html->sel, mouse, - text_box->byte_offset + idx); - - if (selection_dragging(&html->sel)) { - browser_window_set_drag_type(bw, - DRAGGING_SELECTION, - NULL); - status = messages_get("Selecting"); - } - } - else if (mouse & BROWSER_MOUSE_PRESS_1) - selection_clear(&html->sel, true); - break; case GADGET_TEXTBOX: case GADGET_PASSWORD: - status = messages_get("FormTextbox"); - pointer = get_pointer_shape(gadget_box, false); - - if ((mouse & BROWSER_MOUSE_PRESS_1) && - !(mouse & (BROWSER_MOUSE_MOD_1 | - BROWSER_MOUSE_MOD_2))) { - textinput_input_click(c, - gadget_box, - gadget_box_x, - gadget_box_y, - x - gadget_box_x, - y - gadget_box_y); - } - if (text_box) { - int pixel_offset; - size_t idx; - - if (mouse & (BROWSER_MOUSE_DRAG_1 | - BROWSER_MOUSE_DRAG_2)) - selection_init(&html->sel, gadget_box); - - font_plot_style_from_css(text_box->style, - &fstyle); - - nsfont.font_position_in_string(&fstyle, - text_box->text, - text_box->length, - x - gadget_box_x - text_box->x, - &idx, - &pixel_offset); + case GADGET_TEXTAREA: + if (gadget->type == GADGET_TEXTAREA) + status = messages_get("FormTextarea"); + else + status = messages_get("FormTextbox"); - selection_click(&html->sel, mouse, - text_box->byte_offset + idx); + pointer = get_pointer_shape(gadget_box, false); - if (selection_dragging(&html->sel)) - browser_window_set_drag_type(bw, - DRAGGING_SELECTION, - NULL); - } - else if (mouse & BROWSER_MOUSE_PRESS_1) - selection_clear(&html->sel, true); + textarea_mouse_action(gadget->data.text.ta, mouse, + x - gadget_box_x, y - gadget_box_y); break; case GADGET_HIDDEN: /* not possible: no box generated */ @@ -810,11 +760,16 @@ void html_mouse_action(struct content *c, struct browser_window *bw, /* key presses must be directed at the * main browser window, paste text * operations ignored */ + html_drag_type drag_type; + union html_drag_owner drag_owner; if (selection_dragging(&html->sel)) { - browser_window_set_drag_type(bw, - DRAGGING_SELECTION, - NULL); + drag_type = HTML_DRAG_SELECTION; + drag_owner.no_owner = true; + html_set_drag_type(html, + drag_type, + drag_owner, + NULL); status = messages_get( "Selecting"); } @@ -921,35 +876,34 @@ void html_overflow_scroll_callback(void *client_data, html_content *html = (html_content *)data->c; struct box *box = data->box; union content_msg_data msg_data; + html_drag_type drag_type; + union html_drag_owner drag_owner; switch(scrollbar_data->msg) { - case SCROLLBAR_MSG_MOVED: - html__redraw_a_box(html, box); - break; - case SCROLLBAR_MSG_SCROLL_START: - { - struct rect rect = { - .x0 = scrollbar_data->x0, - .y0 = scrollbar_data->y0, - .x1 = scrollbar_data->x1, - .y1 = scrollbar_data->y1 - }; - browser_window_set_drag_type(html->bw, - DRAGGING_CONTENT_SCROLLBAR, &rect); - - html->scrollbar = scrollbar_data->scrollbar; - } - break; - case SCROLLBAR_MSG_SCROLL_FINISHED: - html->scrollbar = NULL; - - browser_window_set_drag_type(html->bw, - DRAGGING_NONE, NULL); + case SCROLLBAR_MSG_MOVED: + html__redraw_a_box(html, box); + break; + case SCROLLBAR_MSG_SCROLL_START: + { + struct rect rect = { + .x0 = scrollbar_data->x0, + .y0 = scrollbar_data->y0, + .x1 = scrollbar_data->x1, + .y1 = scrollbar_data->y1 + }; + drag_type = HTML_DRAG_SCROLLBAR; + drag_owner.scrollbar = scrollbar_data->scrollbar; + html_set_drag_type(html, drag_type, drag_owner, &rect); + } + break; + case SCROLLBAR_MSG_SCROLL_FINISHED: + drag_type = HTML_DRAG_NONE; + drag_owner.no_owner = true; + html_set_drag_type(html, drag_type, drag_owner, NULL); - msg_data.pointer = BROWSER_POINTER_AUTO; - content_broadcast(data->c, CONTENT_MSG_POINTER, - msg_data); - break; + msg_data.pointer = BROWSER_POINTER_AUTO; + content_broadcast(data->c, CONTENT_MSG_POINTER, msg_data); + break; } } @@ -988,3 +942,40 @@ void html_overflow_scroll_drag_end(struct scrollbar *scrollbar, scroll_mouse_x, scroll_mouse_y); } } + +/* Documented in html_internal.h */ +void html_set_drag_type(html_content *html, html_drag_type drag_type, + union html_drag_owner drag_owner, const struct rect *rect) +{ + union content_msg_data msg_data; + + assert(html != NULL); + + html->drag_type = drag_type; + html->drag_owner = drag_owner; + + switch (drag_type) { + case HTML_DRAG_NONE: + assert(drag_owner.no_owner == true); + msg_data.drag.type = CONTENT_DRAG_NONE; + break; + + case HTML_DRAG_SCROLLBAR: + case HTML_DRAG_TEXTAREA_SCROLLBAR: + case HTML_DRAG_CONTENT_SCROLL: + msg_data.drag.type = CONTENT_DRAG_SCROLL; + break; + + case HTML_DRAG_SELECTION: + assert(drag_owner.no_owner == true); + /* Fall through */ + case HTML_DRAG_TEXTAREA_SELECTION: + case HTML_DRAG_CONTENT_SELECTION: + msg_data.drag.type = CONTENT_DRAG_SELECTION; + break; + } + msg_data.drag.rect = rect; + + /* Inform the content's drag status change */ + content_broadcast((struct content *)html, CONTENT_MSG_DRAG, msg_data); +} diff --git a/render/html_internal.h b/render/html_internal.h index 53021a15a..3e562bc39 100644 --- a/render/html_internal.h +++ b/render/html_internal.h @@ -27,6 +27,22 @@ #include "desktop/selection.h" #include "render/html.h" +typedef enum { + HTML_DRAG_NONE, /** No drag */ + HTML_DRAG_SELECTION, /** Own; Text selection */ + HTML_DRAG_SCROLLBAR, /** Not own; drag in scrollbar widget */ + HTML_DRAG_TEXTAREA_SELECTION, /** Not own; drag in textarea widget */ + HTML_DRAG_TEXTAREA_SCROLLBAR, /** Not own; drag in textarea widget */ + HTML_DRAG_CONTENT_SELECTION, /** Not own; drag in child content */ + HTML_DRAG_CONTENT_SCROLL /** Not own; drag in child content */ +} html_drag_type; +union html_drag_owner { + bool no_owner; + struct box *content; + struct scrollbar *scrollbar; + struct box *textarea; +}; /**< For drags we don't own */ + /** Data specific to CONTENT_HTML. */ typedef struct html_content { struct content base; @@ -98,9 +114,10 @@ typedef struct html_content { * object within a page. */ struct html_content *page; - /** Scrollbar capturing all mouse events, updated to any active HTML - * scrollbar, or NULL when no scrollbar drags active */ - struct scrollbar *scrollbar; + /* Current drag type */ + html_drag_type drag_type; + /** Widget capturing all mouse events */ + union html_drag_owner drag_owner; /** Open core-handled form SELECT menu, * or NULL if none currently open. */ @@ -124,6 +141,17 @@ void html_set_status(html_content *c, const char *extra); void html__redraw_a_box(html_content *html, struct box *box); +/** + * Set our drag status, and inform whatever owns the content + * + * \param html HTML content + * \param drag_type Type of drag + * \param drag_owner What owns the drag + * \param rect Pointer movement bounds + */ +void html_set_drag_type(html_content *html, html_drag_type drag_type, + union html_drag_owner drag_owner, const struct rect *rect); + struct browser_window *html_get_browser_window(struct content *c); struct search_context *html_get_search(struct content *c); void html_set_search(struct content *c, struct search_context *s); @@ -162,7 +190,8 @@ bool html_scripts_exec(html_content *c); /* in render/html_forms.c */ struct form *html_forms_get_forms(const char *docenc, dom_html_document *doc); -struct form_control *html_forms_get_control_for_node(struct form *forms, dom_node *node); +struct form_control *html_forms_get_control_for_node(struct form *forms, + dom_node *node); /* Useful dom_string pointers */ struct dom_string; diff --git a/render/html_redraw.c b/render/html_redraw.c index e305b7b08..1dbe093e8 100644 --- a/render/html_redraw.c +++ b/render/html_redraw.c @@ -41,6 +41,7 @@ #include "desktop/options.h" #include "desktop/print.h" #include "desktop/scrollbar.h" +#include "desktop/textarea.h" #include "image/bitmap.h" #include "render/box.h" #include "render/font.h" @@ -2109,7 +2110,11 @@ bool html_redraw_box(const html_content *html, struct box *box, bg_box->type != BOX_TEXT && bg_box->type != BOX_INLINE_END && (bg_box->type != BOX_INLINE || bg_box->object || - bg_box->flags & IFRAME || box->flags & REPLACE_DIM)) { + bg_box->flags & IFRAME || box->flags & REPLACE_DIM || + (bg_box->gadget != NULL && + (bg_box->gadget->type == GADGET_TEXTAREA || + bg_box->gadget->type == GADGET_TEXTBOX || + bg_box->gadget->type == GADGET_PASSWORD)))) { /* find intersection of clip box and border edge */ struct rect p; p.x0 = x - border_left < r.x0 ? r.x0 : x - border_left; @@ -2154,7 +2159,11 @@ bool html_redraw_box(const html_content *html, struct box *box, if (box->style && box->type != BOX_TEXT && box->type != BOX_INLINE_END && (box->type != BOX_INLINE || box->object || - box->flags & IFRAME || box->flags & REPLACE_DIM) && + box->flags & IFRAME || box->flags & REPLACE_DIM || + (box->gadget != NULL && + (box->gadget->type == GADGET_TEXTAREA || + box->gadget->type == GADGET_TEXTBOX || + box->gadget->type == GADGET_PASSWORD))) && (border_top || border_right || border_bottom || border_left)) { if (!html_redraw_borders(box, x_parent, y_parent, @@ -2398,6 +2407,13 @@ bool html_redraw_box(const html_content *html, struct box *box, current_background_color, ctx)) return false; + } else if (box->gadget && + (box->gadget->type == GADGET_TEXTAREA || + box->gadget->type == GADGET_PASSWORD || + box->gadget->type == GADGET_TEXTBOX)) { + textarea_redraw(box->gadget->data.text.ta, x, y, + current_background_color, scale, &r, ctx); + } else if (box->text) { if (!html_redraw_text_box(html, box, x, y, &r, scale, current_background_color, ctx)) diff --git a/render/layout.c b/render/layout.c index 3fd50d18d..80d470c7a 100644 --- a/render/layout.c +++ b/render/layout.c @@ -46,6 +46,7 @@ #include "content/content_protected.h" #include "desktop/options.h" #include "desktop/scrollbar.h" +#include "desktop/textarea.h" #include "render/box.h" #include "render/font.h" #include "render/form.h" @@ -650,6 +651,20 @@ bool layout_block_context(struct box *block, int viewport_height, layout_apply_minmax_height(block, NULL); } + if (block->gadget && + (block->gadget->type == GADGET_TEXTAREA || + block->gadget->type == GADGET_PASSWORD || + block->gadget->type == GADGET_TEXTBOX)) { + int ta_width = block->padding[LEFT] + block->width + + block->padding[RIGHT]; + int ta_height = block->padding[TOP] + block->height + + block->padding[BOTTOM]; + textarea_set_layout(block->gadget->data.text.ta, + ta_width, ta_height, + block->padding[TOP], block->padding[RIGHT], + block->padding[BOTTOM], block->padding[LEFT]); + } + return true; } @@ -1431,8 +1446,10 @@ void layout_float_find_dimensions(int available_width, if (margin[RIGHT] == AUTO) margin[RIGHT] = 0; - padding[RIGHT] += scrollbar_width; - padding[BOTTOM] += scrollbar_width; + if (box->gadget == NULL) { + padding[RIGHT] += scrollbar_width; + padding[BOTTOM] += scrollbar_width; + } if (box->object && !(box->flags & REPLACE_DIM) && content_get_type(box->object) != CONTENT_HTML) { diff --git a/render/textinput.c b/render/textinput.c deleted file mode 100644 index e9c6df7b2..000000000 --- a/render/textinput.c +++ /dev/null @@ -1,2213 +0,0 @@ -/* - * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> - * Copyright 2004 James Bursa <bursa@users.sourceforge.net> - * Copyright 2004 Andrew Timmins <atimmins@blueyonder.co.uk> - * Copyright 2004 John Tytgat <joty@netsurf-browser.org> - * Copyright 2005 Adrian Lees <adrianl@users.sourceforge.net> - * - * This file is part of NetSurf, http://www.netsurf-browser.org/ - * - * NetSurf is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * NetSurf is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/** \file - * HTML form text input handling (implementation) - */ - -#include <assert.h> -#include <ctype.h> -#include <string.h> - -#include <dom/dom.h> - -#include "desktop/browser.h" -#include "desktop/gui.h" -#include "desktop/mouse.h" -#include "desktop/scrollbar.h" -#include "desktop/selection.h" -#include "desktop/textinput.h" -#include "render/box.h" -#include "render/font.h" -#include "render/form.h" -#include "render/html_internal.h" -#include "render/layout.h" -#include "render/textinput.h" -#include "utils/log.h" -#include "utils/talloc.h" -#include "utils/utf8.h" -#include "utils/utils.h" - -/* Define to enable textinput debug */ -#undef TEXTINPUT_DEBUG - - -static bool textinput_textbox_delete(struct content *c, - struct box *text_box, unsigned char_offset, - unsigned utf8_len); - -/* Textarea callbacks */ -static bool textinput_textarea_callback(struct browser_window *bw, - uint32_t key, void *p1, void *p2); -static void textinput_textarea_move_caret(struct browser_window *bw, - void *p1, void *p2); -static bool textinput_textarea_paste_text(struct browser_window *bw, - const char *utf8, unsigned utf8_len, bool last, - void *p1, void *p2); - -/* Text input callbacks */ -static bool textinput_input_callback(struct browser_window *bw, - uint32_t key, void *p1, void *p2); -static void textinput_input_move_caret(struct browser_window *bw, - void *p1, void *p2); -static bool textinput_input_paste_text(struct browser_window *bw, - const char *utf8, unsigned utf8_len, bool last, - void *p1, void *p2); - -#define SPACE_LEN(b) ((b->space == 0) ? 0 : 1) - - -static struct textinput_buffer { - char *buffer; - size_t buffer_len; - size_t length; -} textinput_buffer; - - - -/** - * Given the x,y co-ordinates of a point within a textarea, return the - * TEXT box pointer, and the character and pixel offsets within that - * box at which the caret should be positioned. (eg. for mouse clicks, - * drag-and-drop insertions etc) - * - * \param textarea the textarea being considered - * \param x x ordinate of point - * \param y y ordinate of point - * \param pchar_offset receives the char offset within the TEXT box - * \param ppixel_offset receives the pixel offset within the TEXT box - * \return pointer to TEXT box - */ - -static struct box *textinput_textarea_get_position(struct box *textarea, - int x, int y, int *pchar_offset, int *ppixel_offset) -{ - /* A textarea is an INLINE_BLOCK containing a single - * INLINE_CONTAINER, which contains the text as runs of TEXT - * separated by BR. There is at least one TEXT. The first and - * last boxes are TEXT. Consecutive BR may not be present. These - * constraints are satisfied by using a 0-length TEXT for blank - * lines. */ - - struct box *inline_container, *text_box; - plot_font_style_t fstyle; - size_t char_offset = 0; - - inline_container = textarea->children; - - if (inline_container->y + inline_container->height < y) { - /* below the bottom of the textarea: place caret at end */ - text_box = inline_container->last; - assert(text_box->type == BOX_TEXT); - assert(text_box->text); - font_plot_style_from_css(text_box->style, &fstyle); - /** \todo handle errors */ - nsfont.font_position_in_string(&fstyle, text_box->text, - text_box->length, - (unsigned int)(x - text_box->x), - &char_offset, ppixel_offset); - } else { - /* find the relevant text box */ - y -= inline_container->y; - for (text_box = inline_container->children; - text_box && - text_box->y + text_box->height < y; - text_box = text_box->next) - ; - for (; text_box && text_box->type != BOX_BR && - text_box->y <= y && - text_box->x + text_box->width < x; - text_box = text_box->next) - ; - if (!text_box) { - /* past last text box */ - text_box = inline_container->last; - assert(text_box->type == BOX_TEXT); - assert(text_box->text); - font_plot_style_from_css(text_box->style, &fstyle); - nsfont.font_position_in_string(&fstyle, - text_box->text, - text_box->length, - textarea->width, - &char_offset, - ppixel_offset); - } else { - /* in a text box */ - if (text_box->type == BOX_BR) - text_box = text_box->prev; - else if (y < text_box->y && text_box->prev) { - if (text_box->prev->type == BOX_BR) { - assert(text_box->prev->prev); - text_box = text_box->prev->prev; - } - else - text_box = text_box->prev; - } - assert(text_box->type == BOX_TEXT); - assert(text_box->text); - font_plot_style_from_css(text_box->style, &fstyle); - nsfont.font_position_in_string(&fstyle, - text_box->text, - text_box->length, - (unsigned int)(x - text_box->x), - &char_offset, - ppixel_offset); - } - } - - *pchar_offset = char_offset; - - assert(text_box); - return text_box; -} - - -/** - * Delete some text from a box, or delete the box in its entirety - * - * \param c html content - * \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 - */ - -static bool textinput_delete_handler(struct content *c, struct box *b, - int offset, size_t length) -{ - size_t text_length = b->length + SPACE_LEN(b); - - /* only remove if its not the first box */ - if (offset <= 0 && length >= text_length && b->prev != NULL) { - /* remove the entire box */ - box_unlink_and_free(b); - - return true; - } else - return textinput_textbox_delete(c, b, offset, - min(length, text_length - offset)); -} - - -/** - * Remove the selected text from a text box and gadget (if applicable) - * - * \param c The content containing the selection - * \param s The selection to be removed - */ - -static void textinput_delete_selection(struct content *c, struct selection *s) -{ - size_t start_offset, end_offset; - struct box *text_box; - struct box *end_box; - struct box *next; - size_t sel_len; - int beginning = 0; - - assert(s->defined); - - text_box = selection_get_start(s, &start_offset); - end_box = selection_get_end(s, &end_offset); - sel_len = s->end_idx - s->start_idx; - - /* Clear selection so that deletion from textboxes proceeds */ - selection_clear(s, true); - - /* handle first box */ - textinput_delete_handler(c, text_box, start_offset, sel_len); - if (text_box == end_box) - return; - - for (text_box = text_box->next; text_box != end_box; text_box = next) { - next = text_box->next; - box_unlink_and_free(text_box); - } - - textinput_delete_handler(c, end_box, beginning, end_offset); -} - - -/** - * Insert a number of chars into a text box - * - * \param c html_content - * \param text_box text box - * \param char_offset offset (bytes) at which to insert text - * \param utf8 UTF-8 text to insert - * \param utf8_len length (bytes) of UTF-8 text to insert - * \return true iff successful - */ - -static bool textinput_textbox_insert(struct content *c, - struct box *text_box, unsigned char_offset, const char *utf8, - unsigned utf8_len) -{ - html_content *html = (html_content *)c; - char *text; - struct box *input = text_box->parent->parent; - bool hide; - - if (html->bw && html->sel.defined) - textinput_delete_selection(c, &html->sel); - - /* insert into form gadget (text and password inputs only) */ - if (input->gadget && (input->gadget->type == GADGET_TEXTBOX || - input->gadget->type == GADGET_PASSWORD) && - input->gadget->value) { - size_t form_offset = input->gadget->caret_form_offset; - char *value = realloc(input->gadget->value, - input->gadget->length + utf8_len + 1); - if (!value) { - warn_user("NoMemory", 0); - return true; - } - input->gadget->value = value; - - memmove(input->gadget->value + form_offset + utf8_len, - input->gadget->value + form_offset, - input->gadget->length - form_offset); - memcpy(input->gadget->value + form_offset, utf8, utf8_len); - input->gadget->length += utf8_len; - input->gadget->value[input->gadget->length] = 0; - } - - hide = (input->gadget && input->gadget->type == GADGET_PASSWORD); - if (hide) { - /* determine the number of '*'s to be inserted */ - const char *eutf8 = utf8 + utf8_len; - utf8_len = 0; - while (utf8 < eutf8) { - utf8 += utf8_next(utf8, eutf8 - utf8, 0); - utf8_len++; - } - } - - /* insert in text box */ - text = talloc_realloc(html->bctx, text_box->text, - char, - text_box->length + SPACE_LEN(text_box) + utf8_len + 1); - if (!text) { - warn_user("NoMemory", 0); - return false; - } - text_box->text = text; - - if (text_box->space != 0 && - char_offset == text_box->length + SPACE_LEN(text_box)) { - if (hide) - text_box->space = 0; - else { - unsigned int last_off = utf8_prev(utf8, utf8_len); - if (utf8[last_off] != ' ') - text_box->space = 0; - else - utf8_len = last_off; - } - text_box->text[text_box->length++] = ' '; - } else { - memmove(text_box->text + char_offset + utf8_len, - text_box->text + char_offset, - text_box->length - char_offset); - } - - if (hide) - memset(text_box->text + char_offset, '*', utf8_len); - else - memcpy(text_box->text + char_offset, utf8, utf8_len); - text_box->length += utf8_len; - - /* nothing should assume that the text is terminated, - * but just in case */ - text_box->text[text_box->length] = 0; - - text_box->width = UNKNOWN_WIDTH; - - return true; -} - -/** - * Calculates the form_offset from the box_offset - * - * \param input The root box containing both the textbox and gadget - * \param text_box The textbox containing the caret - * \param char_offset The caret offset within text_box - * \return the translated form_offset - */ - -static size_t textinput_get_form_offset(struct box* input, struct box* text_box, - size_t char_offset) -{ - int uchars; - unsigned int offset; - - for (uchars = 0, offset = 0; offset < char_offset; uchars++) { - if ((text_box->text[offset] & 0x80) == 0x00) { - offset++; - continue; - } - assert((text_box->text[offset] & 0xC0) == 0xC0); - for (++offset; offset < char_offset && - (text_box->text[offset] & 0xC0) == 0x80; - offset++) - /* do nothing */; - } - /* uchars is the number of real Unicode characters at the left - * side of the caret. - */ - for (offset = 0; uchars > 0 && offset < input->gadget->length; - uchars--) { - if ((input->gadget->value[offset] & 0x80) == 0x00) { - offset++; - continue; - } - assert((input->gadget->value[offset] & 0xC0) == 0xC0); - for (++offset; offset < input->gadget->length && - (input->gadget->value[offset] & 0xC0) == 0x80; - offset++) - /* do nothing */; - } - assert(uchars == 0); - return offset; -} - - -/** - * Delete a number of chars from a text box - * - * \param c html content - * \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 - * \return true on success, false otherwise - * - * ::char_offset and ::utf8_len are only considered when there is no selection. - * If there is a selection, the entire selected area is deleted. - */ - -bool textinput_textbox_delete(struct content *c, struct box *text_box, - unsigned char_offset, unsigned utf8_len) -{ - html_content *html = (html_content *)c; - unsigned next_offset = char_offset + utf8_len; - struct box *form = text_box->parent->parent; - - if (html->bw && html->sel.defined) { - textinput_delete_selection(c, &html->sel); - return true; - } - - /* delete from form gadget (text and password inputs only) */ - if (form->gadget && (form->gadget->type == GADGET_TEXTBOX || - form->gadget->type == GADGET_PASSWORD) && - form->gadget->value) { - size_t form_offset = textinput_get_form_offset(form, text_box, - char_offset); - size_t next_offset = textinput_get_form_offset(form, text_box, - char_offset + utf8_len); - - memmove(form->gadget->value + form_offset, - form->gadget->value + next_offset, - form->gadget->length - next_offset); - form->gadget->length -= (next_offset - form_offset); - form->gadget->value[form->gadget->length] = 0; - } - - /* delete from visible textbox */ - if (next_offset <= text_box->length + SPACE_LEN(text_box)) { - /* handle removal of trailing space */ - if (text_box->space != 0 && next_offset > text_box->length) { - if (char_offset > 0) { - /* is the trailing character still a space? */ - int tmp = utf8_prev(text_box->text, char_offset); - if (isspace(text_box->text[tmp])) - char_offset = tmp; - else - text_box->space = 0; - } else { - text_box->space = 0; - } - - text_box->length = char_offset; - } else { - memmove(text_box->text + char_offset, - text_box->text + next_offset, - text_box->length - next_offset); - text_box->length -= utf8_len; - } - - /* nothing should assume that the text is terminated, - * but just in case */ - text_box->text[text_box->length] = 0; - - text_box->width = UNKNOWN_WIDTH; - - return true; - } - - return false; -} - -/** - * Locate the first inline box at the start of this line - * - * \param text_box text box from which to start searching - */ - -static struct box *textinput_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 - */ - -static struct box *textinput_line_end(struct box *text_box) -{ - 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. - */ - -static struct box *textinput_line_above(struct box *text_box) -{ - struct box *prev; - - text_box = textinput_line_start(text_box); - - prev = text_box->prev; - while (prev && prev->type == BOX_BR) - prev = prev->prev; - - return prev ? textinput_line_start(prev) : text_box; -} - - -/** - * Advance to the start of the next line, if there is one. - */ - -static struct box *textinput_line_below(struct box *text_box) -{ - struct box *next; - - text_box = textinput_line_end(text_box); - - next = text_box->next; - while (next && next->type == BOX_BR) - next = next->next; - - return next ? next : text_box; -} - - -/** - * Add some text to the buffer, optionally appending a trailing space. - * - * \param text text to be added - * \param length length of text in bytes - * \param space indicates whether a trailing space should be appended - * \param fstyle The font style - * \return true if successful - */ - -static bool textinput_add_to_buffer(const char *text, size_t length, bool space, - const plot_font_style_t *fstyle) -{ - size_t new_length = textinput_buffer.length + length + (space ? 1 : 0) + 1; - - if (new_length > textinput_buffer.buffer_len) { - size_t new_alloc = new_length + (new_length / 4); - char *new_buff; - - new_buff = realloc(textinput_buffer.buffer, new_alloc); - if (new_buff == NULL) - return false; - - textinput_buffer.buffer = new_buff; - textinput_buffer.buffer_len = new_alloc; - } - - memcpy(textinput_buffer.buffer + textinput_buffer.length, text, length); - textinput_buffer.length += length; - - if (space) - textinput_buffer.buffer[textinput_buffer.length++] = ' '; - - textinput_buffer.buffer[textinput_buffer.length] = '\0'; - - return true; -} - - -/** - * Empty the buffer, called prior to textinput_add_to_buffer sequence - * - * \return true iff successful - */ - -static bool textinput_empty_buffer(void) -{ - const size_t init_size = 1024; - - if (textinput_buffer.buffer_len == 0) { - textinput_buffer.buffer = malloc(init_size); - if (textinput_buffer.buffer == NULL) - return false; - - textinput_buffer.buffer_len = init_size; - } - - textinput_buffer.length = 0; - - return true; -} - - -/** - * Cut a range of text from a text box, - * possibly placing it on the global clipboard. - * - * \param c html content - * \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 - * \param clipboard whether to place text on the clipboard - * \return true iff successful - */ - -static bool textinput_textarea_cut(struct content *c, - struct box *start_box, unsigned start_idx, - struct box *end_box, unsigned end_idx, - bool clipboard) -{ - struct box *box = start_box; - bool success = true; - bool del = false; /* caller expects start_box to persist */ - - if (textinput_empty_buffer() == false) { - return 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 (clipboard && !textinput_add_to_buffer("\n", 1, - false, plot_style_font)) { - return false; - } - box_unlink_and_free(box); - } else { - /* append box text to clipboard and then delete it */ - if (clipboard && - !textinput_add_to_buffer(box->text + start_idx, - box->length - start_idx, - SPACE_LEN(box), plot_style_font)) { - return false; - } - - if (del) { - if (!textinput_delete_handler(c, box, - start_idx, - (box->length + SPACE_LEN(box)) - - start_idx) && clipboard) { - return false; - } - } else { - textinput_textbox_delete(c, box, start_idx, - (box->length + SPACE_LEN(box)) - - start_idx); - } - } - - del = true; - start_idx = 0; - box = next; - } - - /* and the last box */ - if (box) { - if (clipboard && !textinput_add_to_buffer(box->text + start_idx, - end_idx - start_idx, end_idx > box->length, - plot_style_font)) { - success = false; - } else { - if (del) { - if (!textinput_delete_handler(c, box, - start_idx, end_idx - start_idx)) - success = false; - } else { - textinput_textbox_delete(c, box, start_idx, - end_idx - start_idx); - } - } - } - - if (clipboard) { - gui_set_clipboard(textinput_buffer.buffer, - textinput_buffer.length, NULL, 0); - } - - return true; -} - - -/** - * Break a text box into two - * - * \param c html content - * \param text_box text box to be split - * \param char_offset offset (in bytes) at which text box is to be split - */ - -static struct box *textinput_textarea_insert_break(struct content *c, - struct box *text_box, size_t char_offset) -{ - html_content *html = (html_content *)c; - struct box *new_br, *new_text; - char *text; - - text = talloc_array(html->bctx, char, text_box->length + 1); - if (!text) { - warn_user("NoMemory", 0); - return NULL; - } - - new_br = box_create(NULL, text_box->style, false, 0, 0, text_box->title, - 0, html->bctx); - new_text = talloc(html->bctx, struct box); - if (!new_text) { - warn_user("NoMemory", 0); - return NULL; - } - - new_br->type = BOX_BR; - box_insert_sibling(text_box, new_br); - - memcpy(new_text, text_box, sizeof (struct box)); - new_text->flags |= CLONE; - new_text->text = text; - memcpy(new_text->text, text_box->text + char_offset, - text_box->length - char_offset); - new_text->length = text_box->length - char_offset; - text_box->length = char_offset; - text_box->width = new_text->width = UNKNOWN_WIDTH; - box_insert_sibling(new_br, new_text); - - return new_text; -} - - -/** - * Reflow textarea preserving width and height - * - * \param c html content - * \param textarea text area box - * \param inline_container container holding text box - */ - -static void textinput_textarea_reflow(struct content *c, - struct box *textarea, struct box *inline_container) -{ - int width = textarea->width; - int height = textarea->height; - - assert(c != NULL); - - if (!layout_inline_container(inline_container, width, - textarea, 0, 0, (struct html_content *) c)) - warn_user("NoMemory", 0); - textarea->width = width; - textarea->height = height; - layout_calculate_descendant_bboxes(textarea); - box_handle_scrollbars(c, textarea, - box_hscrollbar_present(textarea), - box_vscrollbar_present(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 - */ - -static bool textinput_word_left(const char *text, - size_t *poffset, size_t *pchars) -{ - size_t offset = *poffset; - bool success = false; - size_t nchars = 0; - - /* Skip any spaces immediately prior to the offset */ - while (offset > 0) { - offset = utf8_prev(text, offset); - nchars++; - if (!isspace(text[offset])) break; - } - - /* Now skip all non-space characters */ - while (offset > 0) { - size_t 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 - */ - -static bool textinput_word_right(const char *text, size_t len, - size_t *poffset, size_t *pchars) -{ - size_t offset = *poffset; - bool success = false; - size_t nchars = 0; - - /* Skip all non-space characters after the offset */ - while (offset < len) { - if (isspace(text[offset])) break; - offset = utf8_next(text, len, offset); - nchars++; - } - - /* Now skip all space characters */ - 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; -} - -/** - * Adjust scroll offsets so that the caret is visible - * - * \param c html content where click ocurred - * \param textarea textarea box - * \return true if a change in scroll offsets has occurred -*/ - -static bool textinput_ensure_caret_visible(struct content *c, - struct box *textarea) -{ - html_content *html = (html_content *)c; - int cx, cy; - int scrollx, scrolly; - - assert(textarea->gadget); - - scrollx = scrollbar_get_offset(textarea->scroll_x); - scrolly = scrollbar_get_offset(textarea->scroll_y); - - /* Calculate the caret coordinates */ - cx = textarea->gadget->caret_pixel_offset + - textarea->gadget->caret_text_box->x; - cy = textarea->gadget->caret_text_box->y; - - /* Ensure they are visible */ - if (textarea->scroll_x == NULL) { - scrollx = 0; - } else if (cx - scrollbar_get_offset(textarea->scroll_x) < 0) { - scrollx = cx; - } else if (cx > scrollbar_get_offset(textarea->scroll_x) + - textarea->width) { - scrollx = cx - textarea->width; - } - - if (textarea->scroll_y == NULL) { - scrolly = 0; - } else if (cy - scrollbar_get_offset(textarea->scroll_y) < 0) { - scrolly = cy; - } else if (cy + textarea->gadget->caret_text_box->height > - scrollbar_get_offset(textarea->scroll_y) + - textarea->height) { - scrolly = (cy + textarea->gadget->caret_text_box->height) - - textarea->height; - } - - if ((scrollx == scrollbar_get_offset(textarea->scroll_x)) && - (scrolly == scrollbar_get_offset(textarea->scroll_y))) - return false; - - if (textarea->scroll_x != NULL) { - html->scrollbar = textarea->scroll_x; - scrollbar_set(textarea->scroll_x, scrollx, false); - html->scrollbar = NULL; - } - if (textarea->scroll_y != NULL) { - html->scrollbar = textarea->scroll_x; - scrollbar_set(textarea->scroll_y, scrolly, false); - html->scrollbar = NULL; - } - - return true; -} - - -/** - * Paste a block of text into a textarea at the - * current caret position. - * - * \param bw browser window - * \param utf8 pointer to block of text - * \param utf8_len length (bytes) of text block - * \param last true iff this is the last chunk (update screen too) - * \param p1 pointer to textarea - * \param p2 html content with the text area box - * \return true iff successful - */ - -bool textinput_textarea_paste_text(struct browser_window *bw, - const char *utf8, unsigned utf8_len, bool last, - void *p1, void *p2) -{ - struct box *textarea = p1; - struct content *c = p2; - struct box *inline_container = - textarea->gadget->caret_inline_container; - struct box *text_box = textarea->gadget->caret_text_box; - size_t char_offset = textarea->gadget->caret_box_offset; - int pixel_offset = textarea->gadget->caret_pixel_offset; - const char *ep = utf8 + utf8_len; - const char *p = utf8; - bool success = true; - bool update = last; - - while (p < ep) { - struct box *new_text; - unsigned utf8_len; - - while (p < ep) { - if (*p == '\n' || *p == '\r') break; - p++; - } - - utf8_len = p - utf8; - if (!textinput_textbox_insert(c, text_box, char_offset, - utf8, utf8_len)) - return false; - - char_offset += utf8_len; - if (p == ep) - break; - - new_text = textinput_textarea_insert_break(c, text_box, - char_offset); - if (!new_text) { - /* we still need to update the screen */ - update = true; - success = false; - break; - } - - /* place caret at start of new text box */ - text_box = new_text; - char_offset = 0; - - /* handle CR/LF and LF/CR terminations */ - if ((*p == '\n' && p[1] == '\r') || - (*p == '\r' && p[1] == '\n')) - p++; - utf8 = ++p; - } - -// textarea->gadget->caret_inline_container = inline_container; - textarea->gadget->caret_text_box = text_box; - textarea->gadget->caret_box_offset = char_offset; - - if (update) { - int box_x, box_y; - plot_font_style_t fstyle; - - /* reflow textarea preserving width and height */ - textinput_textarea_reflow(c, textarea, inline_container); - /* reflowing may have broken our caret offset - * this bit should hopefully continue to work if - * textarea_reflow is fixed to update the caret itself */ - char_offset = textarea->gadget->caret_box_offset; - text_box = textarea->gadget->caret_text_box; - - while ((char_offset > text_box->length + SPACE_LEN(text_box)) && - (text_box->next) && - (text_box->next->type == BOX_TEXT)) { -#ifdef TEXTINPUT_DEBUG - LOG(("Caret out of range: Was %d in boxlen %d " - "space %d", char_offset, - text_box->length, SPACE_LEN(text_box))); -#endif - char_offset -= text_box->length + SPACE_LEN(text_box); - text_box = text_box->next; - } - - /* not sure if this will happen or not... - * but won't stick an assert here as we can recover from it */ - if (char_offset > text_box->length) { -#ifdef TEXTINPUT_DEBUG - LOG(("Caret moved beyond end of line: " - "Was %d in boxlen %d", char_offset, - text_box->length)); -#endif - char_offset = text_box->length; - } - - textarea->gadget->caret_text_box = text_box; - textarea->gadget->caret_box_offset = char_offset; - - font_plot_style_from_css(text_box->style, &fstyle); - - nsfont.font_width(&fstyle, text_box->text, - char_offset, &pixel_offset); - - textarea->gadget->caret_pixel_offset = pixel_offset; - - box_coords(textarea, &box_x, &box_y); - box_x += scrollbar_get_offset(textarea->scroll_x); - box_y += scrollbar_get_offset(textarea->scroll_y); - textinput_ensure_caret_visible(c, textarea); - box_x -= scrollbar_get_offset(textarea->scroll_x); - box_y -= scrollbar_get_offset(textarea->scroll_y); - - browser_window_place_caret(bw, - box_x + inline_container->x + text_box->x + - pixel_offset, - box_y + inline_container->y + text_box->y, - text_box->height, - textinput_textarea_callback, - textinput_textarea_paste_text, - textinput_textarea_move_caret, - textarea, c); - - html__redraw_a_box((html_content *)c, textarea); - } - - return success; -} - - -/** - * Move caret to new position after reformatting - * - * \param bw browser window - * \param p1 pointer textarea box - * \param p2 html content with the text area box - * \return none - */ - -void textinput_textarea_move_caret(struct browser_window *bw, - void *p1, void *p2) -{ - struct box *textarea = p1; - struct content *c = p2; - struct box *inline_container = textarea->gadget->caret_inline_container; - struct box *text_box = textarea->gadget->caret_text_box; - size_t char_offset = textarea->gadget->caret_box_offset; - int pixel_offset; - int box_x, box_y; - plot_font_style_t fstyle; - - font_plot_style_from_css(text_box->style, &fstyle); - - box_coords(textarea, &box_x, &box_y); - box_x -= scrollbar_get_offset(textarea->scroll_x); - box_y -= scrollbar_get_offset(textarea->scroll_y); - - nsfont.font_width(&fstyle, text_box->text, - char_offset, &pixel_offset); - - browser_window_place_caret(bw, - box_x + inline_container->x + text_box->x + - pixel_offset, - box_y + inline_container->y + text_box->y, - text_box->height, - textinput_textarea_callback, - textinput_textarea_paste_text, - textinput_textarea_move_caret, - textarea, c); -} - - -/** - * Update display to reflect modified input field - * - * \param bw browser window - * \param input input field - * \param form_offset - * \param box_offset offset of caret within text box - * \param to_textarea caret is to be moved to a textarea - * \param redraw force redraw even if field hasn't scrolled - */ - -static void textinput_input_update_display(struct content *c, struct box *input, - unsigned box_offset, bool to_textarea, bool redraw) -{ - struct box *text_box = input->children->children; - unsigned pixel_offset; - int box_x, box_y; - int dx; - plot_font_style_t fstyle; - html_content *html = (html_content *)c; - - font_plot_style_from_css(text_box->style, &fstyle); - - if (redraw) - nsfont.font_width(&fstyle, text_box->text, text_box->length, - &text_box->width); - - box_coords(input, &box_x, &box_y); - - nsfont.font_width(&fstyle, text_box->text, box_offset, - (int *) &pixel_offset); - - /* Shift text box horizontally, so caret is visible */ - dx = text_box->x; - text_box->x = 0; - if (input->width < text_box->width && - input->width / 2 < (int) pixel_offset) { - /* Make caret appear in centre of text input */ - text_box->x = input->width / 2 - pixel_offset; - /* Clamp if we've shifted too far left */ - if (text_box->x < input->width - text_box->width) - text_box->x = input->width - text_box->width; - } - dx -= text_box->x; - input->gadget->caret_pixel_offset = pixel_offset; - - if (to_textarea) { - /* moving to textarea so need to set these up */ - input->gadget->caret_inline_container = input->children; - input->gadget->caret_text_box = text_box; - } - - input->gadget->caret_box_offset = box_offset; - - browser_window_place_caret(html->bw, - box_x + input->children->x + - text_box->x + pixel_offset, - box_y + input->children->y + text_box->y, - text_box->height, - /* use the appropriate callback */ - to_textarea ? textinput_textarea_callback - : textinput_input_callback, - to_textarea ? textinput_textarea_paste_text - : textinput_input_paste_text, - to_textarea ? textinput_textarea_move_caret - : textinput_input_move_caret, - input, c); - - if (dx || redraw) - html__redraw_a_box(html, input); -} - - -/** - * Key press callback for text areas. - * - * \param bw The browser window containing the text area - * \param key The ucs4 character codepoint - * \param p1 The text area box - * \param p2 The html content with the text area box - * \return true if the keypress is dealt with, false otherwise. It can - * return true even if it ran out of memory; this just means that - * it would have claimed it if it could. - */ -bool textinput_textarea_callback(struct browser_window *bw, uint32_t key, - void *p1, void *p2) -{ - struct box *textarea = p1; - struct content *c = p2; - html_content *html = (html_content *)c; - struct box *inline_container = - textarea->gadget->caret_inline_container; - struct box *text_box = textarea->gadget->caret_text_box; - struct box *new_text; - size_t char_offset = textarea->gadget->caret_box_offset; - int pixel_offset = textarea->gadget->caret_pixel_offset; - int box_x, box_y; - char utf8[6]; - unsigned int utf8_len; - bool scrolled, reflow = false; - bool selection_exists = html->sel.defined; - plot_font_style_t fstyle; - - /* box_dump(textarea, 0); */ -#ifdef TEXTINPUT_DEBUG - LOG(("key %i at %i in '%.*s'", key, char_offset, - (int) text_box->length, text_box->text)); -#endif - - box_coords(textarea, &box_x, &box_y); - box_x -= scrollbar_get_offset(textarea->scroll_x); - box_y -= scrollbar_get_offset(textarea->scroll_y); - - if (!(key <= 0x001F || (0x007F <= key && key <= 0x009F))) { - /* normal character insertion */ - utf8_len = utf8_from_ucs4(key, utf8); - - if (!textinput_textbox_insert(c, text_box, char_offset, - utf8, utf8_len)) - return true; - - char_offset += utf8_len; - reflow = true; - - } else switch (key) { - case KEY_DELETE_LEFT: - if (selection_exists) { - /* Have a selection; delete it */ - textinput_textbox_delete(c, text_box, 0, 0); - } else if (char_offset == 0) { - /* at the start of a text box */ - struct box *prev; - - if (text_box->prev && text_box->prev->type == BOX_BR) { - /* previous box is BR: remove it */ - box_unlink_and_free(text_box->prev); - } - - /* This needs to be after the BR removal, as that may - * result in no previous box existing */ - if (!text_box->prev) - /* at very beginning of text area: ignore */ - return true; - - /* delete space by merging with previous text box */ - prev = text_box->prev; - assert(prev->type == BOX_TEXT); - assert(prev->text); - - char_offset = prev->length; /* caret at join */ - - if (!textinput_textbox_insert(c, prev, prev->length, - text_box->text, text_box->length)) - return true; - - box_unlink_and_free(text_box); - - /* place caret at join (see above) */ - text_box = prev; - - } else { - /* delete a character */ - size_t prev_offset = char_offset; - size_t new_offset = - utf8_prev(text_box->text, char_offset); - - if (textinput_textbox_delete(c, text_box, new_offset, - prev_offset - new_offset)) - char_offset = new_offset; - } - reflow = true; - break; - - case KEY_DELETE_LINE_START: - { - struct box *start_box = textinput_line_start(text_box); - - /* Clear the selection, if one exists */ - if (selection_exists) - selection_clear(&html->sel, false); - - textinput_textarea_cut(c, start_box, 0, text_box, - char_offset, false); - text_box = start_box; - char_offset = 0; - reflow = true; - } - break; - - case KEY_DELETE_LINE_END: - { - struct box *end_box = textinput_line_end(text_box); - - /* Clear the selection, if one exists */ - if (selection_exists) - selection_clear(&html->sel, false); - - if (end_box != text_box || - char_offset < text_box->length + SPACE_LEN(text_box)) { - /* there's something at the end of the line to delete */ - textinput_textarea_cut(c, text_box, - char_offset, end_box, - end_box->length + SPACE_LEN(end_box), - false); - reflow = true; - break; - } - } - /* no break */ - case KEY_DELETE_RIGHT: /* delete to right */ - if (selection_exists) { - /* Delete selection */ - textinput_textbox_delete(c, text_box, 0, 0); - } else if (char_offset >= text_box->length) { - /* at the end of a text box */ - struct box *next; - - if (text_box->next && text_box->next->type == BOX_BR) { - /* next box is a BR: remove it */ - box_unlink_and_free(text_box->next); - } - - /* This test is after the BR removal, as that may - * result in no subsequent box being present */ - if (!text_box->next) - /* at very end of text area: ignore */ - return true; - - /* delete space by merging with next text box */ - - next = text_box->next; - assert(next->type == BOX_TEXT); - assert(next->text); - - if (!textinput_textbox_insert(c, text_box, - text_box->length, - next->text, next->length)) - return true; - - box_unlink_and_free(next); - - /* leave caret at join */ - } else { - /* delete a character */ - size_t next_offset = utf8_next(text_box->text, - text_box->length, char_offset); - - textinput_textbox_delete(c, text_box, char_offset, - next_offset - char_offset); - } - reflow = true; - break; - - case KEY_NL: - case KEY_CR: /* paragraph break */ - if (selection_exists) { - /* If we have a selection, then delete it, - * so it's replaced by the break */ - textinput_textbox_delete(c, text_box, 0, 0); - } - - new_text = textinput_textarea_insert_break(c, text_box, - char_offset); - if (!new_text) - return true; - - /* place caret at start of new text box */ - text_box = new_text; - char_offset = 0; - - reflow = true; - break; - - case KEY_CUT_LINE: - { - struct box *start_box = textinput_line_start(text_box); - struct box *end_box = textinput_line_end(text_box); - - /* Clear the selection, if one exists */ - if (selection_exists) - selection_clear(&html->sel, false); - - textinput_textarea_cut(c, start_box, 0, end_box, - end_box->length, false); - - text_box = start_box; - char_offset = 0; - reflow = true; - } - break; - - case KEY_PASTE: - { - char *buff = NULL; - size_t buff_len; - bool success; - - gui_get_clipboard(&buff, &buff_len); - if (buff == NULL) - return false; - - success = browser_window_paste_text(bw, buff, buff_len, true); - free(buff); - - return success; - } - - case KEY_CUT_SELECTION: - { - size_t start_idx, end_idx; - struct box *start_box = - selection_get_start(&html->sel, &start_idx); - struct box *end_box = selection_get_end(&html->sel, &end_idx); - - if (start_box && end_box) { - selection_clear(&html->sel, false); - textinput_textarea_cut(c, start_box, start_idx, - end_box, end_idx, true); - text_box = start_box; - char_offset = start_idx; - reflow = true; - } - } - break; - - case KEY_RIGHT: - if (selection_exists) { - /* In selection, move caret to end */ - text_box = selection_get_end(&html->sel, &char_offset); - } else if (char_offset < text_box->length) { - /* Within-box movement */ - char_offset = utf8_next(text_box->text, - text_box->length, char_offset); - } else { - /* Between-box movement */ - if (!text_box->next) - /* at end of text area: ignore */ - return true; - - text_box = text_box->next; - if (text_box->type == BOX_BR) - text_box = text_box->next; - char_offset = 0; - } - break; - - case KEY_LEFT: - if (selection_exists) { - /* In selection, move caret to start */ - text_box = selection_get_start(&html->sel, &char_offset); - } else if (char_offset > 0) { - /* Within-box movement */ - char_offset = utf8_prev(text_box->text, char_offset); - } else { - /* Between-box movement */ - if (!text_box->prev) - /* at start of text area: ignore */ - return true; - - text_box = text_box->prev; - if (text_box->type == BOX_BR) - text_box = text_box->prev; - char_offset = text_box->length; - } - break; - - case KEY_UP: - selection_clear(&html->sel, true); - textinput_textarea_click(c, BROWSER_MOUSE_CLICK_1, - textarea, - box_x, box_y, - text_box->x + pixel_offset, - inline_container->y + text_box->y - 1); - return true; - - case KEY_DOWN: - selection_clear(&html->sel, true); - textinput_textarea_click(c, BROWSER_MOUSE_CLICK_1, - textarea, - box_x, box_y, - text_box->x + pixel_offset, - inline_container->y + text_box->y + - text_box->height + 1); - return true; - - case KEY_LINE_START: - text_box = textinput_line_start(text_box); - char_offset = 0; - break; - - case KEY_LINE_END: - text_box = textinput_line_end(text_box); - char_offset = text_box->length; - break; - - case KEY_TEXT_START: - assert(text_box->parent); - - /* place caret at start of first box */ - text_box = text_box->parent->children; - char_offset = 0; - break; - - case KEY_TEXT_END: - assert(text_box->parent); - - /* place caret at end of last box */ - text_box = text_box->parent->last; - char_offset = text_box->length; - break; - - case KEY_WORD_LEFT: - { - bool start_of_word; - /* if there is a selection, caret should stay at beginning */ - if (selection_exists) - break; - - start_of_word = (char_offset <= 0 || - isspace(text_box->text[char_offset - 1])); - - while (!textinput_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; - if (prev && prev->type == BOX_BR) - prev = prev->prev; - } - - if (!prev) { - /* just stay at the start of this box */ - break; - } - - assert(prev->type == BOX_TEXT); - - text_box = prev; - char_offset = prev->length; - } - } - break; - - case KEY_WORD_RIGHT: - { - bool in_word; - /* if there is a selection, caret should move to the end */ - if (selection_exists) { - text_box = selection_get_end(&html->sel, &char_offset); - break; - } - - in_word = (char_offset < text_box->length && - !isspace(text_box->text[char_offset])); - - while (!textinput_word_right(text_box->text, text_box->length, - &char_offset, NULL)) { - struct box *next = text_box->next; - - /* find the next non-BR box */ - if (next && next->type == BOX_BR) - next = next->next; - - if (!next) { - /* just stay at the end of this box */ - char_offset = text_box->length; - break; - } - - assert(next->type == BOX_TEXT); - - 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 = textinput_line_above(text_box); - - if (char_offset > text_box->length) - char_offset = text_box->length; - } - break; - - case KEY_PAGE_DOWN: - { - int nlines = (textarea->height / text_box->height) - 1; - - while (nlines-- > 0) - text_box = textinput_line_below(text_box); - - /* 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 */ - if (char_offset > text_box->length) - char_offset = text_box->length; - } - break; - - default: - return false; - } - - /* - box_dump(textarea, 0); - for (struct box *t = inline_container->children; t; t = t->next) { - assert(t->type == BOX_TEXT); - assert(t->text); - assert(t->parent == inline_container); - if (t->next) assert(t->next->prev == t); - if (t->prev) assert(t->prev->next == t); - if (!t->next) { - assert(inline_container->last == t); - break; - } - if (t->next->type == BOX_BR) { - assert(t->next->next); - t = t->next; - } - } */ - - if (reflow) - textinput_textarea_reflow(c, textarea, inline_container); - - if (text_box->length + SPACE_LEN(text_box) <= char_offset) { - if (text_box->next && text_box->next->type == BOX_TEXT) { - /* the text box has been split when reflowing and - the caret is in the second part */ - char_offset -= (text_box->length + SPACE_LEN(text_box)); - text_box = text_box->next; - assert(text_box); - assert(char_offset <= text_box->length); - /* Scroll back to the left */ - if (textarea->scroll_x != NULL) { - box_x += scrollbar_get_offset( - textarea->scroll_x); - scrollbar_set(textarea->scroll_x, 0, false); - } - } else { - assert(!text_box->next || - (text_box->next && - text_box->next->type == BOX_BR)); - - char_offset = text_box->length + SPACE_LEN(text_box); - } - } - - font_plot_style_from_css(text_box->style, &fstyle); - - nsfont.font_width(&fstyle, text_box->text, char_offset, &pixel_offset); - - selection_clear(&html->sel, true); - - textarea->gadget->caret_inline_container = inline_container; - textarea->gadget->caret_text_box = text_box; - textarea->gadget->caret_box_offset = char_offset; - textarea->gadget->caret_pixel_offset = pixel_offset; - - box_x += scrollbar_get_offset(textarea->scroll_x); - box_y += scrollbar_get_offset(textarea->scroll_y); - scrolled = textinput_ensure_caret_visible(c, textarea); - box_x -= scrollbar_get_offset(textarea->scroll_x); - box_y -= scrollbar_get_offset(textarea->scroll_y); - - browser_window_place_caret(bw, - box_x + inline_container->x + text_box->x + - pixel_offset, - box_y + inline_container->y + text_box->y, - text_box->height, - textinput_textarea_callback, - textinput_textarea_paste_text, - textinput_textarea_move_caret, - textarea, c); - - if (scrolled || reflow) - html__redraw_a_box(html, textarea); - - return true; -} - - -/** - * Handle clicks in a text area by placing the caret. - * - * \param c html content where click occurred - * \param mouse state of mouse buttons and modifier keys - * \param textarea textarea box - * \param box_x position of textarea in global document coordinates - * \param box_y position of textarea in global document coordinates - * \param x coordinate of click relative to textarea - * \param y coordinate of click relative to textarea - */ -void textinput_textarea_click(struct content *c, browser_mouse_state mouse, - struct box *textarea, int box_x, int box_y, int x, int y) -{ - /* A textarea is an INLINE_BLOCK containing a single - * INLINE_CONTAINER, which contains the text as runs of TEXT - * separated by BR. There is at least one TEXT. The first and - * last boxes are TEXT. Consecutive BR may not be present. These - * constraints are satisfied by using a 0-length TEXT for blank - * lines. */ - - int char_offset = 0, pixel_offset = 0; - struct box *inline_container = textarea->children; - struct box *text_box; - bool scrolled; - html_content *html = (html_content *)c; - - text_box = textinput_textarea_get_position(textarea, x, y, - &char_offset, &pixel_offset); - - textarea->gadget->caret_inline_container = inline_container; - textarea->gadget->caret_text_box = text_box; - textarea->gadget->caret_box_offset = char_offset; - textarea->gadget->caret_pixel_offset = pixel_offset; - - box_x += scrollbar_get_offset(textarea->scroll_x); - box_y += scrollbar_get_offset(textarea->scroll_y); - scrolled = textinput_ensure_caret_visible(c, textarea); - box_x -= scrollbar_get_offset(textarea->scroll_x); - box_y -= scrollbar_get_offset(textarea->scroll_y); - - browser_window_place_caret(html->bw, - box_x + inline_container->x + text_box->x + - pixel_offset, - box_y + inline_container->y + text_box->y, - text_box->height, - textinput_textarea_callback, - textinput_textarea_paste_text, - textinput_textarea_move_caret, - textarea, c); - - if (scrolled) - html__redraw_a_box(html, textarea); -} - - -/** - * Paste a block of text into an input field at the caret position. - * - * \param bw browser window - * \param utf8 pointer to block of text - * \param utf8_len length (bytes) of text block - * \param last true iff this is the last chunk (update screen too) - * \param p1 pointer to input box - * \param p2 html content with the input box - * \return true iff successful - */ - -bool textinput_input_paste_text(struct browser_window *bw, - const char *utf8, unsigned utf8_len, bool last, - void *p1, void *p2) -{ - struct box *input = p1; - struct content *c = p2; - struct box *text_box = input->children->children; - size_t box_offset = input->gadget->caret_box_offset; - unsigned int nchars = utf8_length(input->gadget->value); - const char *ep = utf8 + utf8_len; - const char *p = utf8; - bool success = true; - bool update = last; - - /* keep adding chars until we've run out or would exceed - the maximum length of the field (in which we silently - ignore all others) - */ - while (p < ep && nchars < input->gadget->maxlength) { - char buf[80 + 6]; - int nbytes = 0; - - /* how many more chars can we insert in one go? */ - while (p < ep && nbytes < 80 && - nchars < input->gadget->maxlength && - *p != '\n' && *p != '\r') { - unsigned len = utf8_next(p, ep - p, 0); - if (*p == ' ') - nbytes += utf8_from_ucs4(160, &buf[nbytes]); - else { - memcpy(&buf[nbytes], p, len); - nbytes += len; - } - - p += len; - nchars++; - } - - if (!textinput_textbox_insert(c, text_box, box_offset, - buf, nbytes)) { - /* we still need to update the screen */ - update = true; - success = false; - break; - } - box_offset += nbytes; - /* Keep caret_form_offset in sync -- textbox_insert uses this - * to determine where to insert into the gadget's value */ - input->gadget->caret_form_offset += nbytes; - - /* handle CR/LF and LF/CR terminations */ - if (*p == '\n') { - p++; - if (*p == '\r') p++; - } - else if (*p == '\r') { - p++; - if (*p == '\n') p++; - } - } - - if (update) - textinput_input_update_display(c, input, box_offset, - false, true); - - return success; -} - - -/** - * Move caret to new position after reformatting - * - * \param bw browser window - * \param p1 pointer to text input box - * \param p2 html content with the input box - * \return none - */ - -void textinput_input_move_caret(struct browser_window *bw, - void *p1, void *p2) -{ - struct box *input = (struct box *)p1; - struct content *c = p2; - struct box *text_box = input->children->children; - unsigned int box_offset = input->gadget->caret_box_offset; - int pixel_offset; - int box_x, box_y; - plot_font_style_t fstyle; - - font_plot_style_from_css(text_box->style, &fstyle); - - box_coords(input, &box_x, &box_y); - - nsfont.font_width(&fstyle, text_box->text, box_offset, - &pixel_offset); - - browser_window_place_caret(bw, - box_x + input->children->x + - text_box->x + pixel_offset, - box_y + input->children->y + text_box->y, - text_box->height, - textinput_input_callback, - textinput_input_paste_text, - textinput_input_move_caret, - input, c); -} - -/** - * Key press callback for text or password input boxes. - * - * \param bw The browser window containing the input box - * \param key The UCS4 character codepoint - * \param p1 The input box - * \param p2 The html content with the input box - * \return true if the keypress is dealt with, false otherwise. It can - * return true even if it ran out of memory; this just means that - * it would have claimed it if it could. - */ -bool textinput_input_callback(struct browser_window *bw, uint32_t key, - void *p1, void *p2) -{ - struct box *input = (struct box *)p1; - struct content *c = p2; - html_content *html = (html_content *)c; - struct box *text_box = input->children->children; - size_t box_offset = input->gadget->caret_box_offset; - size_t end_offset; - int box_x, box_y; - struct form* form = input->gadget->form; - bool changed = false; - char utf8[6]; - unsigned int utf8_len; - bool to_textarea = false; - bool selection_exists = html->sel.defined; - - input->gadget->caret_form_offset = - textinput_get_form_offset(input, text_box, box_offset); - - /* update the form offset */ - input->gadget->caret_form_offset = - textinput_get_form_offset(input, text_box, box_offset); - - selection_get_end(&html->sel, &end_offset); - - box_coords(input, &box_x, &box_y); - - /* normal character insertion */ - if (!(key <= 0x001F || (0x007F <= key && key <= 0x009F))) { - /* have we exceeded max length of input? */ - utf8_len = utf8_length(input->gadget->value); - if (utf8_len >= input->gadget->maxlength) - return true; - - utf8_len = utf8_from_ucs4(key, utf8); - - if (!textinput_textbox_insert(c, text_box, box_offset, - utf8, utf8_len)) - return true; - - box_offset += utf8_len; - - changed = true; - - } else switch (key) { - case KEY_DELETE_LEFT: - { - int prev_offset, new_offset; - - if (selection_exists) { - textinput_textbox_delete(c, text_box, 0, 0); - } else { - /* Can't delete left from text box start */ - if (box_offset == 0) - return true; - - prev_offset = box_offset; - new_offset = utf8_prev(text_box->text, box_offset); - - if (textinput_textbox_delete(c, text_box, new_offset, - prev_offset - new_offset)) - box_offset = new_offset; - } - - changed = true; - } - break; - - case KEY_DELETE_RIGHT: - { - unsigned next_offset; - - if (selection_exists) { - textinput_textbox_delete(c, text_box, 0, 0); - } else { - /* Can't delete right from text box end */ - if (box_offset >= text_box->length) - return true; - - /* Go to the next valid UTF-8 character */ - next_offset = utf8_next(text_box->text, - text_box->length, box_offset); - - textinput_textbox_delete(c, text_box, box_offset, - next_offset - box_offset); - } - - changed = true; - } - break; - - case KEY_TAB: - { - struct form_control *next_input; - /* Find next text entry field that is actually - * displayed (i.e. has an associated box) */ - for (next_input = input->gadget->next; - next_input && - ((next_input->type != GADGET_TEXTBOX && - next_input->type != GADGET_TEXTAREA && - next_input->type != GADGET_PASSWORD) || - !next_input->box); - next_input = next_input->next) - ; - if (!next_input) - return true; - - input = next_input->box; - box_offset = 0; - to_textarea = next_input->type == GADGET_TEXTAREA; - } - break; - - case KEY_NL: - case KEY_CR: /* Return/Enter hit */ - selection_clear(&html->sel, true); - - if (form) - form_submit(content_get_url(c), bw, form, 0); - return true; - - case KEY_SHIFT_TAB: - { - struct form_control *prev_input; - /* Find previous text entry field that is actually - * displayed (i.e. has an associated box) */ - for (prev_input = input->gadget->prev; - prev_input && - ((prev_input->type != GADGET_TEXTBOX && - prev_input->type != GADGET_TEXTAREA && - prev_input->type != GADGET_PASSWORD) || - !prev_input->box); - prev_input = prev_input->prev) - ; - if (!prev_input) - return true; - - input = prev_input->box; - box_offset = 0; - to_textarea = prev_input->type == GADGET_TEXTAREA; - } - break; - - case KEY_CUT_LINE: - /* Clear the selection, if one exists */ - if (selection_exists) - selection_clear(&html->sel, false); - - textinput_textarea_cut(c, text_box, 0, text_box, - text_box->length, false); - box_offset = 0; - - changed = true; - break; - - case KEY_PASTE: - { - char *buff = NULL; - size_t buff_len; - bool success; - - gui_get_clipboard(&buff, &buff_len); - if (buff == NULL) - return false; - - success = browser_window_paste_text(bw, buff, buff_len, true); - free(buff); - - return success; - } - - case KEY_CUT_SELECTION: - { - size_t start_idx, end_idx; - struct box *start_box = - selection_get_start(&html->sel, &start_idx); - struct box *end_box = selection_get_end(&html->sel, &end_idx); - - if (start_box && end_box) { - selection_clear(&html->sel, false); - textinput_textarea_cut(c, start_box, start_idx, - end_box, end_idx, true); - - box_offset = start_idx; - changed = true; - } - } - break; - - case KEY_RIGHT: - if (selection_exists) { - box_offset = end_offset; - break; - } - - if (box_offset < text_box->length) { - /* Go to the next valid UTF-8 character */ - box_offset = utf8_next(text_box->text, - text_box->length, box_offset); - } - - break; - - case KEY_LEFT: - /* If there is a selection, caret should remain at start */ - if (selection_exists) - break; - - /* Go to the previous valid UTF-8 character */ - box_offset = utf8_prev(text_box->text, box_offset); - break; - - case KEY_LINE_START: - box_offset = 0; - break; - - case KEY_LINE_END: - box_offset = text_box->length; - break; - - case KEY_WORD_LEFT: - /* If there is a selection, caret should remain at start */ - if (selection_exists) - break; - - if (!textinput_word_left(text_box->text, &box_offset, NULL)) - box_offset = 0; - - break; - - case KEY_WORD_RIGHT: - if (selection_exists) { - box_offset = end_offset; - break; - } - - if (!textinput_word_right(text_box->text, text_box->length, - &box_offset, NULL)) - box_offset = text_box->length; - - break; - - case KEY_DELETE_LINE_START: - if (selection_exists) - selection_clear(&html->sel, true); - - if (box_offset == 0) - return true; - - textinput_textarea_cut(c, text_box, 0, text_box, - box_offset, false); - box_offset = 0; - - changed = true; - break; - - case KEY_DELETE_LINE_END: - if (selection_exists) - selection_clear(&html->sel, true); - - if (box_offset >= text_box->length) - return true; - - textinput_textarea_cut(c, text_box, box_offset, - text_box, text_box->length, false); - - changed = true; - break; - - default: - return false; - } - - selection_clear(&html->sel, true); - textinput_input_update_display(c, input, box_offset, - to_textarea, changed); - - return true; -} - - -/** - * Handle clicks in a text or password input box by placing the caret. - * - * \param bw browser window where click occurred - * \param input input box - * \param box_x position of input in global document coordinates - * \param box_y position of input in global document coordinates - * \param x coordinate of click relative to input - * \param y coordinate of click relative to input - */ -void textinput_input_click(struct content *c, struct box *input, - int box_x, int box_y, int x, int y) -{ - size_t char_offset = 0; - int pixel_offset = 0, dx = 0; - struct box *text_box = input->children->children; - plot_font_style_t fstyle; - html_content *html = (html_content *)c; - - font_plot_style_from_css(text_box->style, &fstyle); - - nsfont.font_position_in_string(&fstyle, text_box->text, - text_box->length, x - text_box->x, - &char_offset, &pixel_offset); - assert(char_offset <= text_box->length); - - /* Shift the text box horizontally to ensure that the - * caret position is visible, and ideally centred */ - text_box->x = 0; - if ((input->width < text_box->width) && - (input->width / 2 < pixel_offset)) { - dx = text_box->x; - /* Move left so caret is centred */ - text_box->x = input->width / 2 - pixel_offset; - /* Clamp, so text box's right hand edge coincides - * with the input's right hand edge */ - if (text_box->x < input->width - text_box->width) - text_box->x = input->width - text_box->width; - dx -= text_box->x; - } - input->gadget->caret_box_offset = char_offset; - input->gadget->caret_form_offset = - textinput_get_form_offset(input, text_box, char_offset); - input->gadget->caret_pixel_offset = pixel_offset; - - browser_window_place_caret(html->bw, - box_x + input->children->x + - text_box->x + pixel_offset, - box_y + input->children->y + text_box->y, - text_box->height, - textinput_input_callback, - textinput_input_paste_text, - textinput_input_move_caret, - input, c); - - if (dx) - html__redraw_a_box(html, input); -} - - |