Review: Paul Blokus -- Core text area

John-Mark Bell jmb at netsurf-browser.org
Tue Jun 23 12:13:35 BST 2009


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 <jmb202 at ecs.soton.ac.uk>
+ *
+ * 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





More information about the netsurf-dev mailing list