summaryrefslogtreecommitdiff
path: root/frontends/riscos/textarea.c
diff options
context:
space:
mode:
Diffstat (limited to 'frontends/riscos/textarea.c')
-rw-r--r--frontends/riscos/textarea.c1160
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;
+ }
+}