Precis:

This is the second version of Paul Blokus' core text widget.


Added files


Index: desktop/textarea.c
===================================================================
--- /dev/null   2009-04-16 19:17:07.000000000 +0100
+++ desktop/textarea.c  2009-06-24 17:28:49.000000000 +0100
@@ -0,0 +1,1391 @@
+/*
+ * Copyright 2006 John-Mark Bell <[email protected]>
+ * Copyright 2009 Paul Blokus <[email protected]>
+ *
+ * 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 /0 character*/
+       struct {
+               int line;               /**< Line caret is on */
+               int char_off;           /**< Character index of caret */
+       } 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_radraw_callback redraw_start_callback;
+       textarea_start_radraw_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,
+               bool *line_end);
+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,
+               bool line_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_radraw_callback redraw_start_callback,
+               textarea_end_radraw_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 = 1;
+       
+       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 = 0;
+
+       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 true on success, false otherwise (memory exhaustion or
+ *        readonly flag set)
+ */
+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 false;
+
+       /* Find insertion point */
+       if (index > ta->text_utf8_len)
+               index = ta->text_utf8_len;
+
+       LOG(("inserting at %i\n", index));
+       
+       /*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 true on success, false otherwise (memory exhaustion or
+ *        readonly flag set)
+ */
+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 false;
+
+       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
+ * \param line_end     says whether caret should be positioned at the end of
+ *                     the previos line if it indices on the beginning of
+ *                     a line that was crated by a soft wrap (on a space)
+ * \return true on success false otherwise
+ */
+bool textarea_set_caret(struct text_area *ta, int caret, bool line_end)
+{
+       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 false;
+       
+       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;
+               
+               if (line_end && i > 0 && b_off == ta->lines[i].b_start &&
+                                 ta->text[ta->lines[i].b_start - 1] == ' ') {
+                       b_off--;
+                       --i;
+               }
+                       
+               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
+ * \param line_end     will be updated to true if the caret should be placed at
+ *                     the end of a soft wrapped line false otherwise
+ * \return             character offset
+ */
+unsigned int textarea_get_xy_offset(struct text_area *ta, int x, int y,
+               bool *line_end)
+{
+       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);
+       
+       *line_end = false;
+       /* If the calculated byte offset corresponds with the number of bytes
+       * in the line, and the line has been soft-wrapped, then check line_end
+       * true to ensure the caret offset will be set 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] == ' ')
+               *line_end = true;
+
+       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;
+       bool line_end;
+       
+       if (ta->flags & TEXTAREA_READONLY)
+               return false;
+       
+       c_off = textarea_get_xy_offset(ta, x, y, &line_end);
+       return textarea_set_caret(ta, c_off, line_end);
+}
+
+
+/**
+ * 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 \0 only*/
+       if (ta->text_len == 1)
+               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;
+       bool line_end = false;
+
+       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;
+                               c_line = ta->caret_pos.line;
+                               c_chars = ta->caret_pos.char_off;
+                               
+                               if (c_chars != 0)
+                                       line_end = true;
+                               
+                               ta->caret_pos.line = line;
+                               
+                               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);
+                               
+                               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;
+                               c_line = ta->caret_pos.line;
+                               c_chars = ta->caret_pos.char_off;
+                               
+                               if (c_chars != 0)
+                                       line_end = true;
+                               
+                               ta->caret_pos.line = line;
+                               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);
+                               
+                               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;
+                       
+                       line_end = true;
+                       
+                       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, line_end);
+               
+       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;
+       bool line_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,
+                               &line_end);
+               if (!(ta->flags & TEXTAREA_READONLY))
+                       return textarea_set_caret(ta, -1, line_end);
+       }
+       else if (mouse & BROWSER_MOUSE_HOLDING_1) {
+               c_start = ta->drag_start_char;
+               c_end = textarea_get_xy_offset(ta, x, y, &line_end);
+               return textarea_select(ta, c_start, c_end, line_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;
+       bool line_end;
+
+       c_end = textarea_get_xy_offset(ta, x, y, &line_end);
+       return textarea_select(ta, ta->drag_start_char, c_end, line_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)
+ * \param line_end     true if the selection ends at the end of a soft wrapped
+ *                     line
+ * \return             true on success false otherwise
+ */
+bool textarea_select(struct text_area *ta, int c_start, int c_end,
+               bool line_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, line_end);
+               else
+                       return textarea_set_caret(ta, c_start, line_end);
+       }
+       
+       return true;
+}
+
+
+/**
+ * Removes any CR characters and changes newlines into spaces if it is a single
+ * line textarea.
+ *
+ * \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 CRNL pair delete it ot replace it
+        * with NL 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] = ' ';
+       }
+       
+}
Index: desktop/textarea.h
===================================================================
--- /dev/null   2009-04-16 19:17:07.000000000 +0100
+++ desktop/textarea.h  2009-06-24 17:28:49.000000000 +0100
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2006 John-Mark Bell <[email protected]>
+ * Copyright 2009 Paul Blokus <[email protected]> 
+ *
+ * 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_radraw_callback)(void *data);
+typedef void(*textarea_end_radraw_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_radraw_callback redraw_start_callback,
+               textarea_end_radraw_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, bool line_end);
+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
+


Changed files


 Makefile.sources    |    2 -
 desktop/browser.h   |   17 +++++++-----
 desktop/textinput.h |    1 
 gtk/font_pango.c    |    1 
 gtk/gtk_gui.c       |   72 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtk_gui.h       |    3 ++
 gtk/gtk_plotters.c  |    2 -
 gtk/gtk_window.c    |   43 +------------------------------
 8 files changed, 91 insertions(+), 50 deletions(-)


Index: gtk/gtk_gui.h
===================================================================
--- gtk/gtk_gui.h       (revision 7935)
+++ gtk/gtk_gui.h       (working copy)
@@ -19,6 +19,7 @@
 #ifndef GTK_GUI_H
 #define GTK_GUI_H
 
+#include <inttypes.h>
 #include <stdbool.h>
 #include <gtk/gtk.h>
 #include <glade/glade.h>
@@ -37,5 +38,7 @@
 
 extern GtkDialog *wndOpenFile;
 
+uint32_t gtk_gui_gdkkey_to_nskey(GdkEventKey *);
+
 #endif /* GTK_GUI_H */
 
Index: gtk/gtk_window.c
===================================================================
--- gtk/gtk_window.c    (revision 7935)
+++ gtk/gtk_window.c    (working copy)
@@ -38,7 +38,7 @@
 struct gui_window *window_list = 0;    /**< first entry in win list*/
 int temp_open_background = -1;
 
-static uint32_t gdkkey_to_nskey(GdkEventKey *);
+
 static void nsgtk_gui_window_attach_child(struct gui_window *parent,
                                           struct gui_window *child);
 /* Methods which apply only to a gui_window */
@@ -449,50 +449,11 @@
        return TRUE;
 }
 
-uint32_t gdkkey_to_nskey(GdkEventKey *key)
-{
-        /* this function will need to become much more complex to support
-         * everything that the RISC OS version does.  But this will do for
-         * now.  I hope.
-         */
-
-        switch (key->keyval)
-        {
-                case GDK_BackSpace:             return KEY_DELETE_LEFT;
-                case GDK_Delete:                return KEY_DELETE_RIGHT;
-                case GDK_Linefeed:              return 13;
-                case GDK_Return:                return 10;
-                case GDK_Left:                  return KEY_LEFT;
-                case GDK_Right:                 return KEY_RIGHT;
-                case GDK_Up:                    return KEY_UP;
-                case GDK_Down:                  return KEY_DOWN;
-
-                /* Modifiers - do nothing for now */
-                case GDK_Shift_L:
-                case GDK_Shift_R:
-                case GDK_Control_L:
-                case GDK_Control_R:
-                case GDK_Caps_Lock:
-                case GDK_Shift_Lock:
-                case GDK_Meta_L:
-                case GDK_Meta_R:
-                case GDK_Alt_L:
-                case GDK_Alt_R:
-                case GDK_Super_L:
-                case GDK_Super_R:
-                case GDK_Hyper_L:
-                case GDK_Hyper_R:               return 0;
-
-                default:                        return gdk_keyval_to_unicode(
-                                                               key->keyval);
-        }
-}
-
 gboolean nsgtk_window_keypress_event(GtkWidget *widget, GdkEventKey *event,
                                        gpointer data)
 {
        struct gui_window *g = data;
-       uint32_t nskey = gdkkey_to_nskey(event);
+       uint32_t nskey = gtk_gui_gdkkey_to_nskey(event);
        if (browser_window_key_press(g->bw, nskey))
                return TRUE;
 
Index: gtk/gtk_plotters.c
===================================================================
--- gtk/gtk_plotters.c  (revision 7935)
+++ gtk/gtk_plotters.c  (working copy)
@@ -119,7 +119,7 @@
                line_width = 1;
 
        cairo_set_line_width(current_cr, line_width);
-       cairo_rectangle(current_cr, x0, y0, width, height);
+       cairo_rectangle(current_cr, x0 + 0.5, y0 + 0.5, width, height);
        cairo_stroke(current_cr);
 
        return true;
Index: gtk/font_pango.c
===================================================================
--- gtk/font_pango.c    (revision 7935)
+++ gtk/font_pango.c    (working copy)
@@ -183,6 +183,7 @@
 
        pango_layout_set_width(layout, x * PANGO_SCALE);
        pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
+       pango_layout_set_single_paragraph_mode(layout, true);
        line = pango_layout_get_line(layout, 1);
        if (line)
                index = line->start_index - 1;
Index: gtk/gtk_gui.c
===================================================================
--- gtk/gtk_gui.c       (revision 7935)
+++ gtk/gtk_gui.c       (working copy)
@@ -43,6 +43,7 @@
 #include "desktop/netsurf.h"
 #include "desktop/options.h"
 #include "desktop/save_pdf/pdf_plotters.h"
+#include "desktop/textinput.h"
 #include "gtk/gtk_gui.h"
 #include "gtk/dialogs/gtk_options.h"
 #include "gtk/gtk_completion.h"
@@ -734,3 +735,74 @@
 }
 #endif
 
+uint32_t gtk_gui_gdkkey_to_nskey(GdkEventKey *key)
+{
+        /* this function will need to become much more complex to support
+         * everything that the RISC OS version does.  But this will do for
+         * now.  I hope.
+         */
+        switch (key->keyval)
+        {
+                case GDK_BackSpace:             
+                       if (key->state & GDK_SHIFT_MASK)
+                               return KEY_DELETE_LINE_START;
+                       else
+                               return KEY_DELETE_LEFT;
+                case GDK_Delete:                
+                       if (key->state & GDK_SHIFT_MASK)
+                               return KEY_DELETE_LINE_END;
+                       else
+                               return KEY_DELETE_RIGHT;
+                case GDK_Linefeed:              return 13;
+                case GDK_Return:                return 10;
+                case GDK_Left:                  return KEY_LEFT;
+                case GDK_Right:                 return KEY_RIGHT;
+                case GDK_Up:                    return KEY_UP;
+                case GDK_Down:                  return KEY_DOWN;
+               case GDK_Home:                  
+                       if (key->state & GDK_CONTROL_MASK)
+                               return KEY_TEXT_START;
+                       else
+                               return KEY_LINE_START;
+               case GDK_End:                   
+                       if (key->state & GDK_CONTROL_MASK)
+                               return KEY_TEXT_END;
+                       else
+                               return KEY_LINE_END;
+               case GDK_Page_Up:
+                       return KEY_PAGE_UP;
+               case GDK_Page_Down:
+                       return KEY_PAGE_DOWN;
+               case 'a':
+                       if (key->state & GDK_CONTROL_MASK)
+                               return KEY_SELECT_ALL;
+                       return gdk_keyval_to_unicode(
+                                       key->keyval);
+               case 'u':
+                       if (key->state & GDK_CONTROL_MASK)
+                               return KEY_CLEAR_SELECTION;
+                       return gdk_keyval_to_unicode(
+                                       key->keyval);
+               case GDK_Escape:
+                       return KEY_ESCAPE;
+
+                /* Modifiers - do nothing for now */
+                case GDK_Shift_L:
+                case GDK_Shift_R:
+                case GDK_Control_L:
+                case GDK_Control_R:
+                case GDK_Caps_Lock:
+                case GDK_Shift_Lock:
+                case GDK_Meta_L:
+                case GDK_Meta_R:
+                case GDK_Alt_L:
+                case GDK_Alt_R:
+                case GDK_Super_L:
+                case GDK_Super_R:
+                case GDK_Hyper_L:
+                case GDK_Hyper_R:               return 0;
+
+                default:                       return gdk_keyval_to_unicode(
+                                                               key->keyval);
+        }
+}
Index: Makefile.sources
===================================================================
--- Makefile.sources    (revision 7935)
+++ Makefile.sources    (working copy)
@@ -13,7 +13,7 @@
        layout.c list.c loosen.c table.c textplain.c
 S_UTILS := base64.c filename.c hashtable.c locale.c messages.c talloc.c        
\
        url.c utf8.c utils.c useragent.c
-S_DESKTOP := knockout.c options.c print.c tree.c version.c
+S_DESKTOP := knockout.c options.c print.c tree.c version.c textarea.c
 
 # S_COMMON are sources common to all builds
 S_COMMON := $(addprefix content/,$(S_CONTENT))                         \
Index: desktop/textinput.h
===================================================================
--- desktop/textinput.h (revision 7935)
+++ desktop/textinput.h (working copy)
@@ -28,6 +28,7 @@
 
 #include <stdbool.h>
 
+
 struct browser_window;
 struct box;
 
Index: desktop/browser.h
===================================================================
--- desktop/browser.h   (revision 7935)
+++ desktop/browser.h   (working copy)
@@ -191,21 +191,24 @@
                                         * 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;
 
 


Conflicted files




Removed files



Reply via email to