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"
32 #include "netsurf/browser_window.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"
42 #include "desktop/gui_internal.h"
43 
44 #define CARET_COLOR 0x0000FF
45 #define TA_ALLOC_STEP 512
46 
47 static plot_style_t pstyle_stroke_caret = {
48 	.stroke_type = PLOT_OP_TYPE_SOLID,
49 	.stroke_colour = CARET_COLOR,
50 	.stroke_width = plot_style_int_to_fixed(1),
51 };
52 
53 struct 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 };
58 struct textarea_drag {
59 	textarea_drag_type type;
60 	union {
61 		struct scrollbar* scrollbar;
62 	} data;
63 };
64 
65 struct textarea_utf8 {
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 
73 struct textarea_undo_detail {
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 
82 struct textarea_undo {
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 
88 	struct textarea_utf8 text;
89 };
90 
91 struct 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 */
127 	} caret_pos;
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 */
145 	textarea_client_callback callback;
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  */
textarea_normalise_text(struct textarea * ta,unsigned int b_start,unsigned int b_len)166 static 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;
200 	msg.type = TEXTAREA_MSG_TEXT_MODIFIED;
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  */
textarea_reset_selection(struct textarea * ta)214 static 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  */
textarea_get_caret(struct textarea * ta)226 static int textarea_get_caret(struct textarea *ta)
227 {
228 	/* Ensure caret isn't hidden */
229 	if (ta->caret_pos.byte_off < 0)
230 		textarea_set_caret(ta, 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 */
241 	return ta->lines[ta->caret_pos.line].b_start + ta->caret_pos.byte_off;
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  */
textarea_scroll_visible(struct textarea * ta)251 static bool textarea_scroll_visible(struct textarea *ta)
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 &&
283 			ta->flags & TEXTAREA_MULTILINE) {
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);
292 			xs = scrollbar_get_offset(ta->bar_x);
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 -
309 				ta->pad_top - ta->pad_bottom;
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);
328 			ys = scrollbar_get_offset(ta->bar_y);
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  */
textarea_set_caret_internal(struct textarea * ta,int caret_b)352 static 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 &&
368 			ta->flags & TEXTAREA_INTERNAL_CARET) {
369 		x0 = ta->caret_x - ta->scroll_x;
370 		y0 = ta->caret_y - ta->scroll_y;
371 		width = 2;
372 		height = ta->line_height;
373 
374 		msg.ta = ta;
375 		msg.type = TEXTAREA_MSG_REDRAW_REQUEST;
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 
407 		guit->layout->width(&ta->fstyle,
408 				ta->show->data +
409 				ta->lines[ta->caret_pos.line].b_start,
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;
415 		y = ta->line_height * ta->caret_pos.line + ta->text_y_offset;
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;
436 				msg.type = TEXTAREA_MSG_REDRAW_REQUEST;
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;
449 			msg.type = TEXTAREA_MSG_REDRAW_REQUEST;
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;
471 			msg.type = TEXTAREA_MSG_CARET_UPDATE;
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;
484 		msg.type = TEXTAREA_MSG_CARET_UPDATE;
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  */
textarea_select(struct textarea * ta,int b_start,int b_end,bool force_redraw)503 static 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) {
511 		textarea_clear_selection(ta);
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;
527 	msg.type = TEXTAREA_MSG_REDRAW_REQUEST;
528 	msg.data.redraw.x0 = ta->border_width;
529 	msg.data.redraw.x1 = ta->vis_width - ta->border_width -
530 			((ta->bar_y == NULL) ? 0 : SCROLLBAR_WIDTH);
531 
532 	if (force_redraw) {
533 		/* Asked to redraw everything */
534 		msg.data.redraw.y0 = ta->border_width;
535 		msg.data.redraw.y1 = ta->vis_height - ta->border_width -
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 */
586 		msg.data.redraw.y0 = max(ta->border_width,
587 				ta->line_height * line_start +
588 				ta->text_y_offset - ta->scroll_y);
589 		msg.data.redraw.y1 = min(ta->vis_height - ta->border_width -
590 				((ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH),
591 				ta->line_height * line_end + ta->text_y_offset +
592 				ta->line_height - ta->scroll_y);
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 */
602 		msg.type = TEXTAREA_MSG_SELECTION_REPORT;
603 
604 		msg.data.selection.have_selection = true;
605 		msg.data.selection.read_only = (ta->flags & TEXTAREA_READONLY);
606 
607 		ta->callback(ta->data, &msg);
608 
609 		if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) {
610 			/* Caret hidden, and client is responsible */
611 			msg.type = TEXTAREA_MSG_CARET_UPDATE;
612 			msg.data.caret.type = TEXTAREA_CARET_HIDE;
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  */
textarea_select_fragment(struct textarea * ta)628 static bool textarea_select_fragment(struct textarea * ta)
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  */
textarea_select_paragraph(struct textarea * ta)679 static bool textarea_select_paragraph(struct textarea * ta)
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  */
textarea_scrollbar_callback(void * client_data,struct scrollbar_msg_data * scrollbar_data)719 static 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) {
726 	case SCROLLBAR_MSG_MOVED:
727 		/* Scrolled; redraw everything */
728 		ta->scroll_x = scrollbar_get_offset(ta->bar_x);
729 		ta->scroll_y = scrollbar_get_offset(ta->bar_y);
730 
731 		msg.ta = ta;
732 		msg.type = TEXTAREA_MSG_REDRAW_REQUEST;
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;
759 			msg.type = TEXTAREA_MSG_CARET_UPDATE;
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 
778 	case SCROLLBAR_MSG_SCROLL_START:
779 		ta->drag_info.type = TEXTAREA_DRAG_SCROLLBAR;
780 		ta->drag_info.data.scrollbar = scrollbar_data->scrollbar;
781 
782 		msg.ta = ta;
783 		msg.type = TEXTAREA_MSG_DRAG_REPORT;
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 
790 	case SCROLLBAR_MSG_SCROLL_FINISHED:
791 		ta->drag_info.type = TEXTAREA_DRAG_NONE;
792 
793 		msg.ta = ta;
794 		msg.type = TEXTAREA_MSG_DRAG_REPORT;
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  */
textarea_reflow_singleline(struct textarea * ta,size_t b_off,struct rect * r)816 static 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 		}
834 		ta->lines_alloc_size = LINE_CHUNK_SIZE;
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] =
867 						PASSWORD_REPLACEMENT[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  */
textarea_reflow_multiline(struct textarea * ta,const size_t b_start,const int b_length,struct rect * r)916 static 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 		}
943 		ta->lines_alloc_size = LINE_CHUNK_SIZE;
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,
1034 						ta, textarea_scrollbar_callback,
1035 						     &(ta->bar_x)) != NSERROR_OK) {
1036 					return false;
1037 				}
1038 				if (ta->bar_y != NULL)
1039 					scrollbar_make_pair(ta->bar_x,
1040 							ta->bar_y);
1041 				ta->pad_bottom += SCROLLBAR_WIDTH;
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 +
1063 						LINE_CHUNK_SIZE;
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 */
1112 			scrollbar_destroy(ta->bar_x);
1113 			ta->bar_x = NULL;
1114 			ta->pad_bottom -= SCROLLBAR_WIDTH;
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,
1126 					     ta, textarea_scrollbar_callback,
1127 					     &(ta->bar_y)) != NSERROR_OK) {
1128 				return false;
1129 			}
1130 			if (ta->bar_x != NULL)
1131 				scrollbar_make_pair(ta->bar_x,
1132 						ta->bar_y);
1133 			ta->pad_right += SCROLLBAR_WIDTH;
1134 			restart = true;
1135 
1136 		} else if (line <= scroll_lines && ta->bar_y != NULL) {
1137 			/* Remove vertical scrollbar */
1138 			scrollbar_destroy(ta->bar_y);
1139 			ta->bar_y = NULL;
1140 			ta->pad_right -= SCROLLBAR_WIDTH;
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;
1161 		scrollbar_set_extents(ta->bar_y, h,
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  */
textarea_get_b_off_xy(struct textarea * ta,int x,int y,bool visible)1232 static 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  */
textarea_set_caret_xy(struct textarea * ta,int x,int y,bool visible)1289 static 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  */
textarea_insert_text(struct textarea * ta,const char * text,size_t b_off,size_t b_len,int * byte_delta,struct rect * r)1311 static 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);
1326 		b_off = utf8_bounded_byte_length(ta->text.data,
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 +
1336 				TA_ALLOC_STEP);
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  */
textarea_char_to_byte_offset(struct textarea_utf8 * text,unsigned int start,unsigned int end,size_t * b_start,size_t * b_end)1390 static inline void textarea_char_to_byte_offset(struct textarea_utf8 *text,
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  */
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)1424 static 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;
1476 		c_end += utf8_bounded_length(ta->password.data + b_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) +
1486 					TA_ALLOC_STEP);
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) +
1494 				TA_ALLOC_STEP;
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  */
textarea_copy_to_undo_buffer(struct textarea * ta,size_t b_start,size_t b_end,size_t rep_len)1543 static bool textarea_copy_to_undo_buffer(struct textarea *ta,
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 iff 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  */
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)1618 static 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. */
1626 		if (textarea_copy_to_undo_buffer(ta, b_start, b_end,
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 */
1641 		ta->undo.details[ta->undo.next_detail].b_text_end =
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  */
textarea_undo(struct textarea * ta,bool forward,unsigned int * caret,struct rect * r)1660 static 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 
1670 	if (ta->flags & TEXTAREA_PASSWORD || ta->flags & TEXTAREA_READONLY)
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 */
1707 	textarea_replace_text_internal(ta,
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  */
textarea_drag_end(struct textarea * ta,browser_mouse_state mouse,int x,int y)1744 static 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) {
1753 	case TEXTAREA_DRAG_SCROLLBAR:
1754 		if (ta->drag_info.data.scrollbar == ta->bar_x) {
1755 			x -= ta->border_width;
1756 			y -= ta->vis_height - ta->border_width -
1757 					SCROLLBAR_WIDTH;
1758 		} else {
1759 			x -= ta->vis_width - ta->border_width -
1760 					SCROLLBAR_WIDTH;
1761 			y -= ta->border_width;
1762 		}
1763 		scrollbar_mouse_drag_end(ta->drag_info.data.scrollbar,
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 
1770 	case TEXTAREA_DRAG_SELECTION:
1771 		ta->drag_info.type = TEXTAREA_DRAG_NONE;
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;
1788 	msg.type = TEXTAREA_MSG_DRAG_REPORT;
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  */
textarea_setup_text_offsets(struct textarea * ta)1802 static void textarea_setup_text_offsets(struct textarea *ta)
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  */
textarea_set_text_style(struct textarea * ta,const plot_font_style_t * fstyle,colour selected_text,colour selected_bg)1837 static void textarea_set_text_style(
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 */
textarea_create(const textarea_flags flags,const textarea_setup * setup,textarea_client_callback callback,void * data)1852 struct textarea *textarea_create(const textarea_flags flags,
1853 		const textarea_setup *setup,
1854 		textarea_client_callback callback, void *data)
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 
1889 	textarea_set_text_style(ret,
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;
1901 	ret->drag_info.type = TEXTAREA_DRAG_NONE;
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';
1934 		ret->password.alloc = TA_ALLOC_STEP;
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 
1963 	textarea_setup_text_offsets(ret);
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 */
textarea_destroy(struct textarea * ta)1975 void textarea_destroy(struct textarea *ta)
1976 {
1977 	if (ta->bar_x)
1978 		scrollbar_destroy(ta->bar_x);
1979 	if (ta->bar_y)
1980 		scrollbar_destroy(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 */
textarea_set_text(struct textarea * ta,const char * text)1995 bool 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 */
textarea_drop_text(struct textarea * ta,const char * text,size_t text_length)2032 bool 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;
2065 	msg.type = TEXTAREA_MSG_REDRAW_REQUEST;
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 */
textarea_get_text(struct textarea * ta,char * buf,unsigned int len)2078 int 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 */
textarea_data(struct textarea * ta,unsigned int * len)2101 const 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 */
textarea_set_caret(struct textarea * ta,int caret)2112 bool textarea_set_caret(struct textarea *ta, int caret)
2113 {
2114 	int b_off;
2115 
2116 	if (caret < 0) {
2117 		textarea_set_caret_internal(ta, -1);
2118 	} else if (caret == 0) {
2119 		textarea_set_caret_internal(ta, 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 */
textarea_redraw(struct textarea * ta,int x,int y,colour bg,float scale,const struct rect * clip,const struct redraw_context * ctx)2131 void 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 = {
2146 		.stroke_type = PLOT_OP_TYPE_NONE,
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) {
2432 		scrollbar_redraw(ta->bar_x,
2433 				x / scale + ta->border_width,
2434 				y / scale + ta->vis_height - ta->border_width -
2435 						SCROLLBAR_WIDTH,
2436 				clip, scale, ctx);
2437 	}
2438 
2439 	if (ta->bar_y != NULL) {
2440 		scrollbar_redraw(ta->bar_y,
2441 				x / scale + ta->vis_width - ta->border_width -
2442 						SCROLLBAR_WIDTH,
2443 				y / scale + ta->border_width,
2444 				clip, scale, ctx);
2445 	}
2446 }
2447 
2448 
2449 /* exported interface, documented in textarea.h */
textarea_keypress(struct textarea * ta,uint32_t key)2450 bool 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, 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 = 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;
2486 			textarea_reset_selection(ta);
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;
2499 		case NS_KEY_COPY_SELECTION:
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;
2518 				textarea_reset_selection(ta);
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;
2528 		case NS_KEY_DELETE_RIGHT:
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;
2539 				textarea_reset_selection(ta);
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;
2565 				textarea_reset_selection(ta);
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;
2596 				textarea_reset_selection(ta);
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;
2610 		case NS_KEY_CUT_SELECTION:
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;
2622 				textarea_reset_selection(ta);
2623 			}
2624 			break;
2625 		case NS_KEY_ESCAPE:
2626 			/* Fall through to NS_KEY_CLEAR_SELECTION */
2627 		case 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) {
2635 				textarea_clear_selection(ta);
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) {
2645 				textarea_clear_selection(ta);
2646 			}
2647 			break;
2648 		case NS_KEY_UP:
2649 			if (readonly)
2650 				break;
2651 			if (ta->sel_start != -1) {
2652 				textarea_clear_selection(ta);
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) {
2672 				textarea_clear_selection(ta);
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) {
2709 				textarea_clear_selection(ta);
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) {
2724 				textarea_clear_selection(ta);
2725 			}
2726 			break;
2727 		case NS_KEY_TEXT_START:
2728 			if (readonly)
2729 				break;
2730 			caret = 0;
2731 			if (ta->sel_start != -1) {
2732 				textarea_clear_selection(ta);
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) {
2740 				textarea_clear_selection(ta);
2741 			}
2742 			break;
2743 		case NS_KEY_WORD_LEFT:
2744 			if (readonly)
2745 				break;
2746 			if (caret == 0)
2747 				break;
2748 			caret--;
2749 			while (strchr(sep, ta->show->data[caret]) != NULL &&
2750 					caret > 0)
2751 				caret--;
2752 			for (; caret > 0; caret--) {
2753 				if (strchr(sep, ta->show->data[caret])
2754 						!= NULL) {
2755 					caret++;
2756 					break;
2757 				}
2758 			}
2759 			if (ta->sel_start != -1) {
2760 				textarea_clear_selection(ta);
2761 			}
2762 			break;
2763 		case NS_KEY_WORD_RIGHT:
2764 			if (readonly)
2765 				break;
2766 			if (strchr(sep, ta->show->data[caret]) != NULL &&
2767 					caret < ta->show->len - 1) {
2768 				while (strchr(sep, ta->show->data[caret]) !=
2769 						NULL &&
2770 						caret < ta->show->len - 1) {
2771 					caret++;
2772 				}
2773 				break;
2774 			}
2775 			for (; caret < ta->show->len - 1; caret++) {
2776 				if (strchr(sep, ta->show->data[caret]) != NULL)
2777 					break;
2778 			}
2779 			while (strchr(sep, ta->show->data[caret]) != NULL &&
2780 					caret < ta->show->len - 1)
2781 				caret++;
2782 			if (ta->sel_start != -1) {
2783 				textarea_clear_selection(ta);
2784 			}
2785 			break;
2786 		case NS_KEY_DELETE_LINE:
2787 			if (readonly)
2788 				break;
2789 			if (ta->sel_start != -1) {
2790 				if (!textarea_replace_text(ta,
2791 						ta->sel_start, ta->sel_end,
2792 						"", 0, false, &byte_delta, &r))
2793 					return false;
2794 				redraw = true;
2795 				caret = ta->sel_end;
2796 				textarea_reset_selection(ta);
2797 			} else {
2798 				if (ta->lines[line].b_length != 0) {
2799 					/* Delete line */
2800 					caret = ta->lines[line].b_start;
2801 					b_len = ta->lines[line].b_length;
2802 					if (!textarea_replace_text(ta, caret,
2803 							caret + b_len, "", 0,
2804 							false, &byte_delta, &r))
2805 						return false;
2806 					caret = caret + b_len;
2807 				} else if (caret < ta->show->len - 1) {
2808 					/* Delete blank line */
2809 					if (!textarea_replace_text(ta,
2810 							caret, caret + 1, "", 0,
2811 							false, &byte_delta, &r))
2812 						return false;
2813 					caret++;
2814 				}
2815 				redraw = true;
2816 			}
2817 			caret += byte_delta;
2818 			break;
2819 		case NS_KEY_DELETE_LINE_END:
2820 			if (readonly)
2821 				break;
2822 			if (ta->sel_start != -1) {
2823 				if (!textarea_replace_text(ta,
2824 						ta->sel_start, ta->sel_end,
2825 						"", 0, false, &byte_delta, &r))
2826 					return false;
2827 				redraw = true;
2828 				caret = ta->sel_end;
2829 				textarea_reset_selection(ta);
2830 			} else {
2831 				b_len = ta->lines[line].b_length;
2832 				b_off = ta->lines[line].b_start + b_len;
2833 				if (!textarea_replace_text(ta, caret, b_off,
2834 						"", 0, false, &byte_delta, &r))
2835 					return false;
2836 				caret = b_off;
2837 				redraw = true;
2838 			}
2839 			caret += byte_delta;
2840 			break;
2841 		case NS_KEY_DELETE_LINE_START:
2842 			if (readonly)
2843 				break;
2844 			if (ta->sel_start != -1) {
2845 				if (!textarea_replace_text(ta,
2846 						ta->sel_start, ta->sel_end,
2847 						"", 0, false, &byte_delta, &r))
2848 					return false;
2849 				redraw = true;
2850 				caret = ta->sel_end;
2851 				textarea_reset_selection(ta);
2852 			} else {
2853 				if (!textarea_replace_text(ta,
2854 						caret - ta->caret_pos.byte_off,
2855 						caret, "", 0, false,
2856 						&byte_delta, &r))
2857 					return false;
2858 				redraw = true;
2859 			}
2860 			caret += byte_delta;
2861 			break;
2862 		case NS_KEY_UNDO:
2863 			if (!textarea_undo(ta, false, &caret, &r)) {
2864 				/* We consume the UNDO, even if we can't act
2865 				 * on it. */
2866 				return true;
2867 			}
2868 			if (ta->sel_start != -1) {
2869 				textarea_clear_selection(ta);
2870 			}
2871 			redraw = true;
2872 			break;
2873 		case NS_KEY_REDO:
2874 			if (!textarea_undo(ta, true, &caret, &r)) {
2875 				/* We consume the REDO, even if we can't act
2876 				 * on it. */
2877 				return true;
2878 			}
2879 			if (ta->sel_start != -1) {
2880 				textarea_clear_selection(ta);
2881 			}
2882 			redraw = true;
2883 			break;
2884 		default:
2885 			return false;
2886 	}
2887 
2888 	redraw &= !textarea_set_caret_internal(ta, caret);
2889 
2890 	/* TODO: redraw only the bit that changed */
2891 	msg.ta = ta;
2892 	msg.type = TEXTAREA_MSG_REDRAW_REQUEST;
2893 
2894 	if (bar_x != (ta->bar_x != NULL) || bar_y != (ta->bar_y != NULL) ||
2895 			h_extent != ta->h_extent || v_extent != ta->v_extent) {
2896 		/* Must redraw since scrollbars have changed */
2897 		msg.data.redraw.x0 = ta->border_width;
2898 		msg.data.redraw.y0 = ta->border_width;
2899 		msg.data.redraw.x1 = ta->vis_width - ta->border_width;
2900 		msg.data.redraw.y1 = ta->vis_height - ta->border_width;
2901 		ta->callback(ta->data, &msg);
2902 
2903 	} else if (redraw) {
2904 		msg.data.redraw = r;
2905 		ta->callback(ta->data, &msg);
2906 	}
2907 
2908 	return true;
2909 }
2910 
2911 
2912 /* Handle textarea scrollbar mouse action
2913  * Helper for textarea_mouse_action()
2914  *
2915  * \param ta	Text area
2916  * \param mouse	the mouse state at action moment
2917  * \param x	X coordinate
2918  * \param y	Y coordinate
2919  * \return textarea mouse state
2920  */
textarea_mouse_scrollbar_action(struct textarea * ta,browser_mouse_state mouse,int x,int y)2921 static textarea_mouse_status textarea_mouse_scrollbar_action(
2922 		struct textarea *ta, browser_mouse_state mouse, int x, int y)
2923 {
2924 	int sx, sy; /* xy coord offset for scrollbar */
2925 	int sl; /* scrollbar length */
2926 
2927 	assert(SCROLLBAR_MOUSE_USED == (1 << 0));
2928 	assert(TEXTAREA_MOUSE_SCR_USED == (1 << 3));
2929 
2930 	/* Existing scrollbar drag */
2931 	if (ta->drag_info.type == TEXTAREA_DRAG_SCROLLBAR) {
2932 		/* Scrollbar drag in progress; pass input to scrollbar */
2933 		if (ta->drag_info.data.scrollbar == ta->bar_x) {
2934 			x -= ta->border_width;
2935 			y -= ta->vis_height - ta->border_width -
2936 					SCROLLBAR_WIDTH;
2937 		} else {
2938 			x -= ta->vis_width - ta->border_width -
2939 					SCROLLBAR_WIDTH;
2940 			y -= ta->border_width;
2941 		}
2942 		return (scrollbar_mouse_action(ta->drag_info.data.scrollbar,
2943 				mouse, x, y) << 3);
2944 	}
2945 
2946 	/* Horizontal scrollbar */
2947 	if (ta->bar_x != NULL && ta->drag_info.type == TEXTAREA_DRAG_NONE) {
2948 		/* No drag happening, but mouse input is over scrollbar;
2949 		 * pass input to scrollbar */
2950 		sx = x - ta->border_width;
2951 		sy = y - (ta->vis_height - ta->border_width - SCROLLBAR_WIDTH);
2952 		sl = ta->vis_width - 2 * ta->border_width -
2953 				(ta->bar_y != NULL ? SCROLLBAR_WIDTH : 0);
2954 
2955 		if (sx >= 0 && sy >= 0 && sx < sl && sy < SCROLLBAR_WIDTH) {
2956 			return (scrollbar_mouse_action(ta->bar_x, mouse,
2957 					sx, sy) << 3);
2958 		}
2959 	}
2960 
2961 	/* Vertical scrollbar */
2962 	if (ta->bar_y != NULL && ta->drag_info.type == TEXTAREA_DRAG_NONE) {
2963 		/* No drag happening, but mouse input is over scrollbar;
2964 		 * pass input to scrollbar */
2965 		sx = x - (ta->vis_width - ta->border_width - SCROLLBAR_WIDTH);
2966 		sy = y - ta->border_width;
2967 		sl = ta->vis_height - 2 * ta->border_width;
2968 
2969 		if (sx >= 0 && sy >= 0 && sx < SCROLLBAR_WIDTH && sy < sl) {
2970 			return (scrollbar_mouse_action(ta->bar_y, mouse,
2971 					sx, sy) << 3);
2972 		}
2973 	}
2974 
2975 	return TEXTAREA_MOUSE_NONE;
2976 }
2977 
2978 
2979 /* exported interface, documented in textarea.h */
textarea_mouse_action(struct textarea * ta,browser_mouse_state mouse,int x,int y)2980 textarea_mouse_status textarea_mouse_action(struct textarea *ta,
2981 		browser_mouse_state mouse, int x, int y)
2982 {
2983 	int b_start, b_end;
2984 	unsigned int b_off;
2985 	struct textarea_msg msg;
2986 	textarea_mouse_status status = TEXTAREA_MOUSE_NONE;
2987 
2988 	if (ta->drag_info.type != TEXTAREA_DRAG_NONE &&
2989 			mouse == BROWSER_MOUSE_HOVER) {
2990 		/* There is a drag that we must end */
2991 		textarea_drag_end(ta, mouse, x, y);
2992 	}
2993 
2994 	/* Mouse action might be a scrollbar's responsibility */
2995 	status = textarea_mouse_scrollbar_action(ta, mouse, x, y);
2996 	if (status != TEXTAREA_MOUSE_NONE) {
2997 		/* Mouse action was handled by a scrollbar */
2998 		return status;
2999 	}
3000 
3001 	/* Might be outside textarea, and not dragging */
3002 	if ((x >= ta->vis_width || y >= ta->vis_height) &&
3003 			ta->drag_info.type == TEXTAREA_DRAG_NONE &&
3004 			ta->flags & TEXTAREA_MULTILINE) {
3005 		return status;
3006 	}
3007 
3008 	status |= TEXTAREA_MOUSE_EDITOR;
3009 
3010 	/* Mouse action is textarea's responsibility */
3011 	if (mouse & BROWSER_MOUSE_DOUBLE_CLICK) {
3012 		/* Select word */
3013 		textarea_set_caret_xy(ta, x, y, true);
3014 		textarea_select_fragment(ta);
3015 		status |= TEXTAREA_MOUSE_USED;
3016 
3017 	} else if (mouse & BROWSER_MOUSE_TRIPLE_CLICK) {
3018 		/* Select paragraph */
3019 		textarea_set_caret_xy(ta, x, y, true);
3020 		textarea_select_paragraph(ta);
3021 		status |= TEXTAREA_MOUSE_USED;
3022 
3023 	} else if (mouse & BROWSER_MOUSE_PRESS_1) {
3024 		/* Place caret */
3025 		b_off = textarea_get_b_off_xy(ta, x, y, true);
3026 		ta->drag_start = b_off;
3027 
3028 		textarea_set_caret_internal(ta, b_off);
3029 		if (ta->sel_start != -1) {
3030 			/* Clear selection */
3031 			textarea_clear_selection(ta);
3032 		}
3033 		status |= TEXTAREA_MOUSE_USED;
3034 
3035 	} else if (mouse & BROWSER_MOUSE_PRESS_2) {
3036 		b_off = textarea_get_b_off_xy(ta, x, y, true);
3037 
3038 		if (ta->sel_start != -1) {
3039 			/* Adjust selection */
3040 			b_start = (ta->sel_end - ta->sel_start) / 2 +
3041 					ta->sel_start;
3042 			b_start = ((unsigned)b_start > b_off) ?
3043 					ta->sel_end : ta->sel_start;
3044 			ta->drag_start = b_start;
3045 			textarea_select(ta, b_start, b_off, false);
3046 		} else {
3047 			/* Select to caret */
3048 			b_start = textarea_get_caret(ta);
3049 			ta->drag_start = b_start;
3050 			textarea_select(ta, b_start, b_off, false);
3051 		}
3052 		status |= TEXTAREA_MOUSE_USED;
3053 
3054 	} else if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) {
3055 		/* Selection start */
3056 		b_off = textarea_get_b_off_xy(ta, x, y, true);
3057 		b_start = ta->drag_start;
3058 		b_end = b_off;
3059 		ta->drag_info.type = TEXTAREA_DRAG_SELECTION;
3060 
3061 		msg.ta = ta;
3062 		msg.type = TEXTAREA_MSG_DRAG_REPORT;
3063 		msg.data.drag = ta->drag_info.type;
3064 
3065 		ta->callback(ta->data, &msg);
3066 
3067 		textarea_select(ta, b_start, b_end, false);
3068 		status |= TEXTAREA_MOUSE_USED;
3069 
3070 	} else if (mouse &
3071 			(BROWSER_MOUSE_HOLDING_1 | BROWSER_MOUSE_HOLDING_2) &&
3072 			ta->drag_info.type == TEXTAREA_DRAG_SELECTION) {
3073 		/* Selection track */
3074 		int scrx = 0;
3075 		int scry = 0;
3076 		int w, h;
3077 		bool need_redraw = false;
3078 
3079 		b_off = textarea_get_b_off_xy(ta, x, y, true);
3080 		b_start = ta->drag_start;
3081 		b_end = b_off;
3082 
3083 		w = ta->vis_width - ta->border_width -
3084 				((ta->bar_y == NULL) ? 0 : SCROLLBAR_WIDTH);
3085 		h = ta->vis_height - ta->border_width -
3086 				((ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH);
3087 
3088 		/* selection auto-scroll */
3089 		if (x < ta->border_width)
3090 			scrx = (x - ta->border_width) / 4;
3091 		else if (x > w)
3092 			scrx = (x - w) / 4;
3093 
3094 		if (y < ta->border_width)
3095 			scry = (y - ta->border_width) / 4;
3096 		else if (y > h)
3097 			scry = (y - h) / 4;
3098 
3099 		if (scrx || scry)
3100 			need_redraw = textarea_scroll(ta, scrx, scry);
3101 
3102 		textarea_select(ta, b_start, b_end, need_redraw);
3103 		status |= TEXTAREA_MOUSE_USED;
3104 	}
3105 
3106 	if (ta->sel_start != -1) {
3107 		/* Have selection */
3108 		status |= TEXTAREA_MOUSE_SELECTION;
3109 	}
3110 
3111 	return status;
3112 }
3113 
3114 
3115 /* exported interface, documented in textarea.h */
textarea_clear_selection(struct textarea * ta)3116 bool textarea_clear_selection(struct textarea *ta)
3117 {
3118 	struct textarea_msg msg;
3119 	int line_end, line_start = 0;
3120 
3121 	if (ta->sel_start == -1)
3122 		/* No selection to clear */
3123 		return false;
3124 
3125 	/* Find selection start & end lines */
3126 	for (line_end = 0; line_end < ta->line_count - 1; line_end++)
3127 		if (ta->lines[line_end + 1].b_start > (unsigned)ta->sel_start) {
3128 			line_start = line_end;
3129 			break;
3130 		}
3131 	for (; line_end < ta->line_count - 1; line_end++)
3132 		if (ta->lines[line_end + 1].b_start > (unsigned)ta->sel_end)
3133 			break;
3134 
3135 	/* Clear selection and redraw */
3136 	textarea_reset_selection(ta);
3137 
3138 	msg.ta = ta;
3139 	msg.type = TEXTAREA_MSG_REDRAW_REQUEST;
3140 	msg.data.redraw.x0 = ta->border_width;
3141 	msg.data.redraw.y0 = max(ta->border_width,
3142 			ta->line_height * line_start +
3143 			ta->text_y_offset - ta->scroll_y);
3144 	msg.data.redraw.x1 = ta->vis_width - ta->border_width -
3145 			((ta->bar_y == NULL) ? 0 : SCROLLBAR_WIDTH);
3146 	msg.data.redraw.y1 = min(ta->vis_height - ta->border_width -
3147 			((ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH),
3148 			ta->line_height * line_end + ta->text_y_offset +
3149 			ta->line_height - ta->scroll_y);
3150 
3151 	ta->callback(ta->data, &msg);
3152 
3153 	/* No more selection */
3154 	msg.type = TEXTAREA_MSG_SELECTION_REPORT;
3155 
3156 	msg.data.selection.have_selection = false;
3157 	msg.data.selection.read_only = (ta->flags & TEXTAREA_READONLY);
3158 
3159 	ta->callback(ta->data, &msg);
3160 
3161 	if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) {
3162 		/* Tell client where caret should be placed */
3163 		struct rect cr = {
3164 			.x0 = ta->border_width,
3165 			.y0 = ta->border_width,
3166 			.x1 = ta->vis_width - ta->border_width -
3167 					((ta->bar_y == NULL) ?
3168 					0 : SCROLLBAR_WIDTH),
3169 			.y1 = ta->vis_height - ta->border_width -
3170 					((ta->bar_x == NULL) ?
3171 					0 : SCROLLBAR_WIDTH)
3172 		};
3173 		msg.ta = ta;
3174 		msg.type = TEXTAREA_MSG_CARET_UPDATE;
3175 		msg.data.caret.type = TEXTAREA_CARET_SET_POS;
3176 		msg.data.caret.pos.x = ta->caret_x - ta->scroll_x;
3177 		msg.data.caret.pos.y = ta->caret_y - ta->scroll_y;
3178 		msg.data.caret.pos.height = ta->line_height;
3179 		msg.data.caret.pos.clip = &cr;
3180 
3181 		ta->callback(ta->data, &msg);
3182 	}
3183 
3184 	return true;
3185 }
3186 
3187 
3188 /* exported interface, documented in textarea.h */
textarea_get_selection(struct textarea * ta)3189 char *textarea_get_selection(struct textarea *ta)
3190 {
3191 	char *ret;
3192 	size_t b_start, b_end, b_len;
3193 
3194 	if (ta->sel_start == -1)
3195 		/* No selection get */
3196 		return NULL;
3197 
3198 	b_start = ta->sel_start;
3199 	b_end = ta->sel_end;
3200 	b_len = b_end - b_start;
3201 
3202 	if (b_len == 0)
3203 		/* No selection get */
3204 		return NULL;
3205 
3206 	ret = malloc(b_len + 1); /* Add space for '\0' */
3207 	if (ret == NULL)
3208 		/* Can't get selection; no memory */
3209 		return NULL;
3210 
3211 	memcpy(ret, ta->show->data + b_start, b_len);
3212 	ret[b_len] = '\0';
3213 
3214 	return ret;
3215 }
3216 
3217 
3218 /* exported interface, documented in textarea.h */
textarea_get_dimensions(struct textarea * ta,int * width,int * height)3219 void textarea_get_dimensions(struct textarea *ta, int *width, int *height)
3220 {
3221 	if (width != NULL)
3222 		*width = ta->vis_width;
3223 	if (height != NULL)
3224 		*height = ta->vis_height;
3225 }
3226 
3227 
3228 /* exported interface, documented in textarea.h */
textarea_set_dimensions(struct textarea * ta,int width,int height)3229 void textarea_set_dimensions(struct textarea *ta, int width, int height)
3230 {
3231 	struct rect r = {0, 0, 0, 0};
3232 
3233 	ta->vis_width = width;
3234 	ta->vis_height = height;
3235 
3236 	textarea_setup_text_offsets(ta);
3237 
3238 	if (ta->flags & TEXTAREA_MULTILINE) {
3239 		 textarea_reflow_multiline(ta, 0, ta->show->len -1, &r);
3240 	} else {
3241 		 textarea_reflow_singleline(ta, 0, &r);
3242 	}
3243 }
3244 
3245 
3246 /* exported interface, documented in textarea.h */
textarea_set_layout(struct textarea * ta,const plot_font_style_t * fstyle,int width,int height,int top,int right,int bottom,int left)3247 void textarea_set_layout(
3248 		struct textarea *ta,
3249 		const plot_font_style_t *fstyle,
3250 		int width, int height,
3251 		int top, int right,
3252 		int bottom, int left)
3253 {
3254 	struct rect r = {0, 0, 0, 0};
3255 
3256 	ta->vis_width = width;
3257 	ta->vis_height = height;
3258 	ta->pad_top = top;
3259 	ta->pad_right = right + ((ta->bar_y == NULL) ? 0 : SCROLLBAR_WIDTH);
3260 	ta->pad_bottom = bottom + ((ta->bar_x == NULL) ? 0 : SCROLLBAR_WIDTH);
3261 	ta->pad_left = left;
3262 
3263 	textarea_set_text_style(ta, fstyle,
3264 			ta->sel_fstyle.foreground,
3265 			ta->sel_fstyle.background);
3266 
3267 	textarea_setup_text_offsets(ta);
3268 
3269 	if (ta->flags & TEXTAREA_MULTILINE) {
3270 		 textarea_reflow_multiline(ta, 0, ta->show->len -1, &r);
3271 	} else {
3272 		 textarea_reflow_singleline(ta, 0, &r);
3273 	}
3274 }
3275 
3276 
3277 /* exported interface, documented in textarea.h */
textarea_scroll(struct textarea * ta,int scrx,int scry)3278 bool textarea_scroll(struct textarea *ta, int scrx, int scry)
3279 {
3280 	bool handled_scroll = false;
3281 
3282 	if (ta->flags & TEXTAREA_MULTILINE) {
3283 		/* Multi line textareas have scrollbars to handle this */
3284 		if (ta->bar_x != NULL && scrx != 0 &&
3285 				scrollbar_scroll(ta->bar_x, scrx))
3286 			handled_scroll = true;
3287 		if (ta->bar_y != NULL && scry != 0 &&
3288 				scrollbar_scroll(ta->bar_y, scry))
3289 			handled_scroll = true;
3290 
3291 	} else {
3292 		/* Single line.  Can only scroll horizontally. */
3293 		int xs = ta->scroll_x;
3294 
3295 		/* Apply offset */
3296 		xs += scrx;
3297 
3298 		/* Clamp to limits */
3299 		if (xs < 0)
3300 			xs = 0;
3301 		else if (xs > ta->h_extent - ta->vis_width - ta->border_width)
3302 			xs = ta->h_extent - ta->vis_width - ta->border_width;
3303 
3304 		if (xs != ta->scroll_x) {
3305 			ta->scroll_x = xs;
3306 			handled_scroll = true;
3307 		}
3308 	}
3309 
3310 	return handled_scroll;
3311 }
3312