/* * Copyright 2006 John-Mark Bell * * 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 . */ /** \file * Single/Multi-line UTF-8 text area (implementation) */ #include #include #include #include #include #include #include #include #include #include #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; } }