Precis:

This is Paul Blokus' port of the RISC OS text area to the core codebase.
It provides single and multi-line text editing capabilities in a 
platform-agnostic manner.


Added files


Index: desktop/textarea.c
===================================================================
--- /dev/null   2009-04-16 19:17:07.000000000 +0100
+++ desktop/textarea.c  2009-06-23 11:58:18.000000000 +0100
@@ -0,0 +1,1258 @@
+/*
+ * Copyright 2006 John-Mark Bell <[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
+
+struct line_info {
+       int b_start;            /**< Byte offset of line start */
+       int b_length;           /**< Byte length of line */
+};
+
+struct text_area {
+
+       int x, y;                       /**< Coordinates of the widget
+                                            (top left corner) */
+                                       
+       int scroll_x, scroll_y;
+       
+       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 */
+       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 256
+       struct line_info *lines;        /**< Line info array */
+       
+       /** Callback functions for a redraw request*/
+       textarea_start_radraw_callback redraw_start_callback;
+       textarea_start_radraw_callback redraw_end_callback;
+        
+       intptr_t data; /** < Callback data for both callback functions*/
+       
+       int drag_start_char; /**< Character index at which the drag was 
started*/
+};
+
+
+static void textarea_insert_text(struct text_area *ta, unsigned int index,
+                         const char *text);
+static void textarea_replace_text(struct text_area *ta, unsigned int start,
+                          unsigned int end, const char *text);
+static void textarea_reflow(struct text_area *ta, unsigned int line);
+static int textarea_get_xy_offset(struct text_area *ta, int x, int y);
+static void textarea_set_caret_xy(struct text_area *ta, int x, int y);
+static bool textarea_scroll_visible(struct text_area *ta);
+static void 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);
+// static bool textarea_mouse_click(wimp_pointer *pointer);
+
+
+/**
+ * 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 font_style Font style to use, or 0 for default
+ * \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, intptr_t data)
+{
+       struct text_area *ret;
+
+       if (!redraw_start_callback || !redraw_end_callback) {
+               LOG(("no callback provided"));
+               return 0;
+       }
+       
+       ret = malloc(sizeof(struct text_area));
+       if (!ret) {
+               LOG(("malloc failed"));
+               return 0;
+       }
+
+       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) {
+               LOG(("malloc failed"));
+               free(ret);
+               return 0;
+       }
+       ret->text[0] = '\0';
+       ret->text_alloc = 64;
+       ret->text_len = 1;
+       
+       ret->style = malloc(sizeof(struct css_style));
+       if (!ret->style) {
+               LOG(("malloc failed"));
+               free(ret->text);
+               free(ret);
+               return 0;
+       }
+       memcpy(ret->style, style, sizeof(struct css_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 (struct text_area *)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);
+}
+
+
+/**
+ * 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) {
+                       LOG(("realloc failed"));
+                       return false;
+               }
+               ta->text = temp;
+               ta->text_alloc = len + 64;
+       }
+
+       memcpy(ta->text, text, len);
+       ta->text_len = len;     
+       
+       textarea_normalise_text(ta, 0, len);
+       
+       textarea_reflow(ta, 0);
+
+       return true;
+}
+
+
+/**
+ * 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
+ */
+void textarea_insert_text(struct text_area *ta, unsigned int index,
+               const char *text)
+{
+       unsigned int b_len = strlen(text);
+       size_t b_off, c_len;
+
+       if (ta-> flags & TEXTAREA_READONLY)
+               return;
+
+       c_len = utf8_length(ta->text);
+
+       /* Find insertion point */
+       if (index > c_len)
+               index = c_len;
+
+       LOG(("inserting at %i\n", index));
+       
+       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) {
+                       LOG(("realloc failed"));
+                       return;
+               }
+
+               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;
+       
+       textarea_normalise_text(ta, b_off, b_len);      
+
+       /** \todo calculate line to reflow from */
+       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
+ */
+void textarea_replace_text(struct text_area *ta, unsigned int start,
+               unsigned int end, const char *text)
+{
+       int b_len = strlen(text);
+       size_t b_start, b_end, c_len, diff;
+
+       if (ta-> flags & TEXTAREA_READONLY)
+               return;
+       
+       c_len = utf8_length(ta->text);
+
+       if (start > c_len)
+               start = c_len;
+       if (end > c_len)
+               end = c_len;
+
+       if (start == end)
+               return textarea_insert_text(ta, start, text);
+
+       if (start > end) {
+               int temp = end;
+               end = start;
+               start = temp;
+       }
+
+       diff = end - start;
+
+       for (b_start = 0; start-- > 0;
+                       b_start = utf8_next(ta->text, ta->text_len, b_start))
+               ; /* do nothing */
+
+       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) {
+                       LOG(("realloc failed"));
+                       return;
+               }
+
+               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);      
+       textarea_normalise_text(ta, b_start, b_len);
+
+       /** \todo calculate line to reflow from */
+       textarea_reflow(ta, 0);
+}
+
+
+/**
+ * Set the caret's position
+ *
+ * \param ta Text area
+ * \param caret 0-based character index to place caret at
+ */
+void textarea_set_caret(struct text_area *ta, int caret)
+{
+       int c_len;
+       int b_off;
+       int i;
+       int index;
+       int x, y;
+       int x0, y0, x1, y1;
+       int height;
+       
+               
+       if (ta-> flags & TEXTAREA_READONLY)
+               return;
+       
+       ta->redraw_start_callback(ta->data);
+       
+       c_len = utf8_length(ta->text);
+
+       if (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);
+               
+               /*the redraw might happen in response to a text-change and
+               the caret position might be beyond the current text */
+               if (index > c_len)
+                       index = c_len;
+       
+               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 = css_len2px(&(ta->style->line_height.value.length), 
ta->style) *
+                               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);
+       
+               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 = css_len2px(&(ta->style->line_height.value.length), 
ta->style) *
+                               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, 0x000000, false, false);
+       }
+       ta->redraw_end_callback(ta->data);      
+}
+
+
+/**
+ * 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
+ */
+static int textarea_get_xy_offset(struct text_area *ta, int x, int y)
+{
+       size_t b_off, c_off, temp;
+       int line_height;
+       int line;
+
+       if (!ta->line_count)
+               return 0;
+       
+       line_height = css_len2px(&(ta->style->line_height.value.length),
+                                  ta->style);
+
+       x = x - ta->x - MARGIN_LEFT + ta->scroll_x;
+       y = y - ta->y + ta->scroll_y;
+
+       if (x < 0)
+               x = 0;
+       
+       line = y / 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);
+
+
+       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++;
+
+       /* if the offset is a space at the end of the line set the caret before
+          it otherwise the caret will go on the beginning of the next line
+       */
+       if (b_off == (unsigned)ta->lines[line].b_length &&
+                       ta->text[ta->lines[line].b_start +
+                       ta->lines[line].b_length - 1] == ' ')
+               c_off--;
+       
+       return c_off;
+}
+
+
+/**
+ * Set the caret's position
+ *
+ * \param ta Text area
+ * \param x X position of caret in a window
+ * \param y Y position of caret in a window
+ */
+void textarea_set_caret_xy(struct text_area *ta, int x, int y)
+{
+       int c_off;
+       
+       if (ta->flags & TEXTAREA_READONLY)
+               return;
+       
+       c_off = textarea_get_xy_offset(ta, x, y);
+       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)
+{
+       int c_off = 0, b_off;
+
+       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
+ */
+void 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) {
+               ta->lines =
+                       malloc(LINE_CHUNK_SIZE * sizeof(struct line_info));
+               if (!ta->lines) {
+                       LOG(("malloc failed"));
+                       return;
+               }
+       }
+
+       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;
+       }
+
+       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) {
+                               LOG(("realloc failed"));
+                               return;
+                       }
+
+                       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;
+}
+
+/**
+ * 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 line_height, c_pos, c_len, b_start, b_end, chars, offset;
+
+       
+       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)
+               /* Nothing to redraw */
+               return;
+       
+       line_height = css_len2px(&(ta->style->line_height.value.length),
+                       ta->style);
+       
+       line0 = (y0 - ta->y + ta->scroll_y) / line_height - 1;
+       line1 = (y1 - ta->y + ta->scroll_y) / 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) ?
+                       0xD9D9D9 : 0xFFFFFF);
+       plot.rectangle(ta->x, ta->y, ta->vis_width - 1, ta->vis_height - 1, 1,
+                       0x000000, 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 * 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 (c_pos < ta->selection_end &&
+                               c_pos + c_len > 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);
+                                                                       
+                       if (offset > 0) {
+                               
+                               /*find byte start of the selected part*/
+                               for (b_start = 0; offset > 0; offset--)
+                                       b_start = utf8_next(
+                                                       
&(ta->text[ta->lines[line].b_start]),
+                                                       
ta->lines[line].b_length,
+                                                               b_start);
+                               nsfont.font_width(ta->style,
+                                               
&(ta->text[ta->lines[line].b_start]),
+                                               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 < 
ta->lines[line].b_length;
+                                               chars--) {
+                                       b_end = utf8_next(
+                                                       
&(ta->text[ta->lines[line].b_start]),
+                                                       
ta->lines[line].b_length,
+                                                               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 * line_height 
+ 1 - ta->scroll_y,
+                                       x1 - ta->scroll_x, ta->y + (line + 1) * 
line_height - 1 - ta->scroll_y,
+                                       0xFFDDDD);
+                       
+               }
+               
+               c_pos += c_len;
+
+               y0 = ta->y + line * line_height + 0.75 * 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) ?
+                                       0xD9D9D9 : 0xFFFFFF,
+                               0x000000);
+       }
+}
+
+/**
+ * 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, c_len, l_len;
+       int c_line, c_chars, line, line_height;
+       bool redraw = false;
+
+       caret_init = caret = textarea_get_caret(ta);
+       line = ta->caret_pos.line;
+
+       if (!(key <= 0x001F || (0x007F <= key && key <= 0x009F))) {
+               /* normal character insertion */                
+               length = utf8_from_ucs4(key, utf8);
+               utf8[length] = '\0';
+               
+               textarea_insert_text(ta, caret, utf8);
+               caret++;
+               redraw = true;
+
+       } else switch (key) {
+               case KEY_SELECT_ALL:
+                       c_len = utf8_length(ta->text);
+                       caret = c_len;
+                       
+                       ta->selection_start = 0;
+                       ta->selection_end = c_len;
+                       redraw = true;
+                       break;
+               case KEY_COPY_SELECTION:
+                       break;
+               case KEY_DELETE_LEFT:
+                       if (ta->selection_start != -1) {
+                               textarea_replace_text(ta, ta->selection_start,
+                                               ta->selection_end, "");
+                               ta->selection_start = ta->selection_end = -1;
+                               redraw = true;
+                       } else {
+                               if (caret) {
+                                       textarea_replace_text(ta, caret - 1,
+                                                       caret, "");
+                                       caret--;
+                                       redraw = true;
+                               }
+                       }
+                       break;
+               case KEY_TAB:
+                       break;
+               case KEY_NL:
+                       textarea_insert_text(ta, caret, "\n");
+                       caret++;
+                       ta->selection_start = ta->selection_end = -1;
+                       redraw = true;
+                       break;
+               case KEY_SHIFT_TAB:
+               case KEY_CR:
+               case KEY_CUT_LINE:
+               case KEY_PASTE:
+               case KEY_CUT_SELECTION:
+                       break;
+               case KEY_CLEAR_SELECTION:
+                       ta->selection_start = -1;
+                       ta->selection_end = -1;
+                       redraw = true;
+                       break;
+               case KEY_ESCAPE:
+                       break;
+               case KEY_LEFT:
+                       if (caret)
+                               caret--;
+                       if (ta->selection_start != -1) {
+                               ta->selection_start = ta->selection_end = -1;
+                               redraw = true;
+                       }
+                       break;
+               case KEY_RIGHT:
+                       c_len = utf8_length(ta->text);
+                       if (caret < c_len)
+                               caret++;
+                       if (ta->selection_start != -1) {
+                               ta->selection_start = ta->selection_end = -1;
+                               redraw = true;
+                       }
+                       break;
+               case KEY_PAGE_UP:
+                       if (ta->flags & TEXTAREA_MULTILINE) {
+                               line_height = css_len2px(
+                                               
&(ta->style->line_height.value.length),
+                                               ta->style);
+                               
+                               /* +1 because one line is subtracted in KEY_UP*/
+                               line = ta->caret_pos.line - (ta->vis_height
+                                               + line_height - 1) / line_height
+                                               + 1;
+                       }
+                       /*fall through*/                        
+               case KEY_UP:
+                       if (ta->flags & TEXTAREA_MULTILINE) {
+                               line--;
+                               if (line < 0)
+                                       line = 0;
+                               if (line != ta->caret_pos.line) {
+                                       c_line = ta->caret_pos.line;
+                                       c_chars = ta->caret_pos.char_off;
+                                       
+                                       ta->caret_pos.line = line;
+                                       l_len = utf8_bounded_length(
+                                                       
&(ta->text[ta->lines[ta->caret_pos.line].b_start]),
+                                                       
ta->lines[ta->caret_pos.line].b_length);
+                                       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;
+                               }
+                       }
+                       if (ta->selection_start != -1) {
+                               ta->selection_start = ta->selection_end = -1;
+                               redraw = true;
+                       }
+                       break;
+               case KEY_PAGE_DOWN:
+                       if (ta->flags & TEXTAREA_MULTILINE) {
+                               line_height = css_len2px(
+                                               
&(ta->style->line_height.value.length),
+                                               ta->style);
+                               
+                               /* -1 because one line is added in KEY_DOWN*/
+                               line = ta->caret_pos.line + (ta->vis_height
+                                               + line_height - 1) / line_height
+                                               - 1;
+                       }                       
+                       /* fall through*/
+               case KEY_DOWN:
+                       if (ta->flags & TEXTAREA_MULTILINE) {
+                               line++;
+                               if (line > ta->line_count - 1)
+                                       line = ta->line_count - 1;
+                               if (line != ta->caret_pos.line) {
+                                       c_line = ta->caret_pos.line;
+                                       c_chars = ta->caret_pos.char_off;
+                                       
+                                       ta->caret_pos.line = line;
+                                       l_len = utf8_bounded_length(
+                                                       
&(ta->text[ta->lines[ta->caret_pos.line].b_start]),
+                                                       
ta->lines[ta->caret_pos.line].b_length);
+                                       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;
+                               }
+                       }
+                       if (ta->selection_start != -1) {
+                               ta->selection_start = ta->selection_end = -1;
+                               redraw = true;
+                       }
+                       break;
+               case KEY_DELETE_RIGHT:
+                       if (ta->selection_start != -1) {
+                               textarea_replace_text(ta, ta->selection_start,
+                                               ta->selection_end, "");
+                               ta->selection_start = ta->selection_end = -1;
+                               redraw = true;
+                       } else {
+                               c_len = utf8_length(ta->text);
+                               if (caret < c_len) {
+                                       textarea_replace_text(ta, caret, caret 
+ 1, "");
+                                       redraw = true;
+                               }
+                       }
+                       break;                  
+               case KEY_LINE_START:
+                       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:
+                       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:
+                       caret = 0;
+                       if (ta->selection_start != -1) {
+                               ta->selection_start = ta->selection_end = -1;
+                               redraw = true;
+                       }
+                       break;
+               case KEY_TEXT_END:
+                       caret = utf8_length(ta->text);
+                       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 (ta->selection_start != -1) {
+                               textarea_replace_text(ta, ta->selection_start,
+                                               ta->selection_end, "");
+                               ta->selection_start = ta->selection_end = -1;
+                       } else {
+                               textarea_replace_text(ta, caret, caret + 
utf8_bounded_length(
+                                               
&(ta->text[ta->lines[ta->caret_pos.line].b_start]),
+                                               
ta->lines[ta->caret_pos.line].b_length),
+                                               "");
+                       }
+                       redraw = true;
+                       break;
+               case KEY_DELETE_LINE_START:
+                       if (ta->selection_start != -1) {
+                               textarea_replace_text(ta, ta->selection_start,
+                                               ta->selection_end, "");
+                               ta->selection_start = ta->selection_end = -1;
+                       } else {
+                               textarea_replace_text(ta,
+                                               caret - ta->caret_pos.char_off, 
caret,
+                                               "");
+                               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)
+               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
+ */
+static bool textarea_scroll_visible(struct text_area *ta)
+{
+       int x0, x1, y0, y1, x, y;
+       int index, b_off, line_height;
+       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);
+
+       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);
+       
+       line_height = css_len2px(&(ta->style->line_height.value.length),
+                       ta->style);
+       
+       /* top-left of caret*/
+       x += ta->x + MARGIN_LEFT - ta->scroll_x;
+       y = 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 + line_height > y1) {
+               ta->scroll_y += y + 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
+ */
+void 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);
+               }
+               textarea_set_caret_xy(ta, x, y);
+               
+               return;
+       }
+       
+       if (mouse & BROWSER_MOUSE_DRAG_1) {
+               ta->drag_start_char = textarea_get_xy_offset(ta, x, y);
+               textarea_set_caret(ta, -1);
+               return;
+       }
+       
+       if (mouse & BROWSER_MOUSE_HOLDING_1) {
+               c_start = ta->drag_start_char;
+               c_end = textarea_get_xy_offset(ta, x, y);
+               textarea_select(ta, c_start, c_end);
+               return;
+       }
+}
+
+
+/**
+ * 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
+ */
+void 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);
+       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)
+ */
+void 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 (swap == -1)
+               textarea_set_caret(ta, c_end);
+       else
+               textarea_set_caret(ta, c_start);
+}
+
+
+/**
+ * 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*/
+       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--;
+                               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';
+               }
+               
+               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-23 11:58:18.000000000 +0100
@@ -0,0 +1,61 @@
+/*
+ *
+ * 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;
+
+/* this is temporary only*/
+browser_mouse_state textarea_mouse_state;
+int textarea_pressed_x;
+int textarea_pressed_y;
+
+typedef void(*textarea_start_radraw_callback)(intptr_t data);
+typedef void(*textarea_end_radraw_callback)(intptr_t 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, intptr_t 
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);
+void 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);
+void textarea_mouse_action(struct text_area *ta, browser_mouse_state mouse,
+               int x, int y);
+void 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   |    3 
 gtk/font_pango.c      |    1 
 gtk/gtk_plotters.c    |    2 
 gtk/gtk_scaffolding.c |  289 +++++++++++++++++++++++++++++++++++++++++++++++++-
 gtk/gtk_window.c      |   41 ++++++-
 gtk/gtk_window.h      |    3 
 8 files changed, 340 insertions(+), 18 deletions(-)


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 */
@@ -455,17 +455,48 @@
          * 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_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);
 
                 /* Modifiers - do nothing for now */
                 case GDK_Shift_L:
@@ -483,7 +514,7 @@
                 case GDK_Hyper_L:
                 case GDK_Hyper_R:               return 0;
 
-                default:                        return gdk_keyval_to_unicode(
+                default:                       return gdk_keyval_to_unicode(
                                                                key->keyval);
         }
 }
Index: gtk/gtk_window.h
===================================================================
--- gtk/gtk_window.h    (revision 7935)
+++ gtk/gtk_window.h    (working copy)
@@ -76,6 +76,9 @@
 int nsgtk_gui_window_update_targets(struct gui_window *g);
 void nsgtk_window_destroy_browser(struct gui_window *g);
 
+/*TODO: find a better place for this?*/
+uint32_t gdkkey_to_nskey(GdkEventKey *);
+
 struct browser_window *nsgtk_get_browser_window(struct gui_window *g);
 
 #endif /* NETSURF_GTK_WINDOW_H */
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/gtk_scaffolding.c
===================================================================
--- gtk/gtk_scaffolding.c       (revision 7935)
+++ gtk/gtk_scaffolding.c       (working copy)
@@ -37,6 +37,7 @@
 #endif
 #include "desktop/selection.h"
 #include "desktop/textinput.h"
+#include "desktop/textarea.h"
 #include "gtk/gtk_completion.h"
 #include "gtk/dialogs/gtk_options.h"
 #include "gtk/dialogs/gtk_about.h"
@@ -71,6 +72,15 @@
        GtkDrawingArea          *drawing_area;
 };
 
+struct gtk_treeview_window {
+       GtkWindow *window;
+       GtkScrolledWindow *scrolled;
+       GtkDrawingArea *drawing_area;
+       int mouse_pressed_x;
+       int mouse_pressed_y;
+       browser_mouse_state mouse_state;
+};
+
 struct menu_events {
        const char *widget;
        GCallback handler;
@@ -84,6 +94,8 @@
 static gboolean nsgtk_window_delete_event(GtkWidget *, gpointer);
 static void nsgtk_window_destroy_event(GtkWidget *, gpointer);
 
+static struct gtk_treeview_window global_history_window;
+
 static void nsgtk_window_update_back_forward(struct gtk_scaffolding *);
 static void nsgtk_throb(void *);
 static gboolean nsgtk_window_edit_menu_clicked(GtkWidget *widget,
@@ -116,6 +128,20 @@
 static gboolean nsgtk_history_button_press_event(GtkWidget *, GdkEventButton *,
                gpointer);
 
+gboolean nsgtk_global_history_expose_event(GtkWidget *widget,
+               GdkEventExpose *event, gpointer g);
+static gboolean nsgtk_global_history_button_press_event(GtkWidget *, 
GdkEventButton *,
+               gpointer g);
+static gboolean nsgtk_global_history_button_release_event(GtkWidget *, 
GdkEventButton *,
+               gpointer g);            
+gboolean nsgtk_global_history_motion_notify_event(GtkWidget *widget,
+               GdkEventButton *event, gpointer g);             
+static gboolean nsgtk_global_history_keypress_event(GtkWidget *, GdkEventKey *,
+               gpointer g);
+
+static void redraw_start_callback(intptr_t data);
+static void redraw_end_callback(intptr_t data);
+
 static void nsgtk_attach_menu_handlers(GladeXML *, gpointer);
 static void nsgtk_window_tabs_num_changed(GtkNotebook *notebook,
                GtkWidget *page, guint page_num, struct gtk_scaffolding *g);
@@ -1120,9 +1146,18 @@
 
 MENUHANDLER(global_history)
 {
-       gtk_widget_show(GTK_WIDGET(wndHistory));
-       gdk_window_raise(GTK_WIDGET(wndHistory)->window);
+       //gtk_widget_show(GTK_WIDGET(wndHistory));
+       //gdk_window_raise(GTK_WIDGET(wndHistory)->window);
+       
+       struct gtk_scaffolding *gw = (struct gtk_scaffolding *) g;
 
+       gtk_window_set_default_size(global_history_window.window, 500, 400); 
+       gtk_window_set_position(global_history_window.window, 
GTK_WIN_POS_MOUSE);
+       gtk_window_set_transient_for(global_history_window.window, gw->window);
+       gtk_window_set_opacity(global_history_window.window, 0.9);
+       gtk_widget_show(GTK_WIDGET(global_history_window.window));
+       gdk_window_raise(GTK_WIDGET(global_history_window.window)->window);
+       
        return TRUE;
 }
 
@@ -1203,6 +1238,193 @@
        return TRUE;
 }
 
+/* signal handler functions for the global history window */
+gboolean nsgtk_global_history_expose_event(GtkWidget *widget,
+               GdkEventExpose *event, gpointer g)
+{
+//     struct gtk_history_window *hw = (struct gtk_history_window *)g;
+//     struct browser_window *bw = 
+//                     nsgtk_get_browser_for_gui(hw->g->top_level);
+
+       struct text_area *ta = (struct text_area *) g;
+       
+       current_widget = widget;
+       current_drawable = widget->window;
+       current_gc = gdk_gc_new(current_drawable);
+#ifdef CAIRO_VERSION
+       current_cr = gdk_cairo_create(current_drawable);
+#endif
+       plot = nsgtk_plotters;
+       nsgtk_plot_set_scale(1.0);
+
+       textarea_redraw(ta, 0, 0, 1000, 1000);
+       
+       g_object_unref(current_gc);
+#ifdef CAIRO_VERSION
+       cairo_destroy(current_cr);
+#endif
+       
+       return FALSE;
+}
+
+gboolean nsgtk_global_history_button_press_event(GtkWidget *widget,
+               GdkEventButton *event, gpointer g)
+{      
+       LOG(("X=%g, Y=%g", event->x, event->y));
+
+       struct text_area *ta = (struct text_area *) g;  
+       
+       textarea_pressed_x = event->x;
+       textarea_pressed_y = event->y;  
+
+       if (event->type == GDK_2BUTTON_PRESS)
+               textarea_mouse_state = BROWSER_MOUSE_DOUBLE_CLICK;
+       
+       switch (event->button) {
+               case 1: textarea_mouse_state |= BROWSER_MOUSE_PRESS_1; break;
+               case 3: textarea_mouse_state |= BROWSER_MOUSE_PRESS_2; break;
+       }
+       /* Handle the modifiers too */
+       if (event->state & GDK_SHIFT_MASK)
+               textarea_mouse_state |= BROWSER_MOUSE_MOD_1;
+       if (event->state & GDK_CONTROL_MASK)
+               textarea_mouse_state |= BROWSER_MOUSE_MOD_2;
+       if (event->state & GDK_MOD1_MASK)
+               textarea_mouse_state |= BROWSER_MOUSE_MOD_3;
+               
+       textarea_mouse_action(ta, textarea_mouse_state,
+                       event->x, event->y);
+       
+       return TRUE;
+}
+
+gboolean nsgtk_global_history_button_release_event(GtkWidget *widget,
+               GdkEventButton *event, gpointer g)
+{
+       bool shift = event->state & GDK_SHIFT_MASK;
+       bool ctrl = event->state & GDK_CONTROL_MASK;
+       bool alt = event->state & GDK_MOD1_MASK;
+       struct text_area *ta = (struct text_area *) g;
+
+       /* We consider only button 1 clicks as double clicks.
+       * If the mouse state is PRESS then we are waiting for a release to emit
+       * a click event, otherwise just reset the state to nothing*/
+       if (textarea_mouse_state & BROWSER_MOUSE_DOUBLE_CLICK) {
+               
+               if (textarea_mouse_state & BROWSER_MOUSE_PRESS_1)
+                       textarea_mouse_state ^= BROWSER_MOUSE_PRESS_1;
+               else if (textarea_mouse_state & BROWSER_MOUSE_PRESS_2)
+                       textarea_mouse_state ^= (BROWSER_MOUSE_PRESS_2 |
+                                       BROWSER_MOUSE_DOUBLE_CLICK);
+               
+       } else if (textarea_mouse_state & BROWSER_MOUSE_PRESS_1)
+               textarea_mouse_state ^=
+                               (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_CLICK_1);
+       else if (textarea_mouse_state & BROWSER_MOUSE_PRESS_2)
+               textarea_mouse_state ^=
+                               (BROWSER_MOUSE_PRESS_2 | BROWSER_MOUSE_CLICK_2);
+       
+       /* Handle modifiers being removed */
+       if (textarea_mouse_state & BROWSER_MOUSE_MOD_1 && !shift)
+               textarea_mouse_state ^= BROWSER_MOUSE_MOD_1;
+       if (textarea_mouse_state & BROWSER_MOUSE_MOD_2 && !ctrl)
+               textarea_mouse_state ^= BROWSER_MOUSE_MOD_2;
+       if (textarea_mouse_state & BROWSER_MOUSE_MOD_3 && !alt)
+               textarea_mouse_state ^= BROWSER_MOUSE_MOD_3;
+
+       if (textarea_mouse_state &
+                               (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2
+                               | BROWSER_MOUSE_DOUBLE_CLICK))
+               textarea_mouse_action(ta, textarea_mouse_state,
+                               event->x, event->y);
+       else
+               textarea_drag_end(ta, textarea_mouse_state, event->x, event->y);
+
+       textarea_mouse_state = 0;
+
+       return TRUE;    
+}
+
+gboolean nsgtk_global_history_motion_notify_event(GtkWidget *widget,
+               GdkEventButton *event, gpointer g)
+{
+       bool shift = event->state & GDK_SHIFT_MASK;
+       bool ctrl = event->state & GDK_CONTROL_MASK;
+       bool alt = event->state & GDK_MOD1_MASK;
+       struct text_area *ta = (struct text_area *) g;
+               
+
+       if (textarea_mouse_state & BROWSER_MOUSE_PRESS_1) {
+               /* Start button 1 drag */
+               textarea_mouse_action(ta, BROWSER_MOUSE_DRAG_1,
+                                 textarea_pressed_x, textarea_pressed_y);
+               /* Replace PRESS with HOLDING and declare drag in progress */
+               textarea_mouse_state ^= (BROWSER_MOUSE_PRESS_1 |
+                               BROWSER_MOUSE_HOLDING_1);
+               textarea_mouse_state |= BROWSER_MOUSE_DRAG_ON;
+       }
+       else if (textarea_mouse_state & BROWSER_MOUSE_PRESS_2){
+               /* Start button 2s drag */
+               textarea_mouse_action(ta, BROWSER_MOUSE_DRAG_2,
+                                 textarea_pressed_x, textarea_pressed_y);
+               /* Replace PRESS with HOLDING and declare drag in progress */
+               textarea_mouse_state ^= (BROWSER_MOUSE_PRESS_2 |
+                               BROWSER_MOUSE_HOLDING_2);
+               textarea_mouse_state |= BROWSER_MOUSE_DRAG_ON;
+       }
+       
+       /* Handle modifiers being removed */
+       if (textarea_mouse_state & BROWSER_MOUSE_MOD_1 && !shift)
+               textarea_mouse_state ^= BROWSER_MOUSE_MOD_1;
+       if (textarea_mouse_state & BROWSER_MOUSE_MOD_2 && !ctrl)
+               textarea_mouse_state ^= BROWSER_MOUSE_MOD_2;
+       if (textarea_mouse_state & BROWSER_MOUSE_MOD_3 && !alt)
+               textarea_mouse_state ^= BROWSER_MOUSE_MOD_3;
+       
+       textarea_mouse_action(ta, textarea_mouse_state,
+                       event->x, event->y);
+       
+       return TRUE;
+}
+
+gboolean nsgtk_global_history_keypress_event(GtkWidget *widget,
+               GdkEventKey *event, gpointer g)
+{
+       struct text_area *ta = g;
+       uint32_t nskey = gdkkey_to_nskey(event);
+       
+       if (textarea_keypress(ta, nskey))
+               return TRUE;
+       
+       /*the final user of textarea will probably want to do something here*/
+       
+       return TRUE;    
+}
+
+
+void redraw_start_callback(intptr_t data)
+{
+       current_widget = (GtkWidget *) data;
+       current_drawable = current_widget->window;
+       current_gc = gdk_gc_new(current_drawable);
+#ifdef CAIRO_VERSION
+       current_cr = gdk_cairo_create(current_drawable);
+#endif
+       plot = nsgtk_plotters;
+       nsgtk_plot_set_scale(1.0);              
+}
+
+static void redraw_end_callback(intptr_t data)
+{
+       GtkWidget *widget = (GtkWidget *) data;
+       if (current_widget == widget) {
+               g_object_unref(current_gc);
+#ifdef CAIRO_VERSION
+               cairo_destroy(current_cr);
+#endif
+       }
+}
+
 #define GET_WIDGET(x) glade_xml_get_widget(g->xml, (x))
 
 nsgtk_scaffolding *nsgtk_new_scaffolding(struct gui_window *toplevel)
@@ -1352,6 +1574,39 @@
                        GTK_WIDGET(g->history_window->drawing_area));
        gtk_widget_show(GTK_WIDGET(g->history_window->drawing_area));
 
+       
+       global_history_window.window = 
GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+       gtk_window_set_default_size(global_history_window.window, 400, 400);
+       gtk_window_set_title(global_history_window.window, "NetSurf Global 
History");
+       gtk_window_set_type_hint(global_history_window.window,
+                                GDK_WINDOW_TYPE_HINT_UTILITY);
+       global_history_window.scrolled = 
+                       GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(0, 0));
+       gtk_container_add(GTK_CONTAINER(global_history_window.window),
+                         GTK_WIDGET(global_history_window.scrolled));
+
+       gtk_widget_show(GTK_WIDGET(global_history_window.scrolled));
+       global_history_window.drawing_area =
+                       GTK_DRAWING_AREA(gtk_drawing_area_new());
+
+       gtk_widget_set_events(GTK_WIDGET(global_history_window.drawing_area),
+                       GDK_EXPOSURE_MASK |
+                       GDK_POINTER_MOTION_MASK |
+                       GDK_BUTTON_PRESS_MASK |
+                       GDK_BUTTON_RELEASE_MASK |
+                       GDK_KEY_PRESS_MASK |
+                       GDK_KEY_RELEASE_MASK);
+       gtk_widget_modify_bg(GTK_WIDGET(global_history_window.drawing_area),
+                       GTK_STATE_NORMAL,
+                       &((GdkColor) { 0, 0xffff, 0xffff, 0xffff } ));
+       gtk_scrolled_window_add_with_viewport(global_history_window.scrolled,
+                       GTK_WIDGET(global_history_window.drawing_area));
+       gtk_widget_show(GTK_WIDGET(global_history_window.drawing_area));
+       
gtk_widget_set_size_request(GTK_WIDGET(global_history_window.drawing_area),
+                       1000, 1000);    
+       GTK_WIDGET_SET_FLAGS(GTK_WIDGET(global_history_window.drawing_area),
+                       GTK_CAN_FOCUS);
+       
        /* set up URL bar completion */
        g->url_bar_completion = gtk_entry_completion_new();
        gtk_entry_set_completion(g->url_bar, g->url_bar_completion);
@@ -1366,7 +1621,7 @@
                        "popup-set-width", TRUE,
                        "popup-single-match", TRUE,
                        NULL);
-
+       
        /* set up the throbber. */
        gtk_image_set_from_pixbuf(g->throbber, nsgtk_throbber->framedata[0]);
        g->throb_frame = 0;
@@ -1384,6 +1639,34 @@
        CONNECT(g->history_window->window, "delete_event",
                        gtk_widget_hide_on_delete, NULL);
 
+       struct text_area *ta;
+       
+       ta = textarea_create( 100, 100, 100, 45, TEXTAREA_READONLY,
+                       &css_base_style, redraw_start_callback,
+                       redraw_end_callback,
+                       (intptr_t)global_history_window.drawing_area);
+       textarea_set_text(ta, "012345 67890123");
+       textarea_mouse_state = 0;
+       textarea_pressed_x = 0;
+       textarea_pressed_y = 0;
+       
+       
+       CONNECT(global_history_window.drawing_area, "expose_event",
+                       nsgtk_global_history_expose_event, ta);
+       CONNECT(global_history_window.drawing_area, "button_press_event",
+                       nsgtk_global_history_button_press_event,
+                       ta);
+       CONNECT(global_history_window.drawing_area, "button_release_event",
+                       nsgtk_global_history_button_release_event,
+                       ta);
+       CONNECT(global_history_window.drawing_area, "motion_notify_event",
+                       nsgtk_global_history_motion_notify_event,
+                       ta);
+       CONNECT(global_history_window.drawing_area, "key_press_event",
+                       nsgtk_global_history_keypress_event, ta);
+       CONNECT(global_history_window.window, "delete_event",
+                       gtk_widget_hide_on_delete, NULL);
+       
        g_signal_connect_after(g->notebook, "page-added",
                        G_CALLBACK(nsgtk_window_tabs_num_changed), g);  
        g_signal_connect_after(g->notebook, "page-removed",
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: 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,7 +28,8 @@
 
 #include <stdbool.h>
 
-struct browser_window;
+#include "desktop/browser.h"
+
 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