summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Drake <tlsa@netsurf-browser.org>2011-07-07 17:50:04 +0000
committerMichael Drake <tlsa@netsurf-browser.org>2011-07-07 17:50:04 +0000
commitbc91b72c56fdfe2d74e1b0fbfdeb6d6d84ad103e (patch)
treee6ab0967e23094254bd93ad94da7d55434eb4d73
parente2681caf9cdf056942792a06b74c729a05d7b5b0 (diff)
downloadnetsurf-bc91b72c56fdfe2d74e1b0fbfdeb6d6d84ad103e.tar.gz
netsurf-bc91b72c56fdfe2d74e1b0fbfdeb6d6d84ad103e.tar.bz2
Clean up text input code. Now desktop/textinput.{c|h} is concerend with browser windows only. HTML content specific stuff moved to render/textinput.{c|h} and disassociated from browser windows.
svn path=/trunk/netsurf/; revision=12587
-rw-r--r--Makefile.sources3
-rw-r--r--desktop/browser.c2
-rw-r--r--desktop/browser.h34
-rw-r--r--desktop/textinput.c2125
-rw-r--r--desktop/textinput.h49
-rw-r--r--render/html.c19
-rw-r--r--render/html_interaction.c5
-rw-r--r--render/html_internal.h2
-rw-r--r--render/html_redraw.c2
-rw-r--r--render/textinput.c2176
-rw-r--r--render/textinput.h75
11 files changed, 2314 insertions, 2178 deletions
diff --git a/Makefile.sources b/Makefile.sources
index f090bde4f..85f7624f6 100644
--- a/Makefile.sources
+++ b/Makefile.sources
@@ -13,7 +13,8 @@ S_CSS := css.c dump.c internal.c select.c utils.c
S_RENDER := box.c box_construct.c box_normalise.c \
font.c form.c html.c html_interaction.c html_redraw.c \
- hubbub_binding.c imagemap.c layout.c list.c table.c textplain.c
+ hubbub_binding.c imagemap.c layout.c list.c table.c textinput.c \
+ textplain.c
S_UTILS := base64.c filename.c hashtable.c http.c locale.c messages.c \
talloc.c url.c utf8.c utils.c useragent.c filepath.c log.c
diff --git a/desktop/browser.c b/desktop/browser.c
index c1d70efb8..0fca4a2dd 100644
--- a/desktop/browser.c
+++ b/desktop/browser.c
@@ -880,7 +880,7 @@ nserror browser_window_callback(hlcache_handle *c,
}
if (bw->move_callback)
- bw->move_callback(bw, bw->caret_p);
+ bw->move_callback(bw, bw->caret_p1, bw->caret_p2);
if (!(event->data.background)) {
/* Reformatted content should be redrawn */
diff --git a/desktop/browser.h b/desktop/browser.h
index 7c36bf1d2..d0695f295 100644
--- a/desktop/browser.h
+++ b/desktop/browser.h
@@ -47,12 +47,13 @@ struct bitmap;
struct scroll_msg_data;
struct fetch_multipart_data;
-typedef bool (*browser_caret_callback)(struct browser_window *bw,
- uint32_t key, void *p);
-typedef bool (*browser_paste_callback)(struct browser_window *bw,
- const char *utf8, unsigned utf8_len, bool last, void *p);
+typedef bool (*browser_caret_callback)(struct browser_window *bw, uint32_t key,
+ void *p1, void *p2);
typedef void (*browser_move_callback)(struct browser_window *bw,
- void *p);
+ void *p1, void *p2);
+typedef bool (*browser_paste_callback)(struct browser_window *bw,
+ const char *utf8, unsigned utf8_len, bool last,
+ void *p1, void *p2);
typedef enum {
@@ -87,8 +88,10 @@ struct browser_window {
/** Handler for repositioning caret, or 0. */
browser_move_callback move_callback;
- /** User parameter for caret_callback and paste_callback */
- void *caret_p;
+ /** User parameters for caret_callback, paste_callback, and
+ * move_callback */
+ void *caret_p1;
+ void *caret_p2;
/** Platform specific window data. */
struct gui_window *window;
@@ -242,9 +245,6 @@ struct browser_window *browser_window_find_target(
struct browser_window *bw, const char *target,
browser_mouse_state mouse);
-bool browser_window_key_press(struct browser_window *bw, uint32_t key);
-bool browser_window_paste_text(struct browser_window *bw, const char *utf8,
- unsigned utf8_len, bool last);
void browser_redraw_box(struct hlcache_handle *c, struct box *box);
void browser_select_menu_callback(void *client_data,
@@ -263,6 +263,20 @@ bool browser_window_forward_available(struct browser_window *bw);
bool browser_window_reload_available(struct browser_window *bw);
bool browser_window_stop_available(struct browser_window *bw);
+
+/* In desktop/textinput.c */
+void browser_window_place_caret(struct browser_window *bw,
+ int x, int y, int height,
+ browser_caret_callback caret_cb,
+ browser_paste_callback paste_cb,
+ browser_move_callback move_cb,
+ void *p1, void *p2);
+void browser_window_remove_caret(struct browser_window *bw);
+bool browser_window_key_press(struct browser_window *bw, uint32_t key);
+bool browser_window_paste_text(struct browser_window *bw, const char *utf8,
+ unsigned utf8_len, bool last);
+
+
/**
* Redraw an area of a window
*
diff --git a/desktop/textinput.c b/desktop/textinput.c
index ceb90007e..a351344cf 100644
--- a/desktop/textinput.c
+++ b/desktop/textinput.c
@@ -47,182 +47,6 @@
/* Define to enable textinput debug */
#undef TEXTINPUT_DEBUG
-/** ghost caret used to indicate the insertion point when dragging text
- into a textarea/input field */
-struct caret ghost_caret;
-
-
-static bool textbox_delete(struct browser_window *bw, struct box *text_box,
- unsigned char_offset, unsigned utf8_len);
-
-/* Textarea callbacks */
-static bool browser_window_textarea_callback(struct browser_window *bw,
- uint32_t key, void *p);
-static void browser_window_textarea_move_caret(struct browser_window *bw,
- void *p);
-static bool browser_window_textarea_paste_text(struct browser_window *bw,
- const char *utf8, unsigned utf8_len, bool last, void *handle);
-
-/* Text input callbacks */
-static bool browser_window_input_paste_text(struct browser_window *bw,
- const char *utf8, unsigned utf8_len, bool last, void *handle);
-static void browser_window_input_move_caret(struct browser_window *bw, void *p);
-static bool browser_window_input_callback(struct browser_window *bw,
- uint32_t key, void *p);
-
-#define SPACE_LEN(b) ((b->space == 0) ? 0 : 1)
-
-/**
- * Remove the given text caret from the window by invalidating it
- * and causing its former position to be redrawn.
- *
- * \param c structure describing text caret
- */
-
-void caret_remove(struct caret *c)
-{
- if (c->defined) {
- int w = (c->height + 7) / 8;
- int xc = c->x;
- c->defined = false;
- browser_window_redraw_rect(c->bw,
- xc - w, c->y, 2 * w, c->height);
- }
-}
-
-
-/**
- * Set the given text caret's position within the window (text box
- * and byte/pixel offsets within the UTF-8 content of that text box)
- * and draw it.
- *
- * \param c structure describing text caret
- * \param bw browser window containing caret
- * \param box TEXT box containing caret
- * \param char_offset byte offset within UTF-8 representation
- * \param pixel_offset from left side of box
- */
-
-void caret_set_position(struct caret *c, struct browser_window *bw,
- struct box *text_box, int char_offset, int pixel_offset)
-{
- struct rect r;
- int xc;
- int w;
-
- box_bounds(text_box, &r);
-
- c->bw = bw;
- c->text_box = text_box;
- c->char_offset = char_offset;
-
- c->x = xc = r.x0 + pixel_offset;
- c->y = r.y0;
- c->height = r.y1 - r.y0;
- w = (c->height + 7) / 8;
-
- c->defined = true;
-
- browser_window_redraw_rect(c->bw, xc - w, c->y, w * 2, c->height);
-}
-
-
-/**
- * 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
- */
-
-struct box *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;
-}
-
/**
* Position the caret and assign a callback for key presses.
@@ -234,14 +58,15 @@ struct box *textarea_get_position(struct box *textarea, int x, int y,
* \param caret_cb Callback function for keypresses
* \param paste_cb Callback function for pasting text
* \param move_cb Callback function for caret movement
- * \param p Callback private data pointer, passed to callback function
+ * \param p1 Callback private data pointer, passed to callback function
+ * \param p2 Callback private data pointer, passed to callback function
*/
-static void browser_window_place_caret(struct browser_window *bw,
+void browser_window_place_caret(struct browser_window *bw,
int x, int y, int height,
browser_caret_callback caret_cb,
browser_paste_callback paste_cb,
browser_move_callback move_cb,
- void *p)
+ void *p1, void *p2)
{
struct browser_window *root_bw;
int pos_x = 0;
@@ -258,7 +83,8 @@ static void browser_window_place_caret(struct browser_window *bw,
bw->caret_callback = caret_cb;
bw->paste_callback = paste_cb;
bw->move_callback = move_cb;
- bw->caret_p = p;
+ bw->caret_p1 = p1;
+ bw->caret_p2 = p2;
/* Set focus browser window */
root_bw->focus = bw;
@@ -285,1944 +111,14 @@ void browser_window_remove_caret(struct browser_window *bw)
bw->caret_callback = NULL;
bw->paste_callback = NULL;
bw->move_callback = NULL;
- bw->caret_p = NULL;
-
- selection_clear(bw->sel, true);
-}
-
-
-/**
- * Delete some text from a box, or delete the box in its entirety
- *
- * \param bw browser window
- * \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 delete_handler(struct browser_window *bw, 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 textbox_delete(bw, b, offset,
- min(length, text_length - offset));
-}
-
-
-/**
- * Remove the selected text from a text box and gadget (if applicable)
- *
- * \param s The selection to be removed
- */
-
-static void delete_selection(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 */
- delete_handler(s->bw, 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);
- }
-
- delete_handler(s->bw, end_box, beginning, end_offset);
-}
-
-
-/**
- * Insert a number of chars into a text box
- *
- * \param bw browser window
- * \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 textbox_insert(struct browser_window *bw, struct box *text_box,
- unsigned char_offset, const char *utf8, unsigned utf8_len)
-{
- char *text;
- struct box *input = text_box->parent->parent;
- struct content *current_content;
- bool hide;
-
- assert(bw != NULL);
- assert(bw->current_content != NULL);
-
- /** \todo Stop poking around inside contents.
- * Why is this code in desktop/, anyway? (It's HTML-specific) */
- current_content = hlcache_handle_get_content(bw->current_content);
-
- if (bw->sel->defined)
- delete_selection(bw->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(current_content, 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 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 bw browser window
- * \param text_box text box
- * \param char_offset offset within text box (bytes) of first char to delete
- * \param utf8_len length (bytes) of chars to be deleted
- * \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 textbox_delete(struct browser_window *bw, struct box *text_box,
- unsigned char_offset, unsigned utf8_len)
-{
- unsigned next_offset = char_offset + utf8_len;
- struct box *form = text_box->parent->parent;
-
- if (bw->sel->defined) {
- delete_selection(bw->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 = get_form_offset(form, text_box,
- char_offset);
- size_t next_offset = 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 *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 *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 *line_above(struct box *text_box)
-{
- struct box *prev;
-
- text_box = line_start(text_box);
-
- prev = text_box->prev;
- while (prev && prev->type == BOX_BR)
- prev = prev->prev;
-
- return prev ? line_start(prev) : text_box;
-}
-
-
-/**
- * Advance to the start of the next line, if there is one.
- */
-
-static struct box *line_below(struct box *text_box)
-{
- struct box *next;
-
- text_box = line_end(text_box);
-
- next = text_box->next;
- while (next && next->type == BOX_BR)
- next = next->next;
-
- return next ? next : text_box;
-}
-
-
-/**
- * Cut a range of text from a text box,
- * possibly placing it on the global clipboard.
- *
- * \param bw browser window
- * \param start_box text box at start of range
- * \param start_idx index (bytes) within start box
- * \param end_box text box at end of range
- * \param end_idx index (bytes) within end box
- * \param clipboard whether to place text on the clipboard
- * \return true iff successful
- */
-
-static bool textarea_cut(struct browser_window *bw,
- 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 (clipboard && !gui_empty_clipboard())
- 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 &&
- !gui_add_to_clipboard("\n", 1, false)) {
- gui_commit_clipboard();
- return false;
- }
- box_unlink_and_free(box);
- } else {
- /* append box text to clipboard and then delete it */
- if (clipboard &&
- !gui_add_to_clipboard(box->text + start_idx,
- box->length - start_idx,
- SPACE_LEN(box))) {
- gui_commit_clipboard();
- return false;
- }
-
- if (del) {
- if (!delete_handler(bw, box, start_idx,
- (box->length + SPACE_LEN(box)) -
- start_idx) && clipboard) {
- gui_commit_clipboard();
- return false;
- }
- } else {
- textbox_delete(bw, 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 && !gui_add_to_clipboard(box->text + start_idx,
- end_idx - start_idx, end_idx > box->length)) {
- success = false;
- } else {
- if (del) {
- if (!delete_handler(bw, box, start_idx,
- end_idx - start_idx))
- success = false;
- } else {
- textbox_delete(bw, box, start_idx,
- end_idx - start_idx);
- }
- }
- }
-
- if (clipboard && !gui_commit_clipboard())
- success = false;
-
- return success;
-}
-
-
-/**
- * Break a text box into two
- *
- * \param bw browser window
- * \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 *textarea_insert_break(struct browser_window *bw,
- struct box *text_box, size_t char_offset)
-{
- struct box *new_br, *new_text;
- struct content *current_content;
- char *text;
-
- assert(bw != NULL);
- assert(bw->current_content != NULL);
-
- /** \todo Stop poking around inside content objects */
- current_content = hlcache_handle_get_content(bw->current_content);
-
- text = talloc_array(current_content, 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, current_content);
- new_text = talloc(current_content, 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 bw browser window
- * \param textarea text area box
- * \param inline_container container holding text box
- */
-
-static void textarea_reflow(struct browser_window *bw, struct box *textarea,
- struct box *inline_container)
-{
- struct content *c = hlcache_handle_get_content(bw->current_content);
- 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 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 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 bw browser window where click ocurred
- * \param textarea textarea box
- * \return true if a change in scroll offsets has occurred
-*/
-
-static bool ensure_caret_visible(struct browser_window *bw,
- struct box *textarea)
-{
- html_content *html = (html_content *)
- hlcache_handle_get_content(bw->current_content);
- 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 handle pointer to textarea
- * \return true iff successful
- */
-
-bool browser_window_textarea_paste_text(struct browser_window *bw,
- const char *utf8, unsigned utf8_len, bool last, void *handle)
-{
- struct box *textarea = handle;
- 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 (!textbox_insert(bw, text_box, char_offset, utf8, utf8_len))
- return false;
-
- char_offset += utf8_len;
- if (p == ep)
- break;
-
- new_text = textarea_insert_break(bw, 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 */
- textarea_reflow(bw, 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);
- ensure_caret_visible(bw, 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,
- browser_window_textarea_callback,
- browser_window_textarea_paste_text,
- browser_window_textarea_move_caret,
- textarea);
-
- html_redraw_a_box(bw->current_content, textarea);
- }
-
- return success;
-}
-
-
-/**
- * Move caret to new position after reformatting
- *
- * \param bw browser window
- * \param p pointer textarea box
- * \return none
- */
-
-void browser_window_textarea_move_caret(struct browser_window *bw, void *p)
-{
- struct box *textarea = p;
- 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,
- browser_window_textarea_callback,
- browser_window_textarea_paste_text,
- browser_window_textarea_move_caret,
- textarea);
-}
-
-
-/**
- * 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 input_update_display(struct browser_window *bw, 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;
-
- 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(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 ? browser_window_textarea_callback
- : browser_window_input_callback,
- to_textarea ? browser_window_textarea_paste_text
- : browser_window_input_paste_text,
- to_textarea ? browser_window_textarea_move_caret
- : browser_window_input_move_caret,
- input);
-
- if (dx || redraw)
- html_redraw_a_box(bw->current_content, input);
-}
-
-
-/**
- * Key press callback for text areas.
- *
- * \param bw The browser window containing the text area
- * \param key The ucs4 character codepoint
- * \param p 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 browser_window_textarea_callback(struct browser_window *bw,
- uint32_t key, void *p)
-{
- struct box *textarea = p;
- 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 = bw->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 (!textbox_insert(bw, 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 */
- textbox_delete(bw, 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 (!textbox_insert(bw, 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 (textbox_delete(bw, text_box, new_offset,
- prev_offset - new_offset))
- char_offset = new_offset;
- }
- reflow = true;
- break;
-
- case KEY_DELETE_LINE_START:
- {
- struct box *start_box = line_start(text_box);
-
- /* Clear the selection, if one exists */
- if (selection_exists)
- selection_clear(bw->sel, false);
-
- textarea_cut(bw, 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 = line_end(text_box);
-
- /* Clear the selection, if one exists */
- if (selection_exists)
- selection_clear(bw->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 */
- textarea_cut(bw, 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 */
- textbox_delete(bw, 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 (!textbox_insert(bw, 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);
-
- textbox_delete(bw, 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 */
- textbox_delete(bw, text_box, 0, 0);
- }
-
- new_text = textarea_insert_break(bw, 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 = line_start(text_box);
- struct box *end_box = line_end(text_box);
-
- /* Clear the selection, if one exists */
- if (selection_exists)
- selection_clear(bw->sel, false);
-
- textarea_cut(bw, start_box, 0, end_box, end_box->length, false);
-
- text_box = start_box;
- char_offset = 0;
- reflow = true;
- }
- break;
-
- case KEY_PASTE:
- gui_paste_from_clipboard(bw->window,
- box_x + inline_container->x +
- text_box->x + pixel_offset,
- box_y + inline_container->y + text_box->y);
-
- /* screen updated and caret repositioned already */
- return true;
-
- case KEY_CUT_SELECTION:
- {
- size_t start_idx, end_idx;
- struct box *start_box =
- selection_get_start(bw->sel, &start_idx);
- struct box *end_box = selection_get_end(bw->sel, &end_idx);
-
- if (start_box && end_box) {
- selection_clear(bw->sel, false);
- textarea_cut(bw, start_box, start_idx,
- end_box, end_idx, 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(bw->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(bw->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(bw->sel, true);
- browser_window_textarea_click(bw, 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(bw->sel, true);
- browser_window_textarea_click(bw, 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 = line_start(text_box);
- char_offset = 0;
- break;
-
- case KEY_LINE_END:
- text_box = 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 (!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(bw->sel, &char_offset);
- break;
- }
-
- in_word = (char_offset < text_box->length &&
- !isspace(text_box->text[char_offset]));
-
- while (!word_right(text_box->text, text_box->length,
- &char_offset, NULL)) {
- struct box *next = text_box->next;
-
- /* find the next non-BR box */
- 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 = 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 = 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)
- textarea_reflow(bw, 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);
+ bw->caret_p1 = NULL;
+ bw->caret_p2 = NULL;
selection_clear(bw->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 = ensure_caret_visible(bw, 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,
- browser_window_textarea_callback,
- browser_window_textarea_paste_text,
- browser_window_textarea_move_caret,
- textarea);
-
- if (scrolled || reflow)
- html_redraw_a_box(bw->current_content, textarea);
-
- return true;
-}
-
-
-/**
- * Handle clicks in a text area by placing the caret.
- *
- * \param bw browser window 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 browser_window_textarea_click(struct browser_window *bw,
- 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;
-
- text_box = 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 = ensure_caret_visible(bw, 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,
- browser_window_textarea_callback,
- browser_window_textarea_paste_text,
- browser_window_textarea_move_caret,
- textarea);
-
- if (scrolled)
- html_redraw_a_box(bw->current_content, 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 p pointer to input box
- * \return true iff successful
- */
-
-bool browser_window_input_paste_text(struct browser_window *bw,
- const char *utf8, unsigned utf8_len, bool last, void *handle)
-{
- struct box *input = handle;
- 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 (!textbox_insert(bw, 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)
- input_update_display(bw, input, box_offset, false, true);
-
- return success;
}
/**
- * Move caret to new position after reformatting
- *
- * \param bw browser window
- * \param p pointer to text input box
- * \return none
- */
-
-void browser_window_input_move_caret(struct browser_window *bw, void *p)
-{
- struct box *input = (struct box *)p;
- 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,
- browser_window_input_callback,
- browser_window_input_paste_text,
- browser_window_input_move_caret,
- input);
-}
-
-/**
- * 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 p 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 browser_window_input_callback(struct browser_window *bw,
- uint32_t key, void *p)
-{
- struct box *input = (struct box *)p;
- struct box *text_box = input->children->children;
- size_t box_offset = input->gadget->caret_box_offset;
- size_t end_offset;
- int pixel_offset = input->gadget->caret_pixel_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 = bw->sel->defined;
-
- input->gadget->caret_form_offset =
- get_form_offset(input, text_box, box_offset);
-
- /* update the form offset */
- input->gadget->caret_form_offset =
- get_form_offset(input, text_box, box_offset);
-
- selection_get_end(bw->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 (!textbox_insert(bw, 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) {
- textbox_delete(bw, 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 (textbox_delete(bw, 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) {
- textbox_delete(bw, 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);
-
- textbox_delete(bw, 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(bw->sel, true);
-
- if (form)
- form_submit(bw->current_content, 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(bw->sel, false);
-
- textarea_cut(bw, text_box, 0, text_box, text_box->length,
- false);
- box_offset = 0;
-
- changed = true;
- break;
-
- case KEY_PASTE:
- gui_paste_from_clipboard(bw->window,
- box_x + input->children->x + text_box->x + pixel_offset,
- box_y + input->children->y + text_box->y);
-
- /* screen updated and caret repositioned already */
- return true;
-
- case KEY_CUT_SELECTION:
- {
- size_t start_idx, end_idx;
- struct box *start_box =
- selection_get_start(bw->sel, &start_idx);
- struct box *end_box = selection_get_end(bw->sel, &end_idx);
-
- if (start_box && end_box) {
- selection_clear(bw->sel, false);
- textarea_cut(bw, start_box, start_idx,
- end_box, end_idx, 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 (!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 (!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(bw->sel, true);
-
- if (box_offset == 0)
- return true;
-
- textarea_cut(bw, text_box, 0, text_box, box_offset, false);
- box_offset = 0;
-
- changed = true;
- break;
-
- case KEY_DELETE_LINE_END:
- if (selection_exists)
- selection_clear(bw->sel, true);
-
- if (box_offset >= text_box->length)
- return true;
-
- textarea_cut(bw, text_box, box_offset,
- text_box, text_box->length, false);
-
- changed = true;
- break;
-
- default:
- return false;
- }
-
- selection_clear(bw->sel, true);
- input_update_display(bw, 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 browser_window_input_click(struct browser_window* bw,
- 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;
-
- 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 =
- get_form_offset(input, text_box, char_offset);
- input->gadget->caret_pixel_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,
- browser_window_input_callback,
- browser_window_input_paste_text,
- browser_window_input_move_caret,
- input);
-
- if (dx)
- html_redraw_a_box(bw->current_content, input);
-}
-
-/**
* Handle key presses in a browser window.
*
* \param bw The root browser window
@@ -2261,7 +157,8 @@ bool browser_window_key_press(struct browser_window *bw, uint32_t key)
if (!focus->caret_callback)
return false;
- return focus->caret_callback(focus, key, focus->caret_p);
+ return focus->caret_callback(focus, key,
+ focus->caret_p1, focus->caret_p2);
}
@@ -2282,6 +179,6 @@ bool browser_window_paste_text(struct browser_window *bw, const char *utf8,
return false;
return bw->focus->paste_callback(bw->focus, utf8, utf8_len, last,
- bw->focus->caret_p);
+ bw->focus->caret_p1, bw->focus->caret_p2);
}
diff --git a/desktop/textinput.h b/desktop/textinput.h
index 263638d3a..745addeb6 100644
--- a/desktop/textinput.h
+++ b/desktop/textinput.h
@@ -26,13 +26,6 @@
#ifndef _NETSURF_DESKTOP_TEXTINPUT_H_
#define _NETSURF_DESKTOP_TEXTINPUT_H_
-#include <stdbool.h>
-
-
-struct browser_window;
-struct box;
-
-
enum input_key {
KEY_SELECT_ALL = 1,
@@ -73,46 +66,4 @@ enum input_key {
};
-struct caret
-{
- bool defined;
-
- struct browser_window *bw;
- struct box *text_box;
- size_t char_offset;
-
- /* document co-ordinates of bottom left of caret */
- int x;
- int y;
- int height;
-};
-
-
-/** There's a single ghost caret used to implement
- * drag-and-drop of text into text areas and input fields.
- */
-
-extern struct caret ghost_caret;
-
-
-void caret_set_position(struct caret *c, struct browser_window *bw,
- struct box *text_box, int char_offset, int pixel_offset);
-void caret_remove(struct caret *c);
-
-
-struct box *textarea_get_position(struct box *textarea, int x, int y,
- int *pchar_offset, int *ppixel_offset);
-
-void browser_window_textarea_click(struct browser_window *bw,
- browser_mouse_state mouse,
- struct box *textarea,
- int box_x, int box_y,
- int x, int y);
-
-void browser_window_input_click(struct browser_window* bw,
- struct box *input,
- int box_x, int box_y,
- int x, int y);
-void browser_window_remove_caret(struct browser_window *bw);
-
#endif
diff --git a/render/html.c b/render/html.c
index d77cfd981..9e2b4c338 100644
--- a/render/html.c
+++ b/render/html.c
@@ -1768,6 +1768,25 @@ void html_redraw_a_box(hlcache_handle *h, struct box *box)
/**
+ * Redraw a box.
+ *
+ * \param h content containing the box, of type CONTENT_HTML
+ * \param box box to redraw
+ */
+
+void html__redraw_a_box(struct content *c, struct box *box)
+{
+ int x, y;
+
+ box_coords(box, &x, &y);
+
+ content__request_redraw(c, x, y,
+ box->padding[LEFT] + box->width + box->padding[RIGHT],
+ box->padding[TOP] + box->height + box->padding[BOTTOM]);
+}
+
+
+/**
* Destroy a CONTENT_HTML and free all resources it owns.
*/
diff --git a/render/html_interaction.c b/render/html_interaction.c
index 9b3ffe6df..db68f4629 100644
--- a/render/html_interaction.c
+++ b/render/html_interaction.c
@@ -39,6 +39,7 @@
#include "render/form.h"
#include "render/html_internal.h"
#include "render/imagemap.h"
+#include "render/textinput.h"
#include "utils/messages.h"
#include "utils/utils.h"
@@ -379,7 +380,7 @@ void html_mouse_action(struct content *c, struct browser_window *bw,
gadget_box)
selection_init(bw->sel, gadget_box);
- browser_window_textarea_click(bw,
+ textinput_textarea_click(c,
mouse,
gadget_box,
gadget_box_x,
@@ -422,7 +423,7 @@ void html_mouse_action(struct content *c, struct browser_window *bw,
if ((mouse & BROWSER_MOUSE_PRESS_1) &&
!(mouse & (BROWSER_MOUSE_MOD_1 |
BROWSER_MOUSE_MOD_2))) {
- browser_window_input_click(bw,
+ textinput_input_click(c,
gadget_box,
gadget_box_x,
gadget_box_y,
diff --git a/render/html_internal.h b/render/html_internal.h
index 038055114..c736be58a 100644
--- a/render/html_internal.h
+++ b/render/html_internal.h
@@ -102,6 +102,8 @@ bool html_fetch_object(html_content *c, const char *url, struct box *box,
void html_set_status(html_content *c, const char *extra);
+void html__redraw_a_box(struct content *c, struct box *box);
+
/* in render/html_redraw.c */
bool html_redraw(struct content *c, struct content_redraw_data *data,
const struct rect *clip, const struct redraw_context *ctx);
diff --git a/render/html_redraw.c b/render/html_redraw.c
index 2ab7b4df1..cad0233ee 100644
--- a/render/html_redraw.c
+++ b/render/html_redraw.c
@@ -37,7 +37,6 @@
#include "css/utils.h"
#include "desktop/plotters.h"
#include "desktop/selection.h"
-#include "desktop/textinput.h"
#include "desktop/options.h"
#include "desktop/print.h"
#include "desktop/search.h"
@@ -48,6 +47,7 @@
#include "render/form.h"
#include "render/html_internal.h"
#include "render/layout.h"
+#include "render/textinput.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/utils.h"
diff --git a/render/textinput.c b/render/textinput.c
new file mode 100644
index 000000000..a12d72aaf
--- /dev/null
+++ b/render/textinput.c
@@ -0,0 +1,2176 @@
+/*
+ * 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 "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
+
+/** ghost caret used to indicate the insertion point when dragging text
+ into a textarea/input field */
+struct caret ghost_caret;
+
+
+static bool textbox_delete(struct browser_window *bw, 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)
+
+/**
+ * Remove the given text caret from the window by invalidating it
+ * and causing its former position to be redrawn.
+ *
+ * \param c structure describing text caret
+ */
+
+void caret_remove(struct caret *c)
+{
+ if (c->defined) {
+ int w = (c->height + 7) / 8;
+ int xc = c->x;
+ c->defined = false;
+ browser_window_redraw_rect(c->bw,
+ xc - w, c->y, 2 * w, c->height);
+ }
+}
+
+
+/**
+ * Set the given text caret's position within the window (text box
+ * and byte/pixel offsets within the UTF-8 content of that text box)
+ * and draw it.
+ *
+ * \param c structure describing text caret
+ * \param bw browser window containing caret
+ * \param box TEXT box containing caret
+ * \param char_offset byte offset within UTF-8 representation
+ * \param pixel_offset from left side of box
+ */
+
+void caret_set_position(struct caret *c, struct browser_window *bw,
+ struct box *text_box, int char_offset, int pixel_offset)
+{
+ struct rect r;
+ int xc;
+ int w;
+
+ box_bounds(text_box, &r);
+
+ c->bw = bw;
+ c->text_box = text_box;
+ c->char_offset = char_offset;
+
+ c->x = xc = r.x0 + pixel_offset;
+ c->y = r.y0;
+ c->height = r.y1 - r.y0;
+ w = (c->height + 7) / 8;
+
+ c->defined = true;
+
+ browser_window_redraw_rect(c->bw, xc - w, c->y, w * 2, c->height);
+}
+
+
+/**
+ * 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
+ */
+
+struct box *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 bw browser window
+ * \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 delete_handler(struct browser_window *bw, 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 textbox_delete(bw, b, offset,
+ min(length, text_length - offset));
+}
+
+
+/**
+ * Remove the selected text from a text box and gadget (if applicable)
+ *
+ * \param s The selection to be removed
+ */
+
+static void delete_selection(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 */
+ delete_handler(s->bw, 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);
+ }
+
+ delete_handler(s->bw, end_box, beginning, end_offset);
+}
+
+
+/**
+ * Insert a number of chars into a text box
+ *
+ * \param bw browser window
+ * \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 textbox_insert(struct browser_window *bw, struct box *text_box,
+ unsigned char_offset, const char *utf8, unsigned utf8_len)
+{
+ char *text;
+ struct box *input = text_box->parent->parent;
+ struct content *current_content;
+ bool hide;
+
+ assert(bw != NULL);
+ assert(bw->current_content != NULL);
+
+ /** \todo Stop poking around inside contents.
+ * Why is this code in desktop/, anyway? (It's HTML-specific) */
+ current_content = hlcache_handle_get_content(bw->current_content);
+
+ if (bw->sel->defined)
+ delete_selection(bw->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(current_content, 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 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 bw browser window
+ * \param text_box text box
+ * \param char_offset offset within text box (bytes) of first char to delete
+ * \param utf8_len length (bytes) of chars to be deleted
+ * \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 textbox_delete(struct browser_window *bw, struct box *text_box,
+ unsigned char_offset, unsigned utf8_len)
+{
+ unsigned next_offset = char_offset + utf8_len;
+ struct box *form = text_box->parent->parent;
+
+ if (bw->sel->defined) {
+ delete_selection(bw->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 = get_form_offset(form, text_box,
+ char_offset);
+ size_t next_offset = 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 *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 *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 *line_above(struct box *text_box)
+{
+ struct box *prev;
+
+ text_box = line_start(text_box);
+
+ prev = text_box->prev;
+ while (prev && prev->type == BOX_BR)
+ prev = prev->prev;
+
+ return prev ? line_start(prev) : text_box;
+}
+
+
+/**
+ * Advance to the start of the next line, if there is one.
+ */
+
+static struct box *line_below(struct box *text_box)
+{
+ struct box *next;
+
+ text_box = line_end(text_box);
+
+ next = text_box->next;
+ while (next && next->type == BOX_BR)
+ next = next->next;
+
+ return next ? next : text_box;
+}
+
+
+/**
+ * Cut a range of text from a text box,
+ * possibly placing it on the global clipboard.
+ *
+ * \param bw browser window
+ * \param start_box text box at start of range
+ * \param start_idx index (bytes) within start box
+ * \param end_box text box at end of range
+ * \param end_idx index (bytes) within end box
+ * \param clipboard whether to place text on the clipboard
+ * \return true iff successful
+ */
+
+static bool textarea_cut(struct browser_window *bw,
+ 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 (clipboard && !gui_empty_clipboard())
+ 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 &&
+ !gui_add_to_clipboard("\n", 1, false)) {
+ gui_commit_clipboard();
+ return false;
+ }
+ box_unlink_and_free(box);
+ } else {
+ /* append box text to clipboard and then delete it */
+ if (clipboard &&
+ !gui_add_to_clipboard(box->text + start_idx,
+ box->length - start_idx,
+ SPACE_LEN(box))) {
+ gui_commit_clipboard();
+ return false;
+ }
+
+ if (del) {
+ if (!delete_handler(bw, box, start_idx,
+ (box->length + SPACE_LEN(box)) -
+ start_idx) && clipboard) {
+ gui_commit_clipboard();
+ return false;
+ }
+ } else {
+ textbox_delete(bw, 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 && !gui_add_to_clipboard(box->text + start_idx,
+ end_idx - start_idx, end_idx > box->length)) {
+ success = false;
+ } else {
+ if (del) {
+ if (!delete_handler(bw, box, start_idx,
+ end_idx - start_idx))
+ success = false;
+ } else {
+ textbox_delete(bw, box, start_idx,
+ end_idx - start_idx);
+ }
+ }
+ }
+
+ if (clipboard && !gui_commit_clipboard())
+ success = false;
+
+ return success;
+}
+
+
+/**
+ * Break a text box into two
+ *
+ * \param bw browser window
+ * \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 *textarea_insert_break(struct browser_window *bw,
+ struct box *text_box, size_t char_offset)
+{
+ struct box *new_br, *new_text;
+ struct content *current_content;
+ char *text;
+
+ assert(bw != NULL);
+ assert(bw->current_content != NULL);
+
+ /** \todo Stop poking around inside content objects */
+ current_content = hlcache_handle_get_content(bw->current_content);
+
+ text = talloc_array(current_content, 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, current_content);
+ new_text = talloc(current_content, 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 bw browser window
+ * \param textarea text area box
+ * \param inline_container container holding text box
+ */
+
+static void textarea_reflow(struct browser_window *bw, struct box *textarea,
+ struct box *inline_container)
+{
+ struct content *c = hlcache_handle_get_content(bw->current_content);
+ 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 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 (!textbox_insert(bw, text_box, char_offset, utf8, utf8_len))
+ return false;
+
+ char_offset += utf8_len;
+ if (p == ep)
+ break;
+
+ new_text = textarea_insert_break(bw, 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 */
+ textarea_reflow(bw, 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);
+ 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(bw->current_content, 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 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(c, 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;
+ 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 = bw->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 (!textbox_insert(bw, 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 */
+ textbox_delete(bw, 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 (!textbox_insert(bw, 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 (textbox_delete(bw, text_box, new_offset,
+ prev_offset - new_offset))
+ char_offset = new_offset;
+ }
+ reflow = true;
+ break;
+
+ case KEY_DELETE_LINE_START:
+ {
+ struct box *start_box = line_start(text_box);
+
+ /* Clear the selection, if one exists */
+ if (selection_exists)
+ selection_clear(bw->sel, false);
+
+ textarea_cut(bw, 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 = line_end(text_box);
+
+ /* Clear the selection, if one exists */
+ if (selection_exists)
+ selection_clear(bw->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 */
+ textarea_cut(bw, 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 */
+ textbox_delete(bw, 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 (!textbox_insert(bw, 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);
+
+ textbox_delete(bw, 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 */
+ textbox_delete(bw, text_box, 0, 0);
+ }
+
+ new_text = textarea_insert_break(bw, 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 = line_start(text_box);
+ struct box *end_box = line_end(text_box);
+
+ /* Clear the selection, if one exists */
+ if (selection_exists)
+ selection_clear(bw->sel, false);
+
+ textarea_cut(bw, start_box, 0, end_box, end_box->length, false);
+
+ text_box = start_box;
+ char_offset = 0;
+ reflow = true;
+ }
+ break;
+
+ case KEY_PASTE:
+ gui_paste_from_clipboard(bw->window,
+ box_x + inline_container->x +
+ text_box->x + pixel_offset,
+ box_y + inline_container->y + text_box->y);
+
+ /* screen updated and caret repositioned already */
+ return true;
+
+ case KEY_CUT_SELECTION:
+ {
+ size_t start_idx, end_idx;
+ struct box *start_box =
+ selection_get_start(bw->sel, &start_idx);
+ struct box *end_box = selection_get_end(bw->sel, &end_idx);
+
+ if (start_box && end_box) {
+ selection_clear(bw->sel, false);
+ textarea_cut(bw, start_box, start_idx,
+ end_box, end_idx, 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(bw->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(bw->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(bw->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(bw->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 = line_start(text_box);
+ char_offset = 0;
+ break;
+
+ case KEY_LINE_END:
+ text_box = 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(bw->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 = 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 = 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)
+ textarea_reflow(bw, 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(bw->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 = 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(bw->current_content, 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 = 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 = 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(c, 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 (!textbox_insert(bw, 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)
+ 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;
+ struct box *text_box = input->children->children;
+ size_t box_offset = input->gadget->caret_box_offset;
+ size_t end_offset;
+ int pixel_offset = input->gadget->caret_pixel_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 = bw->sel->defined;
+
+ input->gadget->caret_form_offset =
+ get_form_offset(input, text_box, box_offset);
+
+ /* update the form offset */
+ input->gadget->caret_form_offset =
+ get_form_offset(input, text_box, box_offset);
+
+ selection_get_end(bw->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 (!textbox_insert(bw, 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) {
+ textbox_delete(bw, 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 (textbox_delete(bw, 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) {
+ textbox_delete(bw, 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);
+
+ textbox_delete(bw, 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(bw->sel, true);
+
+ if (form)
+ form_submit(bw->current_content, 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(bw->sel, false);
+
+ textarea_cut(bw, text_box, 0, text_box, text_box->length,
+ false);
+ box_offset = 0;
+
+ changed = true;
+ break;
+
+ case KEY_PASTE:
+ gui_paste_from_clipboard(bw->window,
+ box_x + input->children->x + text_box->x + pixel_offset,
+ box_y + input->children->y + text_box->y);
+
+ /* screen updated and caret repositioned already */
+ return true;
+
+ case KEY_CUT_SELECTION:
+ {
+ size_t start_idx, end_idx;
+ struct box *start_box =
+ selection_get_start(bw->sel, &start_idx);
+ struct box *end_box = selection_get_end(bw->sel, &end_idx);
+
+ if (start_box && end_box) {
+ selection_clear(bw->sel, false);
+ textarea_cut(bw, start_box, start_idx,
+ end_box, end_idx, 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(bw->sel, true);
+
+ if (box_offset == 0)
+ return true;
+
+ textarea_cut(bw, text_box, 0, text_box, box_offset, false);
+ box_offset = 0;
+
+ changed = true;
+ break;
+
+ case KEY_DELETE_LINE_END:
+ if (selection_exists)
+ selection_clear(bw->sel, true);
+
+ if (box_offset >= text_box->length)
+ return true;
+
+ textarea_cut(bw, text_box, box_offset,
+ text_box, text_box->length, false);
+
+ changed = true;
+ break;
+
+ default:
+ return false;
+ }
+
+ selection_clear(bw->sel, true);
+ 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 =
+ 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(c, input);
+}
+
+
diff --git a/render/textinput.h b/render/textinput.h
new file mode 100644
index 000000000..30cdc3036
--- /dev/null
+++ b/render/textinput.h
@@ -0,0 +1,75 @@
+/*
+ * 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>
+ *
+ * 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 (interface)
+ */
+
+#ifndef _NETSURF_RENDER_TEXTINPUT_H_
+#define _NETSURF_RENDER_TEXTINPUT_H_
+
+#include <stdbool.h>
+
+
+struct browser_window;
+struct box;
+struct content;
+
+
+struct caret
+{
+ bool defined;
+
+ struct browser_window *bw;
+ struct content *c;
+
+ struct box *text_box;
+ size_t char_offset;
+
+ /* document co-ordinates of bottom left of caret */
+ int x;
+ int y;
+ int height;
+};
+
+
+/** There's a single ghost caret used to implement
+ * drag-and-drop of text into text areas and input fields.
+ */
+
+extern struct caret ghost_caret;
+
+
+void caret_set_position(struct caret *c, struct browser_window *bw,
+ struct box *text_box, int char_offset, int pixel_offset);
+void caret_remove(struct caret *c);
+
+struct box *textarea_get_position(struct box *textarea, int x, int y,
+ int *pchar_offset, int *ppixel_offset);
+
+
+void textinput_textarea_click(struct content *c, browser_mouse_state mouse,
+ struct box *textarea, int box_x, int box_y, int x, int y);
+
+void textinput_input_click(struct content *c, struct box *input,
+ int box_x, int box_y, int x, int y);
+
+#endif