1 /* Links viewing/manipulation handling */
2 
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6 
7 #include <stdlib.h>
8 #include <string.h>
9 
10 #include "elinks.h"
11 
12 #include "bfu/listmenu.h"
13 #include "bfu/menu.h"
14 #include "dialogs/menu.h"
15 #include "dialogs/status.h"
16 #include "document/document.h"
17 #include "document/forms.h"
18 #include "document/html/parser.h"
19 #include "document/html/renderer.h"
20 #include "document/options.h"
21 #include "document/view.h"
22 #include "ecmascript/ecmascript.h"
23 #include "intl/gettext/libintl.h"
24 #include "main/object.h"
25 #include "protocol/uri.h"
26 #include "session/session.h"
27 #include "session/task.h"
28 #include "terminal/color.h"
29 #include "terminal/draw.h"
30 #include "terminal/kbd.h"
31 #include "terminal/screen.h"
32 #include "terminal/tab.h"
33 #include "terminal/terminal.h"
34 #include "util/conv.h"
35 #include "util/error.h"
36 #include "util/memory.h"
37 #include "util/string.h"
38 #include "viewer/action.h"
39 #include "viewer/text/form.h"
40 #include "viewer/text/link.h"
41 #include "viewer/text/search.h"
42 #include "viewer/text/textarea.h"
43 #include "viewer/text/view.h"
44 #include "viewer/text/vs.h"
45 
46 
47 /* Perhaps some of these would be more fun to have in viewer/common/, dunno.
48  * --pasky */
49 
50 
51 static int
current_link_evhook(struct document_view * doc_view,enum script_event_hook_type type)52 current_link_evhook(struct document_view *doc_view, enum script_event_hook_type type)
53 {
54 #ifdef CONFIG_ECMASCRIPT
55 	struct link *link;
56 	struct script_event_hook *evhook;
57 
58 	assert(doc_view && doc_view->vs);
59 	link = get_current_link(doc_view);
60 	if (!link) return -1;
61 	if (!link->event_hooks) return -1;
62 
63 	if (!doc_view->vs->ecmascript) return -1;
64 
65 	foreach (evhook, *link->event_hooks) {
66 		struct string src = INIT_STRING(evhook->src, strlen(evhook->src));
67 
68 		if (evhook->type != type) continue;
69 		/* TODO: Some even handlers return a bool. */
70 		if (!ecmascript_eval_boolback(doc_view->vs->ecmascript, &src))
71 			return 0;
72 	}
73 
74 	return 1;
75 #else
76 	return -1;
77 #endif
78 }
79 
80 #define current_link_hover(dv) \
81 do { \
82 	current_link_evhook(dv, SEVHOOK_ONMOUSEOVER); \
83 	current_link_evhook(dv, SEVHOOK_ONHOVER); \
84 	current_link_evhook(dv, SEVHOOK_ONFOCUS); \
85 } while (0)
86 #define current_link_blur(dv) \
87 do { \
88 	current_link_evhook(dv, SEVHOOK_ONMOUSEOUT); \
89 	current_link_evhook(dv, SEVHOOK_ONBLUR); \
90 } while (0)
91 
92 
93 void
set_link(struct document_view * doc_view)94 set_link(struct document_view *doc_view)
95 {
96 	assert(doc_view);
97 	if_assert_failed return;
98 
99 	if (current_link_is_visible(doc_view)) return;
100 
101 	find_link_page_down(doc_view);
102 }
103 
104 static inline int
get_link_cursor_offset(struct document_view * doc_view,struct link * link)105 get_link_cursor_offset(struct document_view *doc_view, struct link *link)
106 {
107 	struct form_control *fc;
108 	struct form_state *fs;
109 
110 	switch (link->type) {
111 		case LINK_CHECKBOX:
112 			return 1;
113 
114 		case LINK_BUTTON:
115 			return 2;
116 
117 		case LINK_FIELD:
118 			fc = get_link_form_control(link);
119 			fs = find_form_state(doc_view, fc);
120 			return fs ? fs->state - fs->vpos : 0;
121 
122 		case LINK_AREA:
123 			fc = get_link_form_control(link);
124 			fs = find_form_state(doc_view, fc);
125 			return fs ? area_cursor(fc, fs) : 0;
126 
127 		case LINK_HYPERTEXT:
128 		case LINK_MAP:
129 		case LINK_SELECT:
130 			return 0;
131 	}
132 
133 	return 0;
134 }
135 
136 /* Allocate doc_view->link_bg with enough space to save the colour
137  * and attributes of each point of the given link plus one byte
138  * for the template character. Initialise that template character
139  * with the colour and attributes appropriate for an active link. */
140 static inline struct screen_char *
init_link_drawing(struct document_view * doc_view,struct link * link,int invert)141 init_link_drawing(struct document_view *doc_view, struct link *link, int invert)
142 {
143 	struct document_options *doc_opts;
144 	struct screen_char *template;
145 	enum color_flags color_flags;
146 	enum color_mode color_mode;
147 	struct color_pair colors;
148 
149 	/* Allocate an extra background char to work on here. */
150 	doc_view->link_bg = mem_alloc((1 + link->npoints) * sizeof(*doc_view->link_bg));
151 	if (!doc_view->link_bg) return NULL;
152 
153 	doc_view->link_bg_n = link->npoints;
154 
155 	/* Setup the template char. */
156 	template = &doc_view->link_bg[link->npoints].c;
157 
158 	template->attr = SCREEN_ATTR_STANDOUT;
159 
160 	doc_opts = &doc_view->document->options;
161 
162 	color_flags = (doc_opts->color_flags | COLOR_DECREASE_LIGHTNESS);
163 	color_mode = doc_opts->color_mode;
164 
165 	if (doc_opts->active_link.underline)
166 		template->attr |= SCREEN_ATTR_UNDERLINE;
167 
168 	if (doc_opts->active_link.bold)
169 		template->attr |= SCREEN_ATTR_BOLD;
170 
171 	if (doc_opts->active_link.color) {
172 		colors.foreground = doc_opts->active_link.fg;
173 		colors.background = doc_opts->active_link.bg;
174 	} else {
175 		colors.foreground = link->color.foreground;
176 		colors.background = link->color.background;
177 	}
178 
179 	if (invert && doc_opts->active_link.invert) {
180 		swap_values(color_T, colors.foreground, colors.background);
181 
182 		/* Highlight text-input form-fields correctly if contrast
183 		 * correction is needed. */
184 		if (link_is_textinput(link)) {
185 			/* Make sure to use the options belonging to the
186 			 * current document when checking for fg and bg color
187 			 * usage. */
188 			doc_opts = &doc_view->document->options;
189 
190 			/* Are we fallen angels who didn't want to believe that
191 			 * nothing /is/ nothing and so were born to lose our
192 			 * loved ones and dear friends one by one and finally
193 			 * our own life, to see it proved? --Kerouac */
194 
195 			/* Wipe out all default correction for 16 color mode */
196 			color_flags = (color_flags & ~COLOR_INCREASE_CONTRAST);
197 			/* Make contrast correction invert things properly */
198 			color_flags |= COLOR_ENSURE_INVERTED_CONTRAST;
199 		}
200 	}
201 
202 	set_term_color(template, &colors, color_flags, color_mode);
203 
204 	return template;
205 }
206 
207 /* Save the current link's colours and attributes to doc_view->link_bg
208  * and give it the appropriate colour and attributes for an active link. */
209 void
draw_current_link(struct session * ses,struct document_view * doc_view)210 draw_current_link(struct session *ses, struct document_view *doc_view)
211 {
212 	struct terminal *term = ses->tab->term;
213 	struct screen_char *template;
214 	struct link *link;
215 	int cursor_offset;
216 	int xpos, ypos;
217 	int i;
218 
219 	assert(term && doc_view && doc_view->vs);
220 	if_assert_failed return;
221 
222 	assert(ses->tab == get_current_tab(term));
223 	if_assert_failed return;
224 
225 	assertm(!doc_view->link_bg, "link background not empty");
226 	if_assert_failed mem_free(doc_view->link_bg);
227 
228 	link = get_current_link(doc_view);
229 	if (!link) return;
230 
231 	i = !link_is_textinput(link) || ses->insert_mode == INSERT_MODE_OFF;
232 	template = init_link_drawing(doc_view, link, i);
233 	if (!template) return;
234 
235 	xpos = doc_view->box.x - doc_view->vs->x;
236 	ypos = doc_view->box.y - doc_view->vs->y;
237 
238 	if (ses->insert_mode == INSERT_MODE_OFF
239 	    && ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
240 		/* If we are navigating using cursor routing and not editing a
241 		 * text-input form-field never set the cursor. */
242 		cursor_offset = -1;
243 	} else {
244 		cursor_offset = get_link_cursor_offset(doc_view, link);
245 	}
246 
247 	for (i = 0; i < link->npoints; i++) {
248 		int x = link->points[i].x + xpos;
249 		int y = link->points[i].y + ypos;
250 		struct screen_char *co;
251 
252 		if (!is_in_box(&doc_view->box, x, y)) {
253 			doc_view->link_bg[i].x = -1;
254 			doc_view->link_bg[i].y = -1;
255 			continue;
256 		}
257 
258 		doc_view->link_bg[i].x = x;
259 		doc_view->link_bg[i].y = y;
260 
261 		co = get_char(term, x, y);
262 		copy_screen_chars(&doc_view->link_bg[i].c, co, 1);
263 
264 		if (i == cursor_offset) {
265 			int blockable = (!link_is_textinput(link)
266 					 && co->color != template->color);
267 
268 			set_cursor(term, x, y, blockable);
269 			set_window_ptr(ses->tab, x, y);
270 		}
271 
272  		template->data = co->data;
273  		copy_screen_chars(co, template, 1);
274 		set_screen_dirty(term->screen, y, y);
275 	}
276 }
277 
278 void
free_link(struct document_view * doc_view)279 free_link(struct document_view *doc_view)
280 {
281 	assert(doc_view);
282 	if_assert_failed return;
283 
284 	mem_free_set(&doc_view->link_bg, NULL);
285 	doc_view->link_bg_n = 0;
286 }
287 
288 /* Restore the colours and attributes that the active link had
289  * before it was selected. */
290 void
clear_link(struct terminal * term,struct document_view * doc_view)291 clear_link(struct terminal *term, struct document_view *doc_view)
292 {
293 	assert(term && doc_view);
294 	if_assert_failed return;
295 
296 	if (doc_view->link_bg) {
297 		struct link_bg *link_bg = doc_view->link_bg;
298 		int i;
299 
300 		for (i = doc_view->link_bg_n - 1; i >= 0; i--) {
301 			struct link_bg *bgchar = &link_bg[i];
302 
303 			if (bgchar->x != -1 && bgchar->y != -1) {
304 				struct terminal_screen *screen = term->screen;
305 				struct screen_char *co;
306 
307 				co = get_char(term, bgchar->x, bgchar->y);
308 				copy_screen_chars(co, &bgchar->c, 1);
309 				set_screen_dirty(screen, bgchar->y, bgchar->y);
310 			}
311 		}
312 
313 		free_link(doc_view);
314 	}
315 }
316 
317 struct link *
get_first_link(struct document_view * doc_view)318 get_first_link(struct document_view *doc_view)
319 {
320 	struct link *link, *undef;
321 	struct document *document;
322 	int height;
323 	int i;
324 
325 	assert(doc_view && doc_view->document);
326 	if_assert_failed return NULL;
327 
328 	document = doc_view->document;
329 
330 	if (!document->lines1) return NULL;
331 
332 	height = doc_view->vs->y + doc_view->box.height;
333 	link = undef = document->links + document->nlinks;
334 
335 	for (i = int_max(0, doc_view->vs->y);
336 	     i < int_min(height, document->height);
337 	     i++) {
338 		if (document->lines1[i]
339 		    && document->lines1[i] < link)
340 			link = document->lines1[i];
341 	}
342 
343 	return (link == undef) ? NULL : link;
344 }
345 
346 struct link *
get_last_link(struct document_view * doc_view)347 get_last_link(struct document_view *doc_view)
348 {
349 	struct link *link = NULL;
350 	struct document *document;
351 	int height;
352 	int i;
353 
354 	assert(doc_view && doc_view->document);
355 	if_assert_failed return NULL;
356 
357 	document = doc_view->document;
358 
359 	if (!document->lines2) return NULL;
360 
361 	height = doc_view->vs->y + doc_view->box.height;
362 
363 	for (i = int_max(0, doc_view->vs->y);
364 	     i < int_min(height, document->height);
365 	     i++)
366 		if (document->lines2[i] > link)
367 			link = document->lines2[i];
368 
369 	return link;
370 }
371 
372 static int
link_in_view_x(struct document_view * doc_view,struct link * link)373 link_in_view_x(struct document_view *doc_view, struct link *link)
374 {
375 	int i, dx, width;
376 
377 	assert(doc_view && link);
378 	if_assert_failed return 0;
379 
380 	dx = doc_view->vs->x;
381 	width = doc_view->box.width;
382 
383 	for (i = 0; i < link->npoints; i++) {
384 		int x = link->points[i].x - dx;
385 
386 		if (x >= 0 && x < width)
387 			return 1;
388 	}
389 
390 	return 0;
391 }
392 
393 static int
link_in_view_y(struct document_view * doc_view,struct link * link)394 link_in_view_y(struct document_view *doc_view, struct link *link)
395 {
396 	int i, dy, height;
397 
398 	assert(doc_view && link);
399 	if_assert_failed return 0;
400 
401 	dy = doc_view->vs->y;
402 	height = doc_view->box.height;
403 
404 	for (i = 0; i < link->npoints; i++) {
405 		int y = link->points[i].y - dy;
406 
407 		if (y >= 0 && y < height)
408 			return 1;
409 	}
410 
411 	return 0;
412 }
413 
414 static int
link_in_view(struct document_view * doc_view,struct link * link)415 link_in_view(struct document_view *doc_view, struct link *link)
416 {
417 	assert(doc_view && link);
418 	if_assert_failed return 0;
419 	return link_in_view_y(doc_view, link) && link_in_view_x(doc_view, link);
420 }
421 
422 int
current_link_is_visible(struct document_view * doc_view)423 current_link_is_visible(struct document_view *doc_view)
424 {
425 	struct link *link;
426 
427 	assert(doc_view && doc_view->vs);
428 	if_assert_failed return 0;
429 
430 	link = get_current_link(doc_view);
431 	return (link && link_in_view(doc_view, link));
432 }
433 
434 /* Look for the first and the last link currently visible in our
435  * viewport. */
436 static void
get_visible_links_range(struct document_view * doc_view,int * first,int * last)437 get_visible_links_range(struct document_view *doc_view, int *first, int *last)
438 {
439 	struct document *document = doc_view->document;
440 	int height = int_min(doc_view->vs->y + doc_view->box.height,
441 	                     document->height);
442 	int y;
443 
444 	*first = document->nlinks - 1;
445 	*last = 0;
446 
447 	for (y = int_max(0, doc_view->vs->y); y < height; y++) {
448 		if (document->lines1[y])
449 			int_upper_bound(first, document->lines1[y]
450 					       - document->links);
451 
452 		if (document->lines2[y])
453 			int_lower_bound(last, document->lines2[y]
454 					      - document->links);
455 	}
456 }
457 
458 static int
next_link_in_view_(struct document_view * doc_view,int current,int direction,int (* fn)(struct document_view *,struct link *),void (* cntr)(struct document_view *,struct link *))459 next_link_in_view_(struct document_view *doc_view, int current, int direction,
460 	           int (*fn)(struct document_view *, struct link *),
461 	           void (*cntr)(struct document_view *, struct link *))
462 {
463 	struct document *document;
464 	struct view_state *vs;
465 	int start, end;
466 
467 	assert(doc_view && doc_view->document && doc_view->vs && fn);
468 	if_assert_failed return 0;
469 
470 	document = doc_view->document;
471 	vs = doc_view->vs;
472 
473 	get_visible_links_range(doc_view, &start, &end);
474 
475 	current_link_blur(doc_view);
476 
477 	/* Go from the @current link in @direction until either
478 	 * fn() is happy or we would leave the current viewport. */
479 	while (current >= start && current <= end) {
480 		if (fn(doc_view, &document->links[current])) {
481 			vs->current_link = current;
482 			if (cntr) cntr(doc_view, &document->links[current]);
483 			current_link_hover(doc_view);
484 			return 1;
485 		}
486 		current += direction;
487 	}
488 
489 	vs->current_link = -1;
490 	return 0;
491 }
492 
493 int
next_link_in_view(struct document_view * doc_view,int current,int direction)494 next_link_in_view(struct document_view *doc_view, int current, int direction)
495 {
496 	return next_link_in_view_(doc_view, current, direction, link_in_view, NULL);
497 }
498 
499 int
next_link_in_view_y(struct document_view * doc_view,int current,int direction)500 next_link_in_view_y(struct document_view *doc_view, int current, int direction)
501 {
502 	return next_link_in_view_(doc_view, current, direction, link_in_view_y, set_pos_x);
503 }
504 
505 /* Get the bounding columns of @link at line @y (or all lines if @y == -1). */
506 static void
get_link_x_bounds(struct link * link,int y,int * min_x,int * max_x)507 get_link_x_bounds(struct link *link, int y, int *min_x, int *max_x)
508 {
509 	int point;
510 
511 	if (min_x) *min_x = INT_MAX;
512 	if (max_x) *max_x = 0;
513 
514 	for (point = 0; point < link->npoints; point++) {
515 		if (y >= 0 && link->points[point].y != y)
516 			continue;
517 		if (min_x) int_upper_bound(min_x, link->points[point].x);
518 		if (max_x) int_lower_bound(max_x, link->points[point].x);
519 	}
520 }
521 
522 /* Check whether there is any point between @min_x and @max_x at the line @y
523  * in link @link. */
524 static int
get_link_x_intersect(struct link * link,int y,int min_x,int max_x)525 get_link_x_intersect(struct link *link, int y, int min_x, int max_x)
526 {
527 	int point;
528 
529 	for (point = 0; point < link->npoints; point++) {
530 		if (link->points[point].y != y)
531 			continue;
532 		if (link->points[point].x >= min_x
533 		    && link->points[point].x <= max_x)
534 			return link->points[point].x + 1;
535 	}
536 
537 	return 0;
538 }
539 
540 /* Check whether there is any point between @min_y and @max_y in the column @x
541  * in link @link. */
542 static int
get_link_y_intersect(struct link * link,int x,int min_y,int max_y)543 get_link_y_intersect(struct link *link, int x, int min_y, int max_y)
544 {
545 	int point;
546 
547 	for (point = 0; point < link->npoints; point++) {
548 		if (link->points[point].x != x)
549 			continue;
550 		if (link->points[point].y >= min_y
551 		    && link->points[point].y <= max_y)
552 			return link->points[point].y + 1;
553 	}
554 
555 	return 0;
556 }
557 
558 int
next_link_in_dir(struct document_view * doc_view,int dir_x,int dir_y)559 next_link_in_dir(struct document_view *doc_view, int dir_x, int dir_y)
560 {
561 	struct document *document;
562 	struct view_state *vs;
563 	struct link *link;
564 	int min_x = INT_MAX, max_x = 0;
565 	int min_y, max_y;
566 
567 	assert(doc_view && doc_view->document && doc_view->vs);
568 	if_assert_failed return 0;
569 	assert(dir_x || dir_y);
570 	if_assert_failed return 0;
571 
572 	document = doc_view->document;
573 	vs = doc_view->vs;
574 
575 	link = get_current_link(doc_view);
576 	if (!link) return 0;
577 
578 	/* Find the link's "bounding box" coordinates. */
579 
580 	get_link_x_bounds(link, -1, &min_x, &max_x);
581 
582 	min_y = link->points[0].y;
583 	max_y = link->points[link->npoints - 1].y;
584 
585 	/* Now go from the bounding box edge in the appropriate
586 	 * direction and find the nearest link. */
587 
588 	if (dir_y) {
589 		/* Vertical movement */
590 
591 		/* The current line number */
592 		int y = (dir_y > 0 ? max_y : min_y) + dir_y;
593 		/* The bounding line numbers */
594 		int top = int_max(0, doc_view->vs->y);
595 		int bottom = int_min(doc_view->vs->y + doc_view->box.height,
596 				     document->height);
597 
598 		for (; dir_y > 0 ? y < bottom : y >= top; y += dir_y) {
599 			/* @backup points to the nearest link from the left
600 			 * to the desired position. */
601 			struct link *backup = NULL;
602 
603 			link = document->lines1[y];
604 			if (!link) continue;
605 
606 			/* Go through all the links on line. */
607 			for (; link <= document->lines2[y]; link++) {
608 				int l_min_x, l_max_x;
609 
610 				/* Some links can be totally out of order here,
611 				 * ie. in tables or when using tabindex. */
612 				if (y < link->points[0].y
613 				    || y > link->points[link->npoints - 1].y)
614 					continue;
615 
616 				get_link_x_bounds(link, y, &l_min_x, &l_max_x);
617 				if (l_min_x > max_x) {
618 					/* This link is too at the right. */
619 					if (!backup)
620 						backup = link;
621 					continue;
622 				}
623 				if (l_max_x < min_x) {
624 					/* This link is too at the left. */
625 					backup = link;
626 					continue;
627 				}
628 				/* This link is aligned with the current one. */
629 				goto chose_link;
630 			}
631 
632 			if (backup) {
633 				link = backup;
634 				goto chose_link;
635 			}
636 		}
637 
638 		if (!y || y == document->height) {
639 			/* We just stay at the same place, do not invalidate
640 			 * the link number. */
641 			return 0;
642 		}
643 
644 	} else {
645 		/* Horizontal movement */
646 
647 		/* The current column number */
648 		int x = (dir_x > 0 ? max_x : min_x) + dir_x;
649 		/* How many lines are already past their last link */
650 		int last = 0;
651 
652 		while ((last < max_y - min_y + 1) && (x += dir_x) >= 0) {
653 			int y;
654 
655 			last = 0;
656 
657 			/* Go through all the lines */
658 			for (y = min_y; y <= max_y; y++) {
659 				link = document->lines1[y];
660 				if (!link) continue;
661 
662 				/* Go through all the links on line. */
663 				while (link <= document->lines2[y]) {
664 					if (get_link_y_intersect(link, x,
665 					                         min_y,
666 					                         max_y))
667 						goto chose_link;
668 					link++;
669 				}
670 
671 				/* Check if we already aren't past the last
672 				 * link on this line. */
673 				if (!get_link_x_intersect(document->lines2[y],
674 				                          y, x, INT_MAX))
675 					last++;
676 			}
677 		}
678 
679 		/* We just stay  */
680 		return 0;
681 	}
682 
683 	current_link_blur(doc_view);
684 	vs->current_link = -1;
685 	return 0;
686 
687 chose_link:
688 	/* The link is in bounds, take it. */
689 	current_link_blur(doc_view);
690 	vs->current_link = get_link_index(document, link);
691 	set_pos_x(doc_view, link);
692 	current_link_hover(doc_view);
693 	return 1;
694 }
695 
696 void
set_pos_x(struct document_view * doc_view,struct link * link)697 set_pos_x(struct document_view *doc_view, struct link *link)
698 {
699 	int xm = 0;
700 	int xl = INT_MAX;
701 	int i;
702 
703 	assert(doc_view && link);
704 	if_assert_failed return;
705 
706 	for (i = 0; i < link->npoints; i++) {
707 		int y = link->points[i].y - doc_view->vs->y;
708 
709 		if (y >= 0 && y < doc_view->box.height) {
710 			int_lower_bound(&xm, link->points[i].x + 1);
711 			int_upper_bound(&xl, link->points[i].x);
712 		}
713 	}
714 
715 	if (xl != INT_MAX)
716 		int_bounds(&doc_view->vs->x, xm - doc_view->box.width, xl);
717 }
718 
719 void
set_pos_y(struct document_view * doc_view,struct link * link)720 set_pos_y(struct document_view *doc_view, struct link *link)
721 {
722 	int ym = 0;
723 	int height;
724 	int i;
725 
726 	assert(doc_view && doc_view->document && doc_view->vs && link);
727 	if_assert_failed return;
728 
729 	height = doc_view->document->height;
730 	for (i = 0; i < link->npoints; i++) {
731 		int_lower_bound(&ym, link->points[i].y + 1);
732 		int_upper_bound(&height, link->points[i].y);
733 	}
734 	doc_view->vs->y = (ym + height - doc_view->box.height) / 2;
735 	int_bounds(&doc_view->vs->y, 0,
736 		   doc_view->document->height - doc_view->box.height);
737 }
738 
739 /* direction == 1 -> DOWN
740  * direction == -1 -> UP */
741 static void
find_link(struct document_view * doc_view,int direction,int page_mode)742 find_link(struct document_view *doc_view, int direction, int page_mode)
743 {
744 	struct link **line;
745 	struct link *link = NULL;
746 	int link_pos;
747 	int y, ymin, ymax;
748 
749 	assert(doc_view && doc_view->document && doc_view->vs);
750 	if_assert_failed return;
751 
752 	if (direction == -1) {
753 		/* UP */
754 		line = doc_view->document->lines2;
755 		if (!line) goto nolink;
756 		y = doc_view->vs->y + doc_view->box.height - 1;
757 		int_upper_bound(&y, doc_view->document->height - 1);
758 		if (y < 0) goto nolink;
759 	} else {
760 		/* DOWN */
761 		line = doc_view->document->lines1;
762 		if (!line) goto nolink;
763 		y = doc_view->vs->y;
764 		int_lower_bound(&y, 0);
765 		if (y >= doc_view->document->height) goto nolink;
766 	}
767 
768 	ymin = int_max(0, doc_view->vs->y);
769 	ymax = int_min(doc_view->document->height,
770 		       doc_view->vs->y + doc_view->box.height);
771 
772 	if (direction == -1) {
773 		/* UP */
774 		do {
775 			struct link *cur = line[y--];
776 
777 			if (cur && (!link || cur > link))
778 				link = cur;
779 		} while (y >= ymin && y < ymax);
780 
781 	} else {
782 		/* DOWN */
783 		do {
784 			struct link *cur = line[y++];
785 
786 			if (cur && (!link || cur < link))
787 				link = cur;
788 		} while (y >= ymin && y < ymax);
789 	}
790 
791 	if (!link) goto nolink;
792 
793 	link_pos = link - doc_view->document->links;
794 	if (page_mode) {
795 		/* PAGE */
796 		next_link_in_view(doc_view, link_pos, direction);
797 		return;
798 	}
799 	current_link_blur(doc_view);
800 	doc_view->vs->current_link = link_pos;
801 	set_pos_x(doc_view, link);
802 	current_link_hover(doc_view);
803 	return;
804 
805 nolink:
806 	current_link_blur(doc_view);
807 	doc_view->vs->current_link = -1;
808 }
809 
810 void
find_link_up(struct document_view * doc_view)811 find_link_up(struct document_view *doc_view)
812 {
813 	find_link(doc_view, -1, 0);
814 }
815 
816 void
find_link_page_up(struct document_view * doc_view)817 find_link_page_up(struct document_view *doc_view)
818 {
819 	find_link(doc_view, -1, 1);
820 }
821 
822 void
find_link_down(struct document_view * doc_view)823 find_link_down(struct document_view *doc_view)
824 {
825 	find_link(doc_view, 1, 0);
826 }
827 
828 void
find_link_page_down(struct document_view * doc_view)829 find_link_page_down(struct document_view *doc_view)
830 {
831 	find_link(doc_view, 1, 1);
832 }
833 
834 struct uri *
get_link_uri(struct session * ses,struct document_view * doc_view,struct link * link)835 get_link_uri(struct session *ses, struct document_view *doc_view,
836 	     struct link *link)
837 {
838 	assert(ses && doc_view && link);
839 	if_assert_failed return NULL;
840 
841 	switch (link->type) {
842 		case LINK_HYPERTEXT:
843 		case LINK_MAP:
844 			if (link->where) return get_uri(link->where, 0);
845 			return get_uri(link->where_img, 0);
846 
847 		case LINK_BUTTON:
848 		case LINK_FIELD:
849 			return get_form_uri(ses, doc_view,
850 					    get_link_form_control(link));
851 
852 		default:
853 			return NULL;
854 	}
855 }
856 
857 struct link *
goto_current_link(struct session * ses,struct document_view * doc_view,int do_reload)858 goto_current_link(struct session *ses, struct document_view *doc_view, int do_reload)
859 {
860 	struct link *link;
861 	struct uri *uri;
862 
863 	assert(doc_view && ses);
864 	if_assert_failed return NULL;
865 
866 	link = get_current_link(doc_view);
867 	if (!link) return NULL;
868 
869 	if (link_is_form(link))
870 		uri = get_form_uri(ses, doc_view, get_link_form_control(link));
871 	else
872 		uri = get_link_uri(ses, doc_view, link);
873 
874 	if (!uri) return NULL;
875 
876 	if (link->type == LINK_MAP) {
877 		/* TODO: Test reload? */
878 		goto_imgmap(ses, uri, null_or_stracpy(link->target));
879 
880 	} else {
881 		enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD
882 						 : CACHE_MODE_NORMAL;
883 
884 		goto_uri_frame(ses, uri, link->target, mode);
885 	}
886 
887 	done_uri(uri);
888 	return link;
889 }
890 
891 static enum frame_event_status
activate_link(struct session * ses,struct document_view * doc_view,struct link * link,int do_reload)892 activate_link(struct session *ses, struct document_view *doc_view,
893               struct link *link, int do_reload)
894 {
895 	struct form_control *link_fc;
896 	struct form_state *fs;
897 	struct form *form;
898 
899 	switch (link->type) {
900 	case LINK_HYPERTEXT:
901 	case LINK_MAP:
902 	case LINK_FIELD:
903 	case LINK_AREA:
904 	case LINK_BUTTON:
905 		if (goto_current_link(ses, doc_view, do_reload))
906 			return FRAME_EVENT_OK;
907 
908 		break;
909 	case LINK_CHECKBOX:
910 		link_fc = get_link_form_control(link);
911 
912 		if (form_field_is_readonly(link_fc))
913 			return FRAME_EVENT_OK;
914 
915 		fs = find_form_state(doc_view, link_fc);
916 		if (!fs) return FRAME_EVENT_OK;
917 
918 		if (link_fc->type == FC_CHECKBOX) {
919 			fs->state = !fs->state;
920 
921 			return FRAME_EVENT_REFRESH;
922 		}
923 
924 		/* @link_fc->type must be FC_RADIO, then.  First turn
925 		 * this one on, and then turn off all the other radio
926 		 * buttons in the group.  Do it in this order because
927 		 * further @find_form_state calls may reallocate
928 		 * @doc_view->vs->form_info[] and thereby make the @fs
929 		 * pointer invalid.  This also allows us to re-use
930 		 * @fs in the loop. */
931 		fs->state = 1;
932 		foreach (form, doc_view->document->forms) {
933 			struct form_control *fc;
934 
935 			if (form != link_fc->form)
936 				continue;
937 
938 			foreach (fc, form->items) {
939 				if (fc->type == FC_RADIO
940 				    && !xstrcmp(fc->name, link_fc->name)
941 				    && fc != link_fc) {
942 					fs = find_form_state(doc_view, fc);
943 					if (fs) fs->state = 0;
944 				}
945 			}
946 		}
947 
948 		break;
949 
950 	case LINK_SELECT:
951 		link_fc = get_link_form_control(link);
952 
953 		if (form_field_is_readonly(link_fc))
954 			return FRAME_EVENT_OK;
955 
956 		object_lock(doc_view->document);
957 		add_empty_window(ses->tab->term,
958 				 (void (*)(void *)) release_document,
959 				 doc_view->document);
960 		do_select_submenu(ses->tab->term, link_fc->menu, ses);
961 
962 		break;
963 
964 	default:
965 		INTERNAL("bad link type %d", link->type);
966 	}
967 
968 	return FRAME_EVENT_REFRESH;
969 }
970 
971 enum frame_event_status
enter(struct session * ses,struct document_view * doc_view,int do_reload)972 enter(struct session *ses, struct document_view *doc_view, int do_reload)
973 {
974 	struct link *link;
975 
976 	assert(ses && doc_view && doc_view->vs && doc_view->document);
977 	if_assert_failed return FRAME_EVENT_REFRESH;
978 
979 	link = get_current_link(doc_view);
980 	if (!link) return FRAME_EVENT_REFRESH;
981 
982 
983 	if (!current_link_evhook(doc_view, SEVHOOK_ONCLICK))
984 		return FRAME_EVENT_REFRESH;
985 	return activate_link(ses, doc_view, link, do_reload);
986 }
987 
988 struct link *
get_link_at_coordinates(struct document_view * doc_view,int x,int y)989 get_link_at_coordinates(struct document_view *doc_view, int x, int y)
990 {
991 	struct link *l1, *l2, *link;
992 	int i, height;
993 
994 	assert(doc_view && doc_view->vs && doc_view->document);
995 	if_assert_failed return NULL;
996 
997 	/* If there are no links in in document, there is nothing to do. */
998 	if (!doc_view->document->nlinks) return NULL;
999 
1000 	/* If the coordinates are outside document view, no need to go further. */
1001 	if (x < 0 || x >= doc_view->box.width) return NULL;
1002 	if (y < 0 || y >= doc_view->box.height) return NULL;
1003 
1004 	/* FIXME: This doesn't work. --Zas
1005 	if (!check_mouse_position(ev, &doc_view->box))
1006 		return NULL;
1007 	*/
1008 
1009 	/* Find link candidates. */
1010 	l1 = doc_view->document->links + doc_view->document->nlinks;
1011 	l2 = doc_view->document->links;
1012 	height = int_min(doc_view->document->height,
1013 			 doc_view->vs->y + doc_view->box.height);
1014 
1015 	for (i = doc_view->vs->y; i < height; i++) {
1016 		if (doc_view->document->lines1[i]
1017 		    && doc_view->document->lines1[i] < l1)
1018 			l1 = doc_view->document->lines1[i];
1019 
1020 		if (doc_view->document->lines2[i]
1021 		    && doc_view->document->lines2[i] > l2)
1022 			l2 = doc_view->document->lines2[i];
1023 	}
1024 
1025 	/* Is there a link at the given coordinates? */
1026 	x += doc_view->vs->x;
1027 	y += doc_view->vs->y;
1028 
1029 	for (link = l1; link <= l2; link++) {
1030 		for (i = 0; i < link->npoints; i++)
1031 			if (link->points[i].x == x
1032 			    && link->points[i].y == y)
1033 				return link;
1034 	}
1035 
1036 	return NULL;
1037 }
1038 
1039 /* This is backend of the backend goto_link_number_do() below ;)). */
1040 void
jump_to_link_number(struct session * ses,struct document_view * doc_view,int n)1041 jump_to_link_number(struct session *ses, struct document_view *doc_view, int n)
1042 {
1043 	assert(ses && doc_view && doc_view->vs && doc_view->document);
1044 	if_assert_failed return;
1045 
1046 	if (n < 0 || n >= doc_view->document->nlinks) return;
1047 	current_link_blur(doc_view);
1048 	doc_view->vs->current_link = n;
1049 	if (ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
1050 		struct link *link = get_current_link(doc_view);
1051 		int offset = get_link_cursor_offset(doc_view, link);
1052 
1053 		if (link->npoints > offset) {
1054 			int x = link->points[offset].x
1055 				+ doc_view->box.x - doc_view->vs->x;
1056 			int y = link->points[offset].y
1057 				+ doc_view->box.y - doc_view->vs->y;
1058 
1059 			move_cursor(ses, doc_view, x, y);
1060 		}
1061 	}
1062 	check_vs(doc_view);
1063 	current_link_hover(doc_view);
1064 }
1065 
1066 /* This is common backend for goto_link_number() and try_document_key(). */
1067 static void
goto_link_number_do(struct session * ses,struct document_view * doc_view,int n)1068 goto_link_number_do(struct session *ses, struct document_view *doc_view, int n)
1069 {
1070 	struct link *link;
1071 
1072 	assert(ses && doc_view && doc_view->document);
1073 	if_assert_failed return;
1074 	if (n < 0 || n >= doc_view->document->nlinks) return;
1075 	jump_to_link_number(ses, doc_view, n);
1076 
1077 	link = &doc_view->document->links[n];
1078 	if (!link_is_textinput(link)
1079 	    && get_opt_bool("document.browse.accesskey.auto_follow"))
1080 		enter(ses, doc_view, 0);
1081 }
1082 
1083 void
goto_link_number(struct session * ses,unsigned char * num)1084 goto_link_number(struct session *ses, unsigned char *num)
1085 {
1086 	struct document_view *doc_view;
1087 
1088 	assert(ses && num);
1089 	if_assert_failed return;
1090 	doc_view = current_frame(ses);
1091 	assert(doc_view);
1092 	if_assert_failed return;
1093 	goto_link_number_do(ses, doc_view, atoi(num) - 1);
1094 }
1095 
1096 /* See if this document is interested in the key user pressed. */
1097 enum frame_event_status
try_document_key(struct session * ses,struct document_view * doc_view,struct term_event * ev)1098 try_document_key(struct session *ses, struct document_view *doc_view,
1099 		 struct term_event *ev)
1100 {
1101 	long key;
1102 	int i; /* GOD I HATE C! --FF */ /* YEAH, BRAINFUCK RULEZ! --pasky */
1103 
1104 	assert(ses && doc_view && doc_view->document && doc_view->vs && ev);
1105 	if_assert_failed return FRAME_EVENT_IGNORED;
1106 
1107 	if (!check_kbd_modifier(ev, KBD_MOD_ALT)) {
1108 		/* We accept those only in alt-combo. */
1109 		return FRAME_EVENT_IGNORED;
1110 	}
1111 
1112 	/* Run through all the links and see if one of them is bound to the
1113 	 * key we test.. */
1114 	key = get_kbd_key(ev);
1115 
1116 	i = doc_view->vs->current_link + 1;
1117 	for (; i < doc_view->document->nlinks; i++) {
1118 		struct link *link = &doc_view->document->links[i];
1119 
1120 		if (key == link->accesskey) {	/* FIXME: key vs unicode ... */
1121 			ses->kbdprefix.repeat_count = 0;
1122 			goto_link_number_do(ses, doc_view, i);
1123 			return FRAME_EVENT_REFRESH;
1124 		}
1125 	}
1126 	for (i = 0; i <= doc_view->vs->current_link; i++) {
1127 		struct link *link = &doc_view->document->links[i];
1128 
1129 		if (key == link->accesskey) {	/* FIXME: key vs unicode ... */
1130 			ses->kbdprefix.repeat_count = 0;
1131 			goto_link_number_do(ses, doc_view, i);
1132 			return FRAME_EVENT_REFRESH;
1133 		}
1134 	}
1135 
1136 	return FRAME_EVENT_IGNORED;
1137 }
1138 
1139 /* Open a contextual menu on a link, form or image element. */
1140 /* TODO: This should be completely configurable. */
1141 void
link_menu(struct terminal * term,void * xxx,void * ses_)1142 link_menu(struct terminal *term, void *xxx, void *ses_)
1143 {
1144 	struct session *ses = ses_;
1145 	struct document_view *doc_view;
1146 	struct link *link;
1147 	struct menu_item *mi;
1148 	struct form_control *fc;
1149 
1150 	assert(term && ses);
1151 	if_assert_failed return;
1152 
1153 	doc_view = current_frame(ses);
1154 	mi = new_menu(FREE_LIST);
1155 	if (!mi) return;
1156 	if (!doc_view) goto end;
1157 
1158 	assert(doc_view->vs && doc_view->document);
1159 	if_assert_failed return;
1160 
1161 	link = get_current_link(doc_view);
1162 	if (!link) goto end;
1163 
1164 	if (link->where && !link_is_form(link)) {
1165 		if (link->type == LINK_MAP) {
1166 			add_to_menu(&mi, N_("Display ~usemap"), NULL, ACT_MAIN_LINK_FOLLOW,
1167 				    NULL, NULL, SUBMENU);
1168 		} else {
1169 			add_menu_action(&mi, N_("~Follow link"), ACT_MAIN_LINK_FOLLOW);
1170 
1171 			add_menu_action(&mi, N_("Follow link and r~eload"), ACT_MAIN_LINK_FOLLOW_RELOAD);
1172 
1173 			add_menu_separator(&mi);
1174 
1175 			add_new_win_to_menu(&mi, N_("Open in new ~window"), term);
1176 
1177 			add_menu_action(&mi, N_("Open in new ~tab"), ACT_MAIN_OPEN_LINK_IN_NEW_TAB);
1178 
1179 			add_menu_action(&mi, N_("Open in new tab in ~background"),
1180 					ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND);
1181 
1182 			if (!get_cmd_opt_bool("anonymous")) {
1183 				add_menu_separator(&mi);
1184 				add_menu_action(&mi, N_("~Download link"), ACT_MAIN_LINK_DOWNLOAD);
1185 
1186 #ifdef CONFIG_BOOKMARKS
1187 				add_menu_action(&mi, N_("~Add link to bookmarks"),
1188 						ACT_MAIN_ADD_BOOKMARK_LINK);
1189 #endif
1190 				add_uri_command_to_menu(&mi, PASS_URI_LINK);
1191 			}
1192 		}
1193 	}
1194 
1195 	fc = get_link_form_control(link);
1196 	if (fc) {
1197 		switch (fc->type) {
1198 		case FC_RESET:
1199 			add_menu_action(&mi, N_("~Reset form"), ACT_MAIN_RESET_FORM);
1200 			break;
1201 
1202 		case FC_TEXTAREA:
1203 			if (!form_field_is_readonly(fc)) {
1204 				struct string keystroke;
1205 
1206 				if (init_string(&keystroke))
1207 					add_keystroke_action_to_string(
1208 					                 &keystroke,
1209 					                 ACT_EDIT_OPEN_EXTERNAL,
1210 							 KEYMAP_EDIT);
1211 
1212 				add_to_menu(&mi, N_("Open in ~external editor"),
1213 					    keystroke.source, ACT_MAIN_NONE,
1214 					    menu_textarea_edit, NULL, FREE_RTEXT);
1215 			}
1216 			/* Fall through */
1217 		default:
1218 			add_menu_action(&mi, N_("~Submit form"), ACT_MAIN_SUBMIT_FORM);
1219 			add_menu_action(&mi, N_("Submit form and rel~oad"), ACT_MAIN_SUBMIT_FORM_RELOAD);
1220 
1221 			assert(fc->form);
1222 			if (fc->form->method == FORM_METHOD_GET) {
1223 				add_new_win_to_menu(&mi, N_("Submit form and open in new ~window"), term);
1224 
1225 				add_menu_action(&mi, N_("Submit form and open in new ~tab"),
1226 						ACT_MAIN_OPEN_LINK_IN_NEW_TAB);
1227 
1228 				add_menu_action(&mi, N_("Submit form and open in new tab in ~background"),
1229 						ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND);
1230 			}
1231 
1232 			if (!get_cmd_opt_bool("anonymous"))
1233 				add_menu_action(&mi, N_("Submit form and ~download"), ACT_MAIN_LINK_DOWNLOAD);
1234 
1235 			add_menu_action(&mi, N_("~Reset form"), ACT_MAIN_RESET_FORM);
1236 		}
1237 
1238 		add_to_menu(&mi, N_("Form f~ields"), NULL, ACT_MAIN_LINK_FORM_MENU,
1239 			    NULL, NULL, SUBMENU);
1240 	}
1241 
1242 	if (link->where_img) {
1243 		add_menu_action(&mi, N_("V~iew image"), ACT_MAIN_VIEW_IMAGE);
1244 		if (!get_cmd_opt_bool("anonymous"))
1245 			add_menu_action(&mi, N_("Download ima~ge"), ACT_MAIN_LINK_DOWNLOAD_IMAGE);
1246 	}
1247 
1248 	/* TODO: Make it possible to trigger any script event hooks associated
1249 	 * to the link. --pasky */
1250 
1251 end:
1252 	if (!mi->text) {
1253 		add_to_menu(&mi, N_("No link selected"), NULL, ACT_MAIN_NONE,
1254 			    NULL, NULL, NO_SELECT);
1255 	}
1256 
1257 	do_menu(term, mi, ses, 1);
1258 }
1259 
1260 /* Return current link's title. Pretty trivial. */
1261 unsigned char *
get_current_link_title(struct document_view * doc_view)1262 get_current_link_title(struct document_view *doc_view)
1263 {
1264 	struct link *link;
1265 
1266 	assert(doc_view && doc_view->document && doc_view->vs);
1267 	if_assert_failed return NULL;
1268 
1269 	if (doc_view->document->frame_desc)
1270 		return NULL;
1271 
1272 	link = get_current_link(doc_view);
1273 
1274 	return (link && link->title && *link->title) ? stracpy(link->title) : NULL;
1275 }
1276 
1277 unsigned char *
get_current_link_info(struct session * ses,struct document_view * doc_view)1278 get_current_link_info(struct session *ses, struct document_view *doc_view)
1279 {
1280 	struct link *link;
1281 
1282 	assert(ses && doc_view && doc_view->document && doc_view->vs);
1283 	if_assert_failed return NULL;
1284 
1285 	if (doc_view->document->frame_desc)
1286 		return NULL;
1287 
1288 	link = get_current_link(doc_view);
1289 	if (!link) return NULL;
1290 
1291 	/* TODO: Provide info about script event hooks too. --pasky */
1292 
1293 	if (!link_is_form(link)) {
1294 		struct terminal *term = ses->tab->term;
1295 		struct string str;
1296 		unsigned char *uristring = link->where;
1297 
1298 		if (!init_string(&str)) return NULL;
1299 
1300 		if (!link->where && link->where_img) {
1301 			add_to_string(&str, _("Image", term));
1302 			add_char_to_string(&str, ' ');
1303 			uristring = link->where_img;
1304 
1305 		} else if (link->type == LINK_MAP) {
1306 			add_to_string(&str, _("Usemap", term));
1307 			add_char_to_string(&str, ' ');
1308 		}
1309 
1310 		/* Add the uri with password and post info stripped */
1311 		add_string_uri_to_string(&str, uristring, URI_PUBLIC);
1312 		if (link->accesskey > 0
1313 		    && get_opt_bool("document.browse.accesskey.display")) {
1314 			add_to_string(&str, " (");
1315 			add_accesskey_to_string(&str, link->accesskey);
1316 			add_char_to_string(&str, ')');
1317 		}
1318 
1319 		decode_uri_string_for_display(&str);
1320 		return str.source;
1321 	}
1322 
1323 	if (!get_link_form_control(link)) return NULL;
1324 
1325 	return get_form_info(ses, doc_view);
1326 }
1327