NetSurf
textarea.c
Go to the documentation of this file.
1/*
2 * Copyright 2006 John-Mark Bell <jmb202@ecs.soton.ac.uk>
3 *
4 * This file is part of NetSurf, http://www.netsurf-browser.org/
5 *
6 * NetSurf is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
9 *
10 * NetSurf is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19/** \file
20 * Single/Multi-line UTF-8 text area (implementation)
21 */
22
23#include <inttypes.h>
24#include <stdbool.h>
25#include <stdint.h>
26#include <string.h>
27#include <swis.h>
28#include <oslib/colourtrans.h>
29#include <oslib/osbyte.h>
30#include <oslib/serviceinternational.h>
31#include <oslib/wimp.h>
32#include <oslib/wimpspriteop.h>
33
34#include "utils/log.h"
35#include "utils/utf8.h"
36#include "utils/utils.h"
37
38#include "riscos/gui.h"
39#include "riscos/oslib_pre7.h"
40#include "riscos/textarea.h"
41#include "riscos/ucstables.h"
42#include "riscos/wimp.h"
43#include "riscos/wimp_event.h"
44#include "riscos/wimputils.h"
45
46#define MARGIN_LEFT 8
47#define MARGIN_RIGHT 8
48
49struct line_info {
50 unsigned int b_start; /**< Byte offset of line start */
51 unsigned int b_length; /**< Byte length of line */
52};
53
54struct text_area {
55#define MAGIC (('T'<<24) | ('E'<<16) | ('X'<<8) | 'T')
56 unsigned int magic; /**< Magic word, for sanity */
57
58 unsigned int flags; /**< Textarea flags */
59 unsigned int vis_width; /**< Visible width, in pixels */
60 unsigned int vis_height; /**< Visible height, in pixels */
61 wimp_w window; /**< Window handle */
62
63 char *text; /**< UTF-8 text */
64 unsigned int text_alloc; /**< Size of allocated text */
65 unsigned int text_len; /**< Length of text, in bytes */
66 struct {
67 unsigned int line; /**< Line caret is on */
68 unsigned int char_off; /**< Character index of caret */
70// unsigned int selection_start; /**< Character index of sel start */
71// unsigned int selection_end; /**< Character index of sel end */
72
73 wimp_w parent; /**< Parent window handle */
74 wimp_i icon; /**< Parent icon handle */
75
76 char *font_family; /**< Font family of text */
77 unsigned int font_size; /**< Font size (16ths/pt) */
78 rufl_style font_style; /**< Font style (rufl) */
79 int line_height; /**< Total height of a line, given font size */
80 int line_spacing; /**< Height of line spacing, given font size */
81
82 unsigned int line_count; /**< Count of lines */
83#define LINE_CHUNK_SIZE 256
84 struct line_info *lines; /**< Line info array */
85
86 struct text_area *next; /**< Next text area in list */
87 struct text_area *prev; /**< Prev text area in list */
88};
89
90static wimp_window text_area_definition = {
91 {0, 0, 16, 16},
92 0,
93 0,
94 wimp_TOP,
95 wimp_WINDOW_NEW_FORMAT | wimp_WINDOW_NO_BOUNDS,
96 wimp_COLOUR_BLACK,
97 wimp_COLOUR_LIGHT_GREY,
98 wimp_COLOUR_LIGHT_GREY,
99 wimp_COLOUR_VERY_LIGHT_GREY,
100 wimp_COLOUR_DARK_GREY,
101 wimp_COLOUR_MID_LIGHT_GREY,
102 wimp_COLOUR_CREAM,
103 0,
104 {0, -16384, 16384, 0},
105 wimp_ICON_TEXT | wimp_ICON_HCENTRED | wimp_ICON_VCENTRED,
106 wimp_BUTTON_CLICK << wimp_ICON_BUTTON_TYPE_SHIFT,
107 wimpspriteop_AREA,
108 1,
109 1,
110 {""},
111 0,
112 {}
113};
114
115static void ro_textarea_reflow(struct text_area *ta, unsigned int line);
116static bool ro_textarea_mouse_click(wimp_pointer *pointer);
117static bool ro_textarea_key_press(wimp_key *key);
118static void ro_textarea_redraw(wimp_draw *redraw);
119static void ro_textarea_redraw_internal(wimp_draw *redraw, bool update);
120static void ro_textarea_open(wimp_open *open);
121
122/**
123 * Create a text area
124 *
125 * \param parent Parent window
126 * \param icon Icon in parent window to replace
127 * \param flags Text area flags
128 * \param font_family RUfl font family to use, or NULL for default
129 * \param font_size Font size to use (pt * 16), or 0 for default
130 * \param font_style Font style to use, or 0 for default
131 * \return Opaque handle for textarea or 0 on error
132 */
133uintptr_t ro_textarea_create(wimp_w parent, wimp_i icon, unsigned int flags,
134 const char *font_family, unsigned int font_size,
135 rufl_style font_style)
136{
137 struct text_area *ret;
138 os_error *error;
139
140 ret = malloc(sizeof(struct text_area));
141 if (!ret) {
142 NSLOG(netsurf, INFO, "malloc failed");
143 return 0;
144 }
145
146 ret->parent = parent;
147 ret->icon = icon;
148 ret->magic = MAGIC;
149 ret->flags = flags;
150 ret->text = malloc(64);
151 if (!ret->text) {
152 NSLOG(netsurf, INFO, "malloc failed");
153 free(ret);
154 return 0;
155 }
156 ret->text[0] = '\0';
157 ret->text_alloc = 64;
158 ret->text_len = 1;
159 ret->caret_pos.line = ret->caret_pos.char_off = (unsigned int)-1;
160// ret->selection_start = (unsigned int)-1;
161// ret->selection_end = (unsigned int)-1;
162 ret->font_family = strdup(font_family ? font_family : "Corpus");
163 if (!ret->font_family) {
164 NSLOG(netsurf, INFO, "strdup failed");
165 free(ret->text);
166 free(ret);
167 return 0;
168 }
169 ret->font_size = font_size ? font_size : 192 /* 12pt */;
170 ret->font_style = font_style ? font_style : rufl_WEIGHT_400;
171
172 /** \todo Better line height calculation */
173 ret->line_height = (int)(((ret->font_size * 1.3) / 16) * 2.0) + 1;
174 ret->line_spacing = ret->line_height / 8;
175
176 ret->line_count = 0;
177 ret->lines = 0;
178
180 text_area_definition.title_fg = 0xff;
181 else
182 text_area_definition.title_fg = wimp_COLOUR_BLACK;
183 error = xwimp_create_window(&text_area_definition, &ret->window);
184 if (error) {
185 NSLOG(netsurf, INFO, "xwimp_create_window: 0x%x: %s",
186 error->errnum, error->errmess);
187 free(ret->font_family);
188 free(ret->text);
189 free(ret);
190 return 0;
191 }
192
193 /* set the window dimensions */
194 if (!ro_textarea_update((uintptr_t)ret)) {
195 ro_textarea_destroy((uintptr_t)ret);
196 return 0;
197 }
198
199 /* and register our event handlers */
209
210 return (uintptr_t)ret;
211}
212
213/**
214 * Update the a text area following a change in the parent icon
215 *
216 * \param self Text area to update
217 */
218bool ro_textarea_update(uintptr_t self)
219{
220 struct text_area *ta;
221 wimp_window_state state;
222 wimp_icon_state istate;
223 os_box extent;
224 os_error *error;
225
226 ta = (struct text_area *)self;
227 if (!ta || ta->magic != MAGIC)
228 return false;
229
230 state.w = ta->parent;
231 error = xwimp_get_window_state(&state);
232 if (error) {
233 NSLOG(netsurf, INFO, "xwimp_get_window_state: 0x%x: %s",
234 error->errnum, error->errmess);
235 return false;
236 }
237
238 istate.w = ta->parent;
239 istate.i = ta->icon;
240 error = xwimp_get_icon_state(&istate);
241 if (error) {
242 NSLOG(netsurf, INFO, "xwimp_get_icon_state: 0x%x: %s",
243 error->errnum, error->errmess);
244 return false;
245 }
246
247 state.w = ta->window;
248 state.visible.x1 = state.visible.x0 + istate.icon.extent.x1 -
249 ro_get_vscroll_width(ta->window) - state.xscroll;
250 state.visible.x0 += istate.icon.extent.x0 + 2 - state.xscroll;
251 state.visible.y0 = state.visible.y1 + istate.icon.extent.y0 +
252 ro_get_hscroll_height(ta->window) - state.yscroll;
253 state.visible.y1 += istate.icon.extent.y1 - 2 - state.yscroll;
254
255 if (ta->flags & TEXTAREA_READONLY) {
256 state.visible.x0 += 2;
257 state.visible.x1 -= 4;
258 state.visible.y0 += 2;
259 state.visible.y1 -= 4;
260 }
261
262 /* set our width/height */
263 ta->vis_width = state.visible.x1 - state.visible.x0;
264 ta->vis_height = state.visible.y1 - state.visible.y0;
265
266 /* Set window extent to visible area */
267 extent.x0 = 0;
268 extent.y0 = -ta->vis_height;
269 extent.x1 = ta->vis_width;
270 extent.y1 = 0;
271
272 error = xwimp_set_extent(ta->window, &extent);
273 if (error) {
274 NSLOG(netsurf, INFO, "xwimp_set_extent: 0x%x: %s",
275 error->errnum, error->errmess);
276 return false;
277 }
278
279 /* and open the window */
280 error = xwimp_open_window_nested(PTR_WIMP_OPEN(&state), ta->parent,
281 wimp_CHILD_LINKS_PARENT_VISIBLE_BOTTOM_OR_LEFT
282 << wimp_CHILD_XORIGIN_SHIFT |
283 wimp_CHILD_LINKS_PARENT_VISIBLE_TOP_OR_RIGHT
284 << wimp_CHILD_YORIGIN_SHIFT |
285 wimp_CHILD_LINKS_PARENT_VISIBLE_BOTTOM_OR_LEFT
286 << wimp_CHILD_LS_EDGE_SHIFT |
287 wimp_CHILD_LINKS_PARENT_VISIBLE_BOTTOM_OR_LEFT
288 << wimp_CHILD_RS_EDGE_SHIFT);
289 if (error) {
290 NSLOG(netsurf, INFO, "xwimp_open_window_nested: 0x%x: %s",
291 error->errnum, error->errmess);
292 return false;
293 }
294
295 /* reflow the text */
296 ro_textarea_reflow(ta, 0);
297 return true;
298}
299
300/**
301 * Destroy a text area
302 *
303 * \param self Text area to destroy
304 */
305void ro_textarea_destroy(uintptr_t self)
306{
307 struct text_area *ta;
308 os_error *error;
309
310 ta = (struct text_area *)self;
311 if (!ta || ta->magic != MAGIC)
312 return;
313
314 error = xwimp_delete_window(ta->window);
315 if (error) {
316 NSLOG(netsurf, INFO, "xwimp_delete_window: 0x%x: %s",
317 error->errnum, error->errmess);
318 }
319
321
322 free(ta->font_family);
323 free(ta->text);
324 free(ta);
325}
326
327/**
328 * Set the text in a text area, discarding any current text
329 *
330 * \param self Text area
331 * \param text UTF-8 text to set text area's contents to
332 * \return true on success, false on memory exhaustion
333 */
334bool ro_textarea_set_text(uintptr_t self, const char *text)
335{
336 struct text_area *ta;
337 unsigned int len = strlen(text) + 1;
338
339 ta = (struct text_area *)self;
340 if (!ta || ta->magic != MAGIC) {
341 NSLOG(netsurf, INFO, "magic doesn't match");
342 return true;
343 }
344
345 if (len >= ta->text_alloc) {
346 char *temp = realloc(ta->text, len + 64);
347 if (!temp) {
348 NSLOG(netsurf, INFO, "realloc failed");
349 return false;
350 }
351 ta->text = temp;
352 ta->text_alloc = len+64;
353 }
354
355 memcpy(ta->text, text, len);
356 ta->text_len = len;
357
358 ro_textarea_reflow(ta, 0);
359
360 return true;
361}
362
363/**
364 * Extract the text from a text area
365 *
366 * \param self Text area
367 * \param buf Pointer to buffer to receive data, or NULL
368 * to read length required
369 * \param len Length (bytes) of buffer pointed to by buf, or 0 to read length
370 * \return Length (bytes) written/required or -1 on error
371 */
372int ro_textarea_get_text(uintptr_t self, char *buf, unsigned int len)
373{
374 struct text_area *ta;
375
376 ta = (struct text_area *)self;
377 if (!ta || ta->magic != MAGIC) {
378 NSLOG(netsurf, INFO, "magic doesn't match");
379 return -1;
380 }
381
382 if (buf == NULL && len == 0) {
383 /* want length */
384 return ta->text_len;
385 }
386
387 if (len < ta->text_len) {
388 NSLOG(netsurf, INFO, "buffer too small");
389 return -1;
390 }
391
392 memcpy(buf, ta->text, ta->text_len);
393
394 return ta->text_len;
395}
396
397/**
398 * Insert text into the text area
399 *
400 * \param self Text area
401 * \param index 0-based character index to insert at
402 * \param text UTF-8 text to insert
403 */
404void ro_textarea_insert_text(uintptr_t self, unsigned int index,
405 const char *text)
406{
407 struct text_area *ta;
408 unsigned int b_len = strlen(text);
409 size_t b_off, c_len;
410
411 ta = (struct text_area *)self;
412 if (!ta || ta->magic != MAGIC) {
413 NSLOG(netsurf, INFO, "magic doesn't match");
414 return;
415 }
416
417 c_len = utf8_length(ta->text);
418
419 /* Find insertion point */
420 if (index > c_len)
421 index = c_len;
422
423 for (b_off = 0; index-- > 0;
424 b_off = utf8_next(ta->text, ta->text_len, b_off))
425 ; /* do nothing */
426
427 if (b_len + ta->text_len >= ta->text_alloc) {
428 char *temp = realloc(ta->text, b_len + ta->text_len + 64);
429 if (!temp) {
430 NSLOG(netsurf, INFO, "realloc failed");
431 return;
432 }
433
434 ta->text = temp;
435 ta->text_alloc = b_len + ta->text_len + 64;
436 }
437
438 /* Shift text following up */
439 memmove(ta->text + b_off + b_len, ta->text + b_off,
440 ta->text_len - b_off);
441 /* Insert new text */
442 memcpy(ta->text + b_off, text, b_len);
443
444 ta->text_len += b_len;
445
446 /** \todo calculate line to reflow from */
447 ro_textarea_reflow(ta, 0);
448}
449
450/**
451 * Replace text in a text area
452 *
453 * \param self Text area
454 * \param start Start character index of replaced section (inclusive)
455 * \param end End character index of replaced section (exclusive)
456 * \param text UTF-8 text to insert
457 */
458void ro_textarea_replace_text(uintptr_t self, unsigned int start,
459 unsigned int end, const char *text)
460{
461 struct text_area *ta;
462 int b_len = strlen(text);
463 size_t b_start, b_end, c_len, diff;
464
465 ta = (struct text_area *)self;
466 if (!ta || ta->magic != MAGIC) {
467 NSLOG(netsurf, INFO, "magic doesn't match");
468 return;
469 }
470
471 c_len = utf8_length(ta->text);
472
473 if (start > c_len)
474 start = c_len;
475 if (end > c_len)
476 end = c_len;
477
478 if (start == end)
479 return ro_textarea_insert_text(self, start, text);
480
481 if (start > end) {
482 int temp = end;
483 end = start;
484 start = temp;
485 }
486
487 diff = end - start;
488
489 for (b_start = 0; start-- > 0;
490 b_start = utf8_next(ta->text, ta->text_len, b_start))
491 ; /* do nothing */
492
493 for (b_end = b_start; diff-- > 0;
494 b_end = utf8_next(ta->text, ta->text_len, b_end))
495 ; /* do nothing */
496
497 if (b_len + ta->text_len - (b_end - b_start) >= ta->text_alloc) {
498 char *temp = realloc(ta->text,
499 b_len + ta->text_len - (b_end - b_start) + 64);
500 if (!temp) {
501 NSLOG(netsurf, INFO, "realloc failed");
502 return;
503 }
504
505 ta->text = temp;
506 ta->text_alloc =
507 b_len + ta->text_len - (b_end - b_start) + 64;
508 }
509
510 /* Shift text following to new position */
511 memmove(ta->text + b_start + b_len, ta->text + b_end,
512 ta->text_len - b_end);
513
514 /* Insert new text */
515 memcpy(ta->text + b_start, text, b_len);
516
517 ta->text_len += b_len - (b_end - b_start);
518
519 /** \todo calculate line to reflow from */
520 ro_textarea_reflow(ta, 0);
521}
522
523/**
524 * Set the caret's position
525 *
526 * \param self Text area
527 * \param caret 0-based character index to place caret at
528 */
529void ro_textarea_set_caret(uintptr_t self, unsigned int caret)
530{
531 struct text_area *ta;
532 size_t c_len, b_off;
533 unsigned int i;
534 size_t index;
535 int x;
536 os_coord os_line_height;
537 rufl_code code;
538 os_error *error;
539
540 ta = (struct text_area *)self;
541 if (!ta || ta->magic != MAGIC) {
542 NSLOG(netsurf, INFO, "magic doesn't match");
543 return;
544 }
545
546 c_len = utf8_length(ta->text);
547
548 if (caret > c_len)
549 caret = c_len;
550
551 /* Find byte offset of caret position */
552 for (b_off = 0; caret > 0; caret--)
553 b_off = utf8_next(ta->text, ta->text_len, b_off);
554
555 /* Now find line in which byte offset appears */
556 for (i = 0; i < ta->line_count - 1; i++)
557 if (ta->lines[i + 1].b_start > b_off)
558 break;
559
560 ta->caret_pos.line = i;
561
562 /* Now calculate the char. offset of the caret in this line */
563 for (c_len = 0, ta->caret_pos.char_off = 0;
564 c_len < b_off - ta->lines[i].b_start;
565 c_len = utf8_next(ta->text + ta->lines[i].b_start,
566 ta->lines[i].b_length, c_len))
567 ta->caret_pos.char_off++;
568
569
570 /* Finally, redraw the WIMP caret */
571 index = ro_textarea_get_caret(self);
572 os_line_height.x = 0;
573 os_line_height.y = (int)((float)(ta->line_height - ta->line_spacing) * 0.62) + 1;
574 ro_convert_pixels_to_os_units(&os_line_height, (os_mode)-1);
575
576 for (b_off = 0; index-- > 0; b_off = utf8_next(ta->text, ta->text_len, b_off))
577 ; /* do nothing */
578
579 code = rufl_width(ta->font_family, ta->font_style, ta->font_size,
580 ta->text + ta->lines[ta->caret_pos.line].b_start,
581 b_off - ta->lines[ta->caret_pos.line].b_start, &x);
582 if (code != rufl_OK) {
583 if (code == rufl_FONT_MANAGER_ERROR)
584 NSLOG(netsurf, INFO, "rufl_width: 0x%x: %s",
585 rufl_fm_error->errnum, rufl_fm_error->errmess);
586 else
587 NSLOG(netsurf, INFO, "rufl_width: 0x%x", code);
588 return;
589 }
590
591 error = xwimp_set_caret_position(ta->window, -1, x + MARGIN_LEFT,
592 -((ta->caret_pos.line + 1) * ta->line_height) -
593 ta->line_height / 4 + ta->line_spacing,
594 os_line_height.y, -1);
595 if (error) {
596 NSLOG(netsurf, INFO, "xwimp_set_caret_position: 0x%x: %s",
597 error->errnum, error->errmess);
598 return;
599 }
600}
601
602/**
603 * Set the caret's position
604 *
605 * \param self Text area
606 * \param x X position of caret on the screen
607 * \param y Y position of caret on the screen
608 */
609void ro_textarea_set_caret_xy(uintptr_t self, int x, int y)
610{
611 struct text_area *ta;
612 wimp_window_state state;
613 size_t b_off, c_off, temp;
614 int line;
615 os_coord os_line_height;
616 rufl_code code;
617 os_error *error;
618
619 ta = (struct text_area *)self;
620 if (!ta || ta->magic != MAGIC) {
621 NSLOG(netsurf, INFO, "magic doesn't match");
622 return;
623 }
624
625 if (ta->flags & TEXTAREA_READONLY)
626 return;
627
628 os_line_height.x = 0;
629 os_line_height.y = (int)((float)(ta->line_height - ta->line_spacing) * 0.62) + 1;
630 ro_convert_pixels_to_os_units(&os_line_height, (os_mode)-1);
631
632 state.w = ta->window;
633 error = xwimp_get_window_state(&state);
634 if (error) {
635 NSLOG(netsurf, INFO, "xwimp_get_window_state: 0x%x: %s",
636 error->errnum, error->errmess);
637 return;
638 }
639
640 x = x - (state.visible.x0 - state.xscroll) - MARGIN_LEFT;
641 y = (state.visible.y1 - state.yscroll) - y;
642
643 line = y / ta->line_height;
644
645 if (line < 0)
646 line = 0;
647 if (ta->line_count - 1 < (unsigned)line)
648 line = ta->line_count - 1;
649
650 code = rufl_x_to_offset(ta->font_family, ta->font_style,
651 ta->font_size,
652 ta->text + ta->lines[line].b_start,
653 ta->lines[line].b_length,
654 x, &b_off, &x);
655 if (code != rufl_OK) {
656 if (code == rufl_FONT_MANAGER_ERROR)
657 NSLOG(netsurf, INFO, "rufl_x_to_offset: 0x%x: %s",
658 rufl_fm_error->errnum, rufl_fm_error->errmess);
659 else
660 NSLOG(netsurf, INFO, "rufl_x_to_offset: 0x%x", code);
661 return;
662 }
663
664 for (temp = 0, c_off = 0; temp < b_off + ta->lines[line].b_start;
665 temp = utf8_next(ta->text, ta->text_len, temp))
666 c_off++;
667
668 ro_textarea_set_caret((uintptr_t)ta, c_off);
669}
670
671/**
672 * Get the caret's position
673 *
674 * \param self Text area
675 * \return 0-based character index of caret location, or -1 on error
676 */
677unsigned int ro_textarea_get_caret(uintptr_t self)
678{
679 struct text_area *ta;
680 size_t c_off = 0, b_off;
681
682 ta = (struct text_area *)self;
683 if (!ta || ta->magic != MAGIC) {
684 NSLOG(netsurf, INFO, "magic doesn't match");
685 return -1;
686 }
687
688 /* Calculate character offset of this line's start */
689 for (b_off = 0; b_off < ta->lines[ta->caret_pos.line].b_start;
690 b_off = utf8_next(ta->text, ta->text_len, b_off))
691 c_off++;
692
693 return c_off + ta->caret_pos.char_off;
694}
695
696/** \todo Selection handling */
697
698/**
699 * Reflow a text area from the given line onwards
700 *
701 * \param ta Text area to reflow
702 * \param line Line number to begin reflow on
703 */
704void ro_textarea_reflow(struct text_area *ta, unsigned int line)
705{
706 rufl_code code;
707 char *text;
708 unsigned int len;
709 size_t b_off;
710 int x;
711 char *space;
712 unsigned int line_count = 0;
713 os_box extent;
714 os_error *error;
715
716 /** \todo pay attention to line parameter */
717 /** \todo create horizontal scrollbar if needed */
718
719 ta->line_count = 0;
720
721 if (!ta->lines) {
722 ta->lines =
723 malloc(LINE_CHUNK_SIZE * sizeof(struct line_info));
724 if (!ta->lines) {
725 NSLOG(netsurf, INFO, "malloc failed");
726 return;
727 }
728 }
729
730 if (!(ta->flags & TEXTAREA_MULTILINE)) {
731 /* Single line */
732 ta->lines[line_count].b_start = 0;
733 ta->lines[line_count++].b_length = ta->text_len - 1;
734
736
737 return;
738 }
739
740 for (len = ta->text_len - 1, text = ta->text; len > 0;
741 len -= b_off, text += b_off) {
742 code = rufl_split(ta->font_family, ta->font_style,
743 ta->font_size, text, len,
745 &b_off, &x);
746 if (code != rufl_OK) {
747 if (code == rufl_FONT_MANAGER_ERROR)
748 NSLOG(netsurf, INFO,
749 "rufl_x_to_offset: 0x%x: %s",
750 rufl_fm_error->errnum,
751 rufl_fm_error->errmess);
752 else
753 NSLOG(netsurf, INFO,
754 "rufl_x_to_offset: 0x%x", code);
755 return;
756 }
757
758 if (line_count > 0 && line_count % LINE_CHUNK_SIZE == 0) {
759 struct line_info *temp = realloc(ta->lines,
760 (line_count + LINE_CHUNK_SIZE) *
761 sizeof(struct line_info));
762 if (!temp) {
763 NSLOG(netsurf, INFO, "realloc failed");
764 return;
765 }
766
767 ta->lines = temp;
768 }
769
770 /* handle CR/LF */
771 for (space = text; space < text + b_off; space++) {
772 if (*space == '\r' || *space == '\n')
773 break;
774 }
775
776 if (space != text + b_off) {
777 /* Found newline; use it */
778 ta->lines[line_count].b_start = text - ta->text;
779 ta->lines[line_count++].b_length = space - text;
780
781 /* CRLF / LFCR pair */
782 if (*space == '\r' && *(space + 1) == '\n')
783 space++;
784 else if (*space == '\n' && *(space + 1) == '\r')
785 space++;
786
787 b_off = space + 1 - text;
788
789 if (len - b_off == 0) {
790 /* reached end of input => add last line */
791 ta->lines[line_count].b_start =
792 text + b_off - ta->text;
793 ta->lines[line_count++].b_length = 0;
794 }
795
796 continue;
797 }
798
799 if (len - b_off > 0) {
800 /* find last space (if any) */
801 for (space = text + b_off; space > text; space--)
802 if (*space == ' ')
803 break;
804
805 if (space != text)
806 b_off = space + 1 - text;
807 }
808
809 ta->lines[line_count].b_start = text - ta->text;
810 ta->lines[line_count++].b_length = b_off;
811 }
812
813 ta->line_count = line_count;
814
815 /* and now update extent */
816 extent.x0 = 0;
817 extent.y1 = 0;
818 extent.x1 = ta->vis_width;
819 extent.y0 = -ta->line_height * line_count - ta->line_spacing;
820
821 if (extent.y0 > (int)-ta->vis_height)
822 /* haven't filled window yet */
823 return;
824
825 error = xwimp_set_extent(ta->window, &extent);
826 if (error) {
827 NSLOG(netsurf, INFO, "xwimp_set_extent: 0x%x: %s",
828 error->errnum, error->errmess);
829 return;
830 }
831
832 /* Create vertical scrollbar if we don't already have one */
834 wimp_WINDOW_VSCROLL)) {
835 wimp_window_state state;
836 wimp_w parent;
837 bits linkage;
838 unsigned int vscroll_width;
839
840 /* Save window parent & linkage flags */
841 state.w = ta->window;
842 error = xwimp_get_window_state_and_nesting(&state,
843 &parent, &linkage);
844 if (error) {
845 NSLOG(netsurf, INFO,
846 "xwimp_get_window_state_and_nesting: 0x%x: %s",
847 error->errnum,
848 error->errmess);
849 return;
850 }
851
852 /* Now, attempt to create vertical scrollbar */
854 wimp_WINDOW_VSCROLL,
855 wimp_WINDOW_VSCROLL);
856
857 /* Get new window state */
858 state.w = ta->window;
859 error = xwimp_get_window_state(&state);
860 if (error) {
861 NSLOG(netsurf, INFO,
862 "xwimp_get_window_state: 0x%x: %s",
863 error->errnum,
864 error->errmess);
865 return;
866 }
867
868 /* Get scroll width */
869 vscroll_width = ro_get_vscroll_width(NULL);
870
871 /* Shrink width by difference */
872 state.visible.x1 -= vscroll_width;
873
874 /* and reopen window */
875 error = xwimp_open_window_nested(PTR_WIMP_OPEN(&state),
876 parent, linkage);
877 if (error) {
878 NSLOG(netsurf, INFO,
879 "xwimp_open_window_nested: 0x%x: %s",
880 error->errnum,
881 error->errmess);
882 return;
883 }
884
885 /* finally, update visible width */
886 ta->vis_width -= vscroll_width;
887
888 /* Now we've done that, we have to reflow the text area */
889 ro_textarea_reflow(ta, 0);
890 }
891}
892
893/**
894 * Handle mouse clicks in a text area
895 *
896 * \param pointer Mouse click state block
897 * \return true if click handled, false otherwise
898 */
899bool ro_textarea_mouse_click(wimp_pointer *pointer)
900{
901 struct text_area *ta;
902
903 ta = (struct text_area *)ro_gui_wimp_event_get_user_data(pointer->w);
904
905 ro_textarea_set_caret_xy((uintptr_t)ta, pointer->pos.x, pointer->pos.y);
906 return true;
907}
908
909/**
910 * Handle key presses in a text area
911 *
912 * \param key Key pressed state block
913 * \return true if press handled, false otherwise
914 */
915bool ro_textarea_key_press(wimp_key *key)
916{
917 uint32_t c = (uint32_t) key->c;
918 wimp_key keypress;
919 struct text_area *ta;
920 bool redraw = false;
921 unsigned int c_pos;
922
923 ta = (struct text_area *)ro_gui_wimp_event_get_user_data(key->w);
924
925 if (ta->flags & TEXTAREA_READONLY)
926 return true;
927
928 if (!(c & IS_WIMP_KEY ||
929 (c <= 0x001f || (0x007f <= c && c <= 0x009f)))) {
930 /* normal character - insert */
931 char utf8[7];
932 size_t utf8_len;
933
934 utf8_len = utf8_from_ucs4(c, utf8);
935 utf8[utf8_len] = '\0';
936
937 c_pos = ro_textarea_get_caret((uintptr_t)ta);
938 ro_textarea_insert_text((uintptr_t)ta, c_pos, utf8);
939 ro_textarea_set_caret((uintptr_t)ta, ++c_pos);
940
941 redraw = true;
942 } else {
943 os_error *error;
944 /** \todo handle command keys */
945 switch (c & ~IS_WIMP_KEY) {
946 case 8: /* Backspace */
947 c_pos = ro_textarea_get_caret((uintptr_t)ta);
948 if (c_pos > 0) {
949 ro_textarea_replace_text((uintptr_t)ta,
950 c_pos - 1, c_pos, "");
951 ro_textarea_set_caret((uintptr_t)ta, c_pos - 1);
952 redraw = true;
953 }
954 break;
955 case 21: /* Ctrl + U */
956 ro_textarea_set_text((uintptr_t)ta, "");
957 ro_textarea_set_caret((uintptr_t)ta, 0);
958 redraw = true;
959 break;
960 case wimp_KEY_DELETE:
961 c_pos = ro_textarea_get_caret((uintptr_t)ta);
962 if (os_version < RISCOS5 && c_pos > 0) {
963 ro_textarea_replace_text((uintptr_t)ta,
964 c_pos - 1, c_pos, "");
965 ro_textarea_set_caret((uintptr_t)ta, c_pos - 1);
966 } else {
967 ro_textarea_replace_text((uintptr_t)ta, c_pos,
968 c_pos + 1, "");
969 }
970 redraw = true;
971 break;
972
973 case wimp_KEY_LEFT:
974 c_pos = ro_textarea_get_caret((uintptr_t)ta);
975 if (c_pos > 0)
976 ro_textarea_set_caret((uintptr_t)ta, c_pos - 1);
977 break;
978 case wimp_KEY_RIGHT:
979 c_pos = ro_textarea_get_caret((uintptr_t)ta);
980 ro_textarea_set_caret((uintptr_t)ta, c_pos + 1);
981 break;
982 case wimp_KEY_UP:
983 /** \todo Move caret up a line */
984 break;
985 case wimp_KEY_DOWN:
986 /** \todo Move caret down a line */
987 break;
988
989 case wimp_KEY_HOME:
990 case wimp_KEY_CONTROL | wimp_KEY_LEFT:
991 /** \todo line start */
992 break;
993 case wimp_KEY_CONTROL | wimp_KEY_RIGHT:
994 /** \todo line end */
995 break;
996 case wimp_KEY_CONTROL | wimp_KEY_UP:
997 ro_textarea_set_caret((uintptr_t)ta, 0);
998 break;
999 case wimp_KEY_CONTROL | wimp_KEY_DOWN:
1000 ro_textarea_set_caret((uintptr_t)ta,
1001 utf8_length(ta->text));
1002 break;
1003
1004 case wimp_KEY_COPY:
1005 if (os_version < RISCOS5) {
1006 c_pos = ro_textarea_get_caret((uintptr_t)ta);
1007 ro_textarea_replace_text((uintptr_t)ta, c_pos,
1008 c_pos + 1, "");
1009 } else {
1010 /** \todo line end */
1011 }
1012 break;
1013
1014 /** pass on RETURN and ESCAPE to the parent icon */
1015 case wimp_KEY_RETURN:
1016 if (ta->flags & TEXTAREA_MULTILINE) {
1017 /* Insert newline */
1018 c_pos = ro_textarea_get_caret((uintptr_t)ta);
1019 ro_textarea_insert_text((uintptr_t)ta, c_pos,
1020 "\n");
1021 ro_textarea_set_caret((uintptr_t)ta, ++c_pos);
1022
1023 redraw = true;
1024
1025 break;
1026 }
1028 case wimp_KEY_ESCAPE:
1029 keypress = *key;
1030 keypress.w = ta->parent;
1031 keypress.i = ta->icon;
1032 keypress.index = 0; /* undefined if not in an icon */
1033 error = xwimp_send_message_to_window(wimp_KEY_PRESSED,
1034 (wimp_message*)&keypress, ta->parent,
1035 ta->icon, 0);
1036 if (error) {
1037 NSLOG(netsurf, INFO,
1038 "xwimp_send_message: 0x%x:%s",
1039 error->errnum,
1040 error->errmess);
1041 }
1042 break;
1043 }
1044 }
1045
1046 if (redraw) {
1047 wimp_draw update;
1048
1049 update.w = ta->window;
1050 update.box.x0 = 0;
1051 update.box.y1 = 0;
1052 update.box.x1 = ta->vis_width;
1053 update.box.y0 = -ta->line_height * (ta->line_count + 1);
1054 ro_textarea_redraw_internal(&update, true);
1055 }
1056
1057 return true;
1058}
1059
1060/**
1061 * Handle WIMP redraw requests for text areas
1062 *
1063 * \param redraw Redraw request block
1064 */
1065void ro_textarea_redraw(wimp_draw *redraw)
1066{
1067 ro_textarea_redraw_internal(redraw, false);
1068}
1069
1070/**
1071 * Internal textarea redraw routine
1072 *
1073 * \param redraw Redraw/update request block
1074 * \param update True if update, false if full redraw
1075 */
1076void ro_textarea_redraw_internal(wimp_draw *redraw, bool update)
1077{
1078 struct text_area *ta;
1079 int line;
1080 osbool more;
1081 rufl_code code;
1082 os_error *error;
1083
1084 ta = (struct text_area *)ro_gui_wimp_event_get_user_data(redraw->w);
1085
1086 if (update)
1087 error = xwimp_update_window(redraw, &more);
1088 else
1089 error = xwimp_redraw_window(redraw, &more);
1090 if (error) {
1091 NSLOG(netsurf, INFO, "xwimp_redraw_window: 0x%x: %s",
1092 error->errnum, error->errmess);
1093 return;
1094 }
1095
1096 while (more) {
1097 int line0, line1;
1098 int clip_y0, clip_y1;
1099 clip_y0 = (redraw->box.y1-redraw->yscroll) - redraw->clip.y1;
1100 clip_y1 = (redraw->box.y1-redraw->yscroll) - redraw->clip.y0;
1101
1102 error = xcolourtrans_set_gcol(
1103 (ta->flags & TEXTAREA_READONLY) ? 0xD9D9D900
1104 : 0xFFFFFF00,
1106 os_ACTION_OVERWRITE, 0, 0);
1107 if (error) {
1108 NSLOG(netsurf, INFO,
1109 "xcolourtrans_set_gcol: 0x%x: %s",
1110 error->errnum,
1111 error->errmess);
1112 return;
1113 }
1114
1115 error = xos_clg();
1116 if (error) {
1117 NSLOG(netsurf, INFO, "xos_clg: 0x%x: %s",
1118 error->errnum, error->errmess);
1119 return;
1120 }
1121
1122 if (!ta->lines)
1123 /* Nothing to redraw */
1124 return;
1125
1126 line0 = clip_y0 / ta->line_height - 1;
1127 line1 = clip_y1 / ta->line_height + 1;
1128
1129 if (line0 < 0)
1130 line0 = 0;
1131 if (line1 < 0)
1132 line1 = 0;
1133 if (ta->line_count - 1 < (unsigned)line0)
1134 line0 = ta->line_count - 1;
1135 if (ta->line_count - 1 < (unsigned)line1)
1136 line1 = ta->line_count - 1;
1137 if (line1 < line0)
1138 line1 = line0;
1139
1140 for (line = line0; line <= line1; line++) {
1141 if (ta->lines[line].b_length == 0)
1142 continue;
1143
1144 error = xcolourtrans_set_font_colours(font_CURRENT,
1145 (ta->flags & TEXTAREA_READONLY) ?
1146 0xD9D9D900 : 0xFFFFFF00,
1147 0x00000000, 14, 0, 0, 0);
1148 if (error) {
1149 NSLOG(netsurf, INFO,
1150 "xcolourtrans_set_font_colours: 0x%x: %s",
1151 error->errnum,
1152 error->errmess);
1153 return;
1154 }
1155
1156 code = rufl_paint(ta->font_family, ta->font_style,
1157 ta->font_size,
1158 ta->text + ta->lines[line].b_start,
1159 ta->lines[line].b_length,
1160 redraw->box.x0 - redraw->xscroll + MARGIN_LEFT,
1161 redraw->box.y1 - redraw->yscroll -
1162 ((line + 1) *
1163 ta->line_height - ta->line_spacing),
1164 rufl_BLEND_FONT);
1165 if (code != rufl_OK) {
1166 if (code == rufl_FONT_MANAGER_ERROR)
1167 NSLOG(netsurf, INFO,
1168 "rufl_paint: rufl_FONT_MANAGER_ERROR: 0x%x: %s",
1169 rufl_fm_error->errnum,
1170 rufl_fm_error->errmess);
1171 else
1172 NSLOG(netsurf, INFO,
1173 "rufl_paint: 0x%x", code);
1174 }
1175 }
1176
1177 error = xwimp_get_rectangle(redraw, &more);
1178 if (error) {
1179 NSLOG(netsurf, INFO, "xwimp_get_rectangle: 0x%x: %s",
1180 error->errnum, error->errmess);
1181 return;
1182 }
1183 }
1184}
1185
1186/**
1187 * Handle a WIMP open window request
1188 *
1189 * \param open OpenWindow block
1190 */
1191void ro_textarea_open(wimp_open *open)
1192{
1193 os_error *error;
1194
1195 error = xwimp_open_window(open);
1196 if (error) {
1197 NSLOG(netsurf, INFO, "xwimp_open_window: 0x%x: %s",
1198 error->errnum, error->errmess);
1199 return;
1200 }
1201}
@ TEXTAREA_READONLY
Non-editable.
Definition: textarea.h:43
@ TEXTAREA_MULTILINE
Multiline area.
Definition: textarea.h:42
wimp_w parent
Definition: dialog.c:88
void ro_textarea_replace_text(uintptr_t self, unsigned int start, unsigned int end, const char *text)
Replace text in a text area.
Definition: textarea.c:458
static bool ro_textarea_mouse_click(wimp_pointer *pointer)
Handle mouse clicks in a text area.
Definition: textarea.c:899
bool ro_textarea_update(uintptr_t self)
Update the a text area following a change in the parent icon.
Definition: textarea.c:218
bool ro_textarea_set_text(uintptr_t self, const char *text)
Set the text in a text area, discarding any current text.
Definition: textarea.c:334
static void ro_textarea_reflow(struct text_area *ta, unsigned int line)
Reflow a text area from the given line onwards.
Definition: textarea.c:704
#define MARGIN_RIGHT
Definition: textarea.c:47
static void ro_textarea_open(wimp_open *open)
Handle a WIMP open window request.
Definition: textarea.c:1191
int ro_textarea_get_text(uintptr_t self, char *buf, unsigned int len)
Extract the text from a text area.
Definition: textarea.c:372
static wimp_window text_area_definition
Definition: textarea.c:90
unsigned int ro_textarea_get_caret(uintptr_t self)
Get the caret's position.
Definition: textarea.c:677
#define MARGIN_LEFT
Definition: textarea.c:46
void ro_textarea_set_caret(uintptr_t self, unsigned int caret)
Set the caret's position.
Definition: textarea.c:529
void ro_textarea_destroy(uintptr_t self)
Destroy a text area.
Definition: textarea.c:305
void ro_textarea_set_caret_xy(uintptr_t self, int x, int y)
Set the caret's position.
Definition: textarea.c:609
#define MAGIC
Definition: textarea.c:55
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)
Create a text area.
Definition: textarea.c:133
static bool ro_textarea_key_press(wimp_key *key)
Handle key presses in a text area.
Definition: textarea.c:915
void ro_textarea_insert_text(uintptr_t self, unsigned int index, const char *text)
Insert text into the text area.
Definition: textarea.c:404
static void ro_textarea_redraw_internal(wimp_draw *redraw, bool update)
Internal textarea redraw routine.
Definition: textarea.c:1076
static void ro_textarea_redraw(wimp_draw *redraw)
Handle WIMP redraw requests for text areas.
Definition: textarea.c:1065
#define LINE_CHUNK_SIZE
Definition: textarea.c:83
Single/Multi-line UTF-8 text area (interface)
Netsurf additional integer type formatting macros.
#define NSLOG(catname, level, logmsg, args...)
Definition: log.h:116
Backward compatible defines to make NetSurf buildable with pre-OSLib 7 releases.
#define colourtrans_SET_BG_GCOL
After OSLib 6.90, there was a rename of colourtrans defines in order to avoid namespace clashes: svn ...
Definition: oslib_pre7.h:36
#define colourtrans_USE_ECFS_GCOL
Definition: oslib_pre7.h:39
int os_version
Definition: gui.c:100
#define RISCOS5
Definition: gui.h:26
Interface to utility string handling.
unsigned int b_start
Byte offset of line start.
Definition: textarea.c:50
unsigned int b_length
Byte length of line.
Definition: textarea.c:51
unsigned int text_len
Length of text, in bytes.
Definition: textarea.c:65
struct text_area::@48 caret_pos
unsigned int text_alloc
Size of allocated text.
Definition: textarea.c:64
struct line_info * lines
Line info array.
Definition: textarea.c:84
unsigned int vis_width
Visible width, in pixels.
Definition: textarea.c:59
unsigned int magic
Magic word, for sanity.
Definition: textarea.c:56
unsigned int flags
Textarea flags.
Definition: textarea.c:58
unsigned int line_count
Count of lines.
Definition: textarea.c:82
int line_spacing
Height of line spacing, given font size.
Definition: textarea.c:80
struct text_area * prev
Prev text area in list.
Definition: textarea.c:87
char * text
UTF-8 text.
Definition: textarea.c:63
wimp_i icon
Parent icon handle.
Definition: textarea.c:74
wimp_w parent
Parent window handle.
Definition: textarea.c:73
unsigned int font_size
Font size (16ths/pt)
Definition: textarea.c:77
unsigned int vis_height
Visible height, in pixels.
Definition: textarea.c:60
unsigned int char_off
Character index of caret.
Definition: textarea.c:68
rufl_style font_style
Font style (rufl)
Definition: textarea.c:78
int line_height
Total height of a line, given font size.
Definition: textarea.c:79
wimp_w window
Window handle.
Definition: textarea.c:61
char * font_family
Font family of text.
Definition: textarea.c:76
unsigned int line
Line caret is on.
Definition: textarea.c:67
struct text_area * next
Next text area in list.
Definition: textarea.c:86
UCS conversion tables (interface) This is only used if nothing claims Service_International,...
size_t utf8_from_ucs4(uint32_t c, char *s)
Convert a single UCS4 character into a UTF-8 multibyte sequence.
Definition: utf8.c:56
size_t utf8_next(const char *s, size_t l, size_t o)
Find next legal UTF-8 char in string.
Definition: utf8.c:129
size_t utf8_length(const char *s)
Calculate the length (in characters) of a NULL-terminated UTF-8 string.
Definition: utf8.c:74
UTF-8 manipulation functions (interface).
Interface to a number of general purpose functionality.
#define fallthrough
switch fall through
Definition: utils.h:115
void ro_gui_wimp_update_window_furniture(wimp_w w, wimp_window_flags bic_mask, wimp_window_flags xor_mask)
Sets whether a piece of window furniture is present for a window.
Definition: wimp.c:1016
int ro_get_hscroll_height(wimp_w w)
Gets the horizontal scrollbar height.
Definition: wimp.c:58
bool ro_gui_wimp_check_window_furniture(wimp_w w, wimp_window_flags mask)
Checks whether a piece of window furniture is present for a window.
Definition: wimp.c:1066
void ro_convert_pixels_to_os_units(os_coord *pixels, os_mode mode)
Converts the supplied os_coord from pixels to OS units.
Definition: wimp.c:167
int ro_get_vscroll_width(wimp_w w)
Gets the vertical scrollbar width.
Definition: wimp.c:70
General RISC OS WIMP/OS library functions (interface).
bool ro_gui_wimp_event_register_keypress(wimp_w w, bool(*callback)(wimp_key *key))
Register a function to be called for all keypresses within a particular window.
Definition: wimp_event.c:1461
bool ro_gui_wimp_event_register_redraw_window(wimp_w w, void(*callback)(wimp_draw *redraw))
Register a function to be called for all window redraw operations.
Definition: wimp_event.c:1507
void ro_gui_wimp_event_finalise(wimp_w w)
Free any resources associated with a window.
Definition: wimp_event.c:296
void * ro_gui_wimp_event_get_user_data(wimp_w w)
Gets the user data associated with a window.
Definition: wimp_event.c:486
bool ro_gui_wimp_event_register_mouse_click(wimp_w w, bool(*callback)(wimp_pointer *pointer))
Register a function to be called for all mouse-clicks to icons in a window that don't have registered...
Definition: wimp_event.c:1439
bool ro_gui_wimp_event_set_user_data(wimp_w w, void *user)
Sets the user data associated with a window.
Definition: wimp_event.c:467
bool ro_gui_wimp_event_register_open_window(wimp_w w, void(*callback)(wimp_open *open))
Register a function to be called for all window opening requests.
Definition: wimp_event.c:1477
Automated RISC OS WIMP event handling (interface).
#define IS_WIMP_KEY
Definition: wimp_event.h:37
A collection of grubby utilities for working with OSLib's wimp API.
#define PTR_WIMP_OPEN(pstate)
Definition: wimputils.h:39
static nserror line(const struct redraw_context *ctx, const plot_style_t *style, const struct rect *line)
Plots a line.
Definition: plot.c:579
static nserror text(const struct redraw_context *ctx, const struct plot_font_style *fstyle, int x, int y, const char *text, size_t length)
Text plotting.
Definition: plot.c:978