Precis: This is Paul Blokus' port of the RISC OS text area to the core codebase. It provides single and multi-line text editing capabilities in a platform-agnostic manner.
Added files Index: desktop/textarea.c =================================================================== --- /dev/null 2009-04-16 19:17:07.000000000 +0100 +++ desktop/textarea.c 2009-06-23 11:58:18.000000000 +0100 @@ -0,0 +1,1258 @@ +/* + * Copyright 2006 John-Mark Bell <[email protected]> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** \file + * Single/Multi-line UTF-8 text area (implementation) + */ + +#include <stdint.h> +#include <string.h> +#include "css/css.h" +#include "desktop/textarea.h" +#include "desktop/textinput.h" +#include "desktop/plotters.h" +#include "render/font.h" +#include "utils/log.h" +#include "utils/utf8.h" +#include "utils/utils.h" + +#define MARGIN_LEFT 2 +#define MARGIN_RIGHT 2 + +struct line_info { + int b_start; /**< Byte offset of line start */ + int b_length; /**< Byte length of line */ +}; + +struct text_area { + + int x, y; /**< Coordinates of the widget + (top left corner) */ + + int scroll_x, scroll_y; + + unsigned int flags; /**< Textarea flags */ + int vis_width; /**< Visible width, in pixels */ + int vis_height; /**< Visible height, in pixels */ + + char *text; /**< UTF-8 text */ + unsigned int text_alloc; /**< Size of allocated text */ + unsigned int text_len; /**< Length of text, in bytes */ + struct { + int line; /**< Line caret is on */ + int char_off; /**< Character index of caret */ + } caret_pos; + + int selection_start; /**< Character index of sel start(inclusive) */ + int selection_end; /**< Character index of sel end(exclusive) */ + + struct css_style *style; /**< Text style */ + + int line_count; /**< Count of lines */ +#define LINE_CHUNK_SIZE 256 + struct line_info *lines; /**< Line info array */ + + /** Callback functions for a redraw request*/ + textarea_start_radraw_callback redraw_start_callback; + textarea_start_radraw_callback redraw_end_callback; + + intptr_t data; /** < Callback data for both callback functions*/ + + int drag_start_char; /**< Character index at which the drag was started*/ +}; + + +static void textarea_insert_text(struct text_area *ta, unsigned int index, + const char *text); +static void textarea_replace_text(struct text_area *ta, unsigned int start, + unsigned int end, const char *text); +static void textarea_reflow(struct text_area *ta, unsigned int line); +static int textarea_get_xy_offset(struct text_area *ta, int x, int y); +static void textarea_set_caret_xy(struct text_area *ta, int x, int y); +static bool textarea_scroll_visible(struct text_area *ta); +static void textarea_select(struct text_area *ta, int c_start, int c_end); +static void textarea_normalise_text(struct text_area *ta, + unsigned int b_start, unsigned int b_len); +// static bool textarea_mouse_click(wimp_pointer *pointer); + + +/** + * Create a text area + * + * \param x X coordinate of left border + * \param y Y coordinate of top border + * \param width width of the text area + * \param height width of the text area + * \param flags Text area flags + * \param font_style Font style to use, or 0 for default + * \return Opaque handle for textarea or 0 on error + */ + +struct text_area *textarea_create(int x, int y, int width, int height, + unsigned int flags, const struct css_style *style, + textarea_start_radraw_callback redraw_start_callback, + textarea_end_radraw_callback redraw_end_callback, intptr_t data) +{ + struct text_area *ret; + + if (!redraw_start_callback || !redraw_end_callback) { + LOG(("no callback provided")); + return 0; + } + + ret = malloc(sizeof(struct text_area)); + if (!ret) { + LOG(("malloc failed")); + return 0; + } + + ret->redraw_start_callback = redraw_start_callback; + ret->redraw_end_callback = redraw_end_callback; + ret->data = data; + ret->x = x; + ret->y = y; + ret->vis_width = width; + ret->vis_height = height; + ret->scroll_x = 0; + ret->scroll_y = 0; + ret->drag_start_char = 0; + + + ret->flags = flags; + ret->text = malloc(64); + if (!ret->text) { + LOG(("malloc failed")); + free(ret); + return 0; + } + ret->text[0] = '\0'; + ret->text_alloc = 64; + ret->text_len = 1; + + ret->style = malloc(sizeof(struct css_style)); + if (!ret->style) { + LOG(("malloc failed")); + free(ret->text); + free(ret); + return 0; + } + memcpy(ret->style, style, sizeof(struct css_style)); + + ret->caret_pos.line = ret->caret_pos.char_off = 0; + ret->selection_start = -1; + ret->selection_end = -1; + + ret->line_count = 0; + ret->lines = 0; + + return (struct text_area *)ret; +} + + +/** + * Destroy a text area + * + * \param ta Text area to destroy + */ +void textarea_destroy(struct text_area *ta) +{ + free(ta->text); + free(ta->style); + free(ta); +} + + +/** + * Set the text in a text area, discarding any current text + * + * \param ta Text area + * \param text UTF-8 text to set text area's contents to + * \return true on success, false on memory exhaustion + */ +bool textarea_set_text(struct text_area *ta, const char *text) +{ + unsigned int len = strlen(text) + 1; + + if (len >= ta->text_alloc) { + char *temp = realloc(ta->text, len + 64); + if (!temp) { + LOG(("realloc failed")); + return false; + } + ta->text = temp; + ta->text_alloc = len + 64; + } + + memcpy(ta->text, text, len); + ta->text_len = len; + + textarea_normalise_text(ta, 0, len); + + textarea_reflow(ta, 0); + + return true; +} + + +/** + * Extract the text from a text area + * + * \param ta Text area + * \param buf Pointer to buffer to receive data, or NULL + * to read length required + * \param len Length (bytes) of buffer pointed to by buf, or 0 to read length + * \return Length (bytes) written/required or -1 on error + */ +int textarea_get_text(struct text_area *ta, char *buf, unsigned int len) +{ + if (buf == NULL && len == 0) { + /* want length */ + return ta->text_len; + } + + if (len < ta->text_len) { + LOG(("buffer too small")); + return -1; + } + + memcpy(buf, ta->text, ta->text_len); + + return ta->text_len; +} + + +/** + * Insert text into the text area + * + * \param ta Text area + * \param index 0-based character index to insert at + * \param text UTF-8 text to insert + */ +void textarea_insert_text(struct text_area *ta, unsigned int index, + const char *text) +{ + unsigned int b_len = strlen(text); + size_t b_off, c_len; + + if (ta-> flags & TEXTAREA_READONLY) + return; + + c_len = utf8_length(ta->text); + + /* Find insertion point */ + if (index > c_len) + index = c_len; + + LOG(("inserting at %i\n", index)); + + for (b_off = 0; index-- > 0; + b_off = utf8_next(ta->text, ta->text_len, b_off)) + ; /* do nothing */ + + if (b_len + ta->text_len >= ta->text_alloc) { + char *temp = realloc(ta->text, b_len + ta->text_len + 64); + if (!temp) { + LOG(("realloc failed")); + return; + } + + ta->text = temp; + ta->text_alloc = b_len + ta->text_len + 64; + } + + /* Shift text following up */ + memmove(ta->text + b_off + b_len, ta->text + b_off, + ta->text_len - b_off); + /* Insert new text */ + memcpy(ta->text + b_off, text, b_len); + ta->text_len += b_len; + + textarea_normalise_text(ta, b_off, b_len); + + /** \todo calculate line to reflow from */ + textarea_reflow(ta, 0); +} + + +/** + * Replace text in a text area + * + * \param ta Text area + * \param start Start character index of replaced section (inclusive) + * \param end End character index of replaced section (exclusive) + * \param text UTF-8 text to insert + */ +void textarea_replace_text(struct text_area *ta, unsigned int start, + unsigned int end, const char *text) +{ + int b_len = strlen(text); + size_t b_start, b_end, c_len, diff; + + if (ta-> flags & TEXTAREA_READONLY) + return; + + c_len = utf8_length(ta->text); + + if (start > c_len) + start = c_len; + if (end > c_len) + end = c_len; + + if (start == end) + return textarea_insert_text(ta, start, text); + + if (start > end) { + int temp = end; + end = start; + start = temp; + } + + diff = end - start; + + for (b_start = 0; start-- > 0; + b_start = utf8_next(ta->text, ta->text_len, b_start)) + ; /* do nothing */ + + for (b_end = b_start; diff-- > 0; + b_end = utf8_next(ta->text, ta->text_len, b_end)) + ; /* do nothing */ + + if (b_len + ta->text_len - (b_end - b_start) >= ta->text_alloc) { + char *temp = realloc(ta->text, + b_len + ta->text_len - (b_end - b_start) + 64); + if (!temp) { + LOG(("realloc failed")); + return; + } + + ta->text = temp; + ta->text_alloc = + b_len + ta->text_len - (b_end - b_start) + 64; + } + + /* Shift text following to new position */ + memmove(ta->text + b_start + b_len, ta->text + b_end, + ta->text_len - b_end); + + /* Insert new text */ + memcpy(ta->text + b_start, text, b_len); + + ta->text_len += b_len - (b_end - b_start); + textarea_normalise_text(ta, b_start, b_len); + + /** \todo calculate line to reflow from */ + textarea_reflow(ta, 0); +} + + +/** + * Set the caret's position + * + * \param ta Text area + * \param caret 0-based character index to place caret at + */ +void textarea_set_caret(struct text_area *ta, int caret) +{ + int c_len; + int b_off; + int i; + int index; + int x, y; + int x0, y0, x1, y1; + int height; + + + if (ta-> flags & TEXTAREA_READONLY) + return; + + ta->redraw_start_callback(ta->data); + + c_len = utf8_length(ta->text); + + if (caret > c_len) + caret = c_len; + + height = css_len2px(&(ta->style->font_size.value.length), + ta->style); + /* Delete the old caret */ + if (ta->caret_pos.char_off != -1) { + index = textarea_get_caret(ta); + + /*the redraw might happen in response to a text-change and + the caret position might be beyond the current text */ + if (index > c_len) + index = c_len; + + for (b_off = 0; index-- > 0; + b_off = utf8_next(ta->text, ta->text_len, b_off)) + ; /* do nothing */ + + nsfont.font_width(ta->style, + ta->text + ta->lines[ta->caret_pos.line].b_start, + b_off - ta->lines[ta->caret_pos.line].b_start, + &x); + + x += ta->x + MARGIN_LEFT - ta->scroll_x; + + y = css_len2px(&(ta->style->line_height.value.length), ta->style) * + ta->caret_pos.line + ta->y - ta->scroll_y; + + textarea_redraw(ta, x - 1, y - 1, x + 1, y + height + 1); + } + + /*check if the caret has to be drawn at all*/ + if (caret != -1) { + /* Find byte offset of caret position */ + for (b_off = 0; caret > 0; caret--) + b_off = utf8_next(ta->text, ta->text_len, b_off); + + /* Now find line in which byte offset appears */ + for (i = 0; i < ta->line_count - 1; i++) + if (ta->lines[i + 1].b_start > b_off) + break; + + ta->caret_pos.line = i; + + /* Now calculate the char. offset of the caret in this line */ + for (c_len = 0, ta->caret_pos.char_off = 0; + c_len < b_off - ta->lines[i].b_start; + c_len = utf8_next(ta->text + ta->lines[i].b_start, + ta->lines[i].b_length, c_len)) + ta->caret_pos.char_off++; + + if (textarea_scroll_visible(ta)) + textarea_redraw(ta, ta->x, ta->y, ta->x + ta->vis_width, + ta->y + ta->vis_height); + + /* Finally, redraw the caret */ + index = textarea_get_caret(ta); + + for (b_off = 0; index-- > 0; + b_off = utf8_next(ta->text, ta->text_len, b_off)) + ; /* do nothing */ + + nsfont.font_width(ta->style, + ta->text + ta->lines[ta->caret_pos.line].b_start, + b_off - ta->lines[ta->caret_pos.line].b_start, &x); + + x += ta->x + MARGIN_LEFT - ta->scroll_x; + + y = css_len2px(&(ta->style->line_height.value.length), ta->style) * + ta->caret_pos.line + ta->y - ta->scroll_y; + + x0 = max(x - 1, ta->x + MARGIN_LEFT); + y0 = max(y - 1, ta->y); + x1 = min(x + 1, ta->x + ta->vis_width - MARGIN_RIGHT); + y1 = min(y + height + 1, ta->y + ta->vis_height); + + plot.clip(x0, y0, x1, y1); + plot.line(x, y, x, y + height, 1, 0x000000, false, false); + } + ta->redraw_end_callback(ta->data); +} + + +/** + * get character offset from the beginning of the text for some coordinates + * + * \param ta Text area + * \param x X coordinate + * \param y Y coordinate + * \return character offset + */ +static int textarea_get_xy_offset(struct text_area *ta, int x, int y) +{ + size_t b_off, c_off, temp; + int line_height; + int line; + + if (!ta->line_count) + return 0; + + line_height = css_len2px(&(ta->style->line_height.value.length), + ta->style); + + x = x - ta->x - MARGIN_LEFT + ta->scroll_x; + y = y - ta->y + ta->scroll_y; + + if (x < 0) + x = 0; + + line = y / line_height; + + if (line < 0) + line = 0; + if (ta->line_count - 1 < line) + line = ta->line_count - 1; + + nsfont.font_position_in_string(ta->style, + ta->text + ta->lines[line].b_start, + ta->lines[line].b_length, x, &b_off, &x); + + + for (temp = 0, c_off = 0; temp < b_off + ta->lines[line].b_start; + temp = utf8_next(ta->text, ta->text_len, temp)) + c_off++; + + /* if the offset is a space at the end of the line set the caret before + it otherwise the caret will go on the beginning of the next line + */ + if (b_off == (unsigned)ta->lines[line].b_length && + ta->text[ta->lines[line].b_start + + ta->lines[line].b_length - 1] == ' ') + c_off--; + + return c_off; +} + + +/** + * Set the caret's position + * + * \param ta Text area + * \param x X position of caret in a window + * \param y Y position of caret in a window + */ +void textarea_set_caret_xy(struct text_area *ta, int x, int y) +{ + int c_off; + + if (ta->flags & TEXTAREA_READONLY) + return; + + c_off = textarea_get_xy_offset(ta, x, y); + textarea_set_caret(ta, c_off); +} + + +/** + * Get the caret's position + * + * \param ta Text area + * \return 0-based character index of caret location, or -1 on error + */ +int textarea_get_caret(struct text_area *ta) +{ + int c_off = 0, b_off; + + if (ta->text_len == 1) + return 0; + + /* Calculate character offset of this line's start */ + for (b_off = 0; b_off < ta->lines[ta->caret_pos.line].b_start; + b_off = utf8_next(ta->text, ta->text_len, b_off)) + c_off++; + + return c_off + ta->caret_pos.char_off; +} + +/** + * Reflow a text area from the given line onwards + * + * \param ta Text area to reflow + * \param line Line number to begin reflow on + */ +void textarea_reflow(struct text_area *ta, unsigned int line) +{ + char *text; + unsigned int len; + size_t b_off; + int x; + char *space; + unsigned int line_count = 0; + + /** \todo pay attention to line parameter */ + /** \todo create horizontal scrollbar if needed */ + + ta->line_count = 0; + + if (!ta->lines) { + ta->lines = + malloc(LINE_CHUNK_SIZE * sizeof(struct line_info)); + if (!ta->lines) { + LOG(("malloc failed")); + return; + } + } + + if (!(ta->flags & TEXTAREA_MULTILINE)) { + /* Single line */ + ta->lines[line_count].b_start = 0; + ta->lines[line_count++].b_length = ta->text_len - 1; + + ta->line_count = line_count; + + return; + } + + for (len = ta->text_len - 1, text = ta->text; len > 0; + len -= b_off, text += b_off) { + + nsfont.font_split(ta->style, text, len, + ta->vis_width - MARGIN_LEFT - MARGIN_RIGHT, + &b_off, &x); + + if (line_count > 0 && line_count % LINE_CHUNK_SIZE == 0) { + struct line_info *temp = realloc(ta->lines, + (line_count + LINE_CHUNK_SIZE) * + sizeof(struct line_info)); + if (!temp) { + LOG(("realloc failed")); + return; + } + + ta->lines = temp; + } + + /* handle LF */ + for (space = text; space <= text + b_off; space++) { + if (*space == '\n') + break; + } + + if (space <= text + b_off) { + /* Found newline; use it */ + ta->lines[line_count].b_start = text - ta->text; + ta->lines[line_count++].b_length = space - text; + + b_off = space + 1 - text; + + if (len - b_off == 0) { + /* reached end of input => add last line */ + ta->lines[line_count].b_start = + text + b_off - ta->text; + ta->lines[line_count++].b_length = 0; + } + + continue; + } + + if (len - b_off > 0) { + /* find last space (if any) */ + for (space = text + b_off; space > text; space--) + if (*space == ' ') + break; + + if (space != text) + b_off = space + 1 - text; + } + + ta->lines[line_count].b_start = text - ta->text; + ta->lines[line_count++].b_length = b_off; + } + + ta->line_count = line_count; +} + +/** + * Handle redraw requests for text areas + * + * \param redraw Redraw request block + */ +void textarea_redraw(struct text_area *ta, int x0, int y0, int x1, int y1) +{ + int line0, line1, line; + int line_height, c_pos, c_len, b_start, b_end, chars, offset; + + + if (x1 < ta->x || x0 > ta->x + ta->vis_width || y1 < ta->y || + y0 > ta->y + ta->vis_height) + /* Textarea outside the clipping rectangle */ + return; + + if (!ta->lines) + /* Nothing to redraw */ + return; + + line_height = css_len2px(&(ta->style->line_height.value.length), + ta->style); + + line0 = (y0 - ta->y + ta->scroll_y) / line_height - 1; + line1 = (y1 - ta->y + ta->scroll_y) / line_height + 1; + + if (line0 < 0) + line0 = 0; + if (line1 < 0) + line1 = 0; + if (ta->line_count - 1 < line0) + line0 = ta->line_count - 1; + if (ta->line_count - 1 < line1) + line1 = ta->line_count - 1; + if (line1 < line0) + line1 = line0; + + if (x0 < ta->x) + x0 = ta->x; + if (y0 < ta->y) + y0 = ta->y; + if (x1 > ta->x + ta->vis_width) + x1 = ta->x + ta->vis_width; + if (y1 > ta->y + ta->vis_height) + y1 = ta->y + ta->vis_height; + + plot.clip(x0, y0, x1, y1); + plot.fill(x0, y0, x1, y1, (ta->flags & TEXTAREA_READONLY) ? + 0xD9D9D9 : 0xFFFFFF); + plot.rectangle(ta->x, ta->y, ta->vis_width - 1, ta->vis_height - 1, 1, + 0x000000, false, false); + + if (x0 < ta->x + MARGIN_LEFT) + x0 = ta->x + MARGIN_LEFT; + if (x1 > ta->x + ta->vis_width - MARGIN_RIGHT) + x1 = ta->x + ta->vis_width - MARGIN_RIGHT; + plot.clip(x0, y0, x1, y1); + + if (line0 > 0) + c_pos = utf8_bounded_length(ta->text, + ta->lines[line0].b_start - 1); + else + c_pos = 0; + + for (line = line0; (line <= line1) && + (ta->y + line * line_height <= y1 + ta->scroll_y); + line++) { + if (ta->lines[line].b_length == 0) + continue; + + c_len = utf8_bounded_length( + &(ta->text[ta->lines[line].b_start]), + ta->lines[line].b_length); + + /*if there is a newline between the lines count it too*/ + if (line < ta->line_count - 1 && ta->lines[line + 1].b_start != + ta->lines[line].b_start + ta->lines[line].b_length) + c_len++; + + /*check if a part of the line is selected, won't happen if no + selection (ta->selection_end = -1) + */ + if (c_pos < ta->selection_end && + c_pos + c_len > ta->selection_start) { + + /*offset from the beginning of the line*/ + offset = ta->selection_start - c_pos; + chars = ta->selection_end - c_pos - + (offset > 0 ? offset:0); + + if (offset > 0) { + + /*find byte start of the selected part*/ + for (b_start = 0; offset > 0; offset--) + b_start = utf8_next( + &(ta->text[ta->lines[line].b_start]), + ta->lines[line].b_length, + b_start); + nsfont.font_width(ta->style, + &(ta->text[ta->lines[line].b_start]), + b_start, &x0); + x0 += ta->x + MARGIN_LEFT; + } + else { + x0 = ta->x + MARGIN_LEFT; + b_start = 0; + } + + + if (chars >= 0) { + + /*find byte end of the selected part*/ + for (b_end = b_start; chars > 0 && + b_end < ta->lines[line].b_length; + chars--) { + b_end = utf8_next( + &(ta->text[ta->lines[line].b_start]), + ta->lines[line].b_length, + b_end); + } + } + else + b_end = ta->lines[line].b_length; + + b_end -= b_start; + nsfont.font_width(ta->style, + &(ta->text[ta->lines[line].b_start + b_start]), + b_end, &x1); + x1 += x0; + plot.fill(x0 - ta->scroll_x, ta->y + line * line_height + 1 - ta->scroll_y, + x1 - ta->scroll_x, ta->y + (line + 1) * line_height - 1 - ta->scroll_y, + 0xFFDDDD); + + } + + c_pos += c_len; + + y0 = ta->y + line * line_height + 0.75 * line_height; + + plot.text(ta->x + MARGIN_LEFT - ta->scroll_x, y0 - ta->scroll_y, ta->style, + ta->text + ta->lines[line].b_start, + ta->lines[line].b_length, + (ta->flags & TEXTAREA_READONLY) ? + 0xD9D9D9 : 0xFFFFFF, + 0x000000); + } +} + +/** + * Key press handling for text areas. + * + * \param ta The text area which got the keypress + * \param key The ucs4 character codepoint + * \return true if the keypress is dealt with, false otherwise. + */ +bool textarea_keypress(struct text_area *ta, uint32_t key) +{ + char utf8[6]; + unsigned int caret, caret_init, length, c_len, l_len; + int c_line, c_chars, line, line_height; + bool redraw = false; + + caret_init = caret = textarea_get_caret(ta); + line = ta->caret_pos.line; + + if (!(key <= 0x001F || (0x007F <= key && key <= 0x009F))) { + /* normal character insertion */ + length = utf8_from_ucs4(key, utf8); + utf8[length] = '\0'; + + textarea_insert_text(ta, caret, utf8); + caret++; + redraw = true; + + } else switch (key) { + case KEY_SELECT_ALL: + c_len = utf8_length(ta->text); + caret = c_len; + + ta->selection_start = 0; + ta->selection_end = c_len; + redraw = true; + break; + case KEY_COPY_SELECTION: + break; + case KEY_DELETE_LEFT: + if (ta->selection_start != -1) { + textarea_replace_text(ta, ta->selection_start, + ta->selection_end, ""); + ta->selection_start = ta->selection_end = -1; + redraw = true; + } else { + if (caret) { + textarea_replace_text(ta, caret - 1, + caret, ""); + caret--; + redraw = true; + } + } + break; + case KEY_TAB: + break; + case KEY_NL: + textarea_insert_text(ta, caret, "\n"); + caret++; + ta->selection_start = ta->selection_end = -1; + redraw = true; + break; + case KEY_SHIFT_TAB: + case KEY_CR: + case KEY_CUT_LINE: + case KEY_PASTE: + case KEY_CUT_SELECTION: + break; + case KEY_CLEAR_SELECTION: + ta->selection_start = -1; + ta->selection_end = -1; + redraw = true; + break; + case KEY_ESCAPE: + break; + case KEY_LEFT: + if (caret) + caret--; + if (ta->selection_start != -1) { + ta->selection_start = ta->selection_end = -1; + redraw = true; + } + break; + case KEY_RIGHT: + c_len = utf8_length(ta->text); + if (caret < c_len) + caret++; + if (ta->selection_start != -1) { + ta->selection_start = ta->selection_end = -1; + redraw = true; + } + break; + case KEY_PAGE_UP: + if (ta->flags & TEXTAREA_MULTILINE) { + line_height = css_len2px( + &(ta->style->line_height.value.length), + ta->style); + + /* +1 because one line is subtracted in KEY_UP*/ + line = ta->caret_pos.line - (ta->vis_height + + line_height - 1) / line_height + + 1; + } + /*fall through*/ + case KEY_UP: + if (ta->flags & TEXTAREA_MULTILINE) { + line--; + if (line < 0) + line = 0; + if (line != ta->caret_pos.line) { + c_line = ta->caret_pos.line; + c_chars = ta->caret_pos.char_off; + + ta->caret_pos.line = line; + l_len = utf8_bounded_length( + &(ta->text[ta->lines[ta->caret_pos.line].b_start]), + ta->lines[ta->caret_pos.line].b_length); + ta->caret_pos.char_off = min(l_len, + (unsigned)ta->caret_pos.char_off); + caret = textarea_get_caret(ta); + + ta->caret_pos.line = c_line; + ta->caret_pos.char_off = c_chars; + } + } + if (ta->selection_start != -1) { + ta->selection_start = ta->selection_end = -1; + redraw = true; + } + break; + case KEY_PAGE_DOWN: + if (ta->flags & TEXTAREA_MULTILINE) { + line_height = css_len2px( + &(ta->style->line_height.value.length), + ta->style); + + /* -1 because one line is added in KEY_DOWN*/ + line = ta->caret_pos.line + (ta->vis_height + + line_height - 1) / line_height + - 1; + } + /* fall through*/ + case KEY_DOWN: + if (ta->flags & TEXTAREA_MULTILINE) { + line++; + if (line > ta->line_count - 1) + line = ta->line_count - 1; + if (line != ta->caret_pos.line) { + c_line = ta->caret_pos.line; + c_chars = ta->caret_pos.char_off; + + ta->caret_pos.line = line; + l_len = utf8_bounded_length( + &(ta->text[ta->lines[ta->caret_pos.line].b_start]), + ta->lines[ta->caret_pos.line].b_length); + ta->caret_pos.char_off = min(l_len, + (unsigned)ta->caret_pos.char_off); + caret = textarea_get_caret(ta); + + ta->caret_pos.line = c_line; + ta->caret_pos.char_off = c_chars; + } + } + if (ta->selection_start != -1) { + ta->selection_start = ta->selection_end = -1; + redraw = true; + } + break; + case KEY_DELETE_RIGHT: + if (ta->selection_start != -1) { + textarea_replace_text(ta, ta->selection_start, + ta->selection_end, ""); + ta->selection_start = ta->selection_end = -1; + redraw = true; + } else { + c_len = utf8_length(ta->text); + if (caret < c_len) { + textarea_replace_text(ta, caret, caret + 1, ""); + redraw = true; + } + } + break; + case KEY_LINE_START: + caret -= ta->caret_pos.char_off; + if (ta->selection_start != -1) { + ta->selection_start = ta->selection_end = -1; + redraw = true; + } + break; + case KEY_LINE_END: + caret = utf8_bounded_length(ta->text, + ta->lines[ta->caret_pos.line].b_start + + ta->lines[ta->caret_pos.line].b_length); + if (ta->text[ta->lines[ta->caret_pos.line].b_start + + ta->lines[ta->caret_pos.line].b_length + - 1] == ' ') + caret--; + if (ta->selection_start != -1) { + ta->selection_start = ta->selection_end = -1; + redraw = true; + } + break; + case KEY_TEXT_START: + caret = 0; + if (ta->selection_start != -1) { + ta->selection_start = ta->selection_end = -1; + redraw = true; + } + break; + case KEY_TEXT_END: + caret = utf8_length(ta->text); + if (ta->selection_start != -1) { + ta->selection_start = ta->selection_end = -1; + redraw = true; + } + break; + case KEY_WORD_LEFT: + case KEY_WORD_RIGHT: + break; + case KEY_DELETE_LINE_END: + if (ta->selection_start != -1) { + textarea_replace_text(ta, ta->selection_start, + ta->selection_end, ""); + ta->selection_start = ta->selection_end = -1; + } else { + textarea_replace_text(ta, caret, caret + utf8_bounded_length( + &(ta->text[ta->lines[ta->caret_pos.line].b_start]), + ta->lines[ta->caret_pos.line].b_length), + ""); + } + redraw = true; + break; + case KEY_DELETE_LINE_START: + if (ta->selection_start != -1) { + textarea_replace_text(ta, ta->selection_start, + ta->selection_end, ""); + ta->selection_start = ta->selection_end = -1; + } else { + textarea_replace_text(ta, + caret - ta->caret_pos.char_off, caret, + ""); + caret -= ta->caret_pos.char_off; + } + redraw = true; + break; + default: + return false; + } + + //TODO:redraw only the important part + if (redraw) { + ta->redraw_start_callback(ta->data); + textarea_redraw(ta, ta->x, ta->y, ta->x + ta->vis_width, + ta->y + ta->vis_height); + ta->redraw_end_callback(ta->data); + } + + if (caret != caret_init || redraw) + textarea_set_caret(ta, caret); + + return true; +} + +/** + * Scrolls a textarea to make the caret visible (doesn't perform a redraw) + * + * \param ta The text area to be scrolled + * \return true if textarea was scrolled false otherwise + */ +static bool textarea_scroll_visible(struct text_area *ta) +{ + int x0, x1, y0, y1, x, y; + int index, b_off, line_height; + bool scrolled = false; + + if (ta->caret_pos.char_off == -1) + return false; + + x0 = ta->x + MARGIN_LEFT; + x1 = ta->x + ta->vis_width - MARGIN_RIGHT; + y0 = ta->y; + y1 = ta->y + ta->vis_height; + + index = textarea_get_caret(ta); + + for (b_off = 0; index-- > 0; + b_off = utf8_next(ta->text, ta->text_len, b_off)) + ; /* do nothing */ + + nsfont.font_width(ta->style, + ta->text + ta->lines[ta->caret_pos.line].b_start, + b_off - ta->lines[ta->caret_pos.line].b_start, + &x); + + line_height = css_len2px(&(ta->style->line_height.value.length), + ta->style); + + /* top-left of caret*/ + x += ta->x + MARGIN_LEFT - ta->scroll_x; + y = line_height * ta->caret_pos.line + ta->y - ta->scroll_y; + + /* check and change vertical scroll*/ + if (y < y0) { + ta->scroll_y -= y0 - y; + scrolled = true; + } else if (y + line_height > y1) { + ta->scroll_y += y + line_height - y1; + scrolled = true; + } + + + /* check and change horizontal scroll*/ + if (x < x0) { + ta->scroll_x -= x0 - x ; + scrolled = true; + } else if (x > x1 - 1) { + ta->scroll_x += x - (x1 - 1); + scrolled = true; + } + + return scrolled; +} + + +/** + * Handles all kinds of mouse action + * + * \param ta Text area + * \param mouse the mouse state at action moment + * \param x X coordinate + * \param y Y coordinate + */ +void textarea_mouse_action(struct text_area *ta, browser_mouse_state mouse, + int x, int y) +{ + int c_start, c_end; + + /* mouse button pressed above the text area, move caret*/ + if (mouse & BROWSER_MOUSE_PRESS_1) { + if (ta->selection_start != -1) { + ta->selection_start = ta->selection_end = -1; + ta->redraw_start_callback(ta->data); + textarea_redraw(ta, ta->x, ta->y, ta->x + ta->vis_width, + ta->y + ta->vis_height); + ta->redraw_end_callback(ta->data); + } + textarea_set_caret_xy(ta, x, y); + + return; + } + + if (mouse & BROWSER_MOUSE_DRAG_1) { + ta->drag_start_char = textarea_get_xy_offset(ta, x, y); + textarea_set_caret(ta, -1); + return; + } + + if (mouse & BROWSER_MOUSE_HOLDING_1) { + c_start = ta->drag_start_char; + c_end = textarea_get_xy_offset(ta, x, y); + textarea_select(ta, c_start, c_end); + return; + } +} + + +/** + * Handles the end of a drag operation + * + * \param ta Text area + * \param mouse the mouse state at drag end moment + * \param x X coordinate + * \param y Y coordinate + */ +void textarea_drag_end(struct text_area *ta, browser_mouse_state mouse, + int x, int y) +{ + int c_end; + + c_end = textarea_get_xy_offset(ta, x, y); + textarea_select(ta, ta->drag_start_char, c_end); +} + +/** + * Selects a character range in the textarea and redraws it + * + * \param ta Text area + * \param c_start First character (inclusive) + * \param c_end Last character (exclusive) + */ +void textarea_select(struct text_area *ta, int c_start, int c_end) +{ + int swap = -1; + + /* if start is after end they get swapped, start won't and end will + be selected this way + */ + if (c_start > c_end) { + swap = c_start; + c_start = c_end; + c_end = swap; + } + + ta->selection_start = c_start; + ta->selection_end = c_end; + + ta->redraw_start_callback(ta->data); + textarea_redraw(ta, ta->x, ta->y, ta->x + ta->vis_width, + ta->y + ta->vis_height); + ta->redraw_end_callback(ta->data); + + if (swap == -1) + textarea_set_caret(ta, c_end); + else + textarea_set_caret(ta, c_start); +} + + +/** + * Removes any CR characters and changes newlines into spaces if it is a single + * line textarea. + * + * \param ta Text area + * \param b_start Byte offset to start at + * \param b_len Byte length to check + */ +void textarea_normalise_text(struct text_area *ta, + unsigned int b_start, unsigned int b_len) +{ + bool multi = (ta->flags & TEXTAREA_MULTILINE) ? true:false; + unsigned int index; + + /*remove CR characters*/ + for (index = 0; index < b_len; index++) { + if (ta->text[b_start + index] == '\r') { + if (b_start + index + 1 <= ta->text_len && + ta->text[b_start + index + 1] == '\n') { + ta->text_len--; + memmove(ta->text + b_start + index, + ta->text + b_start + index + 1, + ta->text_len - b_start - index); + } + else + ta->text[b_start + index] = '\n'; + } + + if (!multi && (ta->text[b_start + index] == '\n')) + ta->text[b_start + index] = ' '; + } + +} Index: desktop/textarea.h =================================================================== --- /dev/null 2009-04-16 19:17:07.000000000 +0100 +++ desktop/textarea.h 2009-06-23 11:58:18.000000000 +0100 @@ -0,0 +1,61 @@ +/* + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** \file + * Single/Multi-line UTF-8 text area (interface) + */ + +#ifndef _NETSURF_DESKTOP_TEXTAREA_H_ +#define _NETSURF_DESKTOP_TEXTAREA_H_ + +#include <stdint.h> +#include <stdbool.h> +#include "css/css.h" +#include "desktop/browser.h" + +/* Text area flags */ +#define TEXTAREA_MULTILINE 0x01 /**< Text area is multiline */ +#define TEXTAREA_READONLY 0x02 /**< Text area is read only */ + +struct text_area; + +/* this is temporary only*/ +browser_mouse_state textarea_mouse_state; +int textarea_pressed_x; +int textarea_pressed_y; + +typedef void(*textarea_start_radraw_callback)(intptr_t data); +typedef void(*textarea_end_radraw_callback)(intptr_t data); + +struct text_area *textarea_create(int x, int y, int width, int height, + unsigned int flags, const struct css_style *style, + textarea_start_radraw_callback redraw_start_callback, + textarea_end_radraw_callback redraw_end_callback, intptr_t data); +void textarea_destroy(struct text_area *ta); +bool textarea_set_text(struct text_area *ta, const char *text); +int textarea_get_text(struct text_area *ta, char *buf, unsigned int len); +void textarea_set_caret(struct text_area *ta, int caret); +int textarea_get_caret(struct text_area *ta); +void textarea_redraw(struct text_area *ta, int x0, int y0, int x1, int y1); +bool textarea_keypress(struct text_area *ta, uint32_t key); +void textarea_mouse_action(struct text_area *ta, browser_mouse_state mouse, + int x, int y); +void textarea_drag_end(struct text_area *ta, browser_mouse_state mouse, + int x, int y); + +#endif + Changed files Makefile.sources | 2 desktop/browser.h | 17 +- desktop/textinput.h | 3 gtk/font_pango.c | 1 gtk/gtk_plotters.c | 2 gtk/gtk_scaffolding.c | 289 +++++++++++++++++++++++++++++++++++++++++++++++++- gtk/gtk_window.c | 41 ++++++- gtk/gtk_window.h | 3 8 files changed, 340 insertions(+), 18 deletions(-) Index: gtk/gtk_window.c =================================================================== --- gtk/gtk_window.c (revision 7935) +++ gtk/gtk_window.c (working copy) @@ -38,7 +38,7 @@ struct gui_window *window_list = 0; /**< first entry in win list*/ int temp_open_background = -1; -static uint32_t gdkkey_to_nskey(GdkEventKey *); + static void nsgtk_gui_window_attach_child(struct gui_window *parent, struct gui_window *child); /* Methods which apply only to a gui_window */ @@ -455,17 +455,48 @@ * everything that the RISC OS version does. But this will do for * now. I hope. */ - switch (key->keyval) { - case GDK_BackSpace: return KEY_DELETE_LEFT; - case GDK_Delete: return KEY_DELETE_RIGHT; + case GDK_BackSpace: + if (key->state & GDK_SHIFT_MASK) + return KEY_DELETE_LINE_START; + else + return KEY_DELETE_LEFT; + case GDK_Delete: + if (key->state & GDK_SHIFT_MASK) + return KEY_DELETE_LINE_END; + else + return KEY_DELETE_RIGHT; case GDK_Linefeed: return 13; case GDK_Return: return 10; case GDK_Left: return KEY_LEFT; case GDK_Right: return KEY_RIGHT; case GDK_Up: return KEY_UP; case GDK_Down: return KEY_DOWN; + case GDK_Home: + if (key->state & GDK_CONTROL_MASK) + return KEY_TEXT_START; + else + return KEY_LINE_START; + case GDK_End: + if (key->state & GDK_CONTROL_MASK) + return KEY_TEXT_END; + else + return KEY_LINE_END; + case GDK_Page_Up: + return KEY_PAGE_UP; + case GDK_Page_Down: + return KEY_PAGE_DOWN; + case 'a': + if (key->state & GDK_CONTROL_MASK) + return KEY_SELECT_ALL; + return gdk_keyval_to_unicode( + key->keyval); + case 'u': + if (key->state & GDK_CONTROL_MASK) + return KEY_CLEAR_SELECTION; + return gdk_keyval_to_unicode( + key->keyval); /* Modifiers - do nothing for now */ case GDK_Shift_L: @@ -483,7 +514,7 @@ case GDK_Hyper_L: case GDK_Hyper_R: return 0; - default: return gdk_keyval_to_unicode( + default: return gdk_keyval_to_unicode( key->keyval); } } Index: gtk/gtk_window.h =================================================================== --- gtk/gtk_window.h (revision 7935) +++ gtk/gtk_window.h (working copy) @@ -76,6 +76,9 @@ int nsgtk_gui_window_update_targets(struct gui_window *g); void nsgtk_window_destroy_browser(struct gui_window *g); +/*TODO: find a better place for this?*/ +uint32_t gdkkey_to_nskey(GdkEventKey *); + struct browser_window *nsgtk_get_browser_window(struct gui_window *g); #endif /* NETSURF_GTK_WINDOW_H */ Index: gtk/gtk_plotters.c =================================================================== --- gtk/gtk_plotters.c (revision 7935) +++ gtk/gtk_plotters.c (working copy) @@ -119,7 +119,7 @@ line_width = 1; cairo_set_line_width(current_cr, line_width); - cairo_rectangle(current_cr, x0, y0, width, height); + cairo_rectangle(current_cr, x0 + 0.5, y0 + 0.5, width, height); cairo_stroke(current_cr); return true; Index: gtk/gtk_scaffolding.c =================================================================== --- gtk/gtk_scaffolding.c (revision 7935) +++ gtk/gtk_scaffolding.c (working copy) @@ -37,6 +37,7 @@ #endif #include "desktop/selection.h" #include "desktop/textinput.h" +#include "desktop/textarea.h" #include "gtk/gtk_completion.h" #include "gtk/dialogs/gtk_options.h" #include "gtk/dialogs/gtk_about.h" @@ -71,6 +72,15 @@ GtkDrawingArea *drawing_area; }; +struct gtk_treeview_window { + GtkWindow *window; + GtkScrolledWindow *scrolled; + GtkDrawingArea *drawing_area; + int mouse_pressed_x; + int mouse_pressed_y; + browser_mouse_state mouse_state; +}; + struct menu_events { const char *widget; GCallback handler; @@ -84,6 +94,8 @@ static gboolean nsgtk_window_delete_event(GtkWidget *, gpointer); static void nsgtk_window_destroy_event(GtkWidget *, gpointer); +static struct gtk_treeview_window global_history_window; + static void nsgtk_window_update_back_forward(struct gtk_scaffolding *); static void nsgtk_throb(void *); static gboolean nsgtk_window_edit_menu_clicked(GtkWidget *widget, @@ -116,6 +128,20 @@ static gboolean nsgtk_history_button_press_event(GtkWidget *, GdkEventButton *, gpointer); +gboolean nsgtk_global_history_expose_event(GtkWidget *widget, + GdkEventExpose *event, gpointer g); +static gboolean nsgtk_global_history_button_press_event(GtkWidget *, GdkEventButton *, + gpointer g); +static gboolean nsgtk_global_history_button_release_event(GtkWidget *, GdkEventButton *, + gpointer g); +gboolean nsgtk_global_history_motion_notify_event(GtkWidget *widget, + GdkEventButton *event, gpointer g); +static gboolean nsgtk_global_history_keypress_event(GtkWidget *, GdkEventKey *, + gpointer g); + +static void redraw_start_callback(intptr_t data); +static void redraw_end_callback(intptr_t data); + static void nsgtk_attach_menu_handlers(GladeXML *, gpointer); static void nsgtk_window_tabs_num_changed(GtkNotebook *notebook, GtkWidget *page, guint page_num, struct gtk_scaffolding *g); @@ -1120,9 +1146,18 @@ MENUHANDLER(global_history) { - gtk_widget_show(GTK_WIDGET(wndHistory)); - gdk_window_raise(GTK_WIDGET(wndHistory)->window); + //gtk_widget_show(GTK_WIDGET(wndHistory)); + //gdk_window_raise(GTK_WIDGET(wndHistory)->window); + + struct gtk_scaffolding *gw = (struct gtk_scaffolding *) g; + gtk_window_set_default_size(global_history_window.window, 500, 400); + gtk_window_set_position(global_history_window.window, GTK_WIN_POS_MOUSE); + gtk_window_set_transient_for(global_history_window.window, gw->window); + gtk_window_set_opacity(global_history_window.window, 0.9); + gtk_widget_show(GTK_WIDGET(global_history_window.window)); + gdk_window_raise(GTK_WIDGET(global_history_window.window)->window); + return TRUE; } @@ -1203,6 +1238,193 @@ return TRUE; } +/* signal handler functions for the global history window */ +gboolean nsgtk_global_history_expose_event(GtkWidget *widget, + GdkEventExpose *event, gpointer g) +{ +// struct gtk_history_window *hw = (struct gtk_history_window *)g; +// struct browser_window *bw = +// nsgtk_get_browser_for_gui(hw->g->top_level); + + struct text_area *ta = (struct text_area *) g; + + current_widget = widget; + current_drawable = widget->window; + current_gc = gdk_gc_new(current_drawable); +#ifdef CAIRO_VERSION + current_cr = gdk_cairo_create(current_drawable); +#endif + plot = nsgtk_plotters; + nsgtk_plot_set_scale(1.0); + + textarea_redraw(ta, 0, 0, 1000, 1000); + + g_object_unref(current_gc); +#ifdef CAIRO_VERSION + cairo_destroy(current_cr); +#endif + + return FALSE; +} + +gboolean nsgtk_global_history_button_press_event(GtkWidget *widget, + GdkEventButton *event, gpointer g) +{ + LOG(("X=%g, Y=%g", event->x, event->y)); + + struct text_area *ta = (struct text_area *) g; + + textarea_pressed_x = event->x; + textarea_pressed_y = event->y; + + if (event->type == GDK_2BUTTON_PRESS) + textarea_mouse_state = BROWSER_MOUSE_DOUBLE_CLICK; + + switch (event->button) { + case 1: textarea_mouse_state |= BROWSER_MOUSE_PRESS_1; break; + case 3: textarea_mouse_state |= BROWSER_MOUSE_PRESS_2; break; + } + /* Handle the modifiers too */ + if (event->state & GDK_SHIFT_MASK) + textarea_mouse_state |= BROWSER_MOUSE_MOD_1; + if (event->state & GDK_CONTROL_MASK) + textarea_mouse_state |= BROWSER_MOUSE_MOD_2; + if (event->state & GDK_MOD1_MASK) + textarea_mouse_state |= BROWSER_MOUSE_MOD_3; + + textarea_mouse_action(ta, textarea_mouse_state, + event->x, event->y); + + return TRUE; +} + +gboolean nsgtk_global_history_button_release_event(GtkWidget *widget, + GdkEventButton *event, gpointer g) +{ + bool shift = event->state & GDK_SHIFT_MASK; + bool ctrl = event->state & GDK_CONTROL_MASK; + bool alt = event->state & GDK_MOD1_MASK; + struct text_area *ta = (struct text_area *) g; + + /* We consider only button 1 clicks as double clicks. + * If the mouse state is PRESS then we are waiting for a release to emit + * a click event, otherwise just reset the state to nothing*/ + if (textarea_mouse_state & BROWSER_MOUSE_DOUBLE_CLICK) { + + if (textarea_mouse_state & BROWSER_MOUSE_PRESS_1) + textarea_mouse_state ^= BROWSER_MOUSE_PRESS_1; + else if (textarea_mouse_state & BROWSER_MOUSE_PRESS_2) + textarea_mouse_state ^= (BROWSER_MOUSE_PRESS_2 | + BROWSER_MOUSE_DOUBLE_CLICK); + + } else if (textarea_mouse_state & BROWSER_MOUSE_PRESS_1) + textarea_mouse_state ^= + (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_CLICK_1); + else if (textarea_mouse_state & BROWSER_MOUSE_PRESS_2) + textarea_mouse_state ^= + (BROWSER_MOUSE_PRESS_2 | BROWSER_MOUSE_CLICK_2); + + /* Handle modifiers being removed */ + if (textarea_mouse_state & BROWSER_MOUSE_MOD_1 && !shift) + textarea_mouse_state ^= BROWSER_MOUSE_MOD_1; + if (textarea_mouse_state & BROWSER_MOUSE_MOD_2 && !ctrl) + textarea_mouse_state ^= BROWSER_MOUSE_MOD_2; + if (textarea_mouse_state & BROWSER_MOUSE_MOD_3 && !alt) + textarea_mouse_state ^= BROWSER_MOUSE_MOD_3; + + if (textarea_mouse_state & + (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2 + | BROWSER_MOUSE_DOUBLE_CLICK)) + textarea_mouse_action(ta, textarea_mouse_state, + event->x, event->y); + else + textarea_drag_end(ta, textarea_mouse_state, event->x, event->y); + + textarea_mouse_state = 0; + + return TRUE; +} + +gboolean nsgtk_global_history_motion_notify_event(GtkWidget *widget, + GdkEventButton *event, gpointer g) +{ + bool shift = event->state & GDK_SHIFT_MASK; + bool ctrl = event->state & GDK_CONTROL_MASK; + bool alt = event->state & GDK_MOD1_MASK; + struct text_area *ta = (struct text_area *) g; + + + if (textarea_mouse_state & BROWSER_MOUSE_PRESS_1) { + /* Start button 1 drag */ + textarea_mouse_action(ta, BROWSER_MOUSE_DRAG_1, + textarea_pressed_x, textarea_pressed_y); + /* Replace PRESS with HOLDING and declare drag in progress */ + textarea_mouse_state ^= (BROWSER_MOUSE_PRESS_1 | + BROWSER_MOUSE_HOLDING_1); + textarea_mouse_state |= BROWSER_MOUSE_DRAG_ON; + } + else if (textarea_mouse_state & BROWSER_MOUSE_PRESS_2){ + /* Start button 2s drag */ + textarea_mouse_action(ta, BROWSER_MOUSE_DRAG_2, + textarea_pressed_x, textarea_pressed_y); + /* Replace PRESS with HOLDING and declare drag in progress */ + textarea_mouse_state ^= (BROWSER_MOUSE_PRESS_2 | + BROWSER_MOUSE_HOLDING_2); + textarea_mouse_state |= BROWSER_MOUSE_DRAG_ON; + } + + /* Handle modifiers being removed */ + if (textarea_mouse_state & BROWSER_MOUSE_MOD_1 && !shift) + textarea_mouse_state ^= BROWSER_MOUSE_MOD_1; + if (textarea_mouse_state & BROWSER_MOUSE_MOD_2 && !ctrl) + textarea_mouse_state ^= BROWSER_MOUSE_MOD_2; + if (textarea_mouse_state & BROWSER_MOUSE_MOD_3 && !alt) + textarea_mouse_state ^= BROWSER_MOUSE_MOD_3; + + textarea_mouse_action(ta, textarea_mouse_state, + event->x, event->y); + + return TRUE; +} + +gboolean nsgtk_global_history_keypress_event(GtkWidget *widget, + GdkEventKey *event, gpointer g) +{ + struct text_area *ta = g; + uint32_t nskey = gdkkey_to_nskey(event); + + if (textarea_keypress(ta, nskey)) + return TRUE; + + /*the final user of textarea will probably want to do something here*/ + + return TRUE; +} + + +void redraw_start_callback(intptr_t data) +{ + current_widget = (GtkWidget *) data; + current_drawable = current_widget->window; + current_gc = gdk_gc_new(current_drawable); +#ifdef CAIRO_VERSION + current_cr = gdk_cairo_create(current_drawable); +#endif + plot = nsgtk_plotters; + nsgtk_plot_set_scale(1.0); +} + +static void redraw_end_callback(intptr_t data) +{ + GtkWidget *widget = (GtkWidget *) data; + if (current_widget == widget) { + g_object_unref(current_gc); +#ifdef CAIRO_VERSION + cairo_destroy(current_cr); +#endif + } +} + #define GET_WIDGET(x) glade_xml_get_widget(g->xml, (x)) nsgtk_scaffolding *nsgtk_new_scaffolding(struct gui_window *toplevel) @@ -1352,6 +1574,39 @@ GTK_WIDGET(g->history_window->drawing_area)); gtk_widget_show(GTK_WIDGET(g->history_window->drawing_area)); + + global_history_window.window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + gtk_window_set_default_size(global_history_window.window, 400, 400); + gtk_window_set_title(global_history_window.window, "NetSurf Global History"); + gtk_window_set_type_hint(global_history_window.window, + GDK_WINDOW_TYPE_HINT_UTILITY); + global_history_window.scrolled = + GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(0, 0)); + gtk_container_add(GTK_CONTAINER(global_history_window.window), + GTK_WIDGET(global_history_window.scrolled)); + + gtk_widget_show(GTK_WIDGET(global_history_window.scrolled)); + global_history_window.drawing_area = + GTK_DRAWING_AREA(gtk_drawing_area_new()); + + gtk_widget_set_events(GTK_WIDGET(global_history_window.drawing_area), + GDK_EXPOSURE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK); + gtk_widget_modify_bg(GTK_WIDGET(global_history_window.drawing_area), + GTK_STATE_NORMAL, + &((GdkColor) { 0, 0xffff, 0xffff, 0xffff } )); + gtk_scrolled_window_add_with_viewport(global_history_window.scrolled, + GTK_WIDGET(global_history_window.drawing_area)); + gtk_widget_show(GTK_WIDGET(global_history_window.drawing_area)); + gtk_widget_set_size_request(GTK_WIDGET(global_history_window.drawing_area), + 1000, 1000); + GTK_WIDGET_SET_FLAGS(GTK_WIDGET(global_history_window.drawing_area), + GTK_CAN_FOCUS); + /* set up URL bar completion */ g->url_bar_completion = gtk_entry_completion_new(); gtk_entry_set_completion(g->url_bar, g->url_bar_completion); @@ -1366,7 +1621,7 @@ "popup-set-width", TRUE, "popup-single-match", TRUE, NULL); - + /* set up the throbber. */ gtk_image_set_from_pixbuf(g->throbber, nsgtk_throbber->framedata[0]); g->throb_frame = 0; @@ -1384,6 +1639,34 @@ CONNECT(g->history_window->window, "delete_event", gtk_widget_hide_on_delete, NULL); + struct text_area *ta; + + ta = textarea_create( 100, 100, 100, 45, TEXTAREA_READONLY, + &css_base_style, redraw_start_callback, + redraw_end_callback, + (intptr_t)global_history_window.drawing_area); + textarea_set_text(ta, "012345 67890123"); + textarea_mouse_state = 0; + textarea_pressed_x = 0; + textarea_pressed_y = 0; + + + CONNECT(global_history_window.drawing_area, "expose_event", + nsgtk_global_history_expose_event, ta); + CONNECT(global_history_window.drawing_area, "button_press_event", + nsgtk_global_history_button_press_event, + ta); + CONNECT(global_history_window.drawing_area, "button_release_event", + nsgtk_global_history_button_release_event, + ta); + CONNECT(global_history_window.drawing_area, "motion_notify_event", + nsgtk_global_history_motion_notify_event, + ta); + CONNECT(global_history_window.drawing_area, "key_press_event", + nsgtk_global_history_keypress_event, ta); + CONNECT(global_history_window.window, "delete_event", + gtk_widget_hide_on_delete, NULL); + g_signal_connect_after(g->notebook, "page-added", G_CALLBACK(nsgtk_window_tabs_num_changed), g); g_signal_connect_after(g->notebook, "page-removed", Index: gtk/font_pango.c =================================================================== --- gtk/font_pango.c (revision 7935) +++ gtk/font_pango.c (working copy) @@ -183,6 +183,7 @@ pango_layout_set_width(layout, x * PANGO_SCALE); pango_layout_set_wrap(layout, PANGO_WRAP_WORD); + pango_layout_set_single_paragraph_mode(layout, true); line = pango_layout_get_line(layout, 1); if (line) index = line->start_index - 1; Index: Makefile.sources =================================================================== --- Makefile.sources (revision 7935) +++ Makefile.sources (working copy) @@ -13,7 +13,7 @@ layout.c list.c loosen.c table.c textplain.c S_UTILS := base64.c filename.c hashtable.c locale.c messages.c talloc.c \ url.c utf8.c utils.c useragent.c -S_DESKTOP := knockout.c options.c print.c tree.c version.c +S_DESKTOP := knockout.c options.c print.c tree.c version.c textarea.c # S_COMMON are sources common to all builds S_COMMON := $(addprefix content/,$(S_CONTENT)) \ Index: desktop/textinput.h =================================================================== --- desktop/textinput.h (revision 7935) +++ desktop/textinput.h (working copy) @@ -28,7 +28,8 @@ #include <stdbool.h> -struct browser_window; +#include "desktop/browser.h" + struct box; Index: desktop/browser.h =================================================================== --- desktop/browser.h (revision 7935) +++ desktop/browser.h (working copy) @@ -191,21 +191,24 @@ * a drag. */ BROWSER_MOUSE_CLICK_1 = 4, /* button 1 clicked. */ BROWSER_MOUSE_CLICK_2 = 8, /* button 2 clicked. */ + BROWSER_MOUSE_DOUBLE_CLICK = 16, /* button 1 double clicked */ - BROWSER_MOUSE_DRAG_1 = 16, /* start of button 1 drag operation */ - BROWSER_MOUSE_DRAG_2 = 32, /* start of button 2 drag operation */ + BROWSER_MOUSE_DRAG_1 = 32, /* start of button 1 drag operation */ + BROWSER_MOUSE_DRAG_2 = 64, /* start of button 2 drag operation */ - BROWSER_MOUSE_DRAG_ON = 64, /* a drag operation was started and + BROWSER_MOUSE_DRAG_ON = 128, /* a drag operation was started and * a mouse button is still pressed */ - BROWSER_MOUSE_HOLDING_1 = 128, /* while button 1 drag is in progress */ - BROWSER_MOUSE_HOLDING_2 = 256, /* while button 2 drag is in progress */ + BROWSER_MOUSE_HOLDING_1 = 256, /* while button 1 drag is in progress */ + BROWSER_MOUSE_HOLDING_2 = 512, /* while button 2 drag is in progress */ - BROWSER_MOUSE_MOD_1 = 512, /* primary modifier key pressed + BROWSER_MOUSE_MOD_1 = 1024, /* primary modifier key pressed * (eg. Shift) */ - BROWSER_MOUSE_MOD_2 = 1024 /* secondary modifier key pressed + BROWSER_MOUSE_MOD_2 = 2048, /* secondary modifier key pressed * (eg. Ctrl) */ + BROWSER_MOUSE_MOD_3 = 4096 /* secondary modifier key pressed + * (eg. Alt) */ } browser_mouse_state; Conflicted files Removed files
