summaryrefslogtreecommitdiff
path: root/desktop
diff options
context:
space:
mode:
authorJohn Mark Bell <jmb@netsurf-browser.org>2009-06-27 13:59:25 +0000
committerJohn Mark Bell <jmb@netsurf-browser.org>2009-06-27 13:59:25 +0000
commitca96353d9fac049057535ed7fdff19e43ac79666 (patch)
tree39237c6894679fc7434de9a490458c4d51227270 /desktop
parentfa99a7a3ce6aea22368c75d38866e4a95ab8fd84 (diff)
downloadnetsurf-ca96353d9fac049057535ed7fdff19e43ac79666.tar.gz
netsurf-ca96353d9fac049057535ed7fdff19e43ac79666.tar.bz2
Merged revisions 7764-7977,7979-8058 via svnmerge from
svn://svn.netsurf-browser.org/branches/paulblokus/textinput ........ r7769 | paulblokus | 2009-06-11 22:26:16 +0100 (Thu, 11 Jun 2009) | 4 lines replace global history window with an empty window for future tests add the necessary files first lines ported ........ r7771 | paulblokus | 2009-06-11 23:51:46 +0100 (Thu, 11 Jun 2009) | 1 line more functions ........ r7772 | paulblokus | 2009-06-12 02:07:36 +0100 (Fri, 12 Jun 2009) | 1 line redraw working ........ r7777 | paulblokus | 2009-06-12 11:35:45 +0100 (Fri, 12 Jun 2009) | 3 lines plotter fix make use of the provided clipping rectangle ........ r7781 | paulblokus | 2009-06-12 16:26:51 +0100 (Fri, 12 Jun 2009) | 3 lines callbacks for taxtarea to request a [caret]redraw basic caret handling drawing ........ r7782 | paulblokus | 2009-06-12 22:36:50 +0100 (Fri, 12 Jun 2009) | 1 line single character insertion ........ r7783 | paulblokus | 2009-06-12 22:41:37 +0100 (Fri, 12 Jun 2009) | 1 line single character insertion ........ r7784 | paulblokus | 2009-06-12 23:55:40 +0100 (Fri, 12 Jun 2009) | 3 lines fixed caret clipping arrows, delete and backspace ........ r7812 | paulblokus | 2009-06-16 14:55:41 +0100 (Tue, 16 Jun 2009) | 1 line remove bug causing NS hang on \n in textarea ........ r7816 | paulblokus | 2009-06-16 16:29:48 +0100 (Tue, 16 Jun 2009) | 1 line Enter, Home, End keys ........ r7817 | paulblokus | 2009-06-16 16:56:16 +0100 (Tue, 16 Jun 2009) | 1 line Ctrl + Home/End ........ r7818 | paulblokus | 2009-06-16 17:16:51 +0100 (Tue, 16 Jun 2009) | 1 line redraw caret only on caret moves ........ r7821 | paulblokus | 2009-06-16 20:18:30 +0100 (Tue, 16 Jun 2009) | 1 line line end/start delete ........ r7822 | paulblokus | 2009-06-16 23:43:42 +0100 (Tue, 16 Jun 2009) | 1 line selection drawing + select all ........ r7823 | paulblokus | 2009-06-17 02:31:07 +0100 (Wed, 17 Jun 2009) | 3 lines auto scrolling on caret moves clear selection ........ r7845 | paulblokus | 2009-06-18 17:35:03 +0100 (Thu, 18 Jun 2009) | 1 line page up/down ........ r7846 | paulblokus | 2009-06-18 17:38:45 +0100 (Thu, 18 Jun 2009) | 1 line remove unnecessary fix ........ r7847 | paulblokus | 2009-06-18 18:00:16 +0100 (Thu, 18 Jun 2009) | 1 line clipping fixes ........ r7849 | paulblokus | 2009-06-18 18:21:02 +0100 (Thu, 18 Jun 2009) | 1 line scroll fix ........ r7850 | paulblokus | 2009-06-18 18:45:13 +0100 (Thu, 18 Jun 2009) | 1 line simplified redraw request logic ........ r7855 | paulblokus | 2009-06-18 19:56:24 +0100 (Thu, 18 Jun 2009) | 1 line front end passing mouse events ........ r7858 | paulblokus | 2009-06-18 22:18:39 +0100 (Thu, 18 Jun 2009) | 3 lines drag selection bug fixes ........ r7860 | paulblokus | 2009-06-18 23:32:39 +0100 (Thu, 18 Jun 2009) | 3 lines take selection into account on keypress of different types a few bugs fixed ........ r7876 | paulblokus | 2009-06-19 13:43:07 +0100 (Fri, 19 Jun 2009) | 3 lines pango nsfont_split fix a few textarea fixes ........ r7879 | paulblokus | 2009-06-19 17:33:10 +0100 (Fri, 19 Jun 2009) | 4 lines newline handling seems to work this way clear selection on mouse click more bug fixes ........ r7880 | paulblokus | 2009-06-19 18:16:27 +0100 (Fri, 19 Jun 2009) | 3 lines no caret option selection follows drag ........ r7883 | paulblokus | 2009-06-19 19:08:44 +0100 (Fri, 19 Jun 2009) | 3 lines o width selection bug fix caret at correct side of drag selection ........ r7918 | paulblokus | 2009-06-22 21:01:28 +0100 (Mon, 22 Jun 2009) | 3 lines fix caret positioning at line end CR removal in input methods ........ r7919 | paulblokus | 2009-06-22 21:34:39 +0100 (Mon, 22 Jun 2009) | 1 line fix crash on 0 length text ........ r7926 | paulblokus | 2009-06-23 09:53:56 +0100 (Tue, 23 Jun 2009) | 3 lines change LF into spaces for single line widget text normalisation at one place ........ r7931 | paulblokus | 2009-06-23 10:51:25 +0100 (Tue, 23 Jun 2009) | 1 line cleanup ........ r7933 | paulblokus | 2009-06-23 11:17:22 +0100 (Tue, 23 Jun 2009) | 1 line fix selection draw ........ r7935 | paulblokus | 2009-06-23 11:41:30 +0100 (Tue, 23 Jun 2009) | 1 line guard readonly ........ r7942 | paulblokus | 2009-06-24 08:19:39 +0100 (Wed, 24 Jun 2009) | 1 line applied changes suggested by jmb ........ r7943 | paulblokus | 2009-06-24 09:04:49 +0100 (Wed, 24 Jun 2009) | 1 line little fixes ........ r7945 | paulblokus | 2009-06-24 12:50:14 +0100 (Wed, 24 Jun 2009) | 1 line correct line length and wrapping ........ r7947 | paulblokus | 2009-06-24 14:32:36 +0100 (Wed, 24 Jun 2009) | 3 lines fixed page up/down broken in last commit changed logic for caret positioning on soft breaks ........ r7949 | paulblokus | 2009-06-24 16:31:42 +0100 (Wed, 24 Jun 2009) | 1 line remove temporary/test code ........ r7975 | paulblokus | 2009-06-25 16:00:46 +0100 (Thu, 25 Jun 2009) | 1 line changes suggested by jmb ........ r7976 | paulblokus | 2009-06-25 16:33:23 +0100 (Thu, 25 Jun 2009) | 1 line added ro_ prefix to RISC OS textarea code ........ svn path=/trunk/netsurf/; revision=8060
Diffstat (limited to 'desktop')
-rw-r--r--desktop/browser.h17
-rw-r--r--desktop/textarea.c1379
-rw-r--r--desktop/textarea.h58
-rw-r--r--desktop/textinput.h1
4 files changed, 1448 insertions, 7 deletions
diff --git a/desktop/browser.h b/desktop/browser.h
index 37c643520..1c88b4f44 100644
--- a/desktop/browser.h
+++ b/desktop/browser.h
@@ -191,21 +191,24 @@ typedef enum {
* a drag. */
BROWSER_MOUSE_CLICK_1 = 4, /* button 1 clicked. */
BROWSER_MOUSE_CLICK_2 = 8, /* button 2 clicked. */
+ BROWSER_MOUSE_DOUBLE_CLICK = 16, /* button 1 double clicked */
- BROWSER_MOUSE_DRAG_1 = 16, /* start of button 1 drag operation */
- BROWSER_MOUSE_DRAG_2 = 32, /* start of button 2 drag operation */
+ BROWSER_MOUSE_DRAG_1 = 32, /* start of button 1 drag operation */
+ BROWSER_MOUSE_DRAG_2 = 64, /* start of button 2 drag operation */
- BROWSER_MOUSE_DRAG_ON = 64, /* a drag operation was started and
+ BROWSER_MOUSE_DRAG_ON = 128, /* a drag operation was started and
* a mouse button is still pressed */
- BROWSER_MOUSE_HOLDING_1 = 128, /* while button 1 drag is in progress */
- BROWSER_MOUSE_HOLDING_2 = 256, /* while button 2 drag is in progress */
+ BROWSER_MOUSE_HOLDING_1 = 256, /* while button 1 drag is in progress */
+ BROWSER_MOUSE_HOLDING_2 = 512, /* while button 2 drag is in progress */
- BROWSER_MOUSE_MOD_1 = 512, /* primary modifier key pressed
+ BROWSER_MOUSE_MOD_1 = 1024, /* primary modifier key pressed
* (eg. Shift) */
- BROWSER_MOUSE_MOD_2 = 1024 /* secondary modifier key pressed
+ BROWSER_MOUSE_MOD_2 = 2048, /* secondary modifier key pressed
* (eg. Ctrl) */
+ BROWSER_MOUSE_MOD_3 = 4096 /* secondary modifier key pressed
+ * (eg. Alt) */
} browser_mouse_state;
diff --git a/desktop/textarea.c b/desktop/textarea.c
new file mode 100644
index 000000000..6fbcec0d8
--- /dev/null
+++ b/desktop/textarea.c
@@ -0,0 +1,1379 @@
+/*
+ * Copyright 2006 John-Mark Bell <jmb@netsurf-browser.org>
+ * Copyright 2009 Paul Blokus <paul_pl@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
+ * Single/Multi-line UTF-8 text area (implementation)
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include "css/css.h"
+#include "desktop/textarea.h"
+#include "desktop/textinput.h"
+#include "desktop/plotters.h"
+#include "render/font.h"
+#include "utils/log.h"
+#include "utils/utf8.h"
+#include "utils/utils.h"
+
+#define MARGIN_LEFT 2
+#define MARGIN_RIGHT 2
+#define CARET_COLOR 0x000000
+/* background color for readonly textarea */
+#define READONLY_BG 0xD9D9D9
+#define BACKGROUND_COL 0xFFFFFF
+#define BORDER_COLOR 0x000000
+#define SELECTION_COL 0xFFDDDD
+
+
+struct line_info {
+ unsigned int b_start; /**< Byte offset of line start */
+ unsigned int b_length; /**< Byte length of line */
+};
+
+struct text_area {
+
+ int x, y; /**< Coordinates of the widget
+ * (top left corner) with respect to
+ * canvas origin(these don't change
+ * it is the canvas which gets
+ * scrolled)
+ */
+
+ int scroll_x, scroll_y; /**< scroll offsets of the textarea
+ * content
+ */
+
+ unsigned int flags; /**< Textarea flags */
+ int vis_width; /**< Visible width, in pixels */
+ int vis_height; /**< Visible height, in pixels */
+
+ char *text; /**< UTF-8 text */
+ unsigned int text_alloc; /**< Size of allocated text */
+ unsigned int text_len; /**< Length of text, in bytes */
+ unsigned int text_utf8_len; /**< Length of text, in characters
+ * without the trailing NUL
+ */
+ struct {
+ int line; /**< Line caret is on */
+ int char_off; /**< Character index of caret within the
+ * specified line
+ */
+ } caret_pos;
+
+ int selection_start; /**< Character index of sel start(inclusive) */
+ int selection_end; /**< Character index of sel end(exclusive) */
+
+ struct css_style *style; /**< Text style */
+
+ int line_count; /**< Count of lines */
+#define LINE_CHUNK_SIZE 16
+ struct line_info *lines; /**< Line info array */
+ int line_height; /**< Line height obtained from style */
+
+ /** Callback functions for a redraw request */
+ textarea_start_redraw_callback redraw_start_callback;
+ textarea_start_redraw_callback redraw_end_callback;
+
+ void *data; /** < Callback data for both callback functions */
+
+ int drag_start_char; /**< Character index at which the drag was
+ * started
+ */
+};
+
+
+static bool textarea_insert_text(struct text_area *ta, unsigned int index,
+ const char *text);
+static bool textarea_replace_text(struct text_area *ta, unsigned int start,
+ unsigned int end, const char *text);
+static bool textarea_reflow(struct text_area *ta, unsigned int line);
+static unsigned int textarea_get_xy_offset(struct text_area *ta, int x, int y);
+static bool textarea_set_caret_xy(struct text_area *ta, int x, int y);
+static bool textarea_scroll_visible(struct text_area *ta);
+static bool textarea_select(struct text_area *ta, int c_start, int c_end);
+static void textarea_normalise_text(struct text_area *ta,
+ unsigned int b_start, unsigned int b_len);
+
+/**
+ * Create a text area
+ *
+ * \param x X coordinate of left border
+ * \param y Y coordinate of top border
+ * \param width width of the text area
+ * \param height width of the text area
+ * \param flags text area flags
+ * \param style css style (font style properties are used only)
+ * \param redraw_start_callback will be called when textarea wants to redraw
+ * \param redraw_end_callback will be called when textarea finisjes redrawing
+ * \param data user specified data which will be passed to redraw callbacks
+ * \return Opaque handle for textarea or 0 on error
+ */
+struct text_area *textarea_create(int x, int y, int width, int height,
+ unsigned int flags, const struct css_style *style,
+ textarea_start_redraw_callback redraw_start_callback,
+ textarea_end_redraw_callback redraw_end_callback, void *data)
+{
+ struct text_area *ret;
+
+ if (redraw_start_callback == NULL || redraw_end_callback == NULL) {
+ LOG(("no callback provided"));
+ return NULL;
+ }
+
+ ret = malloc(sizeof(struct text_area));
+ if (ret == NULL) {
+ LOG(("malloc failed"));
+ return NULL;
+ }
+
+ ret->redraw_start_callback = redraw_start_callback;
+ ret->redraw_end_callback = redraw_end_callback;
+ ret->data = data;
+ ret->x = x;
+ ret->y = y;
+ ret->vis_width = width;
+ ret->vis_height = height;
+ ret->scroll_x = 0;
+ ret->scroll_y = 0;
+ ret->drag_start_char = 0;
+
+
+ ret->flags = flags;
+ ret->text = malloc(64);
+ if (ret->text == NULL) {
+ LOG(("malloc failed"));
+ free(ret);
+ return NULL;
+ }
+ ret->text[0] = '\0';
+ ret->text_alloc = 64;
+ ret->text_len = 1;
+ ret->text_utf8_len = 0;
+
+ ret->style = malloc(sizeof(struct css_style));
+ if (ret->style == NULL) {
+ LOG(("malloc failed"));
+ free(ret->text);
+ free(ret);
+ return NULL;
+ }
+ memcpy(ret->style, style, sizeof(struct css_style));
+ ret->line_height = css_len2px(&(style->line_height.value.length),
+ style);
+
+ ret->caret_pos.line = ret->caret_pos.char_off = 0;
+ ret->selection_start = -1;
+ ret->selection_end = -1;
+
+ ret->line_count = 0;
+ ret->lines = NULL;
+
+ return ret;
+}
+
+
+/**
+ * Destroy a text area
+ *
+ * \param ta Text area to destroy
+ */
+void textarea_destroy(struct text_area *ta)
+{
+ free(ta->text);
+ free(ta->style);
+ free(ta->lines);
+ free(ta);
+}
+
+
+/**
+ * Set the text in a text area, discarding any current text
+ *
+ * \param ta Text area
+ * \param text UTF-8 text to set text area's contents to
+ * \return true on success, false on memory exhaustion
+ */
+bool textarea_set_text(struct text_area *ta, const char *text)
+{
+ unsigned int len = strlen(text) + 1;
+
+ if (len >= ta->text_alloc) {
+ char *temp = realloc(ta->text, len + 64);
+ if (temp == NULL) {
+ LOG(("realloc failed"));
+ return false;
+ }
+ ta->text = temp;
+ ta->text_alloc = len + 64;
+ }
+
+ memcpy(ta->text, text, len);
+ ta->text_len = len;
+ ta->text_utf8_len = utf8_length(ta->text);
+
+ textarea_normalise_text(ta, 0, len);
+
+ return textarea_reflow(ta, 0);
+}
+
+
+/**
+ * Extract the text from a text area
+ *
+ * \param ta Text area
+ * \param buf Pointer to buffer to receive data, or NULL
+ * to read length required
+ * \param len Length (bytes) of buffer pointed to by buf, or 0 to read length
+ * \return Length (bytes) written/required or -1 on error
+ */
+int textarea_get_text(struct text_area *ta, char *buf, unsigned int len)
+{
+ if (buf == NULL && len == 0) {
+ /* want length */
+ return ta->text_len;
+ }
+
+ if (len < ta->text_len) {
+ LOG(("buffer too small"));
+ return -1;
+ }
+
+ memcpy(buf, ta->text, ta->text_len);
+
+ return ta->text_len;
+}
+
+
+/**
+ * Insert text into the text area
+ *
+ * \param ta Text area
+ * \param index 0-based character index to insert at
+ * \param text UTF-8 text to insert
+ * \return false on memory exhaustion, true otherwise
+ */
+bool textarea_insert_text(struct text_area *ta, unsigned int index,
+ const char *text)
+{
+ unsigned int b_len = strlen(text);
+ size_t b_off;
+
+ if (ta->flags & TEXTAREA_READONLY)
+ return true;
+
+ /* Find insertion point */
+ if (index > ta->text_utf8_len)
+ index = ta->text_utf8_len;
+
+ /* find byte offset of insertion point */
+ for (b_off = 0; index-- > 0;
+ b_off = utf8_next(ta->text, ta->text_len, b_off))
+ ; /* do nothing */
+
+ if (b_len + ta->text_len >= ta->text_alloc) {
+ char *temp = realloc(ta->text, b_len + ta->text_len + 64);
+ if (temp == NULL) {
+ LOG(("realloc failed"));
+ return false;
+ }
+
+ ta->text = temp;
+ ta->text_alloc = b_len + ta->text_len + 64;
+ }
+
+ /* Shift text following up */
+ memmove(ta->text + b_off + b_len, ta->text + b_off,
+ ta->text_len - b_off);
+ /* Insert new text */
+ memcpy(ta->text + b_off, text, b_len);
+ ta->text_len += b_len;
+ ta->text_utf8_len += utf8_length(text);
+
+ textarea_normalise_text(ta, b_off, b_len);
+
+ /** \todo calculate line to reflow from */
+ return textarea_reflow(ta, 0);
+
+}
+
+
+/**
+ * Replace text in a text area
+ *
+ * \param ta Text area
+ * \param start Start character index of replaced section (inclusive)
+ * \param end End character index of replaced section (exclusive)
+ * \param text UTF-8 text to insert
+ * \return false on memory exhaustion, true otherwise
+ */
+bool textarea_replace_text(struct text_area *ta, unsigned int start,
+ unsigned int end, const char *text)
+{
+ unsigned int b_len = strlen(text);
+ size_t b_start, b_end, diff;
+
+ if (ta->flags & TEXTAREA_READONLY)
+ return true;
+
+ if (start > ta->text_utf8_len)
+ start = ta->text_utf8_len;
+ if (end > ta->text_utf8_len)
+ end = ta->text_utf8_len;
+
+ if (start == end)
+ return textarea_insert_text(ta, start, text);
+
+ if (start > end)
+ return false;
+
+ diff = end - start;
+
+ /* find byte offset of replace start */
+ for (b_start = 0; start-- > 0;
+ b_start = utf8_next(ta->text, ta->text_len, b_start))
+ ; /* do nothing */
+
+ /* find byte length of replaced text */
+ for (b_end = b_start; diff-- > 0;
+ b_end = utf8_next(ta->text, ta->text_len, b_end))
+ ; /* do nothing */
+
+ if (b_len + ta->text_len - (b_end - b_start) >= ta->text_alloc) {
+ char *temp = realloc(ta->text,
+ b_len + ta->text_len - (b_end - b_start) + 64);
+ if (temp == NULL) {
+ LOG(("realloc failed"));
+ return false;
+ }
+
+ ta->text = temp;
+ ta->text_alloc =
+ b_len + ta->text_len - (b_end - b_start) + 64;
+ }
+
+ /* Shift text following to new position */
+ memmove(ta->text + b_start + b_len, ta->text + b_end,
+ ta->text_len - b_end);
+
+ /* Insert new text */
+ memcpy(ta->text + b_start, text, b_len);
+
+ ta->text_len += b_len - (b_end - b_start);
+ ta->text_utf8_len = utf8_length(ta->text);
+ textarea_normalise_text(ta, b_start, b_len);
+
+ /** \todo calculate line to reflow from */
+ return textarea_reflow(ta, 0);
+}
+
+
+/**
+ * Set the caret's position
+ *
+ * \param ta Text area
+ * \param caret 0-based character index to place caret at, -1 removes
+ * the caret
+ * \return true on success false otherwise
+ */
+bool textarea_set_caret(struct text_area *ta, int caret)
+{
+ unsigned int c_len;
+ unsigned int b_off;
+ int i;
+ int index;
+ int x, y;
+ int x0, y0, x1, y1;
+ int height;
+
+
+ if (ta->flags & TEXTAREA_READONLY)
+ return true;
+
+ ta->redraw_start_callback(ta->data);
+
+ c_len = ta->text_utf8_len;
+
+ if (caret != -1 && (unsigned)caret > c_len)
+ caret = c_len;
+
+ height = css_len2px(&(ta->style->font_size.value.length),
+ ta->style);
+ /* Delete the old caret */
+ if (ta->caret_pos.char_off != -1) {
+ index = textarea_get_caret(ta);
+ if (index == -1)
+ return false;
+
+ /* the redraw might happen in response to a text-change and
+ the caret position might be beyond the current text */
+ if ((unsigned)index > c_len)
+ index = c_len;
+
+ /* find byte offset of caret position */
+ for (b_off = 0; index-- > 0;
+ b_off = utf8_next(ta->text,
+ ta->text_len, b_off))
+ ; /* do nothing */
+
+ nsfont.font_width(ta->style,
+ ta->text +
+ ta->lines[ta->caret_pos.line].b_start,
+ b_off - ta->lines[ta->caret_pos.line].b_start,
+ &x);
+
+ x += ta->x + MARGIN_LEFT - ta->scroll_x;
+
+ y = ta->line_height * ta->caret_pos.line + ta->y - ta->scroll_y;
+
+ textarea_redraw(ta, x - 1, y - 1, x + 1, y + height + 1);
+ }
+
+ /* check if the caret has to be drawn at all */
+ if (caret != -1) {
+ /* Find byte offset of caret position */
+ for (b_off = 0; caret > 0; caret--)
+ b_off = utf8_next(ta->text, ta->text_len, b_off);
+
+ /* Now find line in which byte offset appears */
+ for (i = 0; i < ta->line_count - 1; i++)
+ if (ta->lines[i + 1].b_start > b_off)
+ break;
+
+ ta->caret_pos.line = i;
+
+ /* Now calculate the char. offset of the caret in this line */
+ for (c_len = 0, ta->caret_pos.char_off = 0;
+ c_len < b_off - ta->lines[i].b_start;
+ c_len = utf8_next(ta->text +
+ ta->lines[i].b_start,
+ ta->lines[i].b_length, c_len))
+ ta->caret_pos.char_off++;
+
+ if (textarea_scroll_visible(ta))
+ textarea_redraw(ta, ta->x, ta->y, ta->x + ta->vis_width,
+ ta->y + ta->vis_height);
+
+ /* Finally, redraw the caret */
+ index = textarea_get_caret(ta);
+ if (index == -1)
+ return false;
+
+ /* find byte offset of caret position */
+ for (b_off = 0; index-- > 0;
+ b_off = utf8_next(ta->text,
+ ta->text_len, b_off))
+ ; /* do nothing */
+
+ nsfont.font_width(ta->style,
+ ta->text +
+ ta->lines[ta->caret_pos.line].b_start,
+ b_off - ta->lines[ta->caret_pos.line].b_start,
+ &x);
+
+ x += ta->x + MARGIN_LEFT - ta->scroll_x;
+
+ y = ta->line_height * ta->caret_pos.line + ta->y - ta->scroll_y;
+
+ x0 = max(x - 1, ta->x + MARGIN_LEFT);
+ y0 = max(y - 1, ta->y);
+ x1 = min(x + 1, ta->x + ta->vis_width - MARGIN_RIGHT);
+ y1 = min(y + height + 1, ta->y + ta->vis_height);
+
+ plot.clip(x0, y0, x1, y1);
+ plot.line(x, y, x, y + height, 1, CARET_COLOR, false, false);
+ }
+ ta->redraw_end_callback(ta->data);
+
+ return true;
+}
+
+
+/**
+ * get character offset from the beginning of the text for some coordinates
+ *
+ * \param ta Text area
+ * \param x X coordinate
+ * \param y Y coordinate
+ * \return character offset
+ */
+unsigned int textarea_get_xy_offset(struct text_area *ta, int x, int y)
+{
+ size_t b_off, temp;
+ unsigned int c_off;
+ int line;
+
+ if (!ta->line_count)
+ return 0;
+
+ x = x - ta->x - MARGIN_LEFT + ta->scroll_x;
+ y = y - ta->y + ta->scroll_y;
+
+ if (x < 0)
+ x = 0;
+
+ line = y / ta->line_height;
+
+ if (line < 0)
+ line = 0;
+ if (ta->line_count - 1 < line)
+ line = ta->line_count - 1;
+
+ nsfont.font_position_in_string(ta->style,
+ ta->text + ta->lines[line].b_start,
+ ta->lines[line].b_length, x, &b_off, &x);
+
+ /* If the calculated byte offset corresponds with the number of bytes
+ * in the line, and the line has been soft-wrapped, then ensure the
+ * caret offset is before the trailing space character, rather than
+ * after it. Otherwise, the caret will be placed at the start of the
+ * following line, which is undesirable.
+ */
+ if (b_off == (unsigned)ta->lines[line].b_length &&
+ ta->text[ta->lines[line].b_start +
+ ta->lines[line].b_length - 1] == ' ')
+ b_off--;
+
+ for (temp = 0, c_off = 0; temp < b_off + ta->lines[line].b_start;
+ temp = utf8_next(ta->text, ta->text_len, temp))
+ c_off++;
+
+ return c_off;
+}
+
+
+/**
+ * Set the caret's position
+ *
+ * \param ta Text area
+ * \param x X position of caret in a window relative to text area top left
+ * \param y Y position of caret in a window relative to text area top left
+ * \return true on success false otherwise
+ */
+bool textarea_set_caret_xy(struct text_area *ta, int x, int y)
+{
+ unsigned int c_off;
+
+ if (ta->flags & TEXTAREA_READONLY)
+ return true;
+
+ c_off = textarea_get_xy_offset(ta, x, y);
+ return textarea_set_caret(ta, c_off);
+}
+
+
+/**
+ * Get the caret's position
+ *
+ * \param ta Text area
+ * \return 0-based character index of caret location, or -1 on error
+ */
+int textarea_get_caret(struct text_area *ta)
+{
+ unsigned int c_off = 0, b_off;
+
+
+ /* if the text is a trailing NUL only */
+ if (ta->text_utf8_len == 0)
+ return 0;
+
+ /* Calculate character offset of this line's start */
+ for (b_off = 0; b_off < ta->lines[ta->caret_pos.line].b_start;
+ b_off = utf8_next(ta->text, ta->text_len, b_off))
+ c_off++;
+
+ return c_off + ta->caret_pos.char_off;
+}
+
+/**
+ * Reflow a text area from the given line onwards
+ *
+ * \param ta Text area to reflow
+ * \param line Line number to begin reflow on
+ * \return true on success false otherwise
+ */
+bool textarea_reflow(struct text_area *ta, unsigned int line)
+{
+ char *text;
+ unsigned int len;
+ size_t b_off;
+ int x;
+ char *space;
+ unsigned int line_count = 0;
+
+ /** \todo pay attention to line parameter */
+ /** \todo create horizontal scrollbar if needed */
+
+ ta->line_count = 0;
+
+ if (ta->lines == NULL) {
+ ta->lines =
+ malloc(LINE_CHUNK_SIZE * sizeof(struct line_info));
+ if (ta->lines == NULL) {
+ LOG(("malloc failed"));
+ return false;
+ }
+ }
+
+ if (!(ta->flags & TEXTAREA_MULTILINE)) {
+ /* Single line */
+ ta->lines[line_count].b_start = 0;
+ ta->lines[line_count++].b_length = ta->text_len - 1;
+
+ ta->line_count = line_count;
+
+ return true;
+ }
+
+ for (len = ta->text_len - 1, text = ta->text; len > 0;
+ len -= b_off, text += b_off) {
+
+ nsfont.font_split(ta->style, text, len,
+ ta->vis_width - MARGIN_LEFT - MARGIN_RIGHT,
+ &b_off, &x);
+
+ if (line_count > 0 && line_count % LINE_CHUNK_SIZE == 0) {
+ struct line_info *temp = realloc(ta->lines,
+ (line_count + LINE_CHUNK_SIZE) *
+ sizeof(struct line_info));
+ if (temp == NULL) {
+ LOG(("realloc failed"));
+ return false;
+ }
+
+ ta->lines = temp;
+ }
+
+ /* handle LF */
+ for (space = text; space <= text + b_off; space++) {
+ if (*space == '\n')
+ break;
+ }
+
+ if (space <= text + b_off) {
+ /* Found newline; use it */
+ ta->lines[line_count].b_start = text - ta->text;
+ ta->lines[line_count++].b_length = space - text;
+
+ b_off = space + 1 - text;
+
+ if (len - b_off == 0) {
+ /* reached end of input => add last line */
+ ta->lines[line_count].b_start =
+ text + b_off - ta->text;
+ ta->lines[line_count++].b_length = 0;
+ }
+
+ continue;
+ }
+
+ if (len - b_off > 0) {
+ /* find last space (if any) */
+ for (space = text + b_off; space > text; space--)
+ if (*space == ' ')
+ break;
+
+ if (space != text)
+ b_off = space + 1 - text;
+ }
+
+ ta->lines[line_count].b_start = text - ta->text;
+ ta->lines[line_count++].b_length = b_off;
+ }
+
+ ta->line_count = line_count;
+
+ return true;
+}
+
+/**
+ * Handle redraw requests for text areas
+ *
+ * \param redraw Redraw request block
+ */
+void textarea_redraw(struct text_area *ta, int x0, int y0, int x1, int y1)
+{
+ int line0, line1, line;
+ int chars, offset;
+ unsigned int c_pos, c_len, b_start, b_end, line_len;
+ char *line_text;
+
+
+ if (x1 < ta->x || x0 > ta->x + ta->vis_width || y1 < ta->y ||
+ y0 > ta->y + ta->vis_height)
+ /* Textarea outside the clipping rectangle */
+ return;
+
+ if (ta->lines == NULL)
+ /* Nothing to redraw */
+ return;
+
+ line0 = (y0 - ta->y + ta->scroll_y) / ta->line_height - 1;
+ line1 = (y1 - ta->y + ta->scroll_y) / ta->line_height + 1;
+
+ if (line0 < 0)
+ line0 = 0;
+ if (line1 < 0)
+ line1 = 0;
+ if (ta->line_count - 1 < line0)
+ line0 = ta->line_count - 1;
+ if (ta->line_count - 1 < line1)
+ line1 = ta->line_count - 1;
+ if (line1 < line0)
+ line1 = line0;
+
+ if (x0 < ta->x)
+ x0 = ta->x;
+ if (y0 < ta->y)
+ y0 = ta->y;
+ if (x1 > ta->x + ta->vis_width)
+ x1 = ta->x + ta->vis_width;
+ if (y1 > ta->y + ta->vis_height)
+ y1 = ta->y + ta->vis_height;
+
+ plot.clip(x0, y0, x1, y1);
+ plot.fill(x0, y0, x1, y1, (ta->flags & TEXTAREA_READONLY) ?
+ READONLY_BG : BACKGROUND_COL);
+ plot.rectangle(ta->x, ta->y, ta->vis_width - 1, ta->vis_height - 1, 1,
+ BORDER_COLOR, false, false);
+
+ if (x0 < ta->x + MARGIN_LEFT)
+ x0 = ta->x + MARGIN_LEFT;
+ if (x1 > ta->x + ta->vis_width - MARGIN_RIGHT)
+ x1 = ta->x + ta->vis_width - MARGIN_RIGHT;
+ plot.clip(x0, y0, x1, y1);
+
+ if (line0 > 0)
+ c_pos = utf8_bounded_length(ta->text,
+ ta->lines[line0].b_start - 1);
+ else
+ c_pos = 0;
+
+ for (line = line0; (line <= line1) &&
+ (ta->y + line * ta->line_height <= y1 + ta->scroll_y);
+ line++) {
+ if (ta->lines[line].b_length == 0)
+ continue;
+
+ c_len = utf8_bounded_length(
+ &(ta->text[ta->lines[line].b_start]),
+ ta->lines[line].b_length);
+
+ /* if there is a newline between the lines count it too */
+ if (line < ta->line_count - 1 && ta->lines[line + 1].b_start !=
+ ta->lines[line].b_start +
+ ta->lines[line].b_length)
+ c_len++;
+
+ /* check if a part of the line is selected, won't happen if no
+ selection (ta->selection_end = -1) */
+ if (ta->selection_end != -1 &&
+ c_pos < (unsigned)ta->selection_end &&
+ c_pos + c_len > (unsigned)ta->selection_start) {
+
+ /* offset from the beginning of the line */
+ offset = ta->selection_start - c_pos;
+ chars = ta->selection_end - c_pos -
+ (offset > 0 ? offset:0);
+
+ line_text = &(ta->text[ta->lines[line].b_start]);
+ line_len = ta->lines[line].b_length;
+
+ if (offset > 0) {
+
+ /* find byte start of the selected part */
+ for (b_start = 0; offset > 0; offset--)
+ b_start = utf8_next(line_text,
+ line_len,
+ b_start);
+ nsfont.font_width(ta->style, line_text,
+ b_start, &x0);
+ x0 += ta->x + MARGIN_LEFT;
+ }
+ else {
+ x0 = ta->x + MARGIN_LEFT;
+ b_start = 0;
+ }
+
+
+ if (chars >= 0) {
+
+ /* find byte end of the selected part */
+ for (b_end = b_start; chars > 0 &&
+ b_end < line_len;
+ chars--) {
+ b_end = utf8_next(line_text, line_len,
+ b_end);
+ }
+ }
+ else
+ b_end = ta->lines[line].b_length;
+
+ b_end -= b_start;
+ nsfont.font_width(ta->style,
+ &(ta->text[ta->lines[line].b_start +
+ b_start]),
+ b_end, &x1);
+ x1 += x0;
+ plot.fill(x0 - ta->scroll_x, ta->y +
+ line * ta->line_height
+ + 1 - ta->scroll_y,
+ x1 - ta->scroll_x,
+ ta->y + (line + 1) * ta->line_height -
+ 1 - ta->scroll_y,
+ SELECTION_COL);
+
+ }
+
+ c_pos += c_len;
+
+ y0 = ta->y + line * ta->line_height + 0.75 * ta->line_height;
+
+ plot.text(ta->x + MARGIN_LEFT - ta->scroll_x, y0 - ta->scroll_y,
+ ta->style,
+ ta->text + ta->lines[line].b_start,
+ ta->lines[line].b_length,
+ (ta->flags & TEXTAREA_READONLY) ?
+ READONLY_BG : BACKGROUND_COL,
+ ta->style->color);
+ }
+}
+
+/**
+ * Key press handling for text areas.
+ *
+ * \param ta The text area which got the keypress
+ * \param key The ucs4 character codepoint
+ * \return true if the keypress is dealt with, false otherwise.
+ */
+bool textarea_keypress(struct text_area *ta, uint32_t key)
+{
+ char utf8[6];
+ unsigned int caret, caret_init, length, l_len, b_off, b_len;
+ int c_line, c_chars, line;
+ bool redraw = false;
+ bool readonly;
+
+ caret_init = caret = textarea_get_caret(ta);
+ line = ta->caret_pos.line;
+ readonly = (ta->flags & TEXTAREA_READONLY ? true:false);
+
+
+ if (!(key <= 0x001F || (0x007F <= key && key <= 0x009F))) {
+ /* normal character insertion */
+ length = utf8_from_ucs4(key, utf8);
+ utf8[length] = '\0';
+
+ if (!textarea_insert_text(ta, caret, utf8))
+ return false;
+ caret++;
+ redraw = true;
+
+ } else switch (key) {
+ case KEY_SELECT_ALL:
+ caret = ta->text_utf8_len;
+
+ ta->selection_start = 0;
+ ta->selection_end = ta->text_utf8_len;
+ redraw = true;
+ break;
+ case KEY_COPY_SELECTION:
+ break;
+ case KEY_DELETE_LEFT:
+ if (readonly)
+ break;
+ if (ta->selection_start != -1) {
+ if (!textarea_replace_text(ta,
+ ta->selection_start,
+ ta->selection_end, ""))
+ return false;
+ ta->selection_start = ta->selection_end = -1;
+ redraw = true;
+ } else {
+ if (caret) {
+ if (!textarea_replace_text(ta,
+ caret - 1,
+ caret, ""))
+ return false;
+ caret--;
+ redraw = true;
+ }
+ }
+ break;
+ break;
+ case KEY_NL:
+ if (readonly)
+ break;
+ if(!textarea_insert_text(ta, caret, "\n"))
+ return false;
+ caret++;
+ ta->selection_start = ta->selection_end = -1;
+ redraw = true;
+ break;
+ case KEY_CUT_LINE:
+ case KEY_PASTE:
+ case KEY_CUT_SELECTION:
+ break;
+ case KEY_ESCAPE:
+ case KEY_CLEAR_SELECTION:
+ ta->selection_start = -1;
+ ta->selection_end = -1;
+ redraw = true;
+ break;
+ case KEY_LEFT:
+ if (readonly)
+ break;
+ if (caret)
+ caret--;
+ if (ta->selection_start != -1) {
+ ta->selection_start = ta->selection_end = -1;
+ redraw = true;
+ }
+ break;
+ case KEY_RIGHT:
+ if (readonly)
+ break;
+ if (caret < ta->text_utf8_len)
+ caret++;
+ if (ta->selection_start != -1) {
+ ta->selection_start = ta->selection_end = -1;
+ redraw = true;
+ }
+ break;
+ case KEY_PAGE_UP:
+ if (readonly)
+ break;
+ if (ta->flags & TEXTAREA_MULTILINE) {
+ /* +1 because one line is subtracted in
+ KEY_UP */
+ line = ta->caret_pos.line - (ta->vis_height +
+ ta->line_height - 1) /
+ ta->line_height
+ + 1;
+ }
+ /* fall through */
+ case KEY_UP:
+ if (readonly)
+ break;
+ if (ta->selection_start != -1) {
+ ta->selection_start = ta->selection_end = -1;
+ redraw = true;
+ }
+ if (ta->flags & TEXTAREA_MULTILINE) {
+ line--;
+ if (line < 0)
+ line = 0;
+ if (line == ta->caret_pos.line)
+ break;
+
+ b_off = ta->lines[line].b_start;
+ b_len = ta->lines[line].b_length;
+
+ c_line = ta->caret_pos.line;
+ c_chars = ta->caret_pos.char_off;
+
+ if (ta->text[b_off + b_len - 1] == ' '
+ && line < ta->line_count - 1)
+ b_len--;
+
+ l_len = utf8_bounded_length(&(ta->text[b_off]),
+ b_len);
+
+
+ ta->caret_pos.line = line;
+ ta->caret_pos.char_off = min(l_len,
+ (unsigned)
+ ta->caret_pos.char_off);
+
+ caret = textarea_get_caret(ta);
+
+ ta->caret_pos.line = c_line;
+ ta->caret_pos.char_off = c_chars;
+ }
+ break;
+ case KEY_PAGE_DOWN:
+ if (readonly)
+ break;
+ if (ta->flags & TEXTAREA_MULTILINE) {
+ /* -1 because one line is added in KEY_DOWN */
+ line = ta->caret_pos.line + (ta->vis_height +
+ ta->line_height - 1) /
+ ta->line_height
+ - 1;
+ }
+ /* fall through */
+ case KEY_DOWN:
+ if (readonly)
+ break;
+ if (ta->selection_start != -1) {
+ ta->selection_start = ta->selection_end = -1;
+ redraw = true;
+ }
+ if (ta->flags & TEXTAREA_MULTILINE) {
+ line++;
+ if (line > ta->line_count - 1)
+ line = ta->line_count - 1;
+ if (line == ta->caret_pos.line)
+ break;
+
+ b_off = ta->lines[line].b_start;
+ b_len = ta->lines[line].b_length;
+
+ c_line = ta->caret_pos.line;
+ c_chars = ta->caret_pos.char_off;
+
+ if (ta->text[b_off + b_len - 1] == ' '
+ && line < ta->line_count - 1)
+ b_len--;
+
+ l_len = utf8_bounded_length(&(ta->text[b_off]),
+ b_len);
+
+
+ ta->caret_pos.line = line;
+ ta->caret_pos.char_off = min(l_len,
+ (unsigned)
+ ta->caret_pos.char_off);
+
+ caret = textarea_get_caret(ta);
+
+ ta->caret_pos.line = c_line;
+ ta->caret_pos.char_off = c_chars;
+ }
+ break;
+ case KEY_DELETE_RIGHT:
+ if (readonly)
+ break;
+ if (ta->selection_start != -1) {
+ if (!textarea_replace_text(ta,
+ ta->selection_start,
+ ta->selection_end, ""))
+ return false;
+
+ ta->selection_start = ta->selection_end = -1;
+ redraw = true;
+ } else {
+ if (caret < ta->text_utf8_len) {
+ if (!textarea_replace_text(ta, caret,
+ caret + 1, ""))
+ return false;
+ redraw = true;
+ }
+ }
+ break;
+ case KEY_LINE_START:
+ if (readonly)
+ break;
+ caret -= ta->caret_pos.char_off;
+ if (ta->selection_start != -1) {
+ ta->selection_start = ta->selection_end = -1;
+ redraw = true;
+ }
+ break;
+ case KEY_LINE_END:
+ if (readonly)
+ break;
+
+ caret = utf8_bounded_length(ta->text,
+ ta->lines[ta->caret_pos.line].b_start +
+ ta->lines[ta->caret_pos.line].b_length);
+ if (ta->text[ta->lines[ta->caret_pos.line].b_start +
+ ta->lines[ta->caret_pos.line].b_length
+ - 1] == ' ')
+ caret--;
+ if (ta->selection_start != -1) {
+ ta->selection_start = ta->selection_end = -1;
+ redraw = true;
+ }
+ break;
+ case KEY_TEXT_START:
+ if (readonly)
+ break;
+ caret = 0;
+ if (ta->selection_start != -1) {
+ ta->selection_start = ta->selection_end = -1;
+ redraw = true;
+ }
+ break;
+ case KEY_TEXT_END:
+ if (readonly)
+ break;
+ caret = ta->text_utf8_len;
+ if (ta->selection_start != -1) {
+ ta->selection_start = ta->selection_end = -1;
+ redraw = true;
+ }
+ break;
+ case KEY_WORD_LEFT:
+ case KEY_WORD_RIGHT:
+ break;
+ case KEY_DELETE_LINE_END:
+ if (readonly)
+ break;
+ if (ta->selection_start != -1) {
+ if (!textarea_replace_text(ta,
+ ta->selection_start,
+ ta->selection_end, ""))
+ return false;
+ ta->selection_start = ta->selection_end = -1;
+ } else {
+ b_off = ta->lines[ta->caret_pos.line].b_start;
+ b_len = ta->lines[ta->caret_pos.line].b_length;
+ l_len = utf8_bounded_length(&(ta->text[b_off]),
+ b_len);
+ if (!textarea_replace_text(ta, caret,
+ caret + l_len, ""))
+ return false;
+ }
+ redraw = true;
+ break;
+ case KEY_DELETE_LINE_START:
+ if (readonly)
+ break;
+ if (ta->selection_start != -1) {
+ if (!textarea_replace_text(ta,
+ ta->selection_start,
+ ta->selection_end, ""))
+ return false;
+ ta->selection_start = ta->selection_end = -1;
+ } else {
+ if (textarea_replace_text(ta,
+ caret - ta->caret_pos.char_off,
+ caret,
+ ""))
+ return false;
+ caret -= ta->caret_pos.char_off;
+ }
+ redraw = true;
+ break;
+ default:
+ return false;
+ }
+
+ //TODO:redraw only the important part
+ if (redraw) {
+ ta->redraw_start_callback(ta->data);
+ textarea_redraw(ta, ta->x, ta->y, ta->x + ta->vis_width,
+ ta->y + ta->vis_height);
+ ta->redraw_end_callback(ta->data);
+ }
+
+ if (caret != caret_init || redraw)
+ return textarea_set_caret(ta, caret);
+
+ return true;
+}
+
+/**
+ * Scrolls a textarea to make the caret visible (doesn't perform a redraw)
+ *
+ * \param ta The text area to be scrolled
+ * \return true if textarea was scrolled false otherwise
+ */
+bool textarea_scroll_visible(struct text_area *ta)
+{
+ int x0, x1, y0, y1, x, y;
+ int index, b_off;
+ bool scrolled = false;
+
+ if (ta->caret_pos.char_off == -1)
+ return false;
+
+ x0 = ta->x + MARGIN_LEFT;
+ x1 = ta->x + ta->vis_width - MARGIN_RIGHT;
+ y0 = ta->y;
+ y1 = ta->y + ta->vis_height;
+
+ index = textarea_get_caret(ta);
+
+ /* find byte offset of caret position */
+ for (b_off = 0; index-- > 0;
+ b_off = utf8_next(ta->text, ta->text_len, b_off))
+ ; /* do nothing */
+
+ nsfont.font_width(ta->style,
+ ta->text + ta->lines[ta->caret_pos.line].b_start,
+ b_off - ta->lines[ta->caret_pos.line].b_start,
+ &x);
+
+ /* top-left of caret */
+ x += ta->x + MARGIN_LEFT - ta->scroll_x;
+ y = ta->line_height * ta->caret_pos.line + ta->y - ta->scroll_y;
+
+ /* check and change vertical scroll */
+ if (y < y0) {
+ ta->scroll_y -= y0 - y;
+ scrolled = true;
+ } else if (y + ta->line_height > y1) {
+ ta->scroll_y += y + ta->line_height - y1;
+ scrolled = true;
+ }
+
+
+ /* check and change horizontal scroll */
+ if (x < x0) {
+ ta->scroll_x -= x0 - x ;
+ scrolled = true;
+ } else if (x > x1 - 1) {
+ ta->scroll_x += x - (x1 - 1);
+ scrolled = true;
+ }
+
+ return scrolled;
+}
+
+
+/**
+ * Handles all kinds of mouse action
+ *
+ * \param ta Text area
+ * \param mouse the mouse state at action moment
+ * \param x X coordinate
+ * \param y Y coordinate
+ * \return true if action was handled false otherwise
+ */
+bool textarea_mouse_action(struct text_area *ta, browser_mouse_state mouse,
+ int x, int y)
+{
+ int c_start, c_end;
+
+ /* mouse button pressed above the text area, move caret */
+ if (mouse & BROWSER_MOUSE_PRESS_1) {
+ if (ta->selection_start != -1) {
+ ta->selection_start = ta->selection_end = -1;
+ ta->redraw_start_callback(ta->data);
+ textarea_redraw(ta, ta->x, ta->y, ta->x + ta->vis_width,
+ ta->y + ta->vis_height);
+ ta->redraw_end_callback(ta->data);
+ }
+ if (!(ta->flags & TEXTAREA_READONLY))
+ return textarea_set_caret_xy(ta, x, y);
+ }
+ else if (mouse & BROWSER_MOUSE_DRAG_1) {
+ ta->drag_start_char = textarea_get_xy_offset(ta, x, y);
+ if (!(ta->flags & TEXTAREA_READONLY))
+ return textarea_set_caret(ta, -1);
+ }
+ else if (mouse & BROWSER_MOUSE_HOLDING_1) {
+ c_start = ta->drag_start_char;
+ c_end = textarea_get_xy_offset(ta, x, y);
+ return textarea_select(ta, c_start, c_end);
+
+ }
+
+ return true;
+}
+
+
+/**
+ * Handles the end of a drag operation
+ *
+ * \param ta Text area
+ * \param mouse the mouse state at drag end moment
+ * \param x X coordinate
+ * \param y Y coordinate
+ * \return true if drag end was handled false otherwise
+ */
+bool textarea_drag_end(struct text_area *ta, browser_mouse_state mouse,
+ int x, int y)
+{
+ int c_end;
+
+ c_end = textarea_get_xy_offset(ta, x, y);
+ return textarea_select(ta, ta->drag_start_char, c_end);
+}
+
+/**
+ * Selects a character range in the textarea and redraws it
+ *
+ * \param ta Text area
+ * \param c_start First character (inclusive)
+ * \param c_end Last character (exclusive)
+ * \return true on success false otherwise
+ */
+bool textarea_select(struct text_area *ta, int c_start, int c_end)
+{
+ int swap = -1;
+
+ /* if start is after end they get swapped, start won't and end will
+ be selected this way */
+ if (c_start > c_end) {
+ swap = c_start;
+ c_start = c_end;
+ c_end = swap;
+ }
+
+ ta->selection_start = c_start;
+ ta->selection_end = c_end;
+
+ ta->redraw_start_callback(ta->data);
+ textarea_redraw(ta, ta->x, ta->y, ta->x + ta->vis_width,
+ ta->y + ta->vis_height);
+ ta->redraw_end_callback(ta->data);
+
+ if (!(ta->flags & TEXTAREA_READONLY)) {
+ if (swap == -1)
+ return textarea_set_caret(ta, c_end);
+ else
+ return textarea_set_caret(ta, c_start);
+ }
+
+ return true;
+}
+
+
+/**
+ * Normalises any line endings within the text, replacing CRLF or CR with
+ * LF as necessary. If the textarea is single line, then all linebreaks are
+ * converted into spaces.
+ *
+ * \param ta Text area
+ * \param b_start Byte offset to start at
+ * \param b_len Byte length to check
+ */
+void textarea_normalise_text(struct text_area *ta,
+ unsigned int b_start, unsigned int b_len)
+{
+ bool multi = (ta->flags & TEXTAREA_MULTILINE) ? true:false;
+ unsigned int index;
+
+ /* Remove CR characters. If it's a CRLF pair delete it ot replace it
+ * with LF otherwise.
+ */
+ for (index = 0; index < b_len; index++) {
+ if (ta->text[b_start + index] == '\r') {
+ if (b_start + index + 1 <= ta->text_len &&
+ ta->text[b_start + index + 1] == '\n') {
+ ta->text_len--;
+ ta->text_utf8_len--;
+ memmove(ta->text + b_start + index,
+ ta->text + b_start + index + 1,
+ ta->text_len - b_start - index);
+ }
+ else
+ ta->text[b_start + index] = '\n';
+ }
+ /* Replace newlines with spaces if this is a single line
+ * textarea.
+ */
+ if (!multi && (ta->text[b_start + index] == '\n'))
+ ta->text[b_start + index] = ' ';
+ }
+
+}
diff --git a/desktop/textarea.h b/desktop/textarea.h
new file mode 100644
index 000000000..4213a5484
--- /dev/null
+++ b/desktop/textarea.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2006 John-Mark Bell <jmb@netsurf-browser.org>
+ * Copyright 2009 Paul Blokus <paul_pl@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
+ * Single/Multi-line UTF-8 text area (interface)
+ */
+
+#ifndef _NETSURF_DESKTOP_TEXTAREA_H_
+#define _NETSURF_DESKTOP_TEXTAREA_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "css/css.h"
+#include "desktop/browser.h"
+
+/* Text area flags */
+#define TEXTAREA_MULTILINE 0x01 /**< Text area is multiline */
+#define TEXTAREA_READONLY 0x02 /**< Text area is read only */
+
+struct text_area;
+
+typedef void(*textarea_start_redraw_callback)(void *data);
+typedef void(*textarea_end_redraw_callback)(void *data);
+
+struct text_area *textarea_create(int x, int y, int width, int height,
+ unsigned int flags, const struct css_style *style,
+ textarea_start_redraw_callback redraw_start_callback,
+ textarea_end_redraw_callback redraw_end_callback, void *data);
+void textarea_destroy(struct text_area *ta);
+bool textarea_set_text(struct text_area *ta, const char *text);
+int textarea_get_text(struct text_area *ta, char *buf, unsigned int len);
+bool textarea_set_caret(struct text_area *ta, int caret);
+int textarea_get_caret(struct text_area *ta);
+void textarea_redraw(struct text_area *ta, int x0, int y0, int x1, int y1);
+bool textarea_keypress(struct text_area *ta, uint32_t key);
+bool textarea_mouse_action(struct text_area *ta, browser_mouse_state mouse,
+ int x, int y);
+bool textarea_drag_end(struct text_area *ta, browser_mouse_state mouse,
+ int x, int y);
+
+#endif
+
diff --git a/desktop/textinput.h b/desktop/textinput.h
index fb4d98b90..263638d3a 100644
--- a/desktop/textinput.h
+++ b/desktop/textinput.h
@@ -28,6 +28,7 @@
#include <stdbool.h>
+
struct browser_window;
struct box;