Add get_terminal_codepage().
[elinks.git] / src / viewer / text / link.c
blob543a5a868995021ea3d84a354963719f65ab05bf
1 /* Links viewing/manipulation handling */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdlib.h>
8 #include <string.h>
10 #include "elinks.h"
12 #include "bfu/listmenu.h"
13 #include "bfu/menu.h"
14 #include "bfu/style.h"
15 #include "dialogs/menu.h"
16 #include "dialogs/status.h"
17 #include "document/document.h"
18 #include "document/forms.h"
19 #include "document/html/parser.h"
20 #include "document/html/renderer.h"
21 #include "document/options.h"
22 #include "document/view.h"
23 #include "ecmascript/ecmascript.h"
24 #include "intl/gettext/libintl.h"
25 #include "main/object.h"
26 #include "protocol/uri.h"
27 #include "session/session.h"
28 #include "session/task.h"
29 #include "terminal/color.h"
30 #include "terminal/draw.h"
31 #include "terminal/kbd.h"
32 #include "terminal/screen.h"
33 #include "terminal/tab.h"
34 #include "terminal/terminal.h"
35 #include "util/box.h"
36 #include "util/conv.h"
37 #include "util/error.h"
38 #include "util/memory.h"
39 #include "util/string.h"
40 #include "viewer/action.h"
41 #include "viewer/text/form.h"
42 #include "viewer/text/link.h"
43 #include "viewer/text/search.h"
44 #include "viewer/text/textarea.h"
45 #include "viewer/text/view.h"
46 #include "viewer/text/vs.h"
49 /* Perhaps some of these would be more fun to have in viewer/common/, dunno.
50 * --pasky */
53 static int
54 current_link_evhook(struct document_view *doc_view, enum script_event_hook_type type)
56 #ifdef CONFIG_ECMASCRIPT
57 struct link *link;
58 struct script_event_hook *evhook;
60 assert(doc_view && doc_view->vs);
61 link = get_current_link(doc_view);
62 if (!link) return -1;
63 if (!link->event_hooks) return -1;
65 if (!doc_view->vs->ecmascript) return -1;
67 foreach (evhook, *link->event_hooks) {
68 unsigned char *ret;
70 if (evhook->type != type) continue;
71 ret = evhook->src;
72 while ((ret = strstr(ret, "return ")))
73 while (*ret != ' ') *ret++ = ' ';
75 struct string src = INIT_STRING(evhook->src, strlen(evhook->src));
76 /* TODO: Some even handlers return a bool. */
77 if (!ecmascript_eval_boolback(doc_view->vs->ecmascript, &src))
78 return 0;
82 return 1;
83 #else
84 return -1;
85 #endif
88 #define current_link_hover(dv) \
89 do { \
90 current_link_evhook(dv, SEVHOOK_ONMOUSEOVER); \
91 current_link_evhook(dv, SEVHOOK_ONHOVER); \
92 current_link_evhook(dv, SEVHOOK_ONFOCUS); \
93 } while (0)
94 #define current_link_blur(dv) \
95 do { \
96 current_link_evhook(dv, SEVHOOK_ONMOUSEOUT); \
97 current_link_evhook(dv, SEVHOOK_ONBLUR); \
98 } while (0)
101 void
102 set_link(struct document_view *doc_view)
104 assert(doc_view);
105 if_assert_failed return;
107 if (current_link_is_visible(doc_view)) return;
109 find_link_page_down(doc_view);
112 static inline int
113 get_link_cursor_offset(struct document_view *doc_view, struct link *link)
115 struct form_control *fc;
116 struct form_state *fs;
117 #ifdef CONFIG_UTF8
118 /* The encoding of form fields depends on the terminal,
119 * rather than on the document. */
120 int utf8 = doc_view->session->tab->term->utf8_cp;
121 #endif /* CONFIG_UTF8 */
123 switch (link->type) {
124 case LINK_CHECKBOX:
125 return 1;
127 case LINK_BUTTON:
128 return 2;
130 case LINK_FIELD:
131 fc = get_link_form_control(link);
132 fs = find_form_state(doc_view, fc);
133 if (!fs || !fs->value)
134 return 0;
135 #ifdef CONFIG_UTF8
136 else if (utf8) {
137 unsigned char *scroll = fs->value + fs->vpos;
138 unsigned char *point = fs->value + fs->state;
140 if (fs->type == FC_PASSWORD)
141 return utf8_ptr2chars(scroll, point);
142 else
143 return utf8_ptr2cells(scroll, point);
145 #endif /* CONFIG_UTF8 */
146 else
147 return fs->state - fs->vpos;
149 case LINK_AREA:
150 fc = get_link_form_control(link);
151 fs = find_form_state(doc_view, fc);
152 #ifdef CONFIG_UTF8
153 return fs ? area_cursor(fc, fs, utf8) : 0;
154 #else
155 return fs ? area_cursor(fc, fs) : 0;
156 #endif /* CONFIG_UTF8 */
158 case LINK_HYPERTEXT:
159 case LINK_MAP:
160 case LINK_SELECT:
161 return 0;
164 return 0;
167 /** Initialise a static template character with the colour and attributes
168 * appropriate for an active link and return that character. */
169 static inline struct screen_char *
170 init_link_drawing(struct document_view *doc_view, struct link *link, int invert)
172 struct document_options *doc_opts;
173 static struct screen_char template;
174 enum color_flags color_flags;
175 enum color_mode color_mode;
176 struct color_pair colors;
178 template.attr = SCREEN_ATTR_STANDOUT;
180 doc_opts = &doc_view->document->options;
182 color_flags = (doc_opts->color_flags | COLOR_DECREASE_LIGHTNESS);
183 color_mode = doc_opts->color_mode;
185 if (doc_opts->active_link.underline)
186 template.attr |= SCREEN_ATTR_UNDERLINE;
188 if (doc_opts->active_link.bold)
189 template.attr |= SCREEN_ATTR_BOLD;
191 if (doc_opts->active_link.color) {
192 colors.foreground = doc_opts->active_link.fg;
193 colors.background = doc_opts->active_link.bg;
194 } else {
195 colors.foreground = link->color.foreground;
196 colors.background = link->color.background;
199 if (invert && doc_opts->active_link.invert) {
200 swap_values(color_T, colors.foreground, colors.background);
202 /* Highlight text-input form-fields correctly if contrast
203 * correction is needed. */
204 if (link_is_textinput(link)) {
205 /* Make sure to use the options belonging to the
206 * current document when checking for fg and bg color
207 * usage. */
208 doc_opts = &doc_view->document->options;
210 /* Are we fallen angels who didn't want to believe that
211 * nothing /is/ nothing and so were born to lose our
212 * loved ones and dear friends one by one and finally
213 * our own life, to see it proved? --Kerouac */
215 /* Wipe out all default correction for 16 color mode */
216 color_flags = (color_flags & ~COLOR_INCREASE_CONTRAST);
217 /* Make contrast correction invert things properly */
218 color_flags |= COLOR_ENSURE_INVERTED_CONTRAST;
222 set_term_color(&template, &colors, color_flags, color_mode);
224 return &template;
227 /** Give the current link the appropriate colour and attributes. */
228 void
229 draw_current_link(struct session *ses, struct document_view *doc_view)
231 struct terminal *term = ses->tab->term;
232 struct screen_char *template;
233 struct link *link;
234 int cursor_offset;
235 int xpos, ypos;
236 int i;
238 assert(term && doc_view && doc_view->vs);
239 if_assert_failed return;
241 assert(ses->tab == get_current_tab(term));
242 if_assert_failed return;
244 link = get_current_link(doc_view);
245 if (!link) return;
247 i = !link_is_textinput(link) || ses->insert_mode == INSERT_MODE_OFF;
248 template = init_link_drawing(doc_view, link, i);
249 if (!template) return;
251 xpos = doc_view->box.x - doc_view->vs->x;
252 ypos = doc_view->box.y - doc_view->vs->y;
254 if (ses->insert_mode == INSERT_MODE_OFF
255 && ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
256 /* If we are navigating using cursor routing and not editing a
257 * text-input form-field never set the cursor. */
258 cursor_offset = -1;
259 } else {
260 cursor_offset = get_link_cursor_offset(doc_view, link);
263 for (i = 0; i < link->npoints; i++) {
264 int x = link->points[i].x + xpos;
265 int y = link->points[i].y + ypos;
266 struct screen_char *co;
268 if (!is_in_box(&doc_view->box, x, y)) {
269 continue;
272 co = get_char(term, x, y);
274 if (i == cursor_offset) {
275 int blockable = (!link_is_textinput(link)
276 && co->color != template->color);
278 set_cursor(term, x, y, blockable);
279 set_window_ptr(ses->tab, x, y);
282 template->data = co->data;
283 copy_screen_chars(co, template, 1);
284 set_screen_dirty(term->screen, y, y);
287 doc_view->vs->old_current_link = doc_view->vs->current_link;
290 static void
291 draw_link(struct terminal *term, struct document_view *doc_view,
292 struct link *link)
294 int xpos = doc_view->box.x - doc_view->vs->x;
295 int ypos = doc_view->box.y - doc_view->vs->y;
296 int i;
298 for (i = 0; i < link->npoints; ++i) {
299 int x = link->points[i].x;
300 int y = link->points[i].y;
302 if (is_in_box(&doc_view->box, x + xpos, y + ypos)){
303 struct screen_char *ch;
305 ch = get_char(term, x + xpos, y + ypos);
306 copy_struct(ch, &doc_view->document->data[y].chars[x]);
307 set_screen_dirty(term->screen, y + ypos, y + ypos);
312 /** Restore the colours and attributes that the active link had
313 * before it was selected. */
314 void
315 clear_link(struct terminal *term, struct document_view *doc_view)
317 struct link *link = get_current_link(doc_view);
318 struct link *last = get_old_current_link(doc_view);
320 if (last && last != link) {
321 draw_link(term, doc_view, last);
324 doc_view->vs->old_current_link = doc_view->vs->current_link;
327 void
328 highlight_links_with_prefixes_that_start_with_n(struct terminal *term,
329 struct document_view *doc_view,
330 int n)
332 struct color_pair *color = get_bfu_color(term, "searched");
333 int xoffset = doc_view->box.x - doc_view->vs->x;
334 int yoffset = doc_view->box.y - doc_view->vs->y;
335 struct document *document = doc_view->document;
336 int m;
338 for (m = n + 1; n <= document->nlinks; n *= 10, m *= 10) {
339 int linkn;
341 for (linkn = n; linkn < m; ++linkn) {
342 struct link *link = &document->links[linkn - 1];
343 int i;
345 if (linkn > document->nlinks) break;
347 for (i = 0; i < link->npoints; ++i) {
348 int x = link->points[i].x + xoffset;
349 int y = link->points[i].y + yoffset;
351 if (is_in_box(&doc_view->box, x, y))
352 draw_char_color(term, x, y, color);
358 struct link *
359 get_first_link(struct document_view *doc_view)
361 struct link *link, *undef;
362 struct document *document;
363 int height;
364 int i;
366 assert(doc_view && doc_view->document);
367 if_assert_failed return NULL;
369 document = doc_view->document;
371 if (!document->lines1) return NULL;
373 height = doc_view->vs->y + doc_view->box.height;
374 link = undef = document->links + document->nlinks;
376 for (i = int_max(0, doc_view->vs->y);
377 i < int_min(height, document->height);
378 i++) {
379 if (document->lines1[i]
380 && document->lines1[i] < link)
381 link = document->lines1[i];
384 return (link == undef) ? NULL : link;
387 struct link *
388 get_last_link(struct document_view *doc_view)
390 struct link *link = NULL;
391 struct document *document;
392 int height;
393 int i;
395 assert(doc_view && doc_view->document);
396 if_assert_failed return NULL;
398 document = doc_view->document;
400 if (!document->lines2) return NULL;
402 height = doc_view->vs->y + doc_view->box.height;
404 for (i = int_max(0, doc_view->vs->y);
405 i < int_min(height, document->height);
406 i++)
407 if (document->lines2[i] > link)
408 link = document->lines2[i];
410 return link;
413 static int
414 link_in_view_x(struct document_view *doc_view, struct link *link)
416 int i, dx, width;
418 assert(doc_view && link);
419 if_assert_failed return 0;
421 dx = doc_view->vs->x;
422 width = doc_view->box.width;
424 for (i = 0; i < link->npoints; i++) {
425 int x = link->points[i].x - dx;
427 if (x >= 0 && x < width)
428 return 1;
431 return 0;
434 static int
435 link_in_view_y(struct document_view *doc_view, struct link *link)
437 int i, dy, height;
439 assert(doc_view && link);
440 if_assert_failed return 0;
442 dy = doc_view->vs->y;
443 height = doc_view->box.height;
445 for (i = 0; i < link->npoints; i++) {
446 int y = link->points[i].y - dy;
448 if (y >= 0 && y < height)
449 return 1;
452 return 0;
455 static int
456 link_in_view(struct document_view *doc_view, struct link *link)
458 assert(doc_view && link);
459 if_assert_failed return 0;
460 return link_in_view_y(doc_view, link) && link_in_view_x(doc_view, link);
464 current_link_is_visible(struct document_view *doc_view)
466 struct link *link;
468 assert(doc_view && doc_view->vs);
469 if_assert_failed return 0;
471 link = get_current_link(doc_view);
472 return (link && link_in_view(doc_view, link));
475 /** Look for the first and the last link currently visible in our
476 * viewport. */
477 static void
478 get_visible_links_range(struct document_view *doc_view, int *first, int *last)
480 struct document *document = doc_view->document;
481 int height = int_min(doc_view->vs->y + doc_view->box.height,
482 document->height);
483 int y;
485 *first = document->nlinks - 1;
486 *last = 0;
488 for (y = int_max(0, doc_view->vs->y); y < height; y++) {
489 if (document->lines1[y])
490 int_upper_bound(first, document->lines1[y]
491 - document->links);
493 if (document->lines2[y])
494 int_lower_bound(last, document->lines2[y]
495 - document->links);
499 static int
500 next_link_in_view_(struct document_view *doc_view, int current, int direction,
501 int (*fn)(struct document_view *, struct link *),
502 void (*cntr)(struct document_view *, struct link *))
504 struct document *document;
505 struct view_state *vs;
506 int start, end;
508 assert(doc_view && doc_view->document && doc_view->vs && fn);
509 if_assert_failed return 0;
511 document = doc_view->document;
512 vs = doc_view->vs;
514 get_visible_links_range(doc_view, &start, &end);
516 current_link_blur(doc_view);
518 /* Go from the @current link in @direction until either
519 * fn() is happy or we would leave the current viewport. */
520 while (current >= start && current <= end) {
521 if (fn(doc_view, &document->links[current])) {
522 vs->current_link = current;
523 if (cntr) cntr(doc_view, &document->links[current]);
524 current_link_hover(doc_view);
525 return 1;
527 current += direction;
530 vs->current_link = -1;
531 return 0;
535 next_link_in_view(struct document_view *doc_view, int current, int direction)
537 return next_link_in_view_(doc_view, current, direction, link_in_view, NULL);
541 next_link_in_view_y(struct document_view *doc_view, int current, int direction)
543 return next_link_in_view_(doc_view, current, direction, link_in_view_y, set_pos_x);
546 /** Get the bounding columns of @a link at line @a y (or all lines if
547 * @a y == -1). */
548 void
549 get_link_x_bounds(struct link *link, int y, int *min_x, int *max_x)
551 int point;
553 if (min_x) *min_x = INT_MAX;
554 if (max_x) *max_x = 0;
556 for (point = 0; point < link->npoints; point++) {
557 if (y >= 0 && link->points[point].y != y)
558 continue;
559 if (min_x) int_upper_bound(min_x, link->points[point].x);
560 if (max_x) int_lower_bound(max_x, link->points[point].x);
564 /** Check whether there is any point between @a min_x and @a max_x at
565 * the line @a y in link @a link. */
566 static int
567 get_link_x_intersect(struct link *link, int y, int min_x, int max_x)
569 int point;
571 for (point = 0; point < link->npoints; point++) {
572 if (link->points[point].y != y)
573 continue;
574 if (link->points[point].x >= min_x
575 && link->points[point].x <= max_x)
576 return link->points[point].x + 1;
579 return 0;
582 /** Check whether there is any point between @a min_y and @a max_y in
583 * the column @a x in link @a link. */
584 static int
585 get_link_y_intersect(struct link *link, int x, int min_y, int max_y)
587 int point;
589 for (point = 0; point < link->npoints; point++) {
590 if (link->points[point].x != x)
591 continue;
592 if (link->points[point].y >= min_y
593 && link->points[point].y <= max_y)
594 return link->points[point].y + 1;
597 return 0;
601 next_link_in_dir(struct document_view *doc_view, int dir_x, int dir_y)
603 struct document *document;
604 struct view_state *vs;
605 struct link *link;
606 int min_x = INT_MAX, max_x = 0;
607 int min_y, max_y;
609 assert(doc_view && doc_view->document && doc_view->vs);
610 if_assert_failed return 0;
611 assert(dir_x || dir_y);
612 if_assert_failed return 0;
614 document = doc_view->document;
615 vs = doc_view->vs;
617 link = get_current_link(doc_view);
618 if (!link) return 0;
620 /* Find the link's "bounding box" coordinates. */
622 get_link_x_bounds(link, -1, &min_x, &max_x);
624 min_y = link->points[0].y;
625 max_y = link->points[link->npoints - 1].y;
627 /* Now go from the bounding box edge in the appropriate
628 * direction and find the nearest link. */
630 if (dir_y) {
631 /* Vertical movement */
633 /* The current line number */
634 int y = (dir_y > 0 ? max_y : min_y) + dir_y;
635 /* The bounding line numbers */
636 int top = int_max(0, doc_view->vs->y);
637 int bottom = int_min(doc_view->vs->y + doc_view->box.height,
638 document->height);
640 for (; dir_y > 0 ? y < bottom : y >= top; y += dir_y) {
641 /* @backup points to the nearest link from the left
642 * to the desired position. */
643 struct link *backup = NULL;
645 link = document->lines1[y];
646 if (!link) continue;
648 /* Go through all the links on line. */
649 for (; link <= document->lines2[y]; link++) {
650 int l_min_x, l_max_x;
652 /* Some links can be totally out of order here,
653 * ie. in tables or when using tabindex. */
654 if (y < link->points[0].y
655 || y > link->points[link->npoints - 1].y)
656 continue;
658 get_link_x_bounds(link, y, &l_min_x, &l_max_x);
659 if (l_min_x > max_x) {
660 /* This link is too at the right. */
661 if (!backup)
662 backup = link;
663 continue;
665 if (l_max_x < min_x) {
666 /* This link is too at the left. */
667 backup = link;
668 continue;
670 /* This link is aligned with the current one. */
671 goto chose_link;
674 if (backup) {
675 link = backup;
676 goto chose_link;
680 if (!y || y == document->height) {
681 /* We just stay at the same place, do not invalidate
682 * the link number. */
683 return 0;
686 } else {
687 /* Horizontal movement */
689 /* The current column number */
690 int x = (dir_x > 0 ? max_x : min_x) + dir_x;
691 /* How many lines are already past their last link */
692 int last = 0;
694 while ((last < max_y - min_y + 1) && (x += dir_x) >= 0) {
695 int y;
697 last = 0;
699 /* Go through all the lines */
700 for (y = min_y; y <= max_y; y++) {
701 link = document->lines1[y];
702 if (!link) continue;
704 /* Go through all the links on line. */
705 while (link <= document->lines2[y]) {
706 if (get_link_y_intersect(link, x,
707 min_y,
708 max_y))
709 goto chose_link;
710 link++;
713 /* Check if we already aren't past the last
714 * link on this line. */
715 if (!get_link_x_intersect(document->lines2[y],
716 y, x, INT_MAX))
717 last++;
721 /* We just stay */
722 return 0;
725 current_link_blur(doc_view);
726 vs->current_link = -1;
727 return 0;
729 chose_link:
730 /* The link is in bounds, take it. */
731 current_link_blur(doc_view);
732 vs->current_link = get_link_index(document, link);
733 set_pos_x(doc_view, link);
734 current_link_hover(doc_view);
735 return 1;
738 void
739 set_pos_x(struct document_view *doc_view, struct link *link)
741 int xm = 0;
742 int xl = INT_MAX;
743 int i;
745 assert(doc_view && link);
746 if_assert_failed return;
748 for (i = 0; i < link->npoints; i++) {
749 int y = link->points[i].y - doc_view->vs->y;
751 if (y >= 0 && y < doc_view->box.height) {
752 int_lower_bound(&xm, link->points[i].x + 1);
753 int_upper_bound(&xl, link->points[i].x);
757 if (xl != INT_MAX)
758 int_bounds(&doc_view->vs->x, xm - doc_view->box.width, xl);
761 void
762 set_pos_y(struct document_view *doc_view, struct link *link)
764 int ym = 0;
765 int height;
766 int i;
768 assert(doc_view && doc_view->document && doc_view->vs && link);
769 if_assert_failed return;
771 height = doc_view->document->height;
772 for (i = 0; i < link->npoints; i++) {
773 int_lower_bound(&ym, link->points[i].y + 1);
774 int_upper_bound(&height, link->points[i].y);
776 doc_view->vs->y = (ym + height - doc_view->box.height) / 2;
777 int_bounds(&doc_view->vs->y, 0,
778 doc_view->document->height - doc_view->box.height);
781 /** Focus the next link in the specified direction.
782 * @a direction == 1 -> DOWN;
783 * @a direction == -1 -> UP */
784 static void
785 find_link(struct document_view *doc_view, int direction, int page_mode)
787 struct link **line;
788 struct link *link = NULL;
789 int link_pos;
790 int y, ymin, ymax;
792 assert(doc_view && doc_view->document && doc_view->vs);
793 if_assert_failed return;
795 if (direction == -1) {
796 /* UP */
797 line = doc_view->document->lines2;
798 if (!line) goto nolink;
799 y = doc_view->vs->y + doc_view->box.height - 1;
800 int_upper_bound(&y, doc_view->document->height - 1);
801 if (y < 0) goto nolink;
802 } else {
803 /* DOWN */
804 line = doc_view->document->lines1;
805 if (!line) goto nolink;
806 y = doc_view->vs->y;
807 int_lower_bound(&y, 0);
808 if (y >= doc_view->document->height) goto nolink;
811 ymin = int_max(0, doc_view->vs->y);
812 ymax = int_min(doc_view->document->height,
813 doc_view->vs->y + doc_view->box.height);
815 if (direction == -1) {
816 /* UP */
817 do {
818 struct link *cur = line[y--];
820 if (cur && (!link || cur > link))
821 link = cur;
822 } while (y >= ymin && y < ymax);
824 } else {
825 /* DOWN */
826 do {
827 struct link *cur = line[y++];
829 if (cur && (!link || cur < link))
830 link = cur;
831 } while (y >= ymin && y < ymax);
834 if (!link) goto nolink;
836 link_pos = link - doc_view->document->links;
837 if (page_mode) {
838 /* PAGE */
839 next_link_in_view(doc_view, link_pos, direction);
840 return;
842 current_link_blur(doc_view);
843 doc_view->vs->current_link = link_pos;
844 set_pos_x(doc_view, link);
845 current_link_hover(doc_view);
846 return;
848 nolink:
849 current_link_blur(doc_view);
850 doc_view->vs->current_link = -1;
853 void
854 find_link_up(struct document_view *doc_view)
856 find_link(doc_view, -1, 0);
859 void
860 find_link_page_up(struct document_view *doc_view)
862 find_link(doc_view, -1, 1);
865 void
866 find_link_down(struct document_view *doc_view)
868 find_link(doc_view, 1, 0);
871 void
872 find_link_page_down(struct document_view *doc_view)
874 find_link(doc_view, 1, 1);
877 struct uri *
878 get_link_uri(struct session *ses, struct document_view *doc_view,
879 struct link *link)
881 assert(ses && doc_view && link);
882 if_assert_failed return NULL;
884 switch (link->type) {
885 case LINK_HYPERTEXT:
886 case LINK_MAP:
887 if (link->where) return get_uri(link->where, 0);
888 return get_uri(link->where_img, 0);
890 case LINK_BUTTON:
891 case LINK_FIELD:
892 return get_form_uri(ses, doc_view,
893 get_link_form_control(link));
895 default:
896 return NULL;
900 static int
901 call_onsubmit_and_submit(struct session *ses, struct document_view *doc_view,
902 struct form_control *fc, int do_reload)
904 struct uri *uri = NULL;
905 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD : CACHE_MODE_NORMAL;
907 assert(fc->form); /* regardless of whether there is a FORM element */
908 if_assert_failed return 0;
910 #ifdef CONFIG_ECMASCRIPT
911 /* If the form has multiple submit buttons, this does not
912 * explicitly tell the ECMAScript code which of them was
913 * pressed. W3C DOM Level 3 doesn't seem to include such a
914 * feature. */
915 if (fc->type != FC_RESET && fc->form->onsubmit) {
916 struct string code;
918 if (init_string(&code)) {
919 struct view_state *vs = doc_view->vs;
920 struct ecmascript_interpreter *interpreter;
921 int res;
923 if (vs->ecmascript_fragile)
924 ecmascript_reset_state(vs);
925 interpreter = vs->ecmascript;
926 assert(interpreter);
928 add_to_string(&code, fc->form->onsubmit);
929 res = ecmascript_eval_boolback(interpreter, &code);
930 done_string(&code);
931 /* If the user presses Enter in a text field,
932 * and document.browse.forms.auto_submit is
933 * true, and the form has an onsubmit script
934 * that returns false, then insert mode should
935 * end, so return 1 here rather than 0. */
936 if (!res) return 1;
939 #endif /* CONFIG_ECMASCRIPT */
941 uri = get_form_uri(ses, doc_view, fc);
942 if (!uri) return 0;
943 goto_uri_frame(ses, uri, fc->form->target, mode);
944 done_uri(uri);
945 return 1;
948 struct link *
949 goto_current_link(struct session *ses, struct document_view *doc_view, int do_reload)
951 struct link *link;
952 struct uri *uri;
954 assert(doc_view && ses);
955 if_assert_failed return NULL;
957 link = get_current_link(doc_view);
958 if (!link) return NULL;
960 if (link_is_form(link)) {
961 struct form_control *fc = link->data.form_control;
963 if (fc->type != FC_BUTTON
964 && !call_onsubmit_and_submit(ses, doc_view, fc, do_reload))
965 return NULL;
966 else
967 return link;
968 } else
969 uri = get_link_uri(ses, doc_view, link);
971 if (!uri) return NULL;
973 if (link->type == LINK_MAP) {
974 /* TODO: Test reload? */
975 goto_imgmap(ses, uri, null_or_stracpy(link->target));
977 } else {
978 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD
979 : CACHE_MODE_NORMAL;
981 goto_uri_frame(ses, uri, link->target, mode);
984 done_uri(uri);
985 return link;
988 static enum frame_event_status
989 activate_link(struct session *ses, struct document_view *doc_view,
990 struct link *link, int do_reload)
992 struct form_control *link_fc;
993 struct form_state *fs;
994 struct form *form;
996 switch (link->type) {
997 case LINK_HYPERTEXT:
998 case LINK_MAP:
999 case LINK_FIELD:
1000 case LINK_AREA:
1001 case LINK_BUTTON:
1002 if (goto_current_link(ses, doc_view, do_reload))
1003 return FRAME_EVENT_OK;
1004 break;
1005 case LINK_CHECKBOX:
1006 link_fc = get_link_form_control(link);
1008 if (form_field_is_readonly(link_fc))
1009 return FRAME_EVENT_OK;
1011 fs = find_form_state(doc_view, link_fc);
1012 if (!fs) return FRAME_EVENT_OK;
1014 if (link_fc->type == FC_CHECKBOX) {
1015 fs->state = !fs->state;
1017 return FRAME_EVENT_REFRESH;
1020 /* @link_fc->type must be FC_RADIO, then. First turn
1021 * this one on, and then turn off all the other radio
1022 * buttons in the group. Do it in this order because
1023 * further @find_form_state calls may reallocate
1024 * @doc_view->vs->form_info[] and thereby make the @fs
1025 * pointer invalid. This also allows us to re-use
1026 * @fs in the loop. */
1027 fs->state = 1;
1028 foreach (form, doc_view->document->forms) {
1029 struct form_control *fc;
1031 if (form != link_fc->form)
1032 continue;
1034 foreach (fc, form->items) {
1035 if (fc->type == FC_RADIO
1036 && !xstrcmp(fc->name, link_fc->name)
1037 && fc != link_fc) {
1038 fs = find_form_state(doc_view, fc);
1039 if (fs) fs->state = 0;
1044 break;
1046 case LINK_SELECT:
1047 link_fc = get_link_form_control(link);
1049 if (form_field_is_readonly(link_fc))
1050 return FRAME_EVENT_OK;
1052 object_lock(doc_view->document);
1053 add_empty_window(ses->tab->term,
1054 (void (*)(void *)) release_document,
1055 doc_view->document);
1056 do_select_submenu(ses->tab->term, link_fc->menu, ses);
1058 break;
1060 default:
1061 INTERNAL("bad link type %d", link->type);
1064 return FRAME_EVENT_REFRESH;
1067 enum frame_event_status
1068 enter(struct session *ses, struct document_view *doc_view, int do_reload)
1070 struct link *link;
1072 assert(ses && doc_view && doc_view->vs && doc_view->document);
1073 if_assert_failed return FRAME_EVENT_REFRESH;
1075 link = get_current_link(doc_view);
1076 if (!link) return FRAME_EVENT_REFRESH;
1079 if (!current_link_evhook(doc_view, SEVHOOK_ONCLICK))
1080 return FRAME_EVENT_REFRESH;
1081 return activate_link(ses, doc_view, link, do_reload);
1084 /** Get the link at the coordinates @a x and @a y, or NULL if none.
1085 * The coordinates are relative to the document view; not to the
1086 * terminal, nor to the document. So (0, 0) means whatever part of
1087 * the document has been scrolled to the top left corner of the
1088 * document view. */
1089 struct link *
1090 get_link_at_coordinates(struct document_view *doc_view, int x, int y)
1092 struct link *l1, *l2, *link;
1093 int i, height;
1095 assert(doc_view && doc_view->vs && doc_view->document);
1096 if_assert_failed return NULL;
1098 /* If there are no links in in document, there is nothing to do. */
1099 if (!doc_view->document->nlinks) return NULL;
1101 /* If the coordinates are outside document view, no need to go further. */
1102 if (x < 0 || x >= doc_view->box.width) return NULL;
1103 if (y < 0 || y >= doc_view->box.height) return NULL;
1105 /* FIXME: This doesn't work. --Zas
1106 if (!check_mouse_position(ev, &doc_view->box))
1107 return NULL;
1110 /* Find link candidates. */
1111 l1 = doc_view->document->links + doc_view->document->nlinks;
1112 l2 = doc_view->document->links;
1113 height = int_min(doc_view->document->height,
1114 doc_view->vs->y + doc_view->box.height);
1116 for (i = doc_view->vs->y; i < height; i++) {
1117 if (doc_view->document->lines1[i]
1118 && doc_view->document->lines1[i] < l1)
1119 l1 = doc_view->document->lines1[i];
1121 if (doc_view->document->lines2[i]
1122 && doc_view->document->lines2[i] > l2)
1123 l2 = doc_view->document->lines2[i];
1126 /* Is there a link at the given coordinates? */
1127 x += doc_view->vs->x;
1128 y += doc_view->vs->y;
1130 for (link = l1; link <= l2; link++) {
1131 for (i = 0; i < link->npoints; i++)
1132 if (link->points[i].x == x
1133 && link->points[i].y == y)
1134 return link;
1137 return NULL;
1140 /** This is backend of the backend goto_link_number_do() below ;)). */
1141 void
1142 jump_to_link_number(struct session *ses, struct document_view *doc_view, int n)
1144 assert(ses && doc_view && doc_view->vs && doc_view->document);
1145 if_assert_failed return;
1147 if (n < 0 || n >= doc_view->document->nlinks) return;
1148 current_link_blur(doc_view);
1149 doc_view->vs->current_link = n;
1150 if (ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
1151 struct link *link = get_current_link(doc_view);
1152 int offset = get_link_cursor_offset(doc_view, link);
1154 if (link->npoints > offset) {
1155 int x = link->points[offset].x
1156 + doc_view->box.x - doc_view->vs->x;
1157 int y = link->points[offset].y
1158 + doc_view->box.y - doc_view->vs->y;
1160 move_cursor(ses, doc_view, x, y);
1163 check_vs(doc_view);
1164 current_link_hover(doc_view);
1167 /** This is common backend for goto_link_number() and try_document_key(). */
1168 static void
1169 goto_link_number_do(struct session *ses, struct document_view *doc_view, int n)
1171 struct link *link;
1173 assert(ses && doc_view && doc_view->document);
1174 if_assert_failed return;
1175 if (n < 0 || n >= doc_view->document->nlinks) return;
1176 jump_to_link_number(ses, doc_view, n);
1178 link = &doc_view->document->links[n];
1179 if (!link_is_textinput(link)
1180 && get_opt_bool("document.browse.accesskey.auto_follow"))
1181 enter(ses, doc_view, 0);
1184 void
1185 goto_link_number(struct session *ses, unsigned char *num)
1187 struct document_view *doc_view;
1189 assert(ses && num);
1190 if_assert_failed return;
1191 doc_view = current_frame(ses);
1192 assert(doc_view);
1193 if_assert_failed return;
1194 goto_link_number_do(ses, doc_view, atoi(num) - 1);
1197 /** See if this document is interested in the key user pressed. */
1198 enum frame_event_status
1199 try_document_key(struct session *ses, struct document_view *doc_view,
1200 struct term_event *ev)
1202 unicode_val_T key;
1203 int i; /* GOD I HATE C! --FF */ /* YEAH, BRAINFUCK RULEZ! --pasky */
1205 assert(ses && doc_view && doc_view->document && doc_view->vs && ev);
1206 if_assert_failed return FRAME_EVENT_IGNORED;
1208 if (!check_kbd_modifier(ev, KBD_MOD_ALT)
1209 || !is_kbd_character(get_kbd_key(ev))) {
1210 /* We accept only alt-character combos. */
1211 return FRAME_EVENT_IGNORED;
1214 /* The key is a character. Convert it to Unicode so that it
1215 * can be compared with link.accesskey. */
1216 #ifdef CONFIG_UTF8
1217 key = get_kbd_key(ev);
1218 #else /* !CONFIG_UTF8 */
1219 key = cp2u(get_terminal_codepage(ses->tab->term),
1220 get_kbd_key(ev));
1221 #endif /* !CONFIG_UTF8 */
1222 /* If @key now is 0 (which is used in link.accesskey if there
1223 * is no access key) or UCS_REPLACEMENT_CHARACTER, then the
1224 * results may be a little odd, but not really harmful. */
1226 /* Run through all the links and see if one of them is bound to the
1227 * key we test.. */
1229 i = doc_view->vs->current_link + 1;
1230 for (; i < doc_view->document->nlinks; i++) {
1231 struct link *link = &doc_view->document->links[i];
1233 if (key == link->accesskey) {
1234 ses->kbdprefix.repeat_count = 0;
1235 goto_link_number_do(ses, doc_view, i);
1236 return FRAME_EVENT_REFRESH;
1239 for (i = 0; i <= doc_view->vs->current_link; i++) {
1240 struct link *link = &doc_view->document->links[i];
1242 if (key == link->accesskey) {
1243 ses->kbdprefix.repeat_count = 0;
1244 goto_link_number_do(ses, doc_view, i);
1245 return FRAME_EVENT_REFRESH;
1249 return FRAME_EVENT_IGNORED;
1252 /** Open a contextual menu on a link, form or image element.
1253 * @todo TODO: This should be completely configurable. */
1254 void
1255 link_menu(struct terminal *term, void *xxx, void *ses_)
1257 struct session *ses = ses_;
1258 struct document_view *doc_view;
1259 struct link *link;
1260 struct menu_item *mi;
1261 struct form_control *fc;
1263 assert(term && ses);
1264 if_assert_failed return;
1266 doc_view = current_frame(ses);
1267 mi = new_menu(FREE_LIST);
1268 if (!mi) return;
1269 if (!doc_view) goto end;
1271 assert(doc_view->vs && doc_view->document);
1272 if_assert_failed return;
1274 link = get_current_link(doc_view);
1275 if (!link) goto end;
1277 if (link->where && !link_is_form(link)) {
1278 if (link->type == LINK_MAP) {
1279 /* [gettext_accelerator_context(link_menu.map)] */
1280 add_to_menu(&mi, N_("Display ~usemap"), NULL, ACT_MAIN_LINK_FOLLOW,
1281 NULL, NULL, SUBMENU);
1282 /* [gettext_accelerator_context()] */
1283 } else {
1284 /* [gettext_accelerator_context(link_menu.std)] */
1285 add_menu_action(&mi, N_("~Follow link"), ACT_MAIN_LINK_FOLLOW);
1287 add_menu_action(&mi, N_("Follow link and r~eload"), ACT_MAIN_LINK_FOLLOW_RELOAD);
1289 add_menu_separator(&mi);
1291 add_new_win_to_menu(&mi, N_("Open in new ~window"), term);
1293 add_menu_action(&mi, N_("Open in new ~tab"), ACT_MAIN_OPEN_LINK_IN_NEW_TAB);
1295 add_menu_action(&mi, N_("Open in new tab in ~background"),
1296 ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND);
1298 if (!get_cmd_opt_bool("anonymous")) {
1299 add_menu_separator(&mi);
1300 add_menu_action(&mi, N_("~Download link"), ACT_MAIN_LINK_DOWNLOAD);
1302 #ifdef CONFIG_BOOKMARKS
1303 add_menu_action(&mi, N_("~Add link to bookmarks"),
1304 ACT_MAIN_ADD_BOOKMARK_LINK);
1305 #endif
1306 add_uri_command_to_menu(&mi, PASS_URI_LINK,
1307 N_("Pass link URI to e~xternal command"));
1309 /* [gettext_accelerator_context()] */
1313 fc = get_link_form_control(link);
1314 if (fc) {
1315 switch (fc->type) {
1316 case FC_RESET:
1317 /* [gettext_accelerator_context(link_menu.reset)] */
1318 add_menu_action(&mi, N_("~Reset form"), ACT_MAIN_RESET_FORM);
1319 /* [gettext_accelerator_context()] */
1320 break;
1322 case FC_TEXTAREA:
1323 /* [gettext_accelerator_context(link_menu.textarea)] */
1324 if (!form_field_is_readonly(fc)) {
1325 struct string keystroke;
1327 if (init_string(&keystroke))
1328 add_keystroke_action_to_string(
1329 &keystroke,
1330 ACT_EDIT_OPEN_EXTERNAL,
1331 KEYMAP_EDIT);
1333 add_to_menu(&mi, N_("Open in ~external editor"),
1334 keystroke.source, ACT_MAIN_NONE,
1335 menu_textarea_edit, NULL, FREE_RTEXT);
1337 /* [gettext_accelerator_context()] */
1338 /* Fall through */
1339 default:
1340 /* [gettext_accelerator_context(link_menu.textarea, link_menu.form)] */
1341 add_menu_action(&mi, N_("~Submit form"), ACT_MAIN_SUBMIT_FORM);
1342 add_menu_action(&mi, N_("Submit form and rel~oad"), ACT_MAIN_SUBMIT_FORM_RELOAD);
1344 assert(fc->form);
1345 if (fc->form->method == FORM_METHOD_GET) {
1346 add_new_win_to_menu(&mi, N_("Submit form and open in new ~window"), term);
1348 add_menu_action(&mi, N_("Submit form and open in new ~tab"),
1349 ACT_MAIN_OPEN_LINK_IN_NEW_TAB);
1351 add_menu_action(&mi, N_("Submit form and open in new tab in ~background"),
1352 ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND);
1355 if (!get_cmd_opt_bool("anonymous"))
1356 add_menu_action(&mi, N_("Submit form and ~download"), ACT_MAIN_LINK_DOWNLOAD);
1358 add_menu_action(&mi, N_("~Reset form"), ACT_MAIN_RESET_FORM);
1359 /* [gettext_accelerator_context()] */
1362 /* [gettext_accelerator_context(link_menu.reset, link_menu.textarea, link_menu.form)] */
1363 add_to_menu(&mi, N_("Form f~ields"), NULL, ACT_MAIN_LINK_FORM_MENU,
1364 NULL, NULL, SUBMENU);
1365 /* [gettext_accelerator_context()] */
1368 if (link->where_img) {
1369 /* [gettext_accelerator_context(link_menu.map, link_menu.std, link_menu.form)] */
1370 add_menu_action(&mi, N_("V~iew image"), ACT_MAIN_VIEW_IMAGE);
1371 if (!get_cmd_opt_bool("anonymous"))
1372 add_menu_action(&mi, N_("Download ima~ge"), ACT_MAIN_LINK_DOWNLOAD_IMAGE);
1373 /* [gettext_accelerator_context()] */
1376 /** @todo TODO: Make it possible to trigger any script event
1377 * hooks associated to the link. --pasky */
1379 end:
1380 if (!mi->text) {
1381 add_to_menu(&mi, N_("No link selected"), NULL, ACT_MAIN_NONE,
1382 NULL, NULL, NO_SELECT);
1385 do_menu(term, mi, ses, 1);
1388 /** Return current link's title. */
1389 unsigned char *
1390 get_current_link_title(struct document_view *doc_view)
1392 struct link *link;
1394 assert(doc_view && doc_view->document && doc_view->vs);
1395 if_assert_failed return NULL;
1397 if (doc_view->document->frame_desc)
1398 return NULL;
1400 link = get_current_link(doc_view);
1402 if (link && link->title && *link->title) {
1403 unsigned char *link_title, *src;
1404 struct conv_table *convert_table;
1406 convert_table = get_translation_table(doc_view->document->cp,
1407 doc_view->document->options.cp);
1409 link_title = convert_string(convert_table, link->title,
1410 strlen(link->title),
1411 doc_view->document->options.cp,
1412 CSM_DEFAULT, NULL, NULL, NULL);
1413 /* Remove illicit chars. */
1414 #ifdef CONFIG_UTF8
1415 if (link_title && !doc_view->document->options.utf8)
1416 #endif /* CONFIG_UTF8 */
1417 for (src = link_title; *src; src++)
1418 if (!isprint(*src) || iscntrl(*src))
1419 *src = '*';
1421 return link_title;
1424 return NULL;
1427 unsigned char *
1428 get_current_link_info(struct session *ses, struct document_view *doc_view)
1430 struct link *link;
1432 assert(ses && doc_view && doc_view->document && doc_view->vs);
1433 if_assert_failed return NULL;
1435 if (doc_view->document->frame_desc)
1436 return NULL;
1438 link = get_current_link(doc_view);
1439 if (!link) return NULL;
1441 /** @todo TODO: Provide info about script event hooks too. --pasky */
1443 if (!link_is_form(link)) {
1444 struct terminal *term = ses->tab->term;
1445 struct string str;
1446 unsigned char *uristring = link->where;
1448 if (!init_string(&str)) return NULL;
1450 if (!link->where && link->where_img) {
1451 add_to_string(&str, _("Image", term));
1452 add_char_to_string(&str, ' ');
1453 uristring = link->where_img;
1455 } else if (link->type == LINK_MAP) {
1456 add_to_string(&str, _("Usemap", term));
1457 add_char_to_string(&str, ' ');
1460 /* Add the uri with password and post info stripped */
1461 add_string_uri_to_string(&str, uristring, URI_PUBLIC);
1462 if (link->accesskey > 0
1463 && get_opt_bool("document.browse.accesskey.display")) {
1464 add_to_string(&str, " (");
1465 add_accesskey_to_string(&str, link->accesskey);
1466 add_char_to_string(&str, ')');
1469 #ifdef CONFIG_UTF8
1470 if (term->utf8_cp)
1471 decode_uri_string(&str);
1472 else
1473 #endif /* CONFIG_UTF8 */
1474 decode_uri_string_for_display(&str);
1475 return str.source;
1478 if (!get_link_form_control(link)) return NULL;
1480 return get_form_info(ses, doc_view);