diff options
author | Vincent Sanders <vince@kyllikki.org> | 2016-05-05 22:28:51 +0100 |
---|---|---|
committer | Vincent Sanders <vince@kyllikki.org> | 2016-05-15 13:44:34 +0100 |
commit | d21447d096a320a08b3efb2b8768fad0dcdcfd64 (patch) | |
tree | 1a83814b7c9e94b2f13c473261f23dd3a17dee64 /frontends/riscos/textarea.c | |
parent | 2cbb337756d9af5bda4d594964d446439f602551 (diff) | |
download | netsurf-d21447d096a320a08b3efb2b8768fad0dcdcfd64.tar.gz netsurf-d21447d096a320a08b3efb2b8768fad0dcdcfd64.tar.bz2 |
move frontends into sub directory
Diffstat (limited to 'frontends/riscos/textarea.c')
-rw-r--r-- | frontends/riscos/textarea.c | 1160 |
1 files changed, 1160 insertions, 0 deletions
diff --git a/frontends/riscos/textarea.c b/frontends/riscos/textarea.c new file mode 100644 index 000000000..ecf3e0c3d --- /dev/null +++ b/frontends/riscos/textarea.c @@ -0,0 +1,1160 @@ +/* + * Copyright 2006 John-Mark Bell <jmb202@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 <inttypes.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <swis.h> +#include <oslib/colourtrans.h> +#include <oslib/osbyte.h> +#include <oslib/serviceinternational.h> +#include <oslib/wimp.h> +#include <oslib/wimpspriteop.h> + +#include "utils/log.h" +#include "utils/utf8.h" +#include "desktop/browser.h" + +#include "riscos/gui.h" +#include "riscos/oslib_pre7.h" +#include "riscos/textarea.h" +#include "riscos/ucstables.h" +#include "riscos/wimp.h" +#include "riscos/wimp_event.h" +#include "riscos/wimputils.h" + +#define MARGIN_LEFT 8 +#define MARGIN_RIGHT 8 + +struct line_info { + unsigned int b_start; /**< Byte offset of line start */ + unsigned int b_length; /**< Byte length of line */ +}; + +struct text_area { +#define MAGIC (('T'<<24) | ('E'<<16) | ('X'<<8) | 'T') + unsigned int magic; /**< Magic word, for sanity */ + + unsigned int flags; /**< Textarea flags */ + unsigned int vis_width; /**< Visible width, in pixels */ + unsigned int vis_height; /**< Visible height, in pixels */ + wimp_w window; /**< Window handle */ + + char *text; /**< UTF-8 text */ + unsigned int text_alloc; /**< Size of allocated text */ + unsigned int text_len; /**< Length of text, in bytes */ + struct { + unsigned int line; /**< Line caret is on */ + unsigned int char_off; /**< Character index of caret */ + } caret_pos; +// unsigned int selection_start; /**< Character index of sel start */ +// unsigned int selection_end; /**< Character index of sel end */ + + wimp_w parent; /**< Parent window handle */ + wimp_i icon; /**< Parent icon handle */ + + char *font_family; /**< Font family of text */ + unsigned int font_size; /**< Font size (16ths/pt) */ + rufl_style font_style; /**< Font style (rufl) */ + int line_height; /**< Total height of a line, given font size */ + int line_spacing; /**< Height of line spacing, given font size */ + + unsigned int line_count; /**< Count of lines */ +#define LINE_CHUNK_SIZE 256 + struct line_info *lines; /**< Line info array */ + + struct text_area *next; /**< Next text area in list */ + struct text_area *prev; /**< Prev text area in list */ +}; + +static wimp_window text_area_definition = { + {0, 0, 16, 16}, + 0, + 0, + wimp_TOP, + wimp_WINDOW_NEW_FORMAT | wimp_WINDOW_NO_BOUNDS, + wimp_COLOUR_BLACK, + wimp_COLOUR_LIGHT_GREY, + wimp_COLOUR_LIGHT_GREY, + wimp_COLOUR_VERY_LIGHT_GREY, + wimp_COLOUR_DARK_GREY, + wimp_COLOUR_MID_LIGHT_GREY, + wimp_COLOUR_CREAM, + 0, + {0, -16384, 16384, 0}, + wimp_ICON_TEXT | wimp_ICON_HCENTRED | wimp_ICON_VCENTRED, + wimp_BUTTON_CLICK << wimp_ICON_BUTTON_TYPE_SHIFT, + wimpspriteop_AREA, + 1, + 1, + {""}, + 0, + {} +}; + +static void ro_textarea_reflow(struct text_area *ta, unsigned int line); +static bool ro_textarea_mouse_click(wimp_pointer *pointer); +static bool ro_textarea_key_press(wimp_key *key); +static void ro_textarea_redraw(wimp_draw *redraw); +static void ro_textarea_redraw_internal(wimp_draw *redraw, bool update); +static void ro_textarea_open(wimp_open *open); + +/** + * Create a text area + * + * \param parent Parent window + * \param icon Icon in parent window to replace + * \param flags Text area flags + * \param font_family RUfl font family to use, or NULL for default + * \param font_size Font size to use (pt * 16), or 0 for default + * \param font_style Font style to use, or 0 for default + * \return Opaque handle for textarea or 0 on error + */ +uintptr_t ro_textarea_create(wimp_w parent, wimp_i icon, unsigned int flags, + const char *font_family, unsigned int font_size, + rufl_style font_style) +{ + struct text_area *ret; + os_error *error; + + ret = malloc(sizeof(struct text_area)); + if (!ret) { + LOG("malloc failed"); + return 0; + } + + ret->parent = parent; + ret->icon = icon; + ret->magic = MAGIC; + 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->caret_pos.line = ret->caret_pos.char_off = (unsigned int)-1; +// ret->selection_start = (unsigned int)-1; +// ret->selection_end = (unsigned int)-1; + ret->font_family = strdup(font_family ? font_family : "Corpus"); + if (!ret->font_family) { + LOG("strdup failed"); + free(ret->text); + free(ret); + return 0; + } + ret->font_size = font_size ? font_size : 192 /* 12pt */; + ret->font_style = font_style ? font_style : rufl_WEIGHT_400; + + /** \todo Better line height calculation */ + ret->line_height = (int)(((ret->font_size * 1.3) / 16) * 2.0) + 1; + ret->line_spacing = ret->line_height / 8; + + ret->line_count = 0; + ret->lines = 0; + + if (flags & TEXTAREA_READONLY) + text_area_definition.title_fg = 0xff; + else + text_area_definition.title_fg = wimp_COLOUR_BLACK; + error = xwimp_create_window(&text_area_definition, &ret->window); + if (error) { + LOG("xwimp_create_window: 0x%x: %s", error->errnum, error->errmess); + free(ret->font_family); + free(ret->text); + free(ret); + return 0; + } + + /* set the window dimensions */ + if (!ro_textarea_update((uintptr_t)ret)) { + ro_textarea_destroy((uintptr_t)ret); + return 0; + } + + /* and register our event handlers */ + ro_gui_wimp_event_set_user_data(ret->window, ret); + ro_gui_wimp_event_register_mouse_click(ret->window, + ro_textarea_mouse_click); + ro_gui_wimp_event_register_keypress(ret->window, + ro_textarea_key_press); + ro_gui_wimp_event_register_redraw_window(ret->window, + ro_textarea_redraw); + ro_gui_wimp_event_register_open_window(ret->window, + ro_textarea_open); + + return (uintptr_t)ret; +} + +/** + * Update the a text area following a change in the parent icon + * + * \param self Text area to update + */ +bool ro_textarea_update(uintptr_t self) +{ + struct text_area *ta; + wimp_window_state state; + wimp_icon_state istate; + os_box extent; + os_error *error; + + ta = (struct text_area *)self; + if (!ta || ta->magic != MAGIC) + return false; + + state.w = ta->parent; + error = xwimp_get_window_state(&state); + if (error) { + LOG("xwimp_get_window_state: 0x%x: %s", error->errnum, error->errmess); + return false; + } + + istate.w = ta->parent; + istate.i = ta->icon; + error = xwimp_get_icon_state(&istate); + if (error) { + LOG("xwimp_get_icon_state: 0x%x: %s", error->errnum, error->errmess); + return false; + } + + state.w = ta->window; + state.visible.x1 = state.visible.x0 + istate.icon.extent.x1 - + ro_get_vscroll_width(ta->window) - state.xscroll; + state.visible.x0 += istate.icon.extent.x0 + 2 - state.xscroll; + state.visible.y0 = state.visible.y1 + istate.icon.extent.y0 + + ro_get_hscroll_height(ta->window) - state.yscroll; + state.visible.y1 += istate.icon.extent.y1 - 2 - state.yscroll; + + if (ta->flags & TEXTAREA_READONLY) { + state.visible.x0 += 2; + state.visible.x1 -= 4; + state.visible.y0 += 2; + state.visible.y1 -= 4; + } + + /* set our width/height */ + ta->vis_width = state.visible.x1 - state.visible.x0; + ta->vis_height = state.visible.y1 - state.visible.y0; + + /* Set window extent to visible area */ + extent.x0 = 0; + extent.y0 = -ta->vis_height; + extent.x1 = ta->vis_width; + extent.y1 = 0; + + error = xwimp_set_extent(ta->window, &extent); + if (error) { + LOG("xwimp_set_extent: 0x%x: %s", error->errnum, error->errmess); + return false; + } + + /* and open the window */ + error = xwimp_open_window_nested(PTR_WIMP_OPEN(&state), ta->parent, + wimp_CHILD_LINKS_PARENT_VISIBLE_BOTTOM_OR_LEFT + << wimp_CHILD_XORIGIN_SHIFT | + wimp_CHILD_LINKS_PARENT_VISIBLE_TOP_OR_RIGHT + << wimp_CHILD_YORIGIN_SHIFT | + wimp_CHILD_LINKS_PARENT_VISIBLE_BOTTOM_OR_LEFT + << wimp_CHILD_LS_EDGE_SHIFT | + wimp_CHILD_LINKS_PARENT_VISIBLE_BOTTOM_OR_LEFT + << wimp_CHILD_RS_EDGE_SHIFT); + if (error) { + LOG("xwimp_open_window_nested: 0x%x: %s", error->errnum, error->errmess); + return false; + } + + /* reflow the text */ + ro_textarea_reflow(ta, 0); + return true; +} + +/** + * Destroy a text area + * + * \param self Text area to destroy + */ +void ro_textarea_destroy(uintptr_t self) +{ + struct text_area *ta; + os_error *error; + + ta = (struct text_area *)self; + if (!ta || ta->magic != MAGIC) + return; + + error = xwimp_delete_window(ta->window); + if (error) { + LOG("xwimp_delete_window: 0x%x: %s", error->errnum, error->errmess); + } + + ro_gui_wimp_event_finalise(ta->window); + + free(ta->font_family); + free(ta->text); + free(ta); +} + +/** + * Set the text in a text area, discarding any current text + * + * \param self Text area + * \param text UTF-8 text to set text area's contents to + * \return true on success, false on memory exhaustion + */ +bool ro_textarea_set_text(uintptr_t self, const char *text) +{ + struct text_area *ta; + unsigned int len = strlen(text) + 1; + + ta = (struct text_area *)self; + if (!ta || ta->magic != MAGIC) { + LOG("magic doesn't match"); + return true; + } + + 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; + + ro_textarea_reflow(ta, 0); + + return true; +} + +/** + * Extract the text from a text area + * + * \param self 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 ro_textarea_get_text(uintptr_t self, char *buf, unsigned int len) +{ + struct text_area *ta; + + ta = (struct text_area *)self; + if (!ta || ta->magic != MAGIC) { + LOG("magic doesn't match"); + return -1; + } + + 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 self Text area + * \param index 0-based character index to insert at + * \param text UTF-8 text to insert + */ +void ro_textarea_insert_text(uintptr_t self, unsigned int index, + const char *text) +{ + struct text_area *ta; + unsigned int b_len = strlen(text); + size_t b_off, c_len; + + ta = (struct text_area *)self; + if (!ta || ta->magic != MAGIC) { + LOG("magic doesn't match"); + return; + } + + c_len = utf8_length(ta->text); + + /* Find insertion point */ + 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 */ + + 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; + + /** \todo calculate line to reflow from */ + ro_textarea_reflow(ta, 0); +} + +/** + * Replace text in a text area + * + * \param self 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 ro_textarea_replace_text(uintptr_t self, unsigned int start, + unsigned int end, const char *text) +{ + struct text_area *ta; + int b_len = strlen(text); + size_t b_start, b_end, c_len, diff; + + ta = (struct text_area *)self; + if (!ta || ta->magic != MAGIC) { + LOG("magic doesn't match"); + 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 ro_textarea_insert_text(self, 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); + + /** \todo calculate line to reflow from */ + ro_textarea_reflow(ta, 0); +} + +/** + * Set the caret's position + * + * \param self Text area + * \param caret 0-based character index to place caret at + */ +void ro_textarea_set_caret(uintptr_t self, unsigned int caret) +{ + struct text_area *ta; + size_t c_len, b_off; + unsigned int i; + size_t index; + int x; + os_coord os_line_height; + rufl_code code; + os_error *error; + + ta = (struct text_area *)self; + if (!ta || ta->magic != MAGIC) { + LOG("magic doesn't match"); + return; + } + + c_len = utf8_length(ta->text); + + if (caret > c_len) + caret = c_len; + + /* 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++; + + + /* Finally, redraw the WIMP caret */ + index = ro_textarea_get_caret(self); + os_line_height.x = 0; + os_line_height.y = (int)((float)(ta->line_height - ta->line_spacing) * 0.62) + 1; + ro_convert_pixels_to_os_units(&os_line_height, (os_mode)-1); + + for (b_off = 0; index-- > 0; b_off = utf8_next(ta->text, ta->text_len, b_off)) + ; /* do nothing */ + + code = rufl_width(ta->font_family, ta->font_style, ta->font_size, + ta->text + ta->lines[ta->caret_pos.line].b_start, + b_off - ta->lines[ta->caret_pos.line].b_start, &x); + if (code != rufl_OK) { + if (code == rufl_FONT_MANAGER_ERROR) + LOG("rufl_width: 0x%x: %s", rufl_fm_error->errnum, rufl_fm_error->errmess); + else + LOG("rufl_width: 0x%x", code); + return; + } + + error = xwimp_set_caret_position(ta->window, -1, x + MARGIN_LEFT, + -((ta->caret_pos.line + 1) * ta->line_height) - + ta->line_height / 4 + ta->line_spacing, + os_line_height.y, -1); + if (error) { + LOG("xwimp_set_caret_position: 0x%x: %s", error->errnum, error->errmess); + return; + } +} + +/** + * Set the caret's position + * + * \param self Text area + * \param x X position of caret on the screen + * \param y Y position of caret on the screen + */ +void ro_textarea_set_caret_xy(uintptr_t self, int x, int y) +{ + struct text_area *ta; + wimp_window_state state; + size_t b_off, c_off, temp; + int line; + os_coord os_line_height; + rufl_code code; + os_error *error; + + ta = (struct text_area *)self; + if (!ta || ta->magic != MAGIC) { + LOG("magic doesn't match"); + return; + } + + if (ta->flags & TEXTAREA_READONLY) + return; + + os_line_height.x = 0; + os_line_height.y = (int)((float)(ta->line_height - ta->line_spacing) * 0.62) + 1; + ro_convert_pixels_to_os_units(&os_line_height, (os_mode)-1); + + state.w = ta->window; + error = xwimp_get_window_state(&state); + if (error) { + LOG("xwimp_get_window_state: 0x%x: %s", error->errnum, error->errmess); + return; + } + + x = x - (state.visible.x0 - state.xscroll) - MARGIN_LEFT; + y = (state.visible.y1 - state.yscroll) - y; + + line = y / ta->line_height; + + if (line < 0) + line = 0; + if (ta->line_count - 1 < (unsigned)line) + line = ta->line_count - 1; + + code = rufl_x_to_offset(ta->font_family, ta->font_style, + ta->font_size, + ta->text + ta->lines[line].b_start, + ta->lines[line].b_length, + x, &b_off, &x); + if (code != rufl_OK) { + if (code == rufl_FONT_MANAGER_ERROR) + LOG("rufl_x_to_offset: 0x%x: %s", rufl_fm_error->errnum, rufl_fm_error->errmess); + else + LOG("rufl_x_to_offset: 0x%x", code); + return; + } + + 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++; + + ro_textarea_set_caret((uintptr_t)ta, c_off); +} + +/** + * Get the caret's position + * + * \param self Text area + * \return 0-based character index of caret location, or -1 on error + */ +unsigned int ro_textarea_get_caret(uintptr_t self) +{ + struct text_area *ta; + size_t c_off = 0, b_off; + + ta = (struct text_area *)self; + if (!ta || ta->magic != MAGIC) { + LOG("magic doesn't match"); + return -1; + } + + /* 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; +} + +/** \todo Selection handling */ + +/** + * Reflow a text area from the given line onwards + * + * \param ta Text area to reflow + * \param line Line number to begin reflow on + */ +void ro_textarea_reflow(struct text_area *ta, unsigned int line) +{ + rufl_code code; + char *text; + unsigned int len; + size_t b_off; + int x; + char *space; + unsigned int line_count = 0; + os_box extent; + os_error *error; + + /** \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) { + code = rufl_split(ta->font_family, ta->font_style, + ta->font_size, text, len, + ta->vis_width - MARGIN_LEFT - MARGIN_RIGHT, + &b_off, &x); + if (code != rufl_OK) { + if (code == rufl_FONT_MANAGER_ERROR) + LOG("rufl_x_to_offset: 0x%x: %s", rufl_fm_error->errnum, rufl_fm_error->errmess); + else + LOG("rufl_x_to_offset: 0x%x", code); + return; + } + + 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 CR/LF */ + for (space = text; space < text + b_off; space++) { + if (*space == '\r' || *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; + + /* CRLF / LFCR pair */ + if (*space == '\r' && *(space + 1) == '\n') + space++; + else if (*space == '\n' && *(space + 1) == '\r') + space++; + + 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; + + /* and now update extent */ + extent.x0 = 0; + extent.y1 = 0; + extent.x1 = ta->vis_width; + extent.y0 = -ta->line_height * line_count - ta->line_spacing; + + if (extent.y0 > (int)-ta->vis_height) + /* haven't filled window yet */ + return; + + error = xwimp_set_extent(ta->window, &extent); + if (error) { + LOG("xwimp_set_extent: 0x%x: %s", error->errnum, error->errmess); + return; + } + + /* Create vertical scrollbar if we don't already have one */ + if (!ro_gui_wimp_check_window_furniture(ta->window, + wimp_WINDOW_VSCROLL)) { + wimp_window_state state; + wimp_w parent; + bits linkage; + unsigned int vscroll_width; + + /* Save window parent & linkage flags */ + state.w = ta->window; + error = xwimp_get_window_state_and_nesting(&state, + &parent, &linkage); + if (error) { + LOG("xwimp_get_window_state_and_nesting: 0x%x: %s", error->errnum, error->errmess); + return; + } + + /* Now, attempt to create vertical scrollbar */ + ro_gui_wimp_update_window_furniture(ta->window, + wimp_WINDOW_VSCROLL, + wimp_WINDOW_VSCROLL); + + /* Get new window state */ + state.w = ta->window; + error = xwimp_get_window_state(&state); + if (error) { + LOG("xwimp_get_window_state: 0x%x: %s", error->errnum, error->errmess); + return; + } + + /* Get scroll width */ + vscroll_width = ro_get_vscroll_width(NULL); + + /* Shrink width by difference */ + state.visible.x1 -= vscroll_width; + + /* and reopen window */ + error = xwimp_open_window_nested(PTR_WIMP_OPEN(&state), + parent, linkage); + if (error) { + LOG("xwimp_open_window_nested: 0x%x: %s", error->errnum, error->errmess); + return; + } + + /* finally, update visible width */ + ta->vis_width -= vscroll_width; + + /* Now we've done that, we have to reflow the text area */ + ro_textarea_reflow(ta, 0); + } +} + +/** + * Handle mouse clicks in a text area + * + * \param pointer Mouse click state block + * \return true if click handled, false otherwise + */ +bool ro_textarea_mouse_click(wimp_pointer *pointer) +{ + struct text_area *ta; + + ta = (struct text_area *)ro_gui_wimp_event_get_user_data(pointer->w); + + ro_textarea_set_caret_xy((uintptr_t)ta, pointer->pos.x, pointer->pos.y); + return true; +} + +/** + * Handle key presses in a text area + * + * \param key Key pressed state block + * \return true if press handled, false otherwise + */ +bool ro_textarea_key_press(wimp_key *key) +{ + uint32_t c = (uint32_t) key->c; + wimp_key keypress; + struct text_area *ta; + bool redraw = false; + unsigned int c_pos; + + ta = (struct text_area *)ro_gui_wimp_event_get_user_data(key->w); + + if (ta->flags & TEXTAREA_READONLY) + return true; + + if (!(c & IS_WIMP_KEY || + (c <= 0x001f || (0x007f <= c && c <= 0x009f)))) { + /* normal character - insert */ + char utf8[7]; + size_t utf8_len; + + utf8_len = utf8_from_ucs4(c, utf8); + utf8[utf8_len] = '\0'; + + c_pos = ro_textarea_get_caret((uintptr_t)ta); + ro_textarea_insert_text((uintptr_t)ta, c_pos, utf8); + ro_textarea_set_caret((uintptr_t)ta, ++c_pos); + + redraw = true; + } else { + os_error *error; + /** \todo handle command keys */ + switch (c & ~IS_WIMP_KEY) { + case 8: /* Backspace */ + c_pos = ro_textarea_get_caret((uintptr_t)ta); + if (c_pos > 0) { + ro_textarea_replace_text((uintptr_t)ta, + c_pos - 1, c_pos, ""); + ro_textarea_set_caret((uintptr_t)ta, c_pos - 1); + redraw = true; + } + break; + case 21: /* Ctrl + U */ + ro_textarea_set_text((uintptr_t)ta, ""); + ro_textarea_set_caret((uintptr_t)ta, 0); + redraw = true; + break; + case wimp_KEY_DELETE: + c_pos = ro_textarea_get_caret((uintptr_t)ta); + if (os_version < RISCOS5 && c_pos > 0) { + ro_textarea_replace_text((uintptr_t)ta, + c_pos - 1, c_pos, ""); + ro_textarea_set_caret((uintptr_t)ta, c_pos - 1); + } else { + ro_textarea_replace_text((uintptr_t)ta, c_pos, + c_pos + 1, ""); + } + redraw = true; + break; + + case wimp_KEY_LEFT: + c_pos = ro_textarea_get_caret((uintptr_t)ta); + if (c_pos > 0) + ro_textarea_set_caret((uintptr_t)ta, c_pos - 1); + break; + case wimp_KEY_RIGHT: + c_pos = ro_textarea_get_caret((uintptr_t)ta); + ro_textarea_set_caret((uintptr_t)ta, c_pos + 1); + break; + case wimp_KEY_UP: + /** \todo Move caret up a line */ + break; + case wimp_KEY_DOWN: + /** \todo Move caret down a line */ + break; + + case wimp_KEY_HOME: + case wimp_KEY_CONTROL | wimp_KEY_LEFT: + /** \todo line start */ + break; + case wimp_KEY_CONTROL | wimp_KEY_RIGHT: + /** \todo line end */ + break; + case wimp_KEY_CONTROL | wimp_KEY_UP: + ro_textarea_set_caret((uintptr_t)ta, 0); + break; + case wimp_KEY_CONTROL | wimp_KEY_DOWN: + ro_textarea_set_caret((uintptr_t)ta, + utf8_length(ta->text)); + break; + + case wimp_KEY_COPY: + if (os_version < RISCOS5) { + c_pos = ro_textarea_get_caret((uintptr_t)ta); + ro_textarea_replace_text((uintptr_t)ta, c_pos, + c_pos + 1, ""); + } else { + /** \todo line end */ + } + break; + + /** pass on RETURN and ESCAPE to the parent icon */ + case wimp_KEY_RETURN: + if (ta->flags & TEXTAREA_MULTILINE) { + /* Insert newline */ + c_pos = ro_textarea_get_caret((uintptr_t)ta); + ro_textarea_insert_text((uintptr_t)ta, c_pos, + "\n"); + ro_textarea_set_caret((uintptr_t)ta, ++c_pos); + + redraw = true; + + break; + } + /* fall through */ + case wimp_KEY_ESCAPE: + keypress = *key; + keypress.w = ta->parent; + keypress.i = ta->icon; + keypress.index = 0; /* undefined if not in an icon */ + error = xwimp_send_message_to_window(wimp_KEY_PRESSED, + (wimp_message*)&keypress, ta->parent, + ta->icon, 0); + if (error) { + LOG("xwimp_send_message: 0x%x:%s", error->errnum, error->errmess); + } + break; + } + } + + if (redraw) { + wimp_draw update; + + update.w = ta->window; + update.box.x0 = 0; + update.box.y1 = 0; + update.box.x1 = ta->vis_width; + update.box.y0 = -ta->line_height * (ta->line_count + 1); + ro_textarea_redraw_internal(&update, true); + } + + return true; +} + +/** + * Handle WIMP redraw requests for text areas + * + * \param redraw Redraw request block + */ +void ro_textarea_redraw(wimp_draw *redraw) +{ + ro_textarea_redraw_internal(redraw, false); +} + +/** + * Internal textarea redraw routine + * + * \param redraw Redraw/update request block + * \param update True if update, false if full redraw + */ +void ro_textarea_redraw_internal(wimp_draw *redraw, bool update) +{ + struct text_area *ta; + int line; + osbool more; + rufl_code code; + os_error *error; + + ta = (struct text_area *)ro_gui_wimp_event_get_user_data(redraw->w); + + if (update) + error = xwimp_update_window(redraw, &more); + else + error = xwimp_redraw_window(redraw, &more); + if (error) { + LOG("xwimp_redraw_window: 0x%x: %s", error->errnum, error->errmess); + return; + } + + while (more) { + int line0, line1; + int clip_y0, clip_y1; + clip_y0 = (redraw->box.y1-redraw->yscroll) - redraw->clip.y1; + clip_y1 = (redraw->box.y1-redraw->yscroll) - redraw->clip.y0; + + error = xcolourtrans_set_gcol( + (ta->flags & TEXTAREA_READONLY) ? 0xD9D9D900 + : 0xFFFFFF00, + colourtrans_SET_BG_GCOL | colourtrans_USE_ECFS_GCOL, + os_ACTION_OVERWRITE, 0, 0); + if (error) { + LOG("xcolourtrans_set_gcol: 0x%x: %s", error->errnum, error->errmess); + return; + } + + error = xos_clg(); + if (error) { + LOG("xos_clg: 0x%x: %s", error->errnum, error->errmess); + return; + } + + if (!ta->lines) + /* Nothing to redraw */ + return; + + line0 = clip_y0 / ta->line_height - 1; + line1 = clip_y1 / ta->line_height + 1; + + if (line0 < 0) + line0 = 0; + if (line1 < 0) + line1 = 0; + if (ta->line_count - 1 < (unsigned)line0) + line0 = ta->line_count - 1; + if (ta->line_count - 1 < (unsigned)line1) + line1 = ta->line_count - 1; + if (line1 < line0) + line1 = line0; + + for (line = line0; line <= line1; line++) { + if (ta->lines[line].b_length == 0) + continue; + + error = xcolourtrans_set_font_colours(font_CURRENT, + (ta->flags & TEXTAREA_READONLY) ? + 0xD9D9D900 : 0xFFFFFF00, + 0x00000000, 14, 0, 0, 0); + if (error) { + LOG("xcolourtrans_set_font_colours: 0x%x: %s", error->errnum, error->errmess); + return; + } + + code = rufl_paint(ta->font_family, ta->font_style, + ta->font_size, + ta->text + ta->lines[line].b_start, + ta->lines[line].b_length, + redraw->box.x0 - redraw->xscroll + MARGIN_LEFT, + redraw->box.y1 - redraw->yscroll - + ((line + 1) * + ta->line_height - ta->line_spacing), + rufl_BLEND_FONT); + if (code != rufl_OK) { + if (code == rufl_FONT_MANAGER_ERROR) + LOG("rufl_paint: rufl_FONT_MANAGER_ERROR: 0x%x: %s", rufl_fm_error->errnum, rufl_fm_error->errmess); + else + LOG("rufl_paint: 0x%x", code); + } + } + + error = xwimp_get_rectangle(redraw, &more); + if (error) { + LOG("xwimp_get_rectangle: 0x%x: %s", error->errnum, error->errmess); + return; + } + } +} + +/** + * Handle a WIMP open window request + * + * \param open OpenWindow block + */ +void ro_textarea_open(wimp_open *open) +{ + os_error *error; + + error = xwimp_open_window(open); + if (error) { + LOG("xwimp_open_window: 0x%x: %s", error->errnum, error->errmess); + return; + } +} |