NetSurf
textarea.c
Go to the documentation of this file.
1/*
2 * Copyright 2006 John-Mark Bell <jmb@netsurf-browser.org>
3 * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net>
4 *
5 * This file is part of NetSurf, http://www.netsurf-browser.org/
6 *
7 * NetSurf is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; version 2 of the License.
10 *
11 * NetSurf is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/**
21 * \file
22 *
23 * Single/Multi-line UTF-8 text area implementation.
24 */
25
26#include <stdint.h>
27#include <string.h>
28
29#include "utils/log.h"
30#include "utils/utf8.h"
31#include "utils/utils.h"
33#include "netsurf/plotters.h"
34#include "netsurf/mouse.h"
35#include "netsurf/keypress.h"
36#include "netsurf/clipboard.h"
37#include "netsurf/layout.h"
38#include "css/utils.h"
39
40#include "desktop/textarea.h"
41#include "desktop/scrollbar.h"
43
44#define CARET_COLOR 0x0000FF
45#define TA_ALLOC_STEP 512
46
49 .stroke_colour = CARET_COLOR,
50 .stroke_width = plot_style_int_to_fixed(1),
51};
52
53struct line_info {
54 unsigned int b_start; /**< Byte offset of line start */
55 unsigned int b_length; /**< Byte length of line */
56 int width; /**< Width in pixels of line */
57};
60 union {
63};
64
66 char *data; /**< UTF-8 text */
67 unsigned int alloc; /**< Size of allocated text */
68 unsigned int len; /**< Length of text, in bytes */
69 unsigned int utf8_len; /**< Length of text, in characters without
70 * trailing NULL */
71};
72
74 unsigned int b_start; /**< Offset to detail's text in undo buffer */
75 unsigned int b_end; /**< End of text (exclusive) */
76 unsigned int b_limit; /**< End of detail space (exclusive) */
77
78 unsigned int b_text_start; /**< Start of textarea text. */
79 unsigned int b_text_end; /**< End of textarea text (exclusive) */
80};
81
83 unsigned int details_alloc; /**< Details allocated for */
84 unsigned int next_detail; /**< Next detail pos */
85 unsigned int last_detail; /**< Last detail used */
86 struct textarea_undo_detail *details; /**< Array of undo details */
87
89};
90
91struct textarea {
92
93 int scroll_x, scroll_y; /**< scroll offsets for the textarea */
94
95 struct scrollbar *bar_x; /**< Horizontal scroll. */
96 struct scrollbar *bar_y; /**< Vertical scroll. */
97
98 unsigned int flags; /**< Textarea flags */
99 int vis_width; /**< Visible width, in pixels */
100 int vis_height; /**< Visible height, in pixels */
101
102 int pad_top; /**< Top padding, inside border, in pixels */
103 int pad_right; /**< Right padding, inside border, in pixels */
104 int pad_bottom; /**< Bottom padding, inside border, in pixels */
105 int pad_left; /**< Left padding, inside border, in pixels */
106
107 int border_width; /**< Border width, in pixels */
108 colour border_col; /**< Border colour */
109
110 int text_y_offset; /**< Vertical dist to 1st line top */
111 int text_y_offset_baseline; /**< Vertical dist to 1st baseline */
112
113 plot_font_style_t fstyle; /**< Text style, inc. textarea bg col */
114 plot_font_style_t sel_fstyle; /**< Selected text style */
115 int line_height; /**< Line height obtained from style */
116
117 struct textarea_utf8 text; /**< Textarea text content */
118#define PASSWORD_REPLACEMENT "\xe2\x80\xa2"
119#define PASSWORD_REPLACEMENT_W (sizeof(PASSWORD_REPLACEMENT) - 1)
120 struct textarea_utf8 password; /**< Text for obscured display */
121
122 struct textarea_utf8 *show; /**< Points at .text or .password */
123
124 struct {
125 int line; /**< Line caret is on */
126 int byte_off; /**< Character index of caret on line */
128
129 int caret_x; /**< cached X coordinate of the caret */
130 int caret_y; /**< cached Y coordinate of the caret */
131
132 int sel_start; /**< Character index of sel start (inclusive) */
133 int sel_end; /**< Character index of sel end (exclusive) */
134
135 int h_extent; /**< Width of content in px */
136 int v_extent; /**< Height of content in px */
137
138 int line_count; /**< Count of lines */
139
140#define LINE_CHUNK_SIZE 32
141 struct line_info *lines; /**< Line info array */
142 unsigned int lines_alloc_size; /**< Number of LINE_CHUNK_SIZEs */
143
144 /** Callback function for messages to client */
146
147 void *data; /**< Client data for callback */
148
149 int drag_start; /**< Byte offset of drag start (in ta->show) */
150 struct textarea_drag drag_info; /**< Drag information */
151
152 struct textarea_undo undo; /**< Undo/redo information */
153};
154
155
156
157/**
158 * Normalises any line endings within the text, replacing CRLF or CR with
159 * LF as necessary. If the textarea is single line, then all linebreaks are
160 * converted into spaces.
161 *
162 * \param ta Text area
163 * \param b_start Byte offset to start at
164 * \param b_len Byte length to check
165 */
166static void textarea_normalise_text(struct textarea *ta,
167 unsigned int b_start, unsigned int b_len)
168{
169 bool multi = (ta->flags & TEXTAREA_MULTILINE) ? true : false;
170 struct textarea_msg msg;
171 unsigned int index;
172
173 /* Remove CR characters. If it's a CRLF pair delete the CR, or replace
174 * CR with LF otherwise.
175 */
176 for (index = 0; index < b_len; index++) {
177 if (ta->text.data[b_start + index] == '\r') {
178 if (b_start + index + 1 <= ta->text.len &&
179 ta->text.data[b_start + index + 1] ==
180 '\n') {
181 ta->text.len--;
182 ta->text.utf8_len--;
183 memmove(ta->text.data + b_start + index,
184 ta->text.data + b_start +
185 index + 1,
186 ta->text.len - b_start - index);
187 }
188 else
189 ta->text.data[b_start + index] = '\n';
190 }
191 /* Replace newlines with spaces if this is a single line
192 * textarea.
193 */
194 if (!multi && (ta->text.data[b_start + index] == '\n'))
195 ta->text.data[b_start + index] = ' ';
196 }
197
198 /* Build text modified message */
199 msg.ta = ta;
201 msg.data.modified.text = ta->text.data;
202 msg.data.modified.len = ta->text.len;
203
204 /* Pass message to client */
205 ta->callback(ta->data, &msg);
206}
207
208
209/**
210 * Reset the selection (no redraw)
211 *
212 * \param ta Text area
213 */
214static inline void textarea_reset_selection(struct textarea *ta)
215{
216 ta->sel_start = ta->sel_end = -1;
217}
218
219
220/**
221 * Get the caret's position
222 *
223 * \param ta Text area
224 * \return 0-based byte offset of caret location, or -1 on error
225 */
226static int textarea_get_caret(struct textarea *ta)
227{
228 /* Ensure caret isn't hidden */
229 if (ta->caret_pos.byte_off < 0)
231
232 /* If the text is a trailing NULL only */
233 if (ta->text.utf8_len == 0)
234 return 0;
235
236 /* If caret beyond text */
237 if (ta->caret_pos.line >= ta->line_count)
238 return ta->show->len - 1;
239
240 /* Byte offset of line, plus byte offset of caret on line */
242}
243
244
245/**
246 * Scrolls a textarea to make the caret visible (doesn't perform a redraw)
247 *
248 * \param ta The text area to be scrolled
249 * \return true if textarea was scrolled false otherwise
250 */
252{
253 int x0, x1, y0, y1; /* area we want caret inside */
254 int x, y; /* caret pos */
255 int xs = ta->scroll_x;
256 int ys = ta->scroll_y;
257 int vis;
258 int scrollbar_width;
259 bool scrolled = false;
260
261 if (ta->caret_pos.byte_off < 0)
262 return false;
263
264 scrollbar_width = (ta->bar_y == NULL) ? 0 : SCROLLBAR_WIDTH;
265 x0 = ta->border_width + ta->pad_left;
266 x1 = ta->vis_width - (ta->border_width + ta->pad_right);
267
268 /* Adjust scroll pos for reduced extents */
269 vis = ta->vis_width - 2 * ta->border_width - scrollbar_width;
270 if (ta->h_extent - xs < vis)
271 xs -= vis - (ta->h_extent - xs);
272
273 /* Get caret pos on screen */
274 x = ta->caret_x - xs;
275
276 /* scroll as required */
277 if (x < x0)
278 xs += (x - x0);
279 else if (x > x1)
280 xs += (x - x1);
281
282 if (ta->bar_x == NULL && ta->scroll_x != 0 &&
284 /* Scrollbar removed, set to zero */
285 ta->scroll_x = 0;
286 scrolled = true;
287
288 } else if (xs != ta->scroll_x) {
289 /* Scrolled, set new pos. */
290 if (ta->bar_x != NULL) {
291 scrollbar_set(ta->bar_x, xs, false);
293 if (xs != ta->scroll_x) {
294 ta->scroll_x = xs;
295 scrolled = true;
296 }
297
298 } else if (!(ta->flags & TEXTAREA_MULTILINE)) {
299 ta->scroll_x = xs;
300 scrolled = true;
301 }
302 }
303
304 /* check and change vertical scroll */
305 if (ta->flags & TEXTAREA_MULTILINE) {
306 scrollbar_width = (ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH;
307 y0 = 0;
308 y1 = ta->vis_height - 2 * ta->border_width -
310
311 /* Adjust scroll pos for reduced extents */
312 vis = ta->vis_height - 2 * ta->border_width - scrollbar_width;
313 if (ta->v_extent - ys < vis)
314 ys -= vis - (ta->v_extent - ys);
315
316 /* Get caret pos on screen */
317 y = ta->caret_y - ys;
318
319 /* scroll as required */
320 if (y < y0)
321 ys += (y - y0);
322 else if (y + ta->line_height > y1)
323 ys += (y + ta->line_height - y1);
324
325 if (ys != ta->scroll_y && ta->bar_y != NULL) {
326 /* Scrolled, set new pos. */
327 scrollbar_set(ta->bar_y, ys, false);
329 if (ys != ta->scroll_y) {
330 ta->scroll_y = ys;
331 scrolled = true;
332 }
333
334 } else if (ta->bar_y == NULL && ta->scroll_y != 0) {
335 /* Scrollbar removed, set to zero */
336 ta->scroll_y = 0;
337 scrolled = true;
338 }
339 }
340
341 return scrolled;
342}
343
344
345/**
346 * Set the caret position
347 *
348 * \param ta Text area
349 * \param caret_b Byte offset to caret
350 * \return true iff caret placement caused a scroll
351 */
352static bool textarea_set_caret_internal(struct textarea *ta, int caret_b)
353{
354 unsigned int b_off;
355 int i;
356 int index;
357 int x, y;
358 int x0, y0, x1, y1;
359 int width, height;
360 struct textarea_msg msg;
361 bool scrolled = false;
362
363 if (caret_b != -1 && caret_b > (signed)(ta->show->len - 1))
364 caret_b = ta->show->len - 1;
365
366 /* Delete the old caret */
367 if (ta->caret_pos.byte_off != -1 &&
369 x0 = ta->caret_x - ta->scroll_x;
370 y0 = ta->caret_y - ta->scroll_y;
371 width = 2;
373
374 msg.ta = ta;
376 msg.data.redraw.x0 = x0;
377 msg.data.redraw.y0 = y0;
378 msg.data.redraw.x1 = x0 + width;
379 msg.data.redraw.y1 = y0 + height;
380
381 /* Ensure it is hidden */
382 ta->caret_pos.byte_off = -1;
383
384 ta->callback(ta->data, &msg);
385 }
386
387 /* check if the caret has to be drawn at all */
388 if (caret_b != -1) {
389 /* Find byte offset of caret position */
390 b_off = caret_b;
391
392 /* Now find line in which byte offset appears */
393 for (i = 0; i < ta->line_count - 1; i++)
394 if (ta->lines[i + 1].b_start > b_off)
395 break;
396
397 /* Set new caret pos */
398 ta->caret_pos.line = i;
399 ta->caret_pos.byte_off = b_off - ta->lines[i].b_start;
400
401 /* Finally, redraw the caret */
402 index = textarea_get_caret(ta);
403
404 /* find byte offset of caret position */
405 b_off = index;
406
408 ta->show->data +
410 b_off - ta->lines[ta->caret_pos.line].b_start,
411 &x);
412
413 x += ta->border_width + ta->pad_left;
414 ta->caret_x = x;
416 ta->caret_y = y;
417
418 scrolled = textarea_scroll_visible(ta);
419
420 if (!scrolled && ta->flags & TEXTAREA_INTERNAL_CARET) {
421 /* Didn't scroll, just moved caret.
422 * Caret is internal caret, redraw it */
423 x -= ta->scroll_x;
424 y -= ta->scroll_y;
425 x0 = max(x - 1, ta->border_width);
426 y0 = max(y, 0);
427 x1 = min(x + 1, ta->vis_width - ta->border_width);
428 y1 = min(y + ta->line_height,
429 ta->vis_height);
430
431 width = x1 - x0;
432 height = y1 - y0;
433
434 if (width > 0 && height > 0) {
435 msg.ta = ta;
437 msg.data.redraw.x0 = x0;
438 msg.data.redraw.y0 = y0;
439 msg.data.redraw.x1 = x0 + width;
440 msg.data.redraw.y1 = y0 + height;
441
442 ta->callback(ta->data, &msg);
443 }
444 } else if (scrolled && !(ta->flags & TEXTAREA_MULTILINE)) {
445 /* Textarea scrolled, whole area needs redraw */
446 /* With multi-line textareas, the scrollbar
447 * callback will have requested redraw. */
448 msg.ta = ta;
450 msg.data.redraw.x0 = 0;
451 msg.data.redraw.y0 = 0;
452 msg.data.redraw.x1 = ta->vis_width;
453 msg.data.redraw.y1 = ta->vis_height;
454
455 ta->callback(ta->data, &msg);
456 }
457
458 if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) {
459 /* Tell client where caret should be placed */
460 struct rect cr = {
461 .x0 = ta->border_width,
462 .y0 = ta->border_width,
463 .x1 = ta->vis_width - ta->border_width -
464 ((ta->bar_y == NULL) ?
465 0 : SCROLLBAR_WIDTH),
466 .y1 = ta->vis_height - ta->border_width -
467 ((ta->bar_x == NULL) ?
468 0 : SCROLLBAR_WIDTH)
469 };
470 msg.ta = ta;
472 msg.data.caret.type = TEXTAREA_CARET_SET_POS;
473 msg.data.caret.pos.x = x - ta->scroll_x;
474 msg.data.caret.pos.y = y - ta->scroll_y;
475 msg.data.caret.pos.height = ta->line_height;
476 msg.data.caret.pos.clip = &cr;
477
478 ta->callback(ta->data, &msg);
479 }
480
481 } else if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) {
482 /* Caret hidden, and client is responsible: tell client */
483 msg.ta = ta;
485 msg.data.caret.type = TEXTAREA_CARET_HIDE;
486
487 ta->callback(ta->data, &msg);
488 }
489
490 return scrolled;
491}
492
493
494/**
495 * Selects a character range in the textarea and redraws it
496 *
497 * \param ta Text area
498 * \param b_start First character (inclusive) byte offset
499 * \param b_end Last character (exclusive) byte offset
500 * \param force_redraw Redraw whether selection changed or not
501 * \return true on success false otherwise
502 */
503static bool textarea_select(struct textarea *ta, int b_start, int b_end,
504 bool force_redraw)
505{
506 int swap;
507 bool pre_existing_selection = (ta->sel_start != -1);
508 struct textarea_msg msg;
509
510 if (b_start == b_end) {
512 return true;
513 }
514
515 /* Ensure start is the beginning of the selection */
516 if (b_start > b_end) {
517 swap = b_start;
518 b_start = b_end;
519 b_end = swap;
520 }
521
522 if (ta->sel_start == b_start && ta->sel_end == b_end &&
523 !force_redraw)
524 return true;
525
526 msg.ta = ta;
530 ((ta->bar_y == NULL) ? 0 : SCROLLBAR_WIDTH);
531
532 if (force_redraw) {
533 /* Asked to redraw everything */
536 ((ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH);
537 } else {
538 /* Try to minimise redraw region */
539 unsigned int b_low, b_high;
540 int line_start = 0, line_end = 0;
541
542 if (!pre_existing_selection) {
543 /* There's a new selection */
544 b_low = b_start;
545 b_high = b_end;
546
547 } else if (ta->sel_start != b_start && ta->sel_end != b_end) {
548 /* Both ends of the selection have moved */
549 b_low = (ta->sel_start < b_start) ?
550 ta->sel_start : b_start;
551 b_high = (ta->sel_end > b_end) ?
552 ta->sel_end : b_end;
553
554 } else if (ta->sel_start != b_start) {
555 /* Selection start changed */
556 if ((signed)ta->sel_start < b_start) {
557 b_low = ta->sel_start;
558 b_high = b_start;
559 } else {
560 b_low = b_start;
561 b_high = ta->sel_start;
562 }
563
564 } else {
565 /* Selection end changed */
566 if ((signed)ta->sel_end < b_end) {
567 b_low = ta->sel_end;
568 b_high = b_end;
569 } else {
570 b_low = b_end;
571 b_high = ta->sel_end;
572 }
573 }
574
575 /* Find redraw start/end lines */
576 for (line_end = 0; line_end < ta->line_count - 1; line_end++)
577 if (ta->lines[line_end + 1].b_start > b_low) {
578 line_start = line_end;
579 break;
580 }
581 for (; line_end < ta->line_count - 1; line_end++)
582 if (ta->lines[line_end + 1].b_start > b_high)
583 break;
584
585 /* Set vertical redraw range */
587 ta->line_height * line_start +
590 ((ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH),
591 ta->line_height * line_end + ta->text_y_offset +
593 }
594
595 ta->callback(ta->data, &msg);
596
597 ta->sel_start = b_start;
598 ta->sel_end = b_end;
599
600 if (!pre_existing_selection && ta->sel_start != -1) {
601 /* Didn't have a selection before, but do now */
603
604 msg.data.selection.have_selection = true;
606
607 ta->callback(ta->data, &msg);
608
609 if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) {
610 /* Caret hidden, and client is responsible */
613
614 ta->callback(ta->data, &msg);
615 }
616 }
617
618 return true;
619}
620
621
622/**
623 * Selects a text fragment, relative to current caret position.
624 *
625 * \param ta Text area
626 * \return True on success, false otherwise
627 */
629{
630 int caret_pos;
631 size_t sel_start, sel_end;
632
633 /* Fragment separators must be suitable for URLs and ordinary text */
634 static const char *sep = " /:.\r\n";
635
636 caret_pos = textarea_get_caret(ta);
637 if (caret_pos < 0) {
638 return false;
639 }
640
641 if (ta->show->len == 0) {
642 return false;
643 }
644
645 /* Compute byte offset of caret position */
646 for (sel_start = (caret_pos > 0 ? caret_pos - 1 : caret_pos);
647 sel_start > 0; sel_start--) {
648 /* Cache the character offset of the last separator */
649 if (strchr(sep, ta->show->data[sel_start]) != NULL) {
650 /* Found start,
651 * add one to start to skip over separator */
652 sel_start++;
653 break;
654 }
655 }
656
657 /* Search for next separator, if any */
658 for (sel_end = caret_pos; sel_end < ta->show->len - 1; sel_end++) {
659 if (strchr(sep, ta->show->data[sel_end]) != NULL) {
660 break;
661 }
662 }
663
664 if (sel_start < sel_end) {
665 textarea_select(ta, sel_start, sel_end, false);
666 return true;
667 }
668
669 return false;
670}
671
672
673/**
674 * Selects paragraph, at current caret position.
675 *
676 * \param ta textarea widget
677 * \return True on success, false otherwise
678 */
680{
681 int caret_pos;
682 size_t sel_start, sel_end;
683
684 caret_pos = textarea_get_caret(ta);
685 if (caret_pos < 0) {
686 return false;
687 }
688
689 /* Work back from caret, looking for a place to start selection */
690 for (sel_start = (caret_pos > 0 ? caret_pos - 1 : caret_pos);
691 sel_start > 0; sel_start--) {
692 /* Set selection start as character after any new line found */
693 if (ta->show->data[sel_start] == '\n') {
694 /* Add one to start to skip over separator */
695 sel_start++;
696 break;
697 }
698 }
699
700 /* Search for end of selection */
701 for (sel_end = caret_pos; sel_end < ta->show->len - 1; sel_end++) {
702 if (ta->show->data[sel_end] == '\n') {
703 break;
704 }
705 }
706
707 if (sel_start < sel_end) {
708 textarea_select(ta, sel_start, sel_end, false);
709 return true;
710 }
711
712 return false;
713}
714
715
716/**
717 * Callback for scrollbar widget.
718 */
719static void textarea_scrollbar_callback(void *client_data,
720 struct scrollbar_msg_data *scrollbar_data)
721{
722 struct textarea *ta = client_data;
723 struct textarea_msg msg;
724
725 switch(scrollbar_data->msg) {
727 /* Scrolled; redraw everything */
730
731 msg.ta = ta;
733 msg.data.redraw.x0 = 0;
734 msg.data.redraw.y0 = 0;
735 msg.data.redraw.x1 = ta->vis_width;
736 msg.data.redraw.y1 = ta->vis_height;
737
738 ta->callback(ta->data, &msg);
739
740 if (!(ta->flags & TEXTAREA_INTERNAL_CARET) &&
741 ta->sel_start < 0 &&
742 ta->caret_pos.byte_off >= 0) {
743 /* Tell client where caret should be placed */
744 int x = ta->caret_x - ta->scroll_x;
745 int y = ta->caret_y - ta->scroll_y;
746 int h = ta->line_height;
747 struct rect cr = {
748 .x0 = ta->border_width,
749 .y0 = ta->border_width,
750 .x1 = ta->vis_width - ta->border_width -
751 ((ta->bar_y == NULL) ?
752 0 : SCROLLBAR_WIDTH),
753 .y1 = ta->vis_height - ta->border_width -
754 ((ta->bar_x == NULL) ?
755 0 : SCROLLBAR_WIDTH)
756 };
757
758 msg.ta = ta;
760
761 if ((x >= cr.x0 && x < cr.x1) &&
762 (y + h >= cr.y0 && y < cr.y1)) {
763 /* Caret inside textarea */
764 msg.data.caret.type = TEXTAREA_CARET_SET_POS;
765 msg.data.caret.pos.x = x;
766 msg.data.caret.pos.y = y;
767 msg.data.caret.pos.height = h;
768 msg.data.caret.pos.clip = &cr;
769 } else {
770 /* Caret fully outside textarea */
771 msg.data.caret.type = TEXTAREA_CARET_HIDE;
772 }
773
774 ta->callback(ta->data, &msg);
775 }
776 break;
777
780 ta->drag_info.data.scrollbar = scrollbar_data->scrollbar;
781
782 msg.ta = ta;
784 msg.data.drag = ta->drag_info.type;
785
786 /* Tell client we're handling a drag */
787 ta->callback(ta->data, &msg);
788 break;
789
792
793 msg.ta = ta;
795 msg.data.drag = ta->drag_info.type;
796
797 /* Tell client we finished handling the drag */
798 ta->callback(ta->data, &msg);
799 break;
800
801 default:
802 break;
803 }
804}
805
806
807
808/**
809 * Reflow a single line textarea
810 *
811 * \param ta Textarea widget to reflow
812 * \param b_off 0-based byte offset in ta->show's text to start of modification
813 * \param r Modified/reduced to area where redraw is required
814 * \return true on success false otherwise
815 */
816static bool textarea_reflow_singleline(struct textarea *ta, size_t b_off,
817 struct rect *r)
818{
819 int x;
820 int shift;
821 int retained_width = 0;
822 int w = ta->vis_width - 2 * ta->border_width -
823 ta->pad_left - ta->pad_right;
824
825 assert(!(ta->flags & TEXTAREA_MULTILINE));
826
827 if (ta->lines == NULL) {
828 ta->lines =
829 malloc(LINE_CHUNK_SIZE * sizeof(struct line_info));
830 if (ta->lines == NULL) {
831 NSLOG(netsurf, INFO, "malloc failed");
832 return false;
833 }
835
836 ta->lines[0].b_start = 0;
837 ta->lines[0].b_length = 0;
838 ta->lines[0].width = 0;
839 }
840
841 if (ta->flags & TEXTAREA_PASSWORD &&
842 ta->text.utf8_len != ta->password.utf8_len) {
843 /* Make password-obscured text have same number of
844 * characters as underlying text */
845 unsigned int c, b;
846 int diff = ta->text.utf8_len - ta->password.utf8_len;
847 unsigned int rep_len = PASSWORD_REPLACEMENT_W;
848 unsigned int b_len = ta->text.utf8_len * rep_len + 1;
849
850 if (diff > 0 && b_len > ta->password.alloc) {
851 /* Increase password alloaction */
852 char *temp = realloc(ta->password.data,
853 b_len + TA_ALLOC_STEP);
854 if (temp == NULL) {
855 NSLOG(netsurf, INFO, "realloc failed");
856 return false;
857 }
858
859 ta->password.data = temp;
860 ta->password.alloc = b_len + TA_ALLOC_STEP;
861 }
862
863 b_len--;
864 for (c = 0; c < b_len; c += rep_len) {
865 for (b = 0; b < rep_len; b++) {
866 ta->password.data[c + b] =
868 }
869 }
870 ta->password.data[b_len] = '\0';
871 ta->password.len = b_len + 1;
872 ta->password.utf8_len = ta->text.utf8_len;
873 }
874
875 /* Measure new width */
876 guit->layout->width(&ta->fstyle, ta->show->data,
877 ta->show->len - 1, &x);
878
879 /* Get width of retained text */
880 if (b_off != ta->lines[0].b_length) {
881 guit->layout->width(&ta->fstyle, ta->show->data,
882 b_off, &retained_width);
883 } else {
884 retained_width = ta->lines[0].width;
885 }
886
887 shift = ta->border_width + ta->pad_left - ta->scroll_x;
888
889 r->x0 = max(r->x0, retained_width + shift - 1);
890 r->x1 = min(r->x1, max(x, ta->lines[0].width) + shift + 1);
891
892 ta->lines[0].b_start = 0;
893 ta->lines[0].b_length = ta->show->len - 1;
894 ta->lines[0].width = x;
895
896 if (x > w)
897 w = x;
898
899 ta->h_extent = w + ta->pad_left + ta->pad_right;
900 ta->line_count = 1;
901
902 return true;
903}
904
905
906
907/**
908 * Reflow a multiline textarea from the given line onwards
909 *
910 * \param ta Textarea to reflow
911 * \param b_start 0-based byte offset in ta->text to start of modification
912 * \param b_length Byte length of change in textarea text
913 * \param r Modified/reduced to area where redraw is required
914 * \return true on success false otherwise
915 */
916static bool textarea_reflow_multiline(struct textarea *ta,
917 const size_t b_start, const int b_length, struct rect *r)
918{
919 char *text;
920 unsigned int len;
921 unsigned int start;
922 size_t b_off;
923 size_t b_start_line_end;
924 int x;
925 char *space, *para_end;
926 unsigned int line; /* line count */
927 unsigned int scroll_lines;
928 int avail_width;
929 int h_extent; /* horizontal extent */
930 int v_extent; /* vertical extent */
931 bool restart = false;
932 bool skip_line = false;
933
934 assert(ta->flags & TEXTAREA_MULTILINE);
935
936 if (ta->lines == NULL) {
937 ta->lines = calloc(sizeof(struct line_info), LINE_CHUNK_SIZE);
938 if (ta->lines == NULL) {
939 NSLOG(netsurf, INFO,
940 "Failed to allocate memory for textarea lines");
941 return false;
942 }
944 }
945
946 /* Get line of start of changes */
947 for (start = 0; (signed) start < ta->line_count - 1; start++)
948 if (ta->lines[start + 1].b_start > b_start)
949 break;
950
951 /* Find max number of lines before vertical scrollbar is required */
952 scroll_lines = (ta->vis_height - 2 * ta->border_width -
953 ta->pad_top - ta->pad_bottom) /
954 ta->line_height;
955
956 /* Start on the line before the first change, in case the
957 * modification on this line alters what fits on the line
958 * above. For example adding a space or deleting text on
959 * a soft-wrapped line */
960 if (start != 0)
961 start--;
962
963 /* Record original end pos of start line */
964 b_start_line_end = ta->lines[start].b_start + ta->lines[start].b_length;
965
966 /* During layout we may decide we need to restart again from the
967 * textarea's first line. */
968 do {
969 /* If a vertical scrollbar has been added or removed, we need
970 * to restart from the first line in the textarea. */
971 if (restart)
972 start = 0;
973
974 /* Set current line to the starting line */
975 line = start;
976
977 /* Find available width */
978 avail_width = ta->vis_width - 2 * ta->border_width -
979 ta->pad_left - ta->pad_right;
980 if (avail_width < 0)
981 avail_width = 0;
982 h_extent = avail_width;
983
984 /* Set up length of remaining text and offset to current point
985 * in text. Initially set it to start of textarea */
986 len = ta->text.len - 1;
987 text = ta->text.data;
988
989 if (line != 0) {
990 /* Not starting at the beginning of the textarea, so
991 * jump forward, and make sure the horizontal extents
992 * accommodate the width of the skipped lines. */
993 unsigned int i;
994 len -= ta->lines[line].b_start;
995 text += ta->lines[line].b_start;
996
997 for (i = 0; i < line; i++) {
998 if (ta->lines[i].width > h_extent) {
999 h_extent = ta->lines[i].width;
1000 }
1001 }
1002 }
1003
1004 if (ta->text.len == 1) {
1005 /* Handle empty textarea */
1006 assert(ta->text.data[0] == '\0');
1007 ta->lines[line].b_start = 0;
1008 ta->lines[line].b_length = 0;
1009 ta->lines[line++].width = 0;
1010 ta->line_count = 1;
1011 }
1012
1013 restart = false;
1014 for (; len > 0; len -= b_off, text += b_off) {
1015 /* Find end of paragraph */
1016 for (para_end = text; para_end < text + len;
1017 para_end++) {
1018 if (*para_end == '\n')
1019 break;
1020 }
1021
1022 /* Wrap current line in paragraph */
1023 guit->layout->split(&ta->fstyle, text, para_end - text,
1024 avail_width, &b_off, &x);
1025 /* b_off now marks space, or end of paragraph */
1026
1027 if (x > h_extent) {
1028 h_extent = x;
1029 }
1030 if (x > avail_width && ta->bar_x == NULL) {
1031 /* We need to insert a horizontal scrollbar */
1032 int w = ta->vis_width - 2 * ta->border_width;
1033 if (scrollbar_create(true, w, w, w,
1035 &(ta->bar_x)) != NSERROR_OK) {
1036 return false;
1037 }
1038 if (ta->bar_y != NULL)
1040 ta->bar_y);
1042
1043 /* Find new max visible lines */
1044 scroll_lines = (ta->vis_height -
1045 2 * ta->border_width -
1046 ta->pad_top - ta->pad_bottom) /
1047 ta->line_height;
1048 }
1049
1050 /* Ensure enough storage for lines data */
1051 if (line > ta->lines_alloc_size - 2) {
1052 /* Up to two lines my be added in a pass */
1053 struct line_info *temp = realloc(ta->lines,
1054 (line + 2 + LINE_CHUNK_SIZE) *
1055 sizeof(struct line_info));
1056 if (temp == NULL) {
1057 NSLOG(netsurf, INFO, "realloc failed");
1058 return false;
1059 }
1060
1061 ta->lines = temp;
1062 ta->lines_alloc_size = line + 2 +
1064 }
1065
1066 if (para_end == text + b_off && *para_end == '\n') {
1067 /* Not found any spaces to wrap at, and we
1068 * have a newline char */
1069 ta->lines[line].b_start = text - ta->text.data;
1070 ta->lines[line].b_length = para_end - text;
1071 ta->lines[line++].width = x;
1072
1073 /* Jump newline */
1074 b_off++;
1075
1076 if (len - b_off == 0) {
1077 /* reached end of input;
1078 * add last line */
1079 ta->lines[line].b_start = text +
1080 b_off - ta->text.data;
1081 ta->lines[line].b_length = 0;
1082 ta->lines[line++].width = x;
1083 }
1084
1085 if (line > scroll_lines && ta->bar_y == NULL)
1086 break;
1087
1088 continue;
1089
1090 } else if (len - b_off > 0) {
1091 /* soft wrapped, find last space (if any) */
1092 for (space = text + b_off; space > text;
1093 space--) {
1094 if (*space == ' ')
1095 break;
1096 }
1097
1098 if (space != text)
1099 b_off = space + 1 - text;
1100 }
1101
1102 ta->lines[line].b_start = text - ta->text.data;
1103 ta->lines[line].b_length = b_off;
1104 ta->lines[line++].width = x;
1105
1106 if (line > scroll_lines && ta->bar_y == NULL)
1107 break;
1108 }
1109
1110 if (h_extent <= avail_width && ta->bar_x != NULL) {
1111 /* We need to remove a horizontal scrollbar */
1113 ta->bar_x = NULL;
1115
1116 /* Find new max visible lines */
1117 scroll_lines = (ta->vis_height - 2 * ta->border_width -
1118 ta->pad_top - ta->pad_bottom) /
1119 ta->line_height;
1120 }
1121
1122 if (line > scroll_lines && ta->bar_y == NULL) {
1123 /* Add vertical scrollbar */
1124 int h = ta->vis_height - 2 * ta->border_width;
1125 if (scrollbar_create(false, h, h, h,
1127 &(ta->bar_y)) != NSERROR_OK) {
1128 return false;
1129 }
1130 if (ta->bar_x != NULL)
1132 ta->bar_y);
1134 restart = true;
1135
1136 } else if (line <= scroll_lines && ta->bar_y != NULL) {
1137 /* Remove vertical scrollbar */
1139 ta->bar_y = NULL;
1141 restart = true;
1142 }
1143 } while (restart);
1144
1145 h_extent += ta->pad_left + ta->pad_right -
1146 (ta->bar_y != NULL ? SCROLLBAR_WIDTH : 0);
1147 v_extent = line * ta->line_height + ta->pad_top +
1148 ta->pad_bottom -
1149 (ta->bar_x != NULL ? SCROLLBAR_WIDTH : 0);
1150
1151 if (ta->bar_x != NULL) {
1152 /* Set horizontal scrollbar extents */
1153 int w = ta->vis_width - 2 * ta->border_width -
1154 (ta->bar_y != NULL ? SCROLLBAR_WIDTH : 0);
1155 scrollbar_set_extents(ta->bar_x, w, w, h_extent);
1156 }
1157
1158 if (ta->bar_y != NULL) {
1159 /* Set vertical scrollbar extents */
1160 int h = ta->vis_height - 2 * ta->border_width;
1162 h - (ta->bar_x != NULL ? SCROLLBAR_WIDTH : 0),
1163 v_extent);
1164 }
1165
1166 ta->h_extent = h_extent;
1167 ta->v_extent = v_extent;
1168 ta->line_count = line;
1169
1170 /* Update start line end byte pos, if it's increased */
1171 if (ta->lines[start].b_start + ta->lines[start].b_length >
1172 b_start_line_end) {
1173 b_start_line_end = ta->lines[start].b_start +
1174 ta->lines[start].b_length;
1175 }
1176
1177 /* Don't need to redraw above changes, so update redraw request rect */
1178 if (b_start_line_end < b_start && restart == false) {
1179 /* Start line is unchanged */
1180 start++;
1181 skip_line = true;
1182 }
1183
1184 r->y0 = max(r->y0, (signed)(ta->line_height * start +
1185 ta->text_y_offset - ta->scroll_y));
1186
1187 /* Reduce redraw region to single line if possible */
1188 if ((skip_line || start == 0) &&
1189 ta->lines[start].b_start + ta->lines[start].b_length >=
1190 b_start + b_length) {
1191 size_t b_line_end = ta->lines[start].b_start +
1192 ta->lines[start].b_length;
1193 text = ta->text.data + b_line_end;
1194 if (*text == '\0' || *text == '\n') {
1195 r->y1 = min(r->y1, (signed)
1196 (ta->line_height * (start + 1) +
1197 ta->text_y_offset - ta->scroll_y));
1198 if (b_start > ta->lines[start].b_start &&
1199 b_start <= b_line_end) {
1200 /* Remove unchanged text at start of line
1201 * from redraw region */
1202 int retained_width = 0;
1203 size_t retain_end = b_start -
1204 ta->lines[start].b_start;
1205 text = ta->text.data + ta->lines[start].b_start;
1206
1207 guit->layout->width(&ta->fstyle, text,
1208 retain_end, &retained_width);
1209
1210 r->x0 = max(r->x0,
1211 retained_width +
1212 ta->border_width +
1213 ta->pad_left -
1214 ta->scroll_x - 1);
1215 }
1216 }
1217 }
1218
1219 return true;
1220}
1221
1222
1223/**
1224 * get byte offset from the beginning of the text for some coordinates
1225 *
1226 * \param ta textarea widget
1227 * \param x X coordinate
1228 * \param y Y coordinate
1229 * \param visible true iff (x,y) is wrt visiable area, false for global
1230 * \return byte offset
1231 */
1232static size_t textarea_get_b_off_xy(struct textarea *ta, int x, int y,
1233 bool visible)
1234{
1235 size_t bpos; /* Byte position in utf8 string */
1236 int line;
1237
1238 if (!ta->line_count) {
1239 return 0;
1240 }
1241
1242 x = x - ta->border_width - ta->pad_left +
1243 (visible ? ta->scroll_x : 0);
1244 y = y - ta->border_width - ta->pad_top +
1245 (visible ? ta->scroll_y : 0);
1246
1247 if (x < 0)
1248 x = 0;
1249
1250 line = y / ta->line_height;
1251
1252 if (ta->line_count - 1 < line)
1253 line = ta->line_count - 1;
1254 if (line < 0)
1255 line = 0;
1256
1257 /* Get byte position */
1258 guit->layout->position(&ta->fstyle,
1259 ta->show->data + ta->lines[line].b_start,
1260 ta->lines[line].b_length, x, &bpos, &x);
1261
1262
1263 /* If the calculated byte offset corresponds with the number of bytes
1264 * in the line, and the line has been soft-wrapped, then ensure the
1265 * caret offset is before the trailing space character, rather than
1266 * after it. Otherwise, the caret will be placed at the start of the
1267 * following line, which is undesirable.
1268 */
1269 if (ta->flags & TEXTAREA_MULTILINE && ta->lines[line].b_length > 1 &&
1270 bpos == (unsigned)ta->lines[line].b_length &&
1271 ta->show->data[ta->lines[line].b_start +
1272 ta->lines[line].b_length - 1] == ' ')
1273 bpos--;
1274
1275 /* Set the return byte offset */
1276 return bpos + ta->lines[line].b_start;
1277}
1278
1279
1280/**
1281 * Set the caret's position
1282 *
1283 * \param ta textarea widget
1284 * \param x X coordinate
1285 * \param y Y coordinate
1286 * \param visible true iff (x,y) is wrt visiable area, false for global
1287 * \return true iff caret placement caused a scroll
1288 */
1289static bool textarea_set_caret_xy(struct textarea *ta, int x, int y,
1290 bool visible)
1291{
1292 unsigned int b_off = textarea_get_b_off_xy(ta, x, y, visible);
1293
1294 return textarea_set_caret_internal(ta, b_off);
1295}
1296
1297
1298/**
1299 * Insert text into the textarea
1300 *
1301 * \param ta Textarea widget
1302 * \param text UTF-8 text to insert
1303 * \param b_off 0-based byte offset in ta->show's text to insert at
1304 * \param b_len Byte length of UTF-8 text
1305 * \param byte_delta Updated to change in byte count in textarea (ta->show)
1306 * \param r Modified/reduced to area where redraw is required
1307 * \return false on memory exhaustion, true otherwise
1308 *
1309 * Note: b_off must be for ta->show
1310 */
1311static bool textarea_insert_text(struct textarea *ta, const char *text,
1312 size_t b_off, size_t b_len, int *byte_delta, struct rect *r)
1313{
1314 int char_delta;
1315 const size_t show_b_off = b_off;
1316
1317 if (ta->flags & TEXTAREA_READONLY)
1318 return true;
1319
1320 /* If password field, we must convert from ta->password byte offset to
1321 * ta->text byte offset */
1322 if (ta->flags & TEXTAREA_PASSWORD) {
1323 size_t c_off;
1324
1325 c_off = utf8_bounded_length(ta->password.data, b_off);
1327 ta->text.len - 1, c_off);
1328 }
1329
1330 /* Find insertion point */
1331 if (b_off > ta->text.len - 1)
1332 b_off = ta->text.len - 1;
1333
1334 if (b_len + ta->text.len >= ta->text.alloc) {
1335 char *temp = realloc(ta->text.data, b_len + ta->text.len +
1337 if (temp == NULL) {
1338 NSLOG(netsurf, INFO, "realloc failed");
1339 return false;
1340 }
1341
1342 ta->text.data = temp;
1343 ta->text.alloc = b_len + ta->text.len + TA_ALLOC_STEP;
1344 }
1345
1346 /* Shift text following up */
1347 memmove(ta->text.data + b_off + b_len, ta->text.data + b_off,
1348 ta->text.len - b_off);
1349 /* Insert new text */
1350 memcpy(ta->text.data + b_off, text, b_len);
1351
1352 char_delta = ta->text.utf8_len;
1353 *byte_delta = ta->text.len;
1354
1355 /* Update lengths, and normalise */
1356 ta->text.len += b_len;
1357 ta->text.utf8_len += utf8_bounded_length(text, b_len);
1358 textarea_normalise_text(ta, b_off, b_len);
1359
1360 /* Get byte delta */
1361 if (ta->flags & TEXTAREA_PASSWORD) {
1362 char_delta = ta->text.utf8_len - char_delta;
1363 *byte_delta = char_delta * PASSWORD_REPLACEMENT_W;
1364 } else {
1365 *byte_delta = ta->text.len - *byte_delta;
1366 }
1367
1368 /* See to reflow */
1369 if (ta->flags & TEXTAREA_MULTILINE) {
1370 if (!textarea_reflow_multiline(ta, show_b_off, b_len, r))
1371 return false;
1372 } else {
1373 if (!textarea_reflow_singleline(ta, show_b_off, r))
1374 return false;
1375 }
1376
1377 return true;
1378}
1379
1380
1381/**
1382 * Helper for replace_text function converts character offset to byte offset
1383 *
1384 * text utf8 textarea text object
1385 * start start character offset
1386 * end end character offset
1387 * b_start updated to byte offset of start in text
1388 * b_end updated to byte offset of end in text
1389 */
1391 unsigned int start, unsigned int end,
1392 size_t *b_start, size_t *b_end)
1393{
1394 size_t diff = end - start;
1395 /* find byte offset of replace start */
1396 for (*b_start = 0; start-- > 0;
1397 *b_start = utf8_next(text->data, text->len - 1,
1398 *b_start))
1399 ; /* do nothing */
1400
1401 /* find byte length of replaced text */
1402 for (*b_end = *b_start; diff-- > 0;
1403 *b_end = utf8_next(text->data, text->len - 1, *b_end))
1404 ; /* do nothing */
1405}
1406
1407
1408/**
1409 * Perform actual text replacment in a textarea
1410 *
1411 * \param ta Textarea widget
1412 * \param b_start Start byte index of replaced section (inclusive)
1413 * \param b_end End byte index of replaced section (exclusive)
1414 * \param rep Replacement UTF-8 text to insert
1415 * \param rep_len Byte length of replacement UTF-8 text
1416 * \param add_to_clipboard True iff replaced text to be added to clipboard
1417 * \param byte_delta Updated to change in byte count in textarea (ta->show)
1418 * \param r Updated to area where redraw is required
1419 * \return false on memory exhaustion, true otherwise
1420 *
1421 * Note, b_start and b_end must be the byte offsets in ta->show, so in the
1422 * password textarea case, they are for ta->password.
1423 */
1424static bool textarea_replace_text_internal(struct textarea *ta, size_t b_start,
1425 size_t b_end, const char *rep, size_t rep_len,
1426 bool add_to_clipboard, int *byte_delta, struct rect *r)
1427{
1428 int char_delta;
1429 const size_t show_b_off = b_start;
1430 *byte_delta = 0;
1431
1432 if ((ta->flags & TEXTAREA_READONLY) &&
1433 !(rep == NULL && rep_len == 0 && add_to_clipboard))
1434 /* Can't edit if readonly, and we're not just copying */
1435 return true;
1436
1437 if (b_start > ta->show->len - 1)
1438 b_start = ta->show->len - 1;
1439 if (b_end > ta->show->len - 1)
1440 b_end = ta->show->len - 1;
1441
1442 /* Set up initial redraw rect */
1443 r->x0 = ta->border_width;
1444 r->y0 = ta->border_width;
1445 r->x1 = ta->vis_width - ta->border_width -
1446 ((ta->bar_y == NULL) ? 0 : SCROLLBAR_WIDTH);
1447 r->y1 = ta->vis_height - ta->border_width -
1448 ((ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH);
1449
1450 /* Early exit if just inserting */
1451 if (b_start == b_end && rep != NULL)
1452 return textarea_insert_text(ta, rep, b_start, rep_len,
1453 byte_delta, r);
1454
1455 if (b_start > b_end)
1456 return false;
1457
1458 /* Place CUTs on clipboard */
1459 if (add_to_clipboard) {
1460 guit->clipboard->set(ta->show->data + b_start, b_end - b_start,
1461 NULL, 0);
1462 }
1463
1464 if (rep == NULL) {
1465 /* No replacement text */
1466 return true;
1467 }
1468
1469 /* If password field, we must convert from ta->password byte offset to
1470 * ta->text byte offset */
1471 if (ta->flags & TEXTAREA_PASSWORD) {
1472 size_t c_start, c_end;
1473
1474 c_start = utf8_bounded_length(ta->password.data, b_start);
1475 c_end = c_start;
1477 b_end - b_start);
1478 textarea_char_to_byte_offset(&ta->text, c_start, c_end,
1479 &b_start, &b_end);
1480 }
1481
1482 /* Ensure textarea's text buffer is large enough */
1483 if (rep_len + ta->text.len - (b_end - b_start) >= ta->text.alloc) {
1484 char *temp = realloc(ta->text.data,
1485 rep_len + ta->text.len - (b_end - b_start) +
1487 if (temp == NULL) {
1488 NSLOG(netsurf, INFO, "realloc failed");
1489 return false;
1490 }
1491
1492 ta->text.data = temp;
1493 ta->text.alloc = rep_len + ta->text.len - (b_end - b_start) +
1495 }
1496
1497 /* Shift text following to new position */
1498 memmove(ta->text.data + b_start + rep_len, ta->text.data + b_end,
1499 ta->text.len - b_end);
1500
1501 /* Insert new text */
1502 memcpy(ta->text.data + b_start, rep, rep_len);
1503
1504 char_delta = ta->text.utf8_len;
1505 *byte_delta = ta->text.len;
1506
1507 /* Update lengths, and normalise */
1508 ta->text.len += (int)rep_len - (b_end - b_start);
1509 ta->text.utf8_len = utf8_length(ta->text.data);
1510 textarea_normalise_text(ta, b_start, rep_len);
1511
1512 /* Get byte delta */
1513 if (ta->flags & TEXTAREA_PASSWORD) {
1514 char_delta = ta->text.utf8_len - char_delta;
1515 *byte_delta = char_delta * PASSWORD_REPLACEMENT_W;
1516 } else {
1517 *byte_delta = ta->text.len - *byte_delta;
1518 }
1519
1520 /* See to reflow */
1521 if (ta->flags & TEXTAREA_MULTILINE) {
1522 if (!textarea_reflow_multiline(ta, b_start, *byte_delta, r))
1523 return false;
1524 } else {
1525 if (!textarea_reflow_singleline(ta, show_b_off, r))
1526 return false;
1527 }
1528
1529 return true;
1530}
1531
1532
1533/**
1534 * Update undo buffer by adding any text to be replaced, and allocating
1535 * space as appropriate.
1536 *
1537 * \param ta Textarea widget
1538 * \param b_start Start byte index of replaced section (inclusive)
1539 * \param b_end End byte index of replaced section (exclusive)
1540 * \param rep_len Byte length of replacement UTF-8 text
1541 * \return false on memory exhaustion, true otherwise
1542 */
1544 size_t b_start, size_t b_end, size_t rep_len)
1545{
1546 struct textarea_undo *undo;
1547 size_t b_offset;
1548 unsigned int len = b_end - b_start;
1549
1550 undo = &ta->undo;
1551
1552 if (undo->next_detail == 0)
1553 b_offset = 0;
1554 else
1555 b_offset = undo->details[undo->next_detail - 1].b_start +
1556 undo->details[undo->next_detail - 1].b_limit;
1557
1558 len = len > rep_len ? len : rep_len;
1559
1560 if (b_offset + len >= undo->text.alloc) {
1561 /* Need more memory for undo buffer */
1562 char *temp = realloc(undo->text.data,
1563 b_offset + len + TA_ALLOC_STEP);
1564 if (temp == NULL) {
1565 NSLOG(netsurf, INFO, "realloc failed");
1566 return false;
1567 }
1568
1569 undo->text.data = temp;
1570 undo->text.alloc = b_offset + len + TA_ALLOC_STEP;
1571 }
1572
1573 if (undo->next_detail >= undo->details_alloc) {
1574 /* Need more memory for undo details */
1575 struct textarea_undo_detail *temp = realloc(undo->details,
1576 (undo->next_detail + 128) *
1577 sizeof(struct textarea_undo_detail));
1578 if (temp == NULL) {
1579 NSLOG(netsurf, INFO, "realloc failed");
1580 return false;
1581 }
1582
1583 undo->details = temp;
1584 undo->details_alloc = undo->next_detail + 128;
1585 }
1586
1587 /* Put text into buffer */
1588 memcpy(undo->text.data + b_offset, ta->text.data + b_start,
1589 b_end - b_start);
1590
1591 /* Update next_detail */
1592 undo->details[undo->next_detail].b_start = b_offset;
1593 undo->details[undo->next_detail].b_end = b_offset + b_end - b_start;
1594 undo->details[undo->next_detail].b_limit = len;
1595
1596 undo->details[undo->next_detail].b_text_start = b_start;
1597
1598 return true;
1599}
1600
1601
1602/**
1603 * Replace text in a textarea, updating undo buffer.
1604 *
1605 * \param ta Textarea widget
1606 * \param b_start Start byte index of replaced section (inclusive)
1607 * \param b_end End byte index of replaced section (exclusive)
1608 * \param rep Replacement UTF-8 text to insert
1609 * \param rep_len Byte length of replacement UTF-8 text
1610 * \param add_to_clipboard True if replaced text to be added to clipboard
1611 * \param byte_delta Updated to change in byte count in textarea (ta->show)
1612 * \param r Updated to area where redraw is required
1613 * \return false on memory exhaustion, true otherwise
1614 *
1615 * Note, b_start and b_end must be the byte offsets in ta->show, so in the
1616 * password textarea case, they are for ta->password.
1617 */
1618static bool textarea_replace_text(struct textarea *ta, size_t b_start,
1619 size_t b_end, const char *rep, size_t rep_len,
1620 bool add_to_clipboard, int *byte_delta, struct rect *r)
1621{
1622 if (!(b_start != b_end && rep == NULL && add_to_clipboard) &&
1623 !(ta->flags & TEXTAREA_PASSWORD)) {
1624 /* Not just copying to clipboard, and not a password field;
1625 * Sort out undo buffer. */
1627 rep_len) == false)
1628 return false;
1629 }
1630
1631 /* Replace the text in the textarea, and reflow it */
1632 if (textarea_replace_text_internal(ta, b_start, b_end, rep, rep_len,
1633 add_to_clipboard, byte_delta, r) == false) {
1634 return false;
1635 }
1636
1637 if (!(b_start != b_end && rep == NULL && add_to_clipboard) &&
1638 !(ta->flags & TEXTAREA_PASSWORD)) {
1639 /* Not just copying to clipboard, and not a password field;
1640 * Update UNDO buffer */
1642 b_end + *byte_delta;
1643 ta->undo.last_detail = ta->undo.next_detail;
1644 ta->undo.next_detail++;
1645 }
1646
1647 return true;
1648}
1649
1650
1651/**
1652 * Undo or redo previous change.
1653 *
1654 * \param ta Textarea widget
1655 * \param forward Iff true, redo, else undo
1656 * \param caret Updated to new caret pos in textarea (ta->show)
1657 * \param r Updated to area where redraw is required
1658 * \return false if nothing to undo/redo, true otherwise
1659 */
1660static bool textarea_undo(struct textarea *ta, bool forward,
1661 unsigned int *caret, struct rect *r)
1662{
1663 unsigned int detail_n;
1664 struct textarea_undo_detail *detail;
1665 char *temp = NULL;
1666 unsigned int b_len;
1667 unsigned int b_text_len;
1668 int byte_delta;
1669
1671 /* No undo/redo for password or readonly fields */
1672 return false;
1673
1674 if (forward) {
1675 /* Redo */
1676 if (ta->undo.next_detail > ta->undo.last_detail)
1677 /* Nothing to redo */
1678 return false;
1679
1680 detail_n = ta->undo.next_detail;
1681 } else {
1682 /* Undo */
1683 if (ta->undo.next_detail == 0)
1684 /* Nothing to undo */
1685 return false;
1686
1687 detail_n = ta->undo.next_detail - 1;
1688 }
1689
1690 detail = &(ta->undo.details[detail_n]);
1691
1692 b_len = detail->b_end - detail->b_start;
1693 b_text_len = detail->b_text_end - detail->b_text_start;
1694
1695 /* Take copy of any current textarea text that undo/redo will remove */
1696 if (detail->b_text_end > detail->b_text_start) {
1697 temp = malloc(b_text_len);
1698 if (temp == NULL) {
1699 /* TODO */
1700 return false;
1701 }
1702
1703 memcpy(temp, ta->text.data + detail->b_text_start, b_text_len);
1704 }
1705
1706 /* Replace textarea text with undo buffer text */
1708 detail->b_text_start, detail->b_text_end,
1709 ta->undo.text.data + detail->b_start, b_len,
1710 false, &byte_delta, r);
1711
1712 /* Update undo buffer for redo */
1713 if (temp != NULL)
1714 memcpy(ta->undo.text.data + detail->b_start, temp, b_text_len);
1715
1716 detail->b_text_end = detail->b_text_start + b_len;
1717 detail->b_end = detail->b_start + b_text_len;
1718
1719 *caret = detail->b_text_end;
1720
1721 if (forward) {
1722 /* Redo */
1723 ta->undo.next_detail++;
1724 } else {
1725 /* Undo */
1726 ta->undo.next_detail--;
1727 }
1728
1729 free(temp);
1730
1731 return true;
1732}
1733
1734
1735/**
1736 * Handles the end of a drag operation
1737 *
1738 * \param ta Text area
1739 * \param mouse the mouse state at drag end moment
1740 * \param x X coordinate
1741 * \param y Y coordinate
1742 * \return true if drag end was handled false otherwise
1743 */
1744static bool textarea_drag_end(struct textarea *ta, browser_mouse_state mouse,
1745 int x, int y)
1746{
1747 size_t b_end;
1748 struct textarea_msg msg;
1749
1750 assert(ta->drag_info.type != TEXTAREA_DRAG_NONE);
1751
1752 switch (ta->drag_info.type) {
1754 if (ta->drag_info.data.scrollbar == ta->bar_x) {
1755 x -= ta->border_width;
1756 y -= ta->vis_height - ta->border_width -
1758 } else {
1759 x -= ta->vis_width - ta->border_width -
1761 y -= ta->border_width;
1762 }
1764 mouse, x, y);
1765 assert(ta->drag_info.type == TEXTAREA_DRAG_NONE);
1766
1767 /* Return, since drag end already reported to textarea client */
1768 return true;
1769
1772
1773 b_end = textarea_get_b_off_xy(ta, x, y, true);
1774
1775 if (!textarea_select(ta, ta->drag_start, b_end, false))
1776 return false;
1777
1778 break;
1779
1780 default:
1781 return false;
1782 }
1783
1784 /* Report drag end to client, if not already reported */
1785 assert(ta->drag_info.type == TEXTAREA_DRAG_NONE);
1786
1787 msg.ta = ta;
1789 msg.data.drag = ta->drag_info.type;
1790
1791 ta->callback(ta->data, &msg);
1792
1793 return true;
1794}
1795
1796
1797/**
1798 * Setup text offsets after height / border / padding change
1799 *
1800 * \param ta Textarea widget
1801 */
1803{
1804 int text_y_offset, text_y_offset_baseline;
1805
1806 ta->line_height = FIXTOINT(FMUL(FLTTOFIX(1.3), FDIV(FMUL(
1807 nscss_screen_dpi, FDIV(INTTOFIX(ta->fstyle.size),
1808 INTTOFIX(PLOT_STYLE_SCALE))), F_72)));
1809
1810 text_y_offset = text_y_offset_baseline = ta->border_width;
1811 if (ta->flags & TEXTAREA_MULTILINE) {
1812 /* Multiline textarea */
1813 text_y_offset += ta->pad_top;
1814 text_y_offset_baseline +=
1815 (ta->line_height * 3 + 2) / 4 + ta->pad_top;
1816 } else {
1817 /* Single line text area; text is vertically centered */
1818 int vis_height = ta->vis_height - 2 * ta->border_width;
1819 text_y_offset += (vis_height - ta->line_height + 1) / 2;
1820 text_y_offset_baseline +=
1821 (2 * vis_height + ta->line_height + 2) / 4;
1822 }
1823
1824 ta->text_y_offset = text_y_offset;
1825 ta->text_y_offset_baseline = text_y_offset_baseline;
1826}
1827
1828
1829/**
1830 * Set font styles up for a textarea.
1831 *
1832 * \param[in] ta Textarea to update.
1833 * \param[in] fstyle Font style to set in textarea.
1834 * \param[in] selected_text Textarea selected text colour.
1835 * \param[in] selected_bg Textarea selection background colour.
1836 */
1838 struct textarea *ta,
1839 const plot_font_style_t *fstyle,
1840 colour selected_text,
1841 colour selected_bg)
1842{
1843 ta->fstyle = *fstyle;
1844
1845 ta->sel_fstyle = *fstyle;
1846 ta->sel_fstyle.foreground = selected_text;
1847 ta->sel_fstyle.background = selected_bg;
1848}
1849
1850
1851/* exported interface, documented in textarea.h */
1853 const textarea_setup *setup,
1855{
1856 struct textarea *ret;
1857 struct rect r = {0, 0, 0, 0};
1858
1859 /* Sanity check flags */
1860 assert(!(flags & TEXTAREA_MULTILINE &&
1861 flags & TEXTAREA_PASSWORD));
1862
1863 if (callback == NULL) {
1864 NSLOG(netsurf, INFO, "no callback provided");
1865 return NULL;
1866 }
1867
1868 ret = malloc(sizeof(struct textarea));
1869 if (ret == NULL) {
1870 NSLOG(netsurf, INFO, "malloc failed");
1871 return NULL;
1872 }
1873
1874 ret->callback = callback;
1875 ret->data = data;
1876
1877 ret->flags = flags;
1878 ret->vis_width = setup->width;
1879 ret->vis_height = setup->height;
1880
1881 ret->pad_top = setup->pad_top;
1882 ret->pad_right = setup->pad_right;
1883 ret->pad_bottom = setup->pad_bottom;
1884 ret->pad_left = setup->pad_left;
1885
1886 ret->border_width = setup->border_width;
1887 ret->border_col = setup->border_col;
1888
1890 &setup->text,
1891 setup->selected_text,
1892 setup->selected_bg);
1893
1894 ret->scroll_x = 0;
1895 ret->scroll_y = 0;
1896 ret->bar_x = NULL;
1897 ret->bar_y = NULL;
1898 ret->h_extent = setup->width;
1899 ret->v_extent = setup->height;
1900 ret->drag_start = 0;
1902
1903 ret->undo.details_alloc = 0;
1904 ret->undo.next_detail = 0;
1905 ret->undo.last_detail = 0;
1906 ret->undo.details = NULL;
1907
1908 ret->undo.text.data = NULL;
1909 ret->undo.text.alloc = 0;
1910 ret->undo.text.len = 0;
1911 ret->undo.text.utf8_len = 0;
1912
1913
1914 ret->text.data = malloc(TA_ALLOC_STEP);
1915 if (ret->text.data == NULL) {
1916 NSLOG(netsurf, INFO, "malloc failed");
1917 free(ret);
1918 return NULL;
1919 }
1920 ret->text.data[0] = '\0';
1921 ret->text.alloc = TA_ALLOC_STEP;
1922 ret->text.len = 1;
1923 ret->text.utf8_len = 0;
1924
1925 if (flags & TEXTAREA_PASSWORD) {
1926 ret->password.data = malloc(TA_ALLOC_STEP);
1927 if (ret->password.data == NULL) {
1928 NSLOG(netsurf, INFO, "malloc failed");
1929 free(ret->text.data);
1930 free(ret);
1931 return NULL;
1932 }
1933 ret->password.data[0] = '\0';
1935 ret->password.len = 1;
1936 ret->password.utf8_len = 0;
1937
1938 ret->show = &ret->password;
1939
1940 } else {
1941 ret->password.data = NULL;
1942 ret->password.alloc = 0;
1943 ret->password.len = 0;
1944 ret->password.utf8_len = 0;
1945
1946 ret->show = &ret->text;
1947 }
1948
1949 ret->line_height = FIXTOINT(FMUL(FLTTOFIX(1.3), FDIV(FMUL(
1950 nscss_screen_dpi, FDIV(INTTOFIX(setup->text.size),
1951 INTTOFIX(PLOT_STYLE_SCALE))), F_72)));
1952
1953 ret->caret_pos.line = ret->caret_pos.byte_off = -1;
1954 ret->caret_x = 0;
1955 ret->caret_y = 0;
1956 ret->sel_start = -1;
1957 ret->sel_end = -1;
1958
1959 ret->line_count = 0;
1960 ret->lines = NULL;
1961 ret->lines_alloc_size = 0;
1962
1964
1965 if (flags & TEXTAREA_MULTILINE)
1966 textarea_reflow_multiline(ret, 0, 0, &r);
1967 else
1968 textarea_reflow_singleline(ret, 0, &r);
1969
1970 return ret;
1971}
1972
1973
1974/* exported interface, documented in textarea.h */
1976{
1977 if (ta->bar_x)
1979 if (ta->bar_y)
1981
1982 if (ta->flags & TEXTAREA_PASSWORD)
1983 free(ta->password.data);
1984
1985 free(ta->undo.text.data);
1986 free(ta->undo.details);
1987
1988 free(ta->text.data);
1989 free(ta->lines);
1990 free(ta);
1991}
1992
1993
1994/* exported interface, documented in textarea.h */
1995bool textarea_set_text(struct textarea *ta, const char *text)
1996{
1997 unsigned int len = strlen(text) + 1;
1998 struct rect r = {0, 0, 0, 0};
1999
2000 if (len >= ta->text.alloc) {
2001 char *temp = realloc(ta->text.data, len + TA_ALLOC_STEP);
2002 if (temp == NULL) {
2003 NSLOG(netsurf, INFO, "realloc failed");
2004 return false;
2005 }
2006 ta->text.data = temp;
2007 ta->text.alloc = len + TA_ALLOC_STEP;
2008 }
2009
2010 memcpy(ta->text.data, text, len);
2011 ta->text.len = len;
2012 ta->text.utf8_len = utf8_length(ta->text.data);
2013
2014 ta->undo.next_detail = 0;
2015 ta->undo.last_detail = 0;
2016
2017 textarea_normalise_text(ta, 0, len);
2018
2019 if (ta->flags & TEXTAREA_MULTILINE) {
2020 if (!textarea_reflow_multiline(ta, 0, len - 1, &r))
2021 return false;
2022 } else {
2023 if (!textarea_reflow_singleline(ta, 0, &r))
2024 return false;
2025 }
2026
2027 return true;
2028}
2029
2030
2031/* exported interface, documented in textarea.h */
2032bool textarea_drop_text(struct textarea *ta, const char *text,
2033 size_t text_length)
2034{
2035 struct textarea_msg msg;
2036 struct rect r; /**< Redraw rectangle */
2037 unsigned int caret_pos;
2038 int byte_delta;
2039
2040 if (ta->flags & TEXTAREA_READONLY)
2041 return false;
2042
2043 if (text == NULL)
2044 return false;
2045
2046 caret_pos = textarea_get_caret(ta);
2047
2048 if (ta->sel_start != -1) {
2049 if (!textarea_replace_text(ta, ta->sel_start, ta->sel_end,
2050 text, text_length, false, &byte_delta, &r))
2051 return false;
2052
2053 caret_pos = ta->sel_end;
2054 ta->sel_start = ta->sel_end = -1;
2055 } else {
2056 if (!textarea_replace_text(ta, caret_pos, caret_pos,
2057 text, text_length, false, &byte_delta, &r))
2058 return false;
2059 }
2060
2061 caret_pos += byte_delta;
2062 textarea_set_caret_internal(ta, caret_pos);
2063
2064 msg.ta = ta;
2066 msg.data.redraw.x0 = 0;
2067 msg.data.redraw.y0 = 0;
2068 msg.data.redraw.x1 = ta->vis_width;
2069 msg.data.redraw.y1 = ta->vis_height;
2070
2071 ta->callback(ta->data, &msg);
2072
2073 return true;
2074}
2075
2076
2077/* exported interface, documented in textarea.h */
2078int textarea_get_text(struct textarea *ta, char *buf, unsigned int len)
2079{
2080 if (buf == NULL && len == 0) {
2081 /* want length */
2082 return ta->text.len;
2083
2084 } else if (buf == NULL) {
2085 /* Can't write to NULL */
2086 return -1;
2087 }
2088
2089 if (len < ta->text.len) {
2090 NSLOG(netsurf, INFO, "buffer too small");
2091 return -1;
2092 }
2093
2094 memcpy(buf, ta->text.data, ta->text.len);
2095
2096 return ta->text.len;
2097}
2098
2099
2100/* exported interface, documented in textarea.h */
2101const char * textarea_data(struct textarea *ta, unsigned int *len)
2102{
2103 if (len != NULL) {
2104 *len = ta->text.len;
2105 }
2106
2107 return ta->text.data;
2108}
2109
2110
2111/* exported interface, documented in textarea.h */
2112bool textarea_set_caret(struct textarea *ta, int caret)
2113{
2114 int b_off;
2115
2116 if (caret < 0) {
2118 } else if (caret == 0) {
2120 } else {
2121 b_off = utf8_bounded_byte_length(ta->show->data,
2122 ta->show->len - 1, caret);
2123 textarea_set_caret_internal(ta, b_off);
2124 }
2125
2126 return true;
2127}
2128
2129
2130/* exported interface, documented in textarea.h */
2131void textarea_redraw(struct textarea *ta, int x, int y, colour bg, float scale,
2132 const struct rect *clip, const struct redraw_context *ctx)
2133{
2134 int line0, line1, line, left, right, line_y;
2135 int text_y_offset, text_y_offset_baseline;
2136 unsigned int b_pos, b_len, b_len_part, b_end;
2137 unsigned int sel_start, sel_end;
2138 char *line_text;
2139 struct rect r, s;
2140 struct rect rect;
2141 bool selected = false;
2142 plot_font_style_t fstyle;
2143 int fsize = ta->fstyle.size;
2144 int line_height = ta->line_height;
2145 plot_style_t plot_style_fill_bg = {
2147 .stroke_width = 0,
2148 .stroke_colour = NS_TRANSPARENT,
2149 .fill_type = PLOT_OP_TYPE_SOLID,
2150 .fill_colour = ta->border_col
2151 };
2152
2153 r = *clip;
2154
2155 /* Nothing to render if textarea is outside clip rectangle */
2156 if (r.x1 < x || r.y1 < y)
2157 return;
2158 if (scale == 1.0) {
2159 if (r.x0 > x + ta->vis_width || r.y0 > y + ta->vis_height)
2160 return;
2161 } else {
2162 if (r.x0 > x + ta->vis_width * scale ||
2163 r.y0 > y + ta->vis_height * scale)
2164 return;
2165 }
2166
2167 if (ta->lines == NULL)
2168 /* Nothing to redraw */
2169 return;
2170
2171 line0 = (r.y0 - y + ta->scroll_y) / ta->line_height - 1;
2172 line1 = (r.y1 - y + ta->scroll_y) / ta->line_height + 1;
2173
2174 if (line0 < 0)
2175 line0 = 0;
2176 if (line1 < 0)
2177 line1 = 0;
2178 if (ta->line_count - 1 < line0)
2179 line0 = ta->line_count - 1;
2180 if (ta->line_count - 1 < line1)
2181 line1 = ta->line_count - 1;
2182 if (line1 < line0)
2183 line1 = line0;
2184
2185 if (r.x0 < x)
2186 r.x0 = x;
2187 if (r.y0 < y)
2188 r.y0 = y;
2189 if (scale == 1.0) {
2190 if (r.x1 > x + ta->vis_width)
2191 r.x1 = x + ta->vis_width;
2192 if (r.y1 > y + ta->vis_height)
2193 r.y1 = y + ta->vis_height;
2194 } else {
2195 if (r.x1 > x + ta->vis_width * scale)
2196 r.x1 = x + ta->vis_width * scale;
2197 if (r.y1 > y + ta->vis_height * scale)
2198 r.y1 = y + ta->vis_height * scale;
2199 }
2200
2201 ctx->plot->clip(ctx, &r);
2202 if (ta->border_col != NS_TRANSPARENT &&
2203 ta->border_width > 0) {
2204 /* Plot border */
2205 rect.x0 = x;
2206 rect.y0 = y;
2207 rect.x1 = x + ta->vis_width;
2208 rect.y1 = y + ta->vis_height;
2209 ctx->plot->rectangle(ctx, &plot_style_fill_bg, &rect);
2210 }
2211 if (ta->fstyle.background != NS_TRANSPARENT) {
2212 /* Plot background */
2213 plot_style_fill_bg.fill_colour = ta->fstyle.background;
2214 rect.x0 = x + ta->border_width;
2215 rect.y0 = y + ta->border_width;
2216 rect.x1 = x + ta->vis_width - ta->border_width;
2217 rect.y1 = y + ta->vis_height - ta->border_width;
2218 ctx->plot->rectangle(ctx, &plot_style_fill_bg, &rect);
2219 }
2220
2221 if (scale == 1.0) {
2222 if (r.x0 < x + ta->border_width)
2223 r.x0 = x + ta->border_width;
2224 if (r.x1 > x + ta->vis_width - ta->border_width)
2225 r.x1 = x + ta->vis_width - ta->border_width;
2226 if (r.y0 < y + ta->border_width)
2227 r.y0 = y + ta->border_width;
2228 if (r.y1 > y + ta->vis_height - ta->border_width -
2229 (ta->bar_x != NULL ? SCROLLBAR_WIDTH : 0))
2230 r.y1 = y + ta->vis_height - ta->border_width -
2231 (ta->bar_x != NULL ? SCROLLBAR_WIDTH :
2232 0);
2233 } else {
2234 if (r.x0 < x + ta->border_width * scale)
2235 r.x0 = x + ta->border_width * scale;
2236 if (r.x1 > x + (ta->vis_width - ta->border_width) * scale)
2237 r.x1 = x + (ta->vis_width - ta->border_width) * scale;
2238 if (r.y0 < y + ta->border_width * scale)
2239 r.y0 = y + ta->border_width * scale;
2240 if (r.y1 > y + (ta->vis_height - ta->border_width -
2241 (ta->bar_x != NULL ? SCROLLBAR_WIDTH : 0)) *
2242 scale)
2243 r.y1 = y + (ta->vis_height - ta->border_width -
2244 (ta->bar_x != NULL ? SCROLLBAR_WIDTH :
2245 0)) * scale;
2246 }
2247
2248 if (line0 > 0)
2249 b_pos = ta->lines[line0].b_start;
2250 else
2251 b_pos = 0;
2252
2253 text_y_offset = ta->text_y_offset;
2254 text_y_offset_baseline = ta->text_y_offset_baseline;
2255
2256 if (scale != 1.0) {
2257 text_y_offset *= scale;
2258 text_y_offset_baseline *= scale;
2259
2260 fsize *= scale;
2261 line_height *= scale;
2262 }
2263
2264 plot_style_fill_bg.fill_colour = ta->sel_fstyle.background;
2265
2266 for (line = line0;
2267 (line <= line1) && (y + line * ta->line_height <= r.y1 + ta->scroll_y);
2268 line++) {
2269 if (ta->lines[line].b_length == 0) {
2270 b_pos++;
2271 continue;
2272 }
2273
2274 /* reset clip rectangle */
2275 ctx->plot->clip(ctx, &r);
2276
2277 b_len = ta->lines[line].b_length;
2278
2279 b_end = 0;
2280 right = x + ta->border_width + ta->pad_left - ta->scroll_x;
2281
2282 line_y = line * ta->line_height - ta->scroll_y;
2283
2284 if (scale != 1.0) {
2285 line_y *= scale;
2286 }
2287
2288 sel_start = ta->sel_start;
2289 sel_end = ta->sel_end;
2290
2291 if (ta->sel_end == -1 || ta->sel_end == ta->sel_start ||
2292 sel_end < ta->lines[line].b_start ||
2293 sel_start > ta->lines[line].b_start +
2294 ta->lines[line].b_length) {
2295 /* Simple case; no selection on this line */
2296 fstyle = ta->fstyle;
2297 fstyle.size = fsize;
2298
2299 ctx->plot->text(ctx,
2300 &fstyle,
2301 x + ta->border_width + ta->pad_left - ta->scroll_x,
2302 y + line_y + text_y_offset_baseline,
2303 ta->show->data + ta->lines[line].b_start,
2304 ta->lines[line].b_length);
2305
2306 b_pos += b_len;
2307
2308 } else do {
2309 /* get length of part of line */
2310 if (sel_end <= b_pos || sel_start > b_pos + b_len) {
2311 /* rest of line unselected */
2312 selected = false;
2313 b_len_part = b_len;
2314 fstyle = ta->fstyle;
2315
2316 } else if (sel_start <= b_pos &&
2317 sel_end > b_pos + b_len) {
2318 /* rest of line selected */
2319 selected = true;
2320 b_len_part = b_len;
2321 fstyle = ta->sel_fstyle;
2322
2323 } else if (sel_start > b_pos) {
2324 /* next part of line unselected */
2325 selected = false;
2326 b_len_part = sel_start - b_pos;
2327 fstyle = ta->fstyle;
2328
2329 } else if (sel_end > b_pos) {
2330 /* next part of line selected */
2331 selected = true;
2332 b_len_part = sel_end - b_pos;
2333 fstyle = ta->sel_fstyle;
2334
2335 } else {
2336 assert(0);
2337 }
2338 fstyle.size = fsize;
2339
2340 line_text = &(ta->show->data[ta->lines[line].b_start]);
2341
2342 /* find b_end for this part of the line */
2343 b_end += b_len_part;
2344
2345 /* find clip left/right for this part of line */
2346 left = right;
2347 if (b_len_part != b_len) {
2348 guit->layout->width(&fstyle, line_text, b_end,
2349 &right);
2350 } else {
2351 right = ta->lines[line].width;
2352 if (scale != 1.0)
2353 right *= scale;
2354 }
2355 right += x + ta->border_width + ta->pad_left -
2356 ta->scroll_x;
2357
2358 /* set clip rectangle for line part */
2359 s = r;
2360
2361 if (s.x1 <= left || s.x0 > right) {
2362 /* Skip this span, it's outside the visible */
2363 b_pos += b_len_part;
2364 b_len -= b_len_part;
2365 continue;
2366 }
2367
2368 /* Adjust clip rectangle to span limits */
2369 if (s.x0 < left)
2370 s.x0 = left;
2371 if (s.x1 > right)
2372 s.x1 = right;
2373
2374 if (right <= left) {
2375 /* Skip this span, it's outside the visible */
2376 b_pos += b_len_part;
2377 b_len -= b_len_part;
2378 continue;
2379 }
2380
2381 ctx->plot->clip(ctx, &s);
2382
2383 if (selected) {
2384 /* draw selection fill */
2385 rect.x0 = s.x0;
2386 rect.y0 = y + line_y + text_y_offset;
2387 rect.x1 = s.x1;
2388 rect.y1 = y + line_y + line_height + text_y_offset;
2389 ctx->plot->rectangle(ctx, &plot_style_fill_bg, &rect);
2390 }
2391
2392 /* draw text */
2393 ctx->plot->text(ctx,
2394 &fstyle,
2395 x + ta->border_width + ta->pad_left - ta->scroll_x,
2396 y + line_y + text_y_offset_baseline,
2397 ta->show->data + ta->lines[line].b_start,
2398 ta->lines[line].b_length);
2399
2400 b_pos += b_len_part;
2401 b_len -= b_len_part;
2402
2403 } while (b_pos < b_pos + b_len);
2404
2405 /* if there is a newline between the lines, skip it */
2406 if (line < ta->line_count - 1 &&
2407 ta->lines[line + 1].b_start !=
2408 ta->lines[line].b_start +
2409 ta->lines[line].b_length)
2410 b_pos++;
2411 }
2412
2413 if (ta->flags & TEXTAREA_INTERNAL_CARET &&
2414 (ta->sel_end == -1 || ta->sel_start == ta->sel_end) &&
2415 ta->caret_pos.byte_off >= 0) {
2416 /* No native caret, there is no selection, and caret visible */
2417 int caret_y = y - ta->scroll_y + ta->caret_y;
2418
2419 ctx->plot->clip(ctx, &r);
2420
2421 /* Render our own caret */
2422 rect.x0 = x - ta->scroll_x + ta->caret_x;
2423 rect.y0 = caret_y;
2424 rect.x1 = x - ta->scroll_x + ta->caret_x;
2425 rect.y1 = caret_y + ta->line_height;
2426 ctx->plot->line(ctx, &pstyle_stroke_caret, &rect);
2427 }
2428
2429 ctx->plot->clip(ctx, clip);
2430
2431 if (ta->bar_x != NULL) {
2433 x / scale + ta->border_width,
2434 y / scale + ta->vis_height - ta->border_width -
2436 clip, scale, ctx);
2437 }
2438
2439 if (ta->bar_y != NULL) {
2441 x / scale + ta->vis_width - ta->border_width -
2443 y / scale + ta->border_width,
2444 clip, scale, ctx);
2445 }
2446}
2447
2448
2449/* exported interface, documented in textarea.h */
2450bool textarea_keypress(struct textarea *ta, uint32_t key)
2451{
2452 struct textarea_msg msg;
2453 struct rect r; /**< Redraw rectangle */
2454 char utf8[6];
2455 unsigned int caret, caret_copy, length, b_off, b_len;
2456 int h_extent = ta->h_extent;
2457 int v_extent = ta->v_extent;
2458 int line;
2459 int byte_delta = 0;
2460 int x, y;
2461 bool redraw = false;
2462 bool readonly;
2463 bool bar_x = ta->bar_x;
2464 bool bar_y = ta->bar_y;
2465
2466 /* Word separators */
2467 static const char *sep = " .\n";
2468
2469 caret = caret_copy = textarea_get_caret(ta);
2470 line = ta->caret_pos.line;
2471 readonly = (ta->flags & TEXTAREA_READONLY ? true : false);
2472
2473 if (!(key <= 0x001F || (0x007F <= key && key <= 0x009F))) {
2474 /* normal character insertion */
2475 length = utf8_from_ucs4(key, utf8);
2476 utf8[length] = '\0';
2477
2478 if (ta->sel_start != -1) {
2479 if (!textarea_replace_text(ta,
2480 ta->sel_start, ta->sel_end, utf8,
2481 length, false, &byte_delta, &r))
2482 return false;
2483
2484 redraw = true;
2485 caret = ta->sel_end;
2487 } else {
2488 if (!textarea_replace_text(ta, caret, caret,
2489 utf8, length, false, &byte_delta, &r))
2490 return false;
2491 redraw = true;
2492 }
2493 caret += byte_delta;
2494
2495 } else switch (key) {
2496 case NS_KEY_SELECT_ALL:
2497 textarea_select(ta, 0, ta->show->len - 1, true);
2498 return true;
2500 if (ta->sel_start != -1) {
2501 if (!textarea_replace_text(ta,
2502 ta->sel_start, ta->sel_end,
2503 NULL, 0, true, &byte_delta, &r))
2504 return false;
2505 }
2506 break;
2507 case NS_KEY_DELETE_LEFT:
2508 if (readonly)
2509 break;
2510 if (ta->sel_start != -1) {
2511 if (!textarea_replace_text(ta,
2512 ta->sel_start, ta->sel_end,
2513 "", 0, false, &byte_delta, &r))
2514 return false;
2515
2516 redraw = true;
2517 caret = ta->sel_end;
2519 } else if (caret > 0) {
2520 b_off = utf8_prev(ta->show->data, caret);
2521 if (!textarea_replace_text(ta, b_off, caret,
2522 "", 0, false, &byte_delta, &r))
2523 return false;
2524 redraw = true;
2525 }
2526 caret += byte_delta;
2527 break;
2529 if (readonly)
2530 break;
2531 if (ta->sel_start != -1) {
2532 if (!textarea_replace_text(ta,
2533 ta->sel_start, ta->sel_end,
2534 "", 0, false, &byte_delta, &r))
2535 return false;
2536
2537 redraw = true;
2538 caret = ta->sel_end;
2540 } else if (caret < ta->show->len - 1) {
2541 b_off = utf8_next(ta->show->data,
2542 ta->show->len - 1, caret);
2543 if (!textarea_replace_text(ta, caret, b_off,
2544 "", 0, false, &byte_delta, &r))
2545 return false;
2546 caret = b_off;
2547 redraw = true;
2548 }
2549 caret += byte_delta;
2550 break;
2551 case NS_KEY_CR:
2552 case NS_KEY_NL:
2553 if (readonly)
2554 break;
2555
2556 if (ta->sel_start != -1) {
2557 if (!textarea_replace_text(ta,
2558 ta->sel_start, ta->sel_end,
2559 "\n", 1, false,
2560 &byte_delta, &r))
2561 return false;
2562
2563 redraw = true;
2564 caret = ta->sel_end;
2566 } else {
2567 if (!textarea_replace_text(ta, caret, caret,
2568 "\n", 1, false,
2569 &byte_delta, &r))
2570 return false;
2571 redraw = true;
2572 }
2573 caret += byte_delta;
2574 break;
2575 case NS_KEY_PASTE:
2576 {
2577 char *clipboard = NULL;
2578 size_t clipboard_length;
2579
2580 if (readonly)
2581 break;
2582
2583 guit->clipboard->get(&clipboard, &clipboard_length);
2584 if (clipboard == NULL)
2585 return false;
2586
2587 if (ta->sel_start != -1) {
2588 if (!textarea_replace_text(ta,
2589 ta->sel_start, ta->sel_end,
2590 clipboard, clipboard_length,
2591 false, &byte_delta, &r))
2592 return false;
2593
2594 redraw = true;
2595 caret = ta->sel_end;
2597 } else {
2598 if (!textarea_replace_text(ta,
2599 caret, caret,
2600 clipboard, clipboard_length,
2601 false, &byte_delta, &r))
2602 return false;
2603 redraw = true;
2604 }
2605 caret += byte_delta;
2606
2607 free(clipboard);
2608 }
2609 break;
2611 if (readonly)
2612 break;
2613 if (ta->sel_start != -1) {
2614 if (!textarea_replace_text(ta,
2615 ta->sel_start, ta->sel_end,
2616 "", 0, true, &byte_delta, &r))
2617 return false;
2618
2619 redraw = true;
2620 caret = ta->sel_end;
2621 caret += byte_delta;
2623 }
2624 break;
2625 case NS_KEY_ESCAPE:
2626 /* Fall through to NS_KEY_CLEAR_SELECTION */
2628 return textarea_clear_selection(ta);
2629 case NS_KEY_LEFT:
2630 if (readonly)
2631 break;
2632 if (caret > 0)
2633 caret = utf8_prev(ta->show->data, caret);
2634 if (ta->sel_start != -1) {
2636 }
2637 break;
2638 case NS_KEY_RIGHT:
2639 if (readonly)
2640 break;
2641 if (caret < ta->show->len - 1)
2642 caret = utf8_next(ta->show->data,
2643 ta->show->len - 1, caret);
2644 if (ta->sel_start != -1) {
2646 }
2647 break;
2648 case NS_KEY_UP:
2649 if (readonly)
2650 break;
2651 if (ta->sel_start != -1) {
2653 }
2654 if (!(ta->flags & TEXTAREA_MULTILINE))
2655 break;
2656
2657 line--;
2658 if (line < 0)
2659 line = 0;
2660 if (line == ta->caret_pos.line)
2661 break;
2662
2663 x = ta->caret_x;
2664 y = ta->text_y_offset_baseline + line * ta->line_height;
2665 textarea_set_caret_xy(ta, x, y, false);
2666
2667 return true;
2668 case NS_KEY_DOWN:
2669 if (readonly)
2670 break;
2671 if (ta->sel_start != -1) {
2673 }
2674 if (!(ta->flags & TEXTAREA_MULTILINE))
2675 break;
2676
2677 line++;
2678 if (line > ta->line_count - 1)
2679 line = ta->line_count - 1;
2680 if (line == ta->caret_pos.line)
2681 break;
2682
2683 x = ta->caret_x;
2684 y = ta->text_y_offset_baseline + line * ta->line_height;
2685 textarea_set_caret_xy(ta, x, y, false);
2686
2687 return true;
2688 case NS_KEY_PAGE_UP:
2689 if (!(ta->flags & TEXTAREA_MULTILINE))
2690 break;
2691 y = ta->vis_height - 2 * ta->border_width -
2692 ta->pad_top - ta->pad_bottom -
2693 ta->line_height;
2694 textarea_scroll(ta, 0, -y);
2695 return true;
2696 case NS_KEY_PAGE_DOWN:
2697 if (!(ta->flags & TEXTAREA_MULTILINE))
2698 break;
2699 y = ta->vis_height - 2 * ta->border_width -
2700 ta->pad_top - ta->pad_bottom -
2701 ta->line_height;
2702 textarea_scroll(ta, 0, y);
2703 return true;
2704 case NS_KEY_LINE_START:
2705 if (readonly)
2706 break;
2707 caret -= ta->caret_pos.byte_off;
2708 if (ta->sel_start != -1) {
2710 }
2711 break;
2712 case NS_KEY_LINE_END:
2713 if (readonly)
2714 break;
2715
2716 caret = ta->lines[line].b_start +
2717 ta->lines[line].b_length;
2718
2719 if (!(ta->flags & TEXTAREA_PASSWORD) &&
2720 caret > 0 &&
2721 ta->text.data[caret - 1] == ' ')
2722 caret--;
2723 if (ta->sel_start != -1) {
2725 }
2726 break;
2727 case NS_KEY_TEXT_START:
2728 if (readonly)
2729 break;
2730 caret = 0;
2731 if (ta->sel_start != -1) {
2733 }
2734 break;
2735 case NS_KEY_TEXT_END:
2736 if (readonly)
2737 break;
2738 caret = ta->show->len - 1;
2739 if (ta->sel_start != -1) {
2741 }
2742 break;
2743 case NS_KEY_WORD_LEFT:
2744 if (readonly)
2745 break;
2746 if (ta->sel_start != -1) {
2748 }
2749 if (caret == 0)
2750 break;
2751 caret--;
2752 while (strchr(sep, ta->show->data[caret]) != NULL &&
2753 caret > 0)
2754 caret--;
2755 for (; caret > 0; caret--) {
2756 if (strchr(sep, ta->show->data[caret])
2757 != NULL) {
2758 caret++;
2759 break;
2760 }
2761 }
2762 break;
2764 if (readonly)
2765 break;
2766
2767 /* If there is a selection, remove the selected
2768 * characters */
2769 if (ta->sel_start != -1) {
2770 if (!textarea_replace_text(ta, ta->sel_start,
2771 ta->sel_end, "", 0, false,
2772 &byte_delta, &r))
2773 return false;
2774 caret = ta->sel_start;
2776 redraw = true;
2777 break;
2778 }
2779
2780 if (caret == 0)
2781 break;
2782
2783 /* caret goes left until a non-separator is
2784 * encountered */
2785 caret--;
2786 while (strchr(sep, ta->show->data[caret]) != NULL &&
2787 caret > 0)
2788 caret--;
2789
2790 /* caret goes left until a separator is encountered */
2791 for (; caret > 0; caret--) {
2792 if (strchr(sep, ta->show->data[caret]) !=
2793 NULL) {
2794 caret++;
2795 break;
2796 }
2797 }
2798
2799 /* Remove the characters from new caret position to
2800 * original caret position */
2801 if (!textarea_replace_text(ta, caret, caret_copy,
2802 "", 0, false, &byte_delta, &r))
2803 return false;
2804
2805 redraw = true;
2806 break;
2807 case NS_KEY_WORD_RIGHT:
2808 if (readonly)
2809 break;
2810 if (ta->sel_start != -1) {
2812 }
2813 if (caret == ta->show->len - 1)
2814 break;
2815 if (strchr(sep, ta->show->data[caret]) != NULL &&
2816 caret < ta->show->len - 1) {
2817 while (strchr(sep, ta->show->data[caret]) !=
2818 NULL &&
2819 caret < ta->show->len - 1) {
2820 caret++;
2821 }
2822 break;
2823 }
2824 for (; caret < ta->show->len - 1; caret++) {
2825 if (strchr(sep, ta->show->data[caret]) != NULL)
2826 break;
2827 }
2828 while (strchr(sep, ta->show->data[caret]) != NULL &&
2829 caret < ta->show->len - 1)
2830 caret++;
2831 break;
2833 if (readonly)
2834 break;
2835
2836 /* If there is a selection, remove the selected
2837 * characters */
2838 if (ta->sel_start != -1) {
2839 if (!textarea_replace_text(ta, ta->sel_start,
2840 ta->sel_end, "", 0, false,
2841 &byte_delta, &r))
2842 return false;
2843 caret = ta->sel_start;
2845 redraw = true;
2846 break;
2847 }
2848
2849 if (caret == ta->show->len - 1)
2850 break;
2851
2852 /* caret_copy goes right until a non-separator is
2853 * encountered */
2854 while (strchr(sep, ta->show->data[caret_copy]) != NULL
2855 && caret_copy < ta->show->len - 1)
2856 caret_copy++;
2857
2858 /* caret_copy goes right until a separator is
2859 * encountered */
2860 for (; caret_copy < ta->show->len - 1; caret_copy++) {
2861 if (strchr(sep, ta->show->data[caret_copy]) !=
2862 NULL) {
2863 break;
2864 }
2865 }
2866
2867 /* Remove all the characters from original caret
2868 * position to caret_copy */
2869 if (!textarea_replace_text(ta, caret, caret_copy,
2870 "", 0, false, &byte_delta, &r))
2871 return false;
2872
2873 redraw = true;
2874 break;
2875 case NS_KEY_DELETE_LINE:
2876 if (readonly)
2877 break;
2878 if (ta->sel_start != -1) {
2879 if (!textarea_replace_text(ta,
2880 ta->sel_start, ta->sel_end,
2881 "", 0, false, &byte_delta, &r))
2882 return false;
2883 redraw = true;
2884 caret = ta->sel_end;
2886 } else {
2887 if (ta->lines[line].b_length != 0) {
2888 /* Delete line */
2889 caret = ta->lines[line].b_start;
2890 b_len = ta->lines[line].b_length;
2891 if (!textarea_replace_text(ta, caret,
2892 caret + b_len, "", 0,
2893 false, &byte_delta, &r))
2894 return false;
2895 caret = caret + b_len;
2896 } else if (caret < ta->show->len - 1) {
2897 /* Delete blank line */
2898 if (!textarea_replace_text(ta,
2899 caret, caret + 1, "", 0,
2900 false, &byte_delta, &r))
2901 return false;
2902 caret++;
2903 }
2904 redraw = true;
2905 }
2906 caret += byte_delta;
2907 break;
2909 if (readonly)
2910 break;
2911 if (ta->sel_start != -1) {
2912 if (!textarea_replace_text(ta,
2913 ta->sel_start, ta->sel_end,
2914 "", 0, false, &byte_delta, &r))
2915 return false;
2916 redraw = true;
2917 caret = ta->sel_end;
2919 } else {
2920 b_len = ta->lines[line].b_length;
2921 b_off = ta->lines[line].b_start + b_len;
2922 if (!textarea_replace_text(ta, caret, b_off,
2923 "", 0, false, &byte_delta, &r))
2924 return false;
2925 caret = b_off;
2926 redraw = true;
2927 }
2928 caret += byte_delta;
2929 break;
2931 if (readonly)
2932 break;
2933 if (ta->sel_start != -1) {
2934 if (!textarea_replace_text(ta,
2935 ta->sel_start, ta->sel_end,
2936 "", 0, false, &byte_delta, &r))
2937 return false;
2938 redraw = true;
2939 caret = ta->sel_end;
2941 } else {
2942 if (!textarea_replace_text(ta,
2943 caret - ta->caret_pos.byte_off,
2944 caret, "", 0, false,
2945 &byte_delta, &r))
2946 return false;
2947 redraw = true;
2948 }
2949 caret += byte_delta;
2950 break;
2951 case NS_KEY_UNDO:
2952 if (!textarea_undo(ta, false, &caret, &r)) {
2953 /* We consume the UNDO, even if we can't act
2954 * on it. */
2955 return true;
2956 }
2957 if (ta->sel_start != -1) {
2959 }
2960 redraw = true;
2961 break;
2962 case NS_KEY_REDO:
2963 if (!textarea_undo(ta, true, &caret, &r)) {
2964 /* We consume the REDO, even if we can't act
2965 * on it. */
2966 return true;
2967 }
2968 if (ta->sel_start != -1) {
2970 }
2971 redraw = true;
2972 break;
2973 default:
2974 return false;
2975 }
2976
2977 redraw &= !textarea_set_caret_internal(ta, caret);
2978
2979 /* TODO: redraw only the bit that changed */
2980 msg.ta = ta;
2982
2983 if (bar_x != (ta->bar_x != NULL) || bar_y != (ta->bar_y != NULL) ||
2984 h_extent != ta->h_extent || v_extent != ta->v_extent) {
2985 /* Must redraw since scrollbars have changed */
2986 msg.data.redraw.x0 = ta->border_width;
2987 msg.data.redraw.y0 = ta->border_width;
2988 msg.data.redraw.x1 = ta->vis_width - ta->border_width;
2989 msg.data.redraw.y1 = ta->vis_height - ta->border_width;
2990 ta->callback(ta->data, &msg);
2991
2992 } else if (redraw) {
2993 msg.data.redraw = r;
2994 ta->callback(ta->data, &msg);
2995 }
2996
2997 return true;
2998}
2999
3000
3001/* Handle textarea scrollbar mouse action
3002 * Helper for textarea_mouse_action()
3003 *
3004 * \param ta Text area
3005 * \param mouse the mouse state at action moment
3006 * \param x X coordinate
3007 * \param y Y coordinate
3008 * \return textarea mouse state
3009 */
3011 struct textarea *ta, browser_mouse_state mouse, int x, int y)
3012{
3013 int sx, sy; /* xy coord offset for scrollbar */
3014 int sl; /* scrollbar length */
3015
3016 assert(SCROLLBAR_MOUSE_USED == (1 << 0));
3017 assert(TEXTAREA_MOUSE_SCR_USED == (1 << 3));
3018
3019 /* Existing scrollbar drag */
3021 /* Scrollbar drag in progress; pass input to scrollbar */
3022 if (ta->drag_info.data.scrollbar == ta->bar_x) {
3023 x -= ta->border_width;
3024 y -= ta->vis_height - ta->border_width -
3026 } else {
3027 x -= ta->vis_width - ta->border_width -
3029 y -= ta->border_width;
3030 }
3032 mouse, x, y) << 3);
3033 }
3034
3035 /* Horizontal scrollbar */
3036 if (ta->bar_x != NULL && ta->drag_info.type == TEXTAREA_DRAG_NONE) {
3037 /* No drag happening, but mouse input is over scrollbar;
3038 * pass input to scrollbar */
3039 sx = x - ta->border_width;
3040 sy = y - (ta->vis_height - ta->border_width - SCROLLBAR_WIDTH);
3041 sl = ta->vis_width - 2 * ta->border_width -
3042 (ta->bar_y != NULL ? SCROLLBAR_WIDTH : 0);
3043
3044 if (sx >= 0 && sy >= 0 && sx < sl && sy < SCROLLBAR_WIDTH) {
3045 return (scrollbar_mouse_action(ta->bar_x, mouse,
3046 sx, sy) << 3);
3047 }
3048 }
3049
3050 /* Vertical scrollbar */
3051 if (ta->bar_y != NULL && ta->drag_info.type == TEXTAREA_DRAG_NONE) {
3052 /* No drag happening, but mouse input is over scrollbar;
3053 * pass input to scrollbar */
3054 sx = x - (ta->vis_width - ta->border_width - SCROLLBAR_WIDTH);
3055 sy = y - ta->border_width;
3056 sl = ta->vis_height - 2 * ta->border_width;
3057
3058 if (sx >= 0 && sy >= 0 && sx < SCROLLBAR_WIDTH && sy < sl) {
3059 return (scrollbar_mouse_action(ta->bar_y, mouse,
3060 sx, sy) << 3);
3061 }
3062 }
3063
3064 return TEXTAREA_MOUSE_NONE;
3065}
3066
3067
3068/* exported interface, documented in textarea.h */
3070 browser_mouse_state mouse, int x, int y)
3071{
3072 int b_start, b_end;
3073 unsigned int b_off;
3074 struct textarea_msg msg;
3076
3078 mouse == BROWSER_MOUSE_HOVER) {
3079 /* There is a drag that we must end */
3080 textarea_drag_end(ta, mouse, x, y);
3081 }
3082
3083 /* Mouse action might be a scrollbar's responsibility */
3084 status = textarea_mouse_scrollbar_action(ta, mouse, x, y);
3085 if (status != TEXTAREA_MOUSE_NONE) {
3086 /* Mouse action was handled by a scrollbar */
3087 return status;
3088 }
3089
3090 /* Might be outside textarea, and not dragging */
3091 if ((x >= ta->vis_width || y >= ta->vis_height) &&
3094 return status;
3095 }
3096
3097 status |= TEXTAREA_MOUSE_EDITOR;
3098
3099 /* Mouse action is textarea's responsibility */
3100 if (mouse & BROWSER_MOUSE_DOUBLE_CLICK) {
3101 /* Select word */
3102 textarea_set_caret_xy(ta, x, y, true);
3104 status |= TEXTAREA_MOUSE_USED;
3105
3106 } else if (mouse & BROWSER_MOUSE_TRIPLE_CLICK) {
3107 /* Select paragraph */
3108 textarea_set_caret_xy(ta, x, y, true);
3110 status |= TEXTAREA_MOUSE_USED;
3111
3112 } else if (mouse & BROWSER_MOUSE_PRESS_1) {
3113 /* Place caret */
3114 b_off = textarea_get_b_off_xy(ta, x, y, true);
3115 ta->drag_start = b_off;
3116
3118 if (ta->sel_start != -1) {
3119 /* Clear selection */
3121 }
3122 status |= TEXTAREA_MOUSE_USED;
3123
3124 } else if (mouse & BROWSER_MOUSE_PRESS_2) {
3125 b_off = textarea_get_b_off_xy(ta, x, y, true);
3126
3127 if (ta->sel_start != -1) {
3128 /* Adjust selection */
3129 b_start = (ta->sel_end - ta->sel_start) / 2 +
3130 ta->sel_start;
3131 b_start = ((unsigned)b_start > b_off) ?
3132 ta->sel_end : ta->sel_start;
3133 ta->drag_start = b_start;
3134 textarea_select(ta, b_start, b_off, false);
3135 } else {
3136 /* Select to caret */
3137 b_start = textarea_get_caret(ta);
3138 ta->drag_start = b_start;
3139 textarea_select(ta, b_start, b_off, false);
3140 }
3141 status |= TEXTAREA_MOUSE_USED;
3142
3143 } else if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) {
3144 /* Selection start */
3145 b_off = textarea_get_b_off_xy(ta, x, y, true);
3146 b_start = ta->drag_start;
3147 b_end = b_off;
3149
3150 msg.ta = ta;
3152 msg.data.drag = ta->drag_info.type;
3153
3154 ta->callback(ta->data, &msg);
3155
3156 textarea_select(ta, b_start, b_end, false);
3157 status |= TEXTAREA_MOUSE_USED;
3158
3159 } else if (mouse &
3162 /* Selection track */
3163 int scrx = 0;
3164 int scry = 0;
3165 int w, h;
3166 bool need_redraw = false;
3167
3168 b_off = textarea_get_b_off_xy(ta, x, y, true);
3169 b_start = ta->drag_start;
3170 b_end = b_off;
3171
3172 w = ta->vis_width - ta->border_width -
3173 ((ta->bar_y == NULL) ? 0 : SCROLLBAR_WIDTH);
3174 h = ta->vis_height - ta->border_width -
3175 ((ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH);
3176
3177 /* selection auto-scroll */
3178 if (x < ta->border_width)
3179 scrx = (x - ta->border_width) / 4;
3180 else if (x > w)
3181 scrx = (x - w) / 4;
3182
3183 if (y < ta->border_width)
3184 scry = (y - ta->border_width) / 4;
3185 else if (y > h)
3186 scry = (y - h) / 4;
3187
3188 if (scrx || scry)
3189 need_redraw = textarea_scroll(ta, scrx, scry);
3190
3191 textarea_select(ta, b_start, b_end, need_redraw);
3192 status |= TEXTAREA_MOUSE_USED;
3193 }
3194
3195 if (ta->sel_start != -1) {
3196 /* Have selection */
3197 status |= TEXTAREA_MOUSE_SELECTION;
3198 }
3199
3200 return status;
3201}
3202
3203
3204/* exported interface, documented in textarea.h */
3206{
3207 struct textarea_msg msg;
3208 int line_end, line_start = 0;
3209
3210 if (ta->sel_start == -1)
3211 /* No selection to clear */
3212 return false;
3213
3214 /* Find selection start & end lines */
3215 for (line_end = 0; line_end < ta->line_count - 1; line_end++)
3216 if (ta->lines[line_end + 1].b_start > (unsigned)ta->sel_start) {
3217 line_start = line_end;
3218 break;
3219 }
3220 for (; line_end < ta->line_count - 1; line_end++)
3221 if (ta->lines[line_end + 1].b_start > (unsigned)ta->sel_end)
3222 break;
3223
3224 /* Clear selection and redraw */
3226
3227 msg.ta = ta;
3229 msg.data.redraw.x0 = ta->border_width;
3231 ta->line_height * line_start +
3234 ((ta->bar_y == NULL) ? 0 : SCROLLBAR_WIDTH);
3236 ((ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH),
3237 ta->line_height * line_end + ta->text_y_offset +
3239
3240 ta->callback(ta->data, &msg);
3241
3242 /* No more selection */
3244
3245 msg.data.selection.have_selection = false;
3247
3248 ta->callback(ta->data, &msg);
3249
3250 if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) {
3251 /* Tell client where caret should be placed */
3252 struct rect cr = {
3253 .x0 = ta->border_width,
3254 .y0 = ta->border_width,
3255 .x1 = ta->vis_width - ta->border_width -
3256 ((ta->bar_y == NULL) ?
3257 0 : SCROLLBAR_WIDTH),
3258 .y1 = ta->vis_height - ta->border_width -
3259 ((ta->bar_x == NULL) ?
3260 0 : SCROLLBAR_WIDTH)
3261 };
3262 msg.ta = ta;
3264 msg.data.caret.type = TEXTAREA_CARET_SET_POS;
3265 msg.data.caret.pos.x = ta->caret_x - ta->scroll_x;
3266 msg.data.caret.pos.y = ta->caret_y - ta->scroll_y;
3267 msg.data.caret.pos.height = ta->line_height;
3268 msg.data.caret.pos.clip = &cr;
3269
3270 ta->callback(ta->data, &msg);
3271 }
3272
3273 return true;
3274}
3275
3276
3277/* exported interface, documented in textarea.h */
3279{
3280 char *ret;
3281 size_t b_start, b_end, b_len;
3282
3283 if (ta->sel_start == -1)
3284 /* No selection get */
3285 return NULL;
3286
3287 b_start = ta->sel_start;
3288 b_end = ta->sel_end;
3289 b_len = b_end - b_start;
3290
3291 if (b_len == 0)
3292 /* No selection get */
3293 return NULL;
3294
3295 ret = malloc(b_len + 1); /* Add space for '\0' */
3296 if (ret == NULL)
3297 /* Can't get selection; no memory */
3298 return NULL;
3299
3300 memcpy(ret, ta->show->data + b_start, b_len);
3301 ret[b_len] = '\0';
3302
3303 return ret;
3304}
3305
3306
3307/* exported interface, documented in textarea.h */
3308void textarea_get_dimensions(struct textarea *ta, int *width, int *height)
3309{
3310 if (width != NULL)
3311 *width = ta->vis_width;
3312 if (height != NULL)
3313 *height = ta->vis_height;
3314}
3315
3316
3317/* exported interface, documented in textarea.h */
3319{
3320 struct rect r = {0, 0, 0, 0};
3321
3322 ta->vis_width = width;
3323 ta->vis_height = height;
3324
3326
3327 if (ta->flags & TEXTAREA_MULTILINE) {
3328 textarea_reflow_multiline(ta, 0, ta->show->len -1, &r);
3329 } else {
3330 textarea_reflow_singleline(ta, 0, &r);
3331 }
3332}
3333
3334
3335/* exported interface, documented in textarea.h */
3337 struct textarea *ta,
3338 const plot_font_style_t *fstyle,
3339 int width, int height,
3340 int top, int right,
3341 int bottom, int left)
3342{
3343 struct rect r = {0, 0, 0, 0};
3344
3345 ta->vis_width = width;
3346 ta->vis_height = height;
3347 ta->pad_top = top;
3348 ta->pad_right = right + ((ta->bar_y == NULL) ? 0 : SCROLLBAR_WIDTH);
3349 ta->pad_bottom = bottom + ((ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH);
3350 ta->pad_left = left;
3351
3352 textarea_set_text_style(ta, fstyle,
3355
3357
3358 if (ta->flags & TEXTAREA_MULTILINE) {
3359 textarea_reflow_multiline(ta, 0, ta->show->len -1, &r);
3360 } else {
3361 textarea_reflow_singleline(ta, 0, &r);
3362 }
3363}
3364
3365
3366/* exported interface, documented in textarea.h */
3367bool textarea_scroll(struct textarea *ta, int scrx, int scry)
3368{
3369 bool handled_scroll = false;
3370
3371 if (ta->flags & TEXTAREA_MULTILINE) {
3372 /* Multi line textareas have scrollbars to handle this */
3373 if (ta->bar_x != NULL && scrx != 0 &&
3374 scrollbar_scroll(ta->bar_x, scrx))
3375 handled_scroll = true;
3376 if (ta->bar_y != NULL && scry != 0 &&
3377 scrollbar_scroll(ta->bar_y, scry))
3378 handled_scroll = true;
3379
3380 } else {
3381 /* Single line. Can only scroll horizontally. */
3382 int xs = ta->scroll_x;
3383
3384 /* Apply offset */
3385 xs += scrx;
3386
3387 /* Clamp to limits */
3388 if (xs < 0)
3389 xs = 0;
3390 else if (xs > ta->h_extent - ta->vis_width - ta->border_width)
3391 xs = ta->h_extent - ta->vis_width - ta->border_width;
3392
3393 if (xs != ta->scroll_x) {
3394 ta->scroll_x = xs;
3395 handled_scroll = true;
3396 }
3397 }
3398
3399 return handled_scroll;
3400}
Browser window creation and manipulation interface.
static int line_height(const css_unit_ctx *unit_len_ctx, const css_computed_style *style)
Calculate line height from a style.
Definition: layout.c:2677
css_fixed nscss_screen_dpi
Screen DPI in fixed point units: defaults to 90, which RISC OS uses.
Definition: css.c:44
static void textarea_setup_text_offsets(struct textarea *ta)
Setup text offsets after height / border / padding change.
Definition: textarea.c:1802
static size_t textarea_get_b_off_xy(struct textarea *ta, int x, int y, bool visible)
get byte offset from the beginning of the text for some coordinates
Definition: textarea.c:1232
static bool textarea_insert_text(struct textarea *ta, const char *text, size_t b_off, size_t b_len, int *byte_delta, struct rect *r)
Insert text into the textarea.
Definition: textarea.c:1311
#define PASSWORD_REPLACEMENT_W
Definition: textarea.c:119
static bool textarea_replace_text(struct textarea *ta, size_t b_start, size_t b_end, const char *rep, size_t rep_len, bool add_to_clipboard, int *byte_delta, struct rect *r)
Replace text in a textarea, updating undo buffer.
Definition: textarea.c:1618
static void textarea_reset_selection(struct textarea *ta)
Reset the selection (no redraw)
Definition: textarea.c:214
static plot_style_t pstyle_stroke_caret
Definition: textarea.c:47
bool textarea_scroll(struct textarea *ta, int scrx, int scry)
Scroll a textarea by an amount.
Definition: textarea.c:3367
void textarea_redraw(struct textarea *ta, int x, int y, colour bg, float scale, const struct rect *clip, const struct redraw_context *ctx)
Handle redraw requests for text areas.
Definition: textarea.c:2131
void textarea_set_dimensions(struct textarea *ta, int width, int height)
Set the dimensions of a textarea.
Definition: textarea.c:3318
static void textarea_normalise_text(struct textarea *ta, unsigned int b_start, unsigned int b_len)
Normalises any line endings within the text, replacing CRLF or CR with LF as necessary.
Definition: textarea.c:166
static bool textarea_drag_end(struct textarea *ta, browser_mouse_state mouse, int x, int y)
Handles the end of a drag operation.
Definition: textarea.c:1744
static bool textarea_reflow_singleline(struct textarea *ta, size_t b_off, struct rect *r)
Reflow a single line textarea.
Definition: textarea.c:816
static bool textarea_select_paragraph(struct textarea *ta)
Selects paragraph, at current caret position.
Definition: textarea.c:679
struct textarea * textarea_create(const textarea_flags flags, const textarea_setup *setup, textarea_client_callback callback, void *data)
Create a text area.
Definition: textarea.c:1852
bool textarea_clear_selection(struct textarea *ta)
Clear any selection in the textarea.
Definition: textarea.c:3205
#define PASSWORD_REPLACEMENT
Definition: textarea.c:118
static void textarea_scrollbar_callback(void *client_data, struct scrollbar_msg_data *scrollbar_data)
Callback for scrollbar widget.
Definition: textarea.c:719
void textarea_destroy(struct textarea *ta)
Destroy a text area.
Definition: textarea.c:1975
bool textarea_set_caret(struct textarea *ta, int caret)
Set the caret's position.
Definition: textarea.c:2112
static bool textarea_undo(struct textarea *ta, bool forward, unsigned int *caret, struct rect *r)
Undo or redo previous change.
Definition: textarea.c:1660
static bool textarea_select(struct textarea *ta, int b_start, int b_end, bool force_redraw)
Selects a character range in the textarea and redraws it.
Definition: textarea.c:503
bool textarea_set_text(struct textarea *ta, const char *text)
Set the text in a text area, discarding any current text.
Definition: textarea.c:1995
int textarea_get_text(struct textarea *ta, char *buf, unsigned int len)
Extract the text from a text area.
Definition: textarea.c:2078
const char * textarea_data(struct textarea *ta, unsigned int *len)
Access text data in a text area.
Definition: textarea.c:2101
static int textarea_get_caret(struct textarea *ta)
Get the caret's position.
Definition: textarea.c:226
static textarea_mouse_status textarea_mouse_scrollbar_action(struct textarea *ta, browser_mouse_state mouse, int x, int y)
Definition: textarea.c:3010
static bool textarea_select_fragment(struct textarea *ta)
Selects a text fragment, relative to current caret position.
Definition: textarea.c:628
textarea_mouse_status textarea_mouse_action(struct textarea *ta, browser_mouse_state mouse, int x, int y)
Handles all kinds of mouse action.
Definition: textarea.c:3069
bool textarea_drop_text(struct textarea *ta, const char *text, size_t text_length)
Insert the text in a text area at the caret, replacing any selection.
Definition: textarea.c:2032
char * textarea_get_selection(struct textarea *ta)
Get selected text.
Definition: textarea.c:3278
#define TA_ALLOC_STEP
Definition: textarea.c:45
void textarea_get_dimensions(struct textarea *ta, int *width, int *height)
Gets the dimensions of a textarea.
Definition: textarea.c:3308
static bool textarea_copy_to_undo_buffer(struct textarea *ta, size_t b_start, size_t b_end, size_t rep_len)
Update undo buffer by adding any text to be replaced, and allocating space as appropriate.
Definition: textarea.c:1543
static void textarea_char_to_byte_offset(struct textarea_utf8 *text, unsigned int start, unsigned int end, size_t *b_start, size_t *b_end)
Helper for replace_text function converts character offset to byte offset.
Definition: textarea.c:1390
void textarea_set_layout(struct textarea *ta, const plot_font_style_t *fstyle, int width, int height, int top, int right, int bottom, int left)
Set the dimensions and padding of a textarea.
Definition: textarea.c:3336
static void textarea_set_text_style(struct textarea *ta, const plot_font_style_t *fstyle, colour selected_text, colour selected_bg)
Set font styles up for a textarea.
Definition: textarea.c:1837
#define CARET_COLOR
Definition: textarea.c:44
static bool textarea_set_caret_xy(struct textarea *ta, int x, int y, bool visible)
Set the caret's position.
Definition: textarea.c:1289
static bool textarea_replace_text_internal(struct textarea *ta, size_t b_start, size_t b_end, const char *rep, size_t rep_len, bool add_to_clipboard, int *byte_delta, struct rect *r)
Perform actual text replacment in a textarea.
Definition: textarea.c:1424
static bool textarea_set_caret_internal(struct textarea *ta, int caret_b)
Set the caret position.
Definition: textarea.c:352
#define LINE_CHUNK_SIZE
Definition: textarea.c:140
bool textarea_keypress(struct textarea *ta, uint32_t key)
Key press handling for text areas.
Definition: textarea.c:2450
static bool textarea_reflow_multiline(struct textarea *ta, const size_t b_start, const int b_length, struct rect *r)
Reflow a multiline textarea from the given line onwards.
Definition: textarea.c:916
static bool textarea_scroll_visible(struct textarea *ta)
Scrolls a textarea to make the caret visible (doesn't perform a redraw)
Definition: textarea.c:251
Single/Multi-line UTF-8 text area interface.
void(* textarea_client_callback)(void *data, struct textarea_msg *msg)
Client callback for the textarea.
Definition: textarea.h:156
textarea_flags
Text area flags.
Definition: textarea.h:40
@ TEXTAREA_INTERNAL_CARET
Render own caret.
Definition: textarea.h:44
@ TEXTAREA_PASSWORD
Obscured display.
Definition: textarea.h:45
@ TEXTAREA_READONLY
Non-editable.
Definition: textarea.h:43
@ TEXTAREA_MULTILINE
Multiline area.
Definition: textarea.h:42
textarea_mouse_status
Text area mouse input status flags.
Definition: textarea.h:130
@ TEXTAREA_MOUSE_SCR_USED
Scrollbar action.
Definition: textarea.h:135
@ TEXTAREA_MOUSE_USED
Took action with input.
Definition: textarea.h:132
@ TEXTAREA_MOUSE_SELECTION
Hover: selection.
Definition: textarea.h:134
@ TEXTAREA_MOUSE_NONE
Not relevant.
Definition: textarea.h:131
@ TEXTAREA_MOUSE_EDITOR
Hover: caret pointer.
Definition: textarea.h:133
textarea_drag_type
Textarea drag status.
Definition: textarea.h:52
@ TEXTAREA_DRAG_SCROLLBAR
Definition: textarea.h:54
@ TEXTAREA_DRAG_NONE
Definition: textarea.h:53
@ TEXTAREA_DRAG_SELECTION
Definition: textarea.h:55
@ TEXTAREA_MSG_SELECTION_REPORT
Textarea text selection presence.
Definition: textarea.h:64
@ TEXTAREA_MSG_TEXT_MODIFIED
Textarea text modified.
Definition: textarea.h:67
@ TEXTAREA_MSG_DRAG_REPORT
Textarea drag start/end report.
Definition: textarea.h:63
@ TEXTAREA_MSG_REDRAW_REQUEST
Textarea redraw request.
Definition: textarea.h:65
@ TEXTAREA_MSG_CARET_UPDATE
Textarea caret.
Definition: textarea.h:66
@ NSERROR_OK
No error.
Definition: errors.h:30
static GtkClipboard * clipboard
Definition: selection.c:31
struct netsurf_table * guit
The global interface table.
Definition: gui_factory.c:49
Interface to core interface table.
Interface to platform-specific clipboard operations.
Interface to platform-specific layout operation table.
Core mouse and pointer states.
browser_mouse_state
Mouse state.
Definition: mouse.h:43
@ BROWSER_MOUSE_PRESS_1
button 1 pressed
Definition: mouse.h:50
@ BROWSER_MOUSE_PRESS_2
button 2 pressed
Definition: mouse.h:52
@ BROWSER_MOUSE_HOVER
No mouse buttons pressed, May be used to indicate hover or end of drag.
Definition: mouse.h:47
@ BROWSER_MOUSE_TRIPLE_CLICK
button triple clicked
Definition: mouse.h:62
@ BROWSER_MOUSE_DOUBLE_CLICK
button double clicked
Definition: mouse.h:60
@ BROWSER_MOUSE_DRAG_1
start of button 1 drag
Definition: mouse.h:65
@ BROWSER_MOUSE_HOLDING_2
during button 2 drag
Definition: mouse.h:75
@ BROWSER_MOUSE_HOLDING_1
during button 1 drag
Definition: mouse.h:73
@ BROWSER_MOUSE_DRAG_2
start of button 2 drag
Definition: mouse.h:67
Target independent plotting interface.
Interface to key press operations.
@ NS_KEY_DELETE_WORD_RIGHT
Definition: keypress.h:64
@ NS_KEY_REDO
Definition: keypress.h:71
@ NS_KEY_DELETE_LINE_START
Definition: keypress.h:68
@ NS_KEY_LINE_START
Definition: keypress.h:57
@ NS_KEY_CR
Definition: keypress.h:40
@ NS_KEY_RIGHT
Definition: keypress.h:51
@ NS_KEY_DELETE_WORD_LEFT
Definition: keypress.h:62
@ NS_KEY_LEFT
Definition: keypress.h:50
@ NS_KEY_SELECT_ALL
Definition: keypress.h:32
@ NS_KEY_PASTE
Definition: keypress.h:43
@ NS_KEY_COPY_SELECTION
Definition: keypress.h:33
@ NS_KEY_DOWN
Definition: keypress.h:53
@ NS_KEY_CUT_SELECTION
Definition: keypress.h:44
@ NS_KEY_WORD_LEFT
Definition: keypress.h:61
@ NS_KEY_DELETE_LINE_END
Definition: keypress.h:67
@ NS_KEY_PAGE_UP
Definition: keypress.h:65
@ NS_KEY_PAGE_DOWN
Definition: keypress.h:66
@ NS_KEY_UNDO
Definition: keypress.h:70
@ NS_KEY_TEXT_START
Definition: keypress.h:59
@ NS_KEY_LINE_END
Definition: keypress.h:58
@ NS_KEY_TEXT_END
Definition: keypress.h:60
@ NS_KEY_DELETE_RIGHT
Definition: keypress.h:55
@ NS_KEY_NL
Definition: keypress.h:38
@ NS_KEY_CLEAR_SELECTION
Definition: keypress.h:45
@ NS_KEY_WORD_RIGHT
Definition: keypress.h:63
@ NS_KEY_DELETE_LINE
Definition: keypress.h:42
@ NS_KEY_DELETE_LEFT
Definition: keypress.h:35
@ NS_KEY_UP
Definition: keypress.h:52
@ NS_KEY_ESCAPE
Definition: keypress.h:47
#define NSLOG(catname, level, logmsg, args...)
Definition: log.h:116
#define plot_style_int_to_fixed(v)
Definition: plot_style.h:51
@ PLOT_OP_TYPE_NONE
No operation.
Definition: plot_style.h:66
@ PLOT_OP_TYPE_SOLID
Solid colour.
Definition: plot_style.h:67
#define NS_TRANSPARENT
Transparent colour value.
Definition: plot_style.h:39
#define PLOT_STYLE_SCALE
Scaling factor for plot styles.
Definition: plot_style.h:45
int width
Definition: gui.c:159
int height
Definition: gui.c:160
scrollbar_mouse_status scrollbar_mouse_action(struct scrollbar *s, browser_mouse_state mouse, int x, int y)
Handle mouse actions other then drag ends.
Definition: scrollbar.c:768
void scrollbar_make_pair(struct scrollbar *horizontal, struct scrollbar *vertical)
Connect a horizontal and a vertical scrollbar into a pair so that they co-operate during 2D drags.
Definition: scrollbar.c:989
int scrollbar_get_offset(struct scrollbar *s)
Get the current scroll offset to the visible part of the full area.
Definition: scrollbar.c:627
nserror scrollbar_create(bool horizontal, int length, int full_size, int visible_size, void *client_data, scrollbar_client_callback client_callback, struct scrollbar **s)
Create a scrollbar.
Definition: scrollbar.c:93
nserror scrollbar_redraw(struct scrollbar *s, int x, int y, const struct rect *clip, float scale, const struct redraw_context *ctx)
Redraw a part of the scrollbar.
Definition: scrollbar.c:239
bool scrollbar_scroll(struct scrollbar *s, int change)
Scroll the scrollbar by given amount.
Definition: scrollbar.c:562
void scrollbar_destroy(struct scrollbar *s)
Destroy a scrollbar.
Definition: scrollbar.c:138
void scrollbar_set(struct scrollbar *s, int value, bool bar_pos)
Set the scroll value of the scrollbar.
Definition: scrollbar.c:512
void scrollbar_set_extents(struct scrollbar *s, int length, int visible_size, int full_size)
Set the length of the scrollbar widget, the size of the visible area, and the size of the full area.
Definition: scrollbar.c:639
void scrollbar_mouse_drag_end(struct scrollbar *s, browser_mouse_state mouse, int x, int y)
Handle end of mouse drags.
Definition: scrollbar.c:932
Scrollbar widget interface.
@ SCROLLBAR_MOUSE_USED
Took action with input.
Definition: scrollbar.h:72
@ SCROLLBAR_MSG_MOVED
the scroll value has changed
Definition: scrollbar.h:47
@ SCROLLBAR_MSG_SCROLL_START
a scrollbar drag has started, all mouse events should be passed to the scrollbar regardless of the co...
Definition: scrollbar.h:48
@ SCROLLBAR_MSG_SCROLL_FINISHED
cancel a scrollbar drag
Definition: scrollbar.h:53
#define SCROLLBAR_WIDTH
Definition: scrollbar.h:32
Interface to utility string handling.
void(* get)(char **buffer, size_t *length)
Core asks front end for clipboard contents.
Definition: clipboard.h:49
void(* set)(const char *buffer, size_t length, nsclipboard_styles styles[], int n_styles)
Core tells front end to put given text in clipboard.
Definition: clipboard.h:59
nserror(* position)(const struct plot_font_style *fstyle, const char *string, size_t length, int x, size_t *char_offset, int *actual_x)
Find the position in a string where an x coordinate falls.
Definition: layout.h:63
nserror(* width)(const struct plot_font_style *fstyle, const char *string, size_t length, int *width)
Measure the width of a string.
Definition: layout.h:49
nserror(* split)(const struct plot_font_style *fstyle, const char *string, size_t length, int x, size_t *char_offset, int *actual_x)
Find where to split a string to make it fit a width.
Definition: layout.h:88
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
int width
Width in pixels of line.
Definition: textarea.c:56
struct gui_clipboard_table * clipboard
Clipboard table.
Definition: gui_table.h:78
struct gui_layout_table * layout
Layout table.
Definition: gui_table.h:154
Font style for plotting.
Definition: plot_style.h:111
colour foreground
Colour of text.
Definition: plot_style.h:123
plot_style_fixed size
Font size, in pt.
Definition: plot_style.h:119
colour background
Background colour to blend to, if appropriate.
Definition: plot_style.h:122
Plot style for stroke/fill plotters.
Definition: plot_style.h:76
colour fill_colour
Colour of fill.
Definition: plot_style.h:81
plot_operation_type_t stroke_type
Stroke plot type.
Definition: plot_style.h:77
nserror(* line)(const struct redraw_context *ctx, const plot_style_t *pstyle, const struct rect *line)
Plots a line.
Definition: plotters.h:170
nserror(* text)(const struct redraw_context *ctx, const plot_font_style_t *fstyle, int x, int y, const char *text, size_t length)
Text plotting.
Definition: plotters.h:278
nserror(* rectangle)(const struct redraw_context *ctx, const plot_style_t *pstyle, const struct rect *rectangle)
Plots a rectangle.
Definition: plotters.h:188
nserror(* clip)(const struct redraw_context *ctx, const struct rect *clip)
Sets a clip rectangle for subsequent plot operations.
Definition: plotters.h:111
Rectangle coordinates.
Definition: types.h:40
int x0
Definition: types.h:41
int y0
Top left.
Definition: types.h:41
int x1
Definition: types.h:42
int y1
Bottom right.
Definition: types.h:42
Redraw context.
Definition: plotters.h:51
const struct plotter_table * plot
Current plot operation table.
Definition: plotters.h:73
scrollbar message context data
Definition: scrollbar.h:59
struct scrollbar * scrollbar
Definition: scrollbar.h:60
scrollbar_msg msg
Definition: scrollbar.h:61
Scrollbar context.
Definition: scrollbar.c:44
textarea_drag_type type
Definition: textarea.c:59
union textarea_drag::@77 data
struct scrollbar * scrollbar
Definition: textarea.c:61
textarea message
Definition: textarea.h:74
struct textarea_msg::@79::@81 caret
With _CARET_UPDATE.
unsigned int len
Byte length of text.
Definition: textarea.h:99
struct textarea_msg::@79::@80 selection
With _SELECTION_REPORT.
struct textarea_msg::@79::@81::@84 pos
With _CARET_SET_POS.
bool read_only
Selection can't be cut.
Definition: textarea.h:82
struct textarea * ta
The textarea widget.
Definition: textarea.h:75
const char * text
UTF8 text.
Definition: textarea.h:98
textarea_msg_type type
Indicates message data type.
Definition: textarea.h:77
struct rect redraw
With _REDRAW_REQUEST.
Definition: textarea.h:84
int height
Caret height.
Definition: textarea.h:93
@ TEXTAREA_CARET_HIDE
Hide.
Definition: textarea.h:88
struct textarea_msg::@79::@82 modified
With _TEXT_MODIFIED.
union textarea_msg::@79 data
Depends on msg type.
int y
Caret y-coord.
Definition: textarea.h:92
bool have_selection
Selection exists.
Definition: textarea.h:81
textarea_drag_type drag
With _DRAG_REPORT.
Definition: textarea.h:79
int x
Caret x-coord.
Definition: textarea.h:91
struct rect * clip
Caret clip rect.
Definition: textarea.h:94
textarea setup parameters
Definition: textarea.h:108
int pad_top
Textarea top padding.
Definition: textarea.h:112
colour border_col
Textarea border colour.
Definition: textarea.h:118
int height
Textarea height.
Definition: textarea.h:110
int width
Textarea width.
Definition: textarea.h:109
int border_width
Textarea border width.
Definition: textarea.h:117
int pad_right
Textarea right padding.
Definition: textarea.h:113
int pad_left
Textarea left padding.
Definition: textarea.h:115
plot_font_style_t text
Textarea background colour and font.
Definition: textarea.h:122
int pad_bottom
Textarea bottom padding.
Definition: textarea.h:114
colour selected_text
Textarea selected text colour.
Definition: textarea.h:120
colour selected_bg
Textarea selection background colour.
Definition: textarea.h:121
unsigned int b_text_end
End of textarea text (exclusive)
Definition: textarea.c:79
unsigned int b_limit
End of detail space (exclusive)
Definition: textarea.c:76
unsigned int b_text_start
Start of textarea text.
Definition: textarea.c:78
unsigned int b_start
Offset to detail's text in undo buffer.
Definition: textarea.c:74
unsigned int b_end
End of text (exclusive)
Definition: textarea.c:75
struct textarea_utf8 text
Definition: textarea.c:88
unsigned int last_detail
Last detail used.
Definition: textarea.c:85
struct textarea_undo_detail * details
Array of undo details.
Definition: textarea.c:86
unsigned int details_alloc
Details allocated for.
Definition: textarea.c:83
unsigned int next_detail
Next detail pos.
Definition: textarea.c:84
char * data
UTF-8 text.
Definition: textarea.c:66
unsigned int utf8_len
Length of text, in characters without trailing NULL.
Definition: textarea.c:69
unsigned int len
Length of text, in bytes.
Definition: textarea.c:68
unsigned int alloc
Size of allocated text.
Definition: textarea.c:67
int scroll_x
Definition: textarea.c:93
int caret_y
cached Y coordinate of the caret
Definition: textarea.c:130
int line
Line caret is on.
Definition: textarea.c:125
int sel_end
Character index of sel end (exclusive)
Definition: textarea.c:133
int pad_right
Right padding, inside border, in pixels.
Definition: textarea.c:103
int byte_off
Character index of caret on line.
Definition: textarea.c:126
plot_font_style_t fstyle
Text style, inc.
Definition: textarea.c:113
unsigned int lines_alloc_size
Number of LINE_CHUNK_SIZEs.
Definition: textarea.c:142
int h_extent
Width of content in px.
Definition: textarea.c:135
int vis_height
Visible height, in pixels.
Definition: textarea.c:100
unsigned int flags
Textarea flags.
Definition: textarea.c:98
struct textarea_utf8 text
Textarea text content.
Definition: textarea.c:117
textarea_client_callback callback
Callback function for messages to client.
Definition: textarea.c:145
int pad_left
Left padding, inside border, in pixels.
Definition: textarea.c:105
int border_width
Border width, in pixels.
Definition: textarea.c:107
int text_y_offset_baseline
Vertical dist to 1st baseline.
Definition: textarea.c:111
void * data
Client data for callback.
Definition: textarea.c:147
plot_font_style_t sel_fstyle
Selected text style.
Definition: textarea.c:114
int pad_bottom
Bottom padding, inside border, in pixels.
Definition: textarea.c:104
int line_count
Count of lines.
Definition: textarea.c:138
int vis_width
Visible width, in pixels.
Definition: textarea.c:99
colour border_col
Border colour.
Definition: textarea.c:108
struct scrollbar * bar_x
Horizontal scroll.
Definition: textarea.c:95
int drag_start
Byte offset of drag start (in ta->show)
Definition: textarea.c:149
struct textarea_utf8 * show
Points at .text or .password.
Definition: textarea.c:122
struct textarea_undo undo
Undo/redo information.
Definition: textarea.c:152
struct scrollbar * bar_y
Vertical scroll.
Definition: textarea.c:96
struct textarea_utf8 password
Text for obscured display.
Definition: textarea.c:120
int text_y_offset
Vertical dist to 1st line top.
Definition: textarea.c:110
struct line_info * lines
Line info array.
Definition: textarea.c:141
struct textarea_drag drag_info
Drag information.
Definition: textarea.c:150
int scroll_y
scroll offsets for the textarea
Definition: textarea.c:93
int v_extent
Height of content in px.
Definition: textarea.c:136
struct textarea::@78 caret_pos
int sel_start
Character index of sel start (inclusive)
Definition: textarea.c:132
int pad_top
Top padding, inside border, in pixels.
Definition: textarea.c:102
int line_height
Line height obtained from style.
Definition: textarea.c:115
int caret_x
cached X coordinate of the caret
Definition: textarea.c:129
uint32_t colour
Colour type: XBGR.
Definition: types.h:35
size_t utf8_prev(const char *s, size_t o)
Find previous legal UTF-8 char in string.
Definition: utf8.c:117
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_bounded_length(const char *s, size_t l)
Calculated the length (in characters) of a bounded UTF-8 string.
Definition: utf8.c:80
size_t utf8_length(const char *s)
Calculate the length (in characters) of a NULL-terminated UTF-8 string.
Definition: utf8.c:74
size_t utf8_bounded_byte_length(const char *s, size_t l, size_t c)
Calculate the length (in bytes) of a bounded UTF-8 string.
Definition: utf8.c:93
UTF-8 manipulation functions (interface).
Interface to a number of general purpose functionality.
#define min(x, y)
Definition: utils.h:46
#define max(x, y)
Definition: utils.h:50
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
static nserror clip(const struct redraw_context *ctx, const struct rect *clip)
Sets a clip rectangle for subsequent plot operations.
Definition: plot.c:357