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(a)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