netsurf/riscos/textarea.c
John Mark Bell ca96353d9f Merged revisions 7764-7977,7979-8058 via svnmerge from
svn://svn.netsurf-browser.org/branches/paulblokus/textinput

........
  r7769 | paulblokus | 2009-06-11 22:26:16 +0100 (Thu, 11 Jun 2009) | 4 lines
  
  replace global history window with an empty window for future tests
  add the necessary files
  first lines ported
........
  r7771 | paulblokus | 2009-06-11 23:51:46 +0100 (Thu, 11 Jun 2009) | 1 line
  
  more functions
........
  r7772 | paulblokus | 2009-06-12 02:07:36 +0100 (Fri, 12 Jun 2009) | 1 line
  
  redraw working
........
  r7777 | paulblokus | 2009-06-12 11:35:45 +0100 (Fri, 12 Jun 2009) | 3 lines
  
  plotter fix
  make use of the provided clipping rectangle
........
  r7781 | paulblokus | 2009-06-12 16:26:51 +0100 (Fri, 12 Jun 2009) | 3 lines
  
  callbacks for taxtarea to request a [caret]redraw
  basic caret handling drawing
........
  r7782 | paulblokus | 2009-06-12 22:36:50 +0100 (Fri, 12 Jun 2009) | 1 line
  
  single character insertion
........
  r7783 | paulblokus | 2009-06-12 22:41:37 +0100 (Fri, 12 Jun 2009) | 1 line
  
  single character insertion
........
  r7784 | paulblokus | 2009-06-12 23:55:40 +0100 (Fri, 12 Jun 2009) | 3 lines
  
  fixed caret clipping
  arrows, delete and backspace
........
  r7812 | paulblokus | 2009-06-16 14:55:41 +0100 (Tue, 16 Jun 2009) | 1 line
  
  remove bug causing NS hang on \n in textarea
........
  r7816 | paulblokus | 2009-06-16 16:29:48 +0100 (Tue, 16 Jun 2009) | 1 line
  
  Enter, Home, End keys
........
  r7817 | paulblokus | 2009-06-16 16:56:16 +0100 (Tue, 16 Jun 2009) | 1 line
  
  Ctrl + Home/End
........
  r7818 | paulblokus | 2009-06-16 17:16:51 +0100 (Tue, 16 Jun 2009) | 1 line
  
  redraw caret only on caret moves
........
  r7821 | paulblokus | 2009-06-16 20:18:30 +0100 (Tue, 16 Jun 2009) | 1 line
  
  line end/start delete
........
  r7822 | paulblokus | 2009-06-16 23:43:42 +0100 (Tue, 16 Jun 2009) | 1 line
  
  selection drawing + select all
........
  r7823 | paulblokus | 2009-06-17 02:31:07 +0100 (Wed, 17 Jun 2009) | 3 lines
  
  auto scrolling on caret moves
  clear selection
........
  r7845 | paulblokus | 2009-06-18 17:35:03 +0100 (Thu, 18 Jun 2009) | 1 line
  
  page up/down
........
  r7846 | paulblokus | 2009-06-18 17:38:45 +0100 (Thu, 18 Jun 2009) | 1 line
  
   remove unnecessary fix
........
  r7847 | paulblokus | 2009-06-18 18:00:16 +0100 (Thu, 18 Jun 2009) | 1 line
  
  clipping fixes
........
  r7849 | paulblokus | 2009-06-18 18:21:02 +0100 (Thu, 18 Jun 2009) | 1 line
  
  scroll fix
........
  r7850 | paulblokus | 2009-06-18 18:45:13 +0100 (Thu, 18 Jun 2009) | 1 line
  
  simplified redraw request logic
........
  r7855 | paulblokus | 2009-06-18 19:56:24 +0100 (Thu, 18 Jun 2009) | 1 line
  
  front end passing mouse events
........
  r7858 | paulblokus | 2009-06-18 22:18:39 +0100 (Thu, 18 Jun 2009) | 3 lines
  
  drag selection
  bug fixes
........
  r7860 | paulblokus | 2009-06-18 23:32:39 +0100 (Thu, 18 Jun 2009) | 3 lines
  
  take selection into account on keypress of different types
  a few bugs fixed
........
  r7876 | paulblokus | 2009-06-19 13:43:07 +0100 (Fri, 19 Jun 2009) | 3 lines
  
  pango nsfont_split fix
  a few textarea fixes
........
  r7879 | paulblokus | 2009-06-19 17:33:10 +0100 (Fri, 19 Jun 2009) | 4 lines
  
  newline handling seems to work this way
  clear selection on mouse click
  more bug fixes
........
  r7880 | paulblokus | 2009-06-19 18:16:27 +0100 (Fri, 19 Jun 2009) | 3 lines
  
  no caret option
  selection follows drag
........
  r7883 | paulblokus | 2009-06-19 19:08:44 +0100 (Fri, 19 Jun 2009) | 3 lines
  
  o width selection bug fix
  caret at correct side of drag selection
........
  r7918 | paulblokus | 2009-06-22 21:01:28 +0100 (Mon, 22 Jun 2009) | 3 lines
  
  fix caret positioning at line end
  CR removal in input methods
........
  r7919 | paulblokus | 2009-06-22 21:34:39 +0100 (Mon, 22 Jun 2009) | 1 line
  
  fix crash on 0 length text
........
  r7926 | paulblokus | 2009-06-23 09:53:56 +0100 (Tue, 23 Jun 2009) | 3 lines
  
  change LF into spaces for single line widget
  text normalisation at one place
........
  r7931 | paulblokus | 2009-06-23 10:51:25 +0100 (Tue, 23 Jun 2009) | 1 line
  
  cleanup
........
  r7933 | paulblokus | 2009-06-23 11:17:22 +0100 (Tue, 23 Jun 2009) | 1 line
  
  fix selection draw
........
  r7935 | paulblokus | 2009-06-23 11:41:30 +0100 (Tue, 23 Jun 2009) | 1 line
  
  guard readonly
........
  r7942 | paulblokus | 2009-06-24 08:19:39 +0100 (Wed, 24 Jun 2009) | 1 line
  
  applied changes suggested by jmb
........
  r7943 | paulblokus | 2009-06-24 09:04:49 +0100 (Wed, 24 Jun 2009) | 1 line
  
  little fixes
........
  r7945 | paulblokus | 2009-06-24 12:50:14 +0100 (Wed, 24 Jun 2009) | 1 line
  
  correct line length and wrapping
........
  r7947 | paulblokus | 2009-06-24 14:32:36 +0100 (Wed, 24 Jun 2009) | 3 lines
  
  fixed page up/down broken in last commit
  changed logic for caret positioning on soft breaks
........
  r7949 | paulblokus | 2009-06-24 16:31:42 +0100 (Wed, 24 Jun 2009) | 1 line
  
  remove temporary/test code
........
  r7975 | paulblokus | 2009-06-25 16:00:46 +0100 (Thu, 25 Jun 2009) | 1 line
  
  changes suggested by jmb
........
  r7976 | paulblokus | 2009-06-25 16:33:23 +0100 (Thu, 25 Jun 2009) | 1 line
  
  added ro_ prefix to RISC OS textarea code
........

svn path=/trunk/netsurf/; revision=8060
2009-06-27 13:59:25 +00:00

1186 lines
29 KiB
C

/*
* 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 "rufl.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"
#include "utils/log.h"
#include "utils/utf8.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
* \param 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;
char utf8[7];
size_t utf8_len;
bool redraw = false;
unsigned int c_pos;
os_error *error;
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 */
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 {
/** \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 clip_x0, clip_y0, clip_x1, clip_y1;
int line0, line1, 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) {
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_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;
}
}