From 8db70d8e2df01bbd1245e790d3594bb72409956d Mon Sep 17 00:00:00 2001 From: John Mark Bell Date: Mon, 26 Jun 2006 01:14:33 +0000 Subject: UTF-8 capable single/multi-line text area/display field - needs optimisation, completion of outstanding todos. Use this to display SSL certificate issuer and subject information. svn path=/trunk/netsurf/; revision=2647 --- riscos/textarea.c | 1240 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1240 insertions(+) create mode 100644 riscos/textarea.c (limited to 'riscos/textarea.c') diff --git a/riscos/textarea.c b/riscos/textarea.c new file mode 100644 index 000000000..3b861e3a7 --- /dev/null +++ b/riscos/textarea.c @@ -0,0 +1,1240 @@ +/* + * This file is part of NetSurf, http://netsurf.sourceforge.net/ + * Licensed under the GNU General Public License, + * http://www.opensource.org/licenses/gpl-license + * Copyright 2006 John-Mark Bell + */ + +/** \file + * Single/Multi-line UTF-8 text area (implementation) + */ + +#include +#include +#include + +#include "swis.h" + +#include "oslib/colourtrans.h" +#include "oslib/osbyte.h" +#include "oslib/serviceinternational.h" +#include "oslib/wimp.h" +#include "oslib/wimpspriteop.h" + +#include "rufl.h" + +#include "netsurf/riscos/textarea.h" +#include "netsurf/riscos/ucstables.h" +#include "netsurf/riscos/wimp.h" +#include "netsurf/riscos/wimp_event.h" +#include "netsurf/utils/log.h" +#include "netsurf/utils/utf8.h" + +struct line_info { + unsigned int b_start; /**< Byte offset of line start */ + unsigned int b_length; /**< Byte length of line */ +}; + +static 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 */ + + char *font_family; /**< Font family of text */ + unsigned int font_size; /**< Font size (16ths/pt) */ + int line_height; /**< Height of a line, 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 */ +} *text_areas; + +static wimp_window text_area_definition = { + {0, 0, 16, 16}, + 0, + 0, + wimp_TOP, + wimp_WINDOW_NEW_FORMAT, + 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 struct text_area *textarea_from_w(wimp_w self); +static void textarea_reflow(struct text_area *ta, unsigned int line); +static bool textarea_mouse_click(wimp_pointer *pointer); +static bool textarea_key_press(wimp_key *key); +static void textarea_redraw(wimp_draw *redraw); +static void textarea_redraw_internal(wimp_draw *redraw, bool update); +static void 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 + * \return Opaque handle for textarea or 0 on error + */ +uintptr_t textarea_create(wimp_w parent, wimp_i icon, unsigned int flags, + const char *font_family, unsigned int font_size) +{ + struct text_area *ret; + wimp_window_state state; + wimp_icon_state istate; + os_box extent; + os_error *error; + + ret = malloc(sizeof(struct text_area)); + if (!ret) { + LOG(("malloc failed")); + return 0; + } + + 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 */; + + /** \todo Better line height calculation */ + ret->line_height = (int)(((ret->font_size * 1.25) / 16) * 2.0) + 1; + + 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; + } + + state.w = parent; + error = xwimp_get_window_state(&state); + if (error) { + LOG(("xwimp_get_window_state: 0x%x: %s", + error->errnum, error->errmess)); + free(ret->font_family); + free(ret->text); + free(ret); + return 0; + } + + istate.w = parent; + istate.i = icon; + error = xwimp_get_icon_state(&istate); + if (error) { + LOG(("xwimp_get_icon_state: 0x%x: %s", + error->errnum, error->errmess)); + free(ret->font_family); + free(ret->text); + free(ret); + return 0; + } + + state.w = ret->window; + state.visible.x1 = state.visible.x0 + istate.icon.extent.x1 - + ro_get_vscroll_width(ret->window); + state.visible.x0 += istate.icon.extent.x0; + state.visible.y0 = state.visible.y1 + istate.icon.extent.y0 + + ro_get_hscroll_height(ret->window); + state.visible.y1 += istate.icon.extent.y1; + + /* set our width/height */ + ret->vis_width = state.visible.x1 - state.visible.x0; + ret->vis_height = state.visible.y1 - state.visible.y0; + + /* Set window extent to visible area */ + extent.x0 = 0; + extent.y0 = -ret->vis_height; + extent.x1 = ret->vis_width; + extent.y1 = 0; + + error = xwimp_set_extent(ret->window, &extent); + if (error) { + LOG(("xwimp_set_extent: 0x%x: %s", + error->errnum, error->errmess)); + free(ret->font_family); + free(ret->text); + free(ret); + return 0; + } + + /* and open the window */ + error = xwimp_open_window_nested((wimp_open *)&state, 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_TOP_OR_RIGHT + << wimp_CHILD_BS_EDGE_SHIFT | + wimp_CHILD_LINKS_PARENT_VISIBLE_TOP_OR_RIGHT + << wimp_CHILD_RS_EDGE_SHIFT | + wimp_CHILD_LINKS_PARENT_VISIBLE_TOP_OR_RIGHT + << wimp_CHILD_TS_EDGE_SHIFT); + if (error) { + LOG(("xwimp_open_window_nested: 0x%x: %s", + error->errnum, error->errmess)); + free(ret->font_family); + free(ret->text); + free(ret); + return 0; + } + + /* Insert into list */ + ret->prev = NULL; + ret->next = text_areas; + if (text_areas) + text_areas->prev = ret; + text_areas = ret; + + /* and register our event handlers */ + ro_gui_wimp_event_register_mouse_click(ret->window, + textarea_mouse_click); + ro_gui_wimp_event_register_keypress(ret->window, + textarea_key_press); + ro_gui_wimp_event_register_redraw_window(ret->window, + textarea_redraw); + ro_gui_wimp_event_register_open_window(ret->window, + textarea_open); + + return (uintptr_t)ret; +} + +/** + * Destroy a text area + * + * \param self Text area to destroy + */ +void 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); + + /* Remove from list */ + if (ta->next) + ta->next->prev = ta->prev; + + if (ta->prev) + ta->prev->next = ta->next; + else + text_areas = ta->next; + + + 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 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; + + 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 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 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 */ + 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 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; + + 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 textarea_insert_text(self, start, text); + + if (start > end) { + int temp = end; + end = start; + start = temp; + } + + for (b_start = 0; start-- > 0; + b_start = utf8_next(ta->text, ta->text_len, b_start)) + ; /* do nothing */ + + for (b_end = b_start; end-- > 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 */ + textarea_reflow(ta, 0); +} + +/** + * Set the caret's position + * + * \param self Text area + * \param caret 0-based character index to place caret at + */ +void textarea_set_caret(uintptr_t self, unsigned int caret) +{ + struct text_area *ta; + size_t c_len, b_off; + unsigned int i; + + 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; + + /* Finally, 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++; +} + +/** + * Get the caret's position + * + * \param self Text area + * \return 0-based character index of caret location, or -1 on error + */ +unsigned int 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 */ + +/** + * Find a text area in the list + * + * \param self Text area to find + * \return Pointer to text area, or NULL if not found + */ +struct text_area *textarea_from_w(wimp_w self) +{ + struct text_area *ta; + + for (ta = text_areas; ta; ta = ta->next) + if (ta->window == self) + return ta; + + return NULL; +} + +/** + * 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) +{ + 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; + + 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, rufl_WEIGHT_400, + ta->font_size, text, len, ta->vis_width, + &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 + 1); + + 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_outline outline; + wimp_window_state state; + wimp_w parent; + bits linkage; + unsigned int old_w; + + /* 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; + } + + /* Read existing window outline */ + outline.w = ta->window; + error = xwimp_get_window_outline(&outline); + if (error) { + LOG(("xwimp_get_window_outline: 0x%x: %s", + error->errnum, error->errmess)); + return; + } + + /* Save width */ + old_w = outline.outline.x1 - outline.outline.x0; + + /* Now, attempt to create vertical scrollbar */ + ro_gui_wimp_update_window_furniture(ta->window, 0, + wimp_WINDOW_VSCROLL); + + /* Read new window outline */ + outline.w = ta->window; + error = xwimp_get_window_outline(&outline); + if (error) { + LOG(("xwimp_get_window_outline: 0x%x: %s", + error->errnum, error->errmess)); + return; + } + + /* Calculate difference in widths */ + old_w = (outline.outline.x1 - outline.outline.x0) - old_w; + + /* 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; + } + + /* Shrink width by difference */ + state.visible.x1 -= old_w; + + /* and reopen window */ + error = xwimp_open_window_nested((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 -= old_w; + + /* Now we've done that, we have to reflow the text area */ + 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 textarea_mouse_click(wimp_pointer *pointer) +{ + struct text_area *ta; + wimp_window_state state; + size_t b_off, c_off, temp; + int x, y, line; + os_coord os_line_height; + rufl_code code; + os_error *error; + + ta = textarea_from_w(pointer->w); + if (!ta) + return false; + + /** \todo modify for selection model */ + if (ta->flags & TEXTAREA_READONLY) + return true; + + os_line_height.x = 0; + os_line_height.y = (int)((float)ta->line_height * 0.6) + 1; + ro_convert_pixels_to_os_units(&os_line_height, (os_mode)-1); + + state.w = pointer->w; + error = xwimp_get_window_state(&state); + if (error) { + LOG(("xwimp_get_window_state: 0x%x: %s", + error->errnum, error->errmess)); + return false; + } + + x = pointer->pos.x - (state.visible.x0 - state.xscroll); + y = (state.visible.y1 - state.yscroll) - pointer->pos.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, rufl_WEIGHT_400, + 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 false; + } + + 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++; + + textarea_set_caret((uintptr_t)ta, c_off); + + error = xwimp_set_caret_position(state.w, -1, + x, + -((ta->caret_pos.line + 1) * ta->line_height) - + ta->line_height / 4, + os_line_height.y, -1); + if (error) { + LOG(("xwimp_set_caret_position: 0x%x: %s", + error->errnum, error->errmess)); + return false; + } + + return true; +} + +/** + * Handle key presses in a text area + * + * \param key Key pressed state block + * \param true if press handled, false otherwise + */ +bool textarea_key_press(wimp_key *key) +{ + static int *ucstable = NULL; + static int alphabet = 0; + static wchar_t wc = 0; /* buffer for UTF8 alphabet */ + static int shift = 0; + + struct text_area *ta; + wchar_t c = (wchar_t)key->c; + int t_alphabet; + char utf8[7]; + size_t utf8_len; + os_error *error; + + ta = textarea_from_w(key->w); + if (!ta) + return false; + + if (ta->flags & TEXTAREA_READONLY) + return true; + + /* In order to make sensible use of the 0x80->0xFF ranges specified + * in the RISC OS 8bit alphabets, we must do the following: + * + * + Read the currently selected alphabet + * + Acquire a pointer to the UCS conversion table for this alphabet: + * + Try using ServiceInternational 8 to get the table + * + If that fails, use our internal table (see ucstables.c) + * + If the alphabet is not UTF8 and the conversion table exists: + * + Lookup UCS code in the conversion table + * + If code is -1 (i.e. undefined): + * + Use codepoint 0xFFFD instead + * + If the alphabet is UTF8, we must buffer input, thus: + * + If the keycode is < 0x80: + * + Handle it directly + * + If the keycode is a UTF8 sequence start: + * + Initialise the buffer appropriately + * + Otherwise: + * + OR in relevant bits from keycode to buffer + * + If we've received an entire UTF8 character: + * + Handle UCS code + * + Otherwise: + * + Simply handle the keycode directly, as there's no easy way + * of performing the mapping from keycode -> UCS4 codepoint. + */ + error = xosbyte1(osbyte_ALPHABET_NUMBER, 127, 0, &t_alphabet); + if (error) { + LOG(("failed reading alphabet: 0x%x: %s", + error->errnum, error->errmess)); + /* prevent any corruption of ucstable */ + t_alphabet = alphabet; + } + + if (t_alphabet != alphabet) { + osbool unclaimed; + /* Alphabet has changed, so read UCS table location */ + alphabet = t_alphabet; + + error = xserviceinternational_get_ucs_conversion_table( + alphabet, &unclaimed, + (void**)&ucstable); + if (error) { + LOG(("failed reading UCS conversion table: 0x%x: %s", + error->errnum, error->errmess)); + /* try using our own table instead */ + ucstable = ucstable_from_alphabet(alphabet); + } + if (unclaimed) + /* Service wasn't claimed so use our own ucstable */ + ucstable = ucstable_from_alphabet(alphabet); + } + + if (c < 256) { + if (alphabet != 111 /* UTF8 */ && ucstable != NULL) { + /* defined in this alphabet? */ + if (ucstable[c] == -1) + return true; + + /* read UCS4 value out of table */ + c = ucstable[c]; + } + else if (alphabet == 111 /* UTF8 */) { + if ((c & 0x80) == 0x00 || (c & 0xC0) == 0xC0) { + /* UTF8 start sequence */ + if ((c & 0xE0) == 0xC0) { + wc = ((c & 0x1F) << 6); + shift = 1; + return true; + } + else if ((c & 0xF0) == 0xE0) { + wc = ((c & 0x0F) << 12); + shift = 2; + return true; + } + else if ((c & 0xF8) == 0xF0) { + wc = ((c & 0x07) << 18); + shift = 3; + return true; + } + /* These next two have been removed + * from RFC3629, but there's no + * guarantee that RISC OS won't + * generate a UCS4 value outside the + * UTF16 plane, so we handle them + * anyway. */ + else if ((c & 0xFC) == 0xF8) { + wc = ((c & 0x03) << 24); + shift = 4; + } + else if ((c & 0xFE) == 0xFC) { + wc = ((c & 0x01) << 30); + shift = 5; + } + else if (c >= 0x80) { + /* If this ever happens, + * RISC OS' UTF8 keyboard + * drivers are broken */ + LOG(("unexpected UTF8 start" + " byte %x (ignoring)", + c)); + return true; + } + /* Anything else is ASCII, so just + * handle it directly. */ + } + else { + if ((c & 0xC0) != 0x80) { + /* If this ever happens, + * RISC OS' UTF8 keyboard + * drivers are broken */ + LOG(("unexpected keycode: " + "%x (ignoring)", c)); + return true; + } + + /* Continuation of UTF8 character */ + wc |= ((c & 0x3F) << (6 * --shift)); + if (shift > 0) + /* partial character */ + return true; + else + /* got entire character, so + * fetch from buffer and + * handle it */ + c = wc; + } + } + + utf8_len = utf8_from_ucs4(c, utf8); + utf8[utf8_len] = '\0'; + + { + wimp_draw update; + wimp_window_state state; + size_t b_off, index; + int x; + os_coord os_line_height; + rufl_code code; + unsigned int c_pos = + textarea_get_caret((uintptr_t)ta); + textarea_insert_text((uintptr_t)ta, c_pos, utf8); + textarea_set_caret((uintptr_t)ta, ++c_pos); + + index = c_pos; + + os_line_height.x = 0; + os_line_height.y = + (int)((float)ta->line_height * 0.6) + 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, rufl_WEIGHT_400, + 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 true; + } + + 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 true; + } + + error = xwimp_set_caret_position(ta->window, -1, + x, + -((ta->caret_pos.line + 1) * + ta->line_height) - + ta->line_height / 4, + os_line_height.y, -1); + if (error) { + LOG(("xwimp_set_caret_position: 0x%x: %s", + error->errnum, error->errmess)); + return true; + } + + 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); + + textarea_redraw_internal(&update, true); + } + } + + /** \todo handle command keys */ + + return true; +} + +/** + * Handle WIMP redraw requests for text areas + * + * \param redraw Redraw request block + */ +void textarea_redraw(wimp_draw *redraw) +{ + 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 textarea_redraw_internal(wimp_draw *redraw, bool update) +{ + struct text_area *ta; + int clip_x0, clip_y0, clip_x1, clip_y1; + int line0, line1, line; + osbool more; + rufl_code code; + os_error *error; + + ta = textarea_from_w(redraw->w); + if (!ta) + return; + + 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) { + clip_x0 = redraw->clip.x0 - (redraw->box.x0-redraw->xscroll); + clip_y0 = (redraw->box.y1-redraw->yscroll) - redraw->clip.y1; + clip_x1 = redraw->clip.x1 - (redraw->box.x0-redraw->xscroll); + clip_y1 = (redraw->box.y1-redraw->yscroll) - redraw->clip.y0; + + error = xcolourtrans_set_gcol( + (ta->flags & TEXTAREA_READONLY) ? 0xD9D9D900 + : 0xFFFFFF00, + colourtrans_SET_BG | colourtrans_USE_ECFS, + 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, rufl_WEIGHT_400, + ta->font_size, + ta->text + ta->lines[line].b_start, + ta->lines[line].b_length, + redraw->box.x0 - redraw->xscroll, + redraw->box.y1 - redraw->yscroll - + ((line + 1) * + ta->line_height), + 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 textarea_open(wimp_open *open) +{ + struct text_area *ta; + os_error *error; + + ta = textarea_from_w(open->w); + if (!ta) + return; + + error = xwimp_open_window(open); + if (error) { + LOG(("xwimp_open_window: 0x%x: %s", + error->errnum, error->errmess)); + return; + } +} -- cgit v1.2.3