Always do reload after submitting forms.
[elinks.git] / src / viewer / text / link.c
blob529f17f9a7445b11cf421ad1fa72479dc2319312
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/options.h"
20 #include "document/view.h"
21 #include "ecmascript/ecmascript.h"
22 #include "intl/gettext/libintl.h"
23 #include "main/object.h"
24 #include "protocol/uri.h"
25 #include "session/session.h"
26 #include "session/task.h"
27 #include "terminal/color.h"
28 #include "terminal/draw.h"
29 #include "terminal/kbd.h"
30 #include "terminal/screen.h"
31 #include "terminal/tab.h"
32 #include "terminal/terminal.h"
33 #include "util/box.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"
47 /* Perhaps some of these would be more fun to have in viewer/common/, dunno.
48 * --pasky */
51 static int
52 current_link_evhook(struct document_view *doc_view, enum script_event_hook_type type)
54 #ifdef CONFIG_ECMASCRIPT
55 struct link *link;
56 struct script_event_hook *evhook;
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;
63 if (!doc_view->vs->ecmascript) return -1;
65 foreach (evhook, *link->event_hooks) {
66 unsigned char *ret;
68 if (evhook->type != type) continue;
69 ret = evhook->src;
70 while ((ret = strstr(ret, "return ")))
71 while (*ret != ' ') *ret++ = ' ';
73 struct string src = INIT_STRING(evhook->src, strlen(evhook->src));
74 /* TODO: Some even handlers return a bool. */
75 if (!ecmascript_eval_boolback(doc_view->vs->ecmascript, &src))
76 return 0;
80 return 1;
81 #else
82 return -1;
83 #endif
86 #define current_link_hover(dv) \
87 do { \
88 current_link_evhook(dv, SEVHOOK_ONMOUSEOVER); \
89 current_link_evhook(dv, SEVHOOK_ONHOVER); \
90 current_link_evhook(dv, SEVHOOK_ONFOCUS); \
91 } while (0)
92 #define current_link_blur(dv) \
93 do { \
94 current_link_evhook(dv, SEVHOOK_ONMOUSEOUT); \
95 current_link_evhook(dv, SEVHOOK_ONBLUR); \
96 } while (0)
99 void
100 set_link(struct document_view *doc_view)
102 assert(doc_view);
103 if_assert_failed return;
105 if (current_link_is_visible(doc_view)) return;
107 find_link_page_down(doc_view);
110 static inline int
111 get_link_cursor_offset(struct document_view *doc_view, struct link *link)
113 struct form_control *fc;
114 struct form_state *fs;
115 #ifdef CONFIG_UTF8
116 /* The encoding of form fields depends on the terminal,
117 * rather than on the document. */
118 int utf8 = doc_view->session->tab->term->utf8_cp;
119 #endif /* CONFIG_UTF8 */
121 switch (link->type) {
122 case LINK_CHECKBOX:
123 return 1;
125 case LINK_BUTTON:
126 return 2;
128 case LINK_FIELD:
129 fc = get_link_form_control(link);
130 fs = find_form_state(doc_view, fc);
131 if (!fs || !fs->value)
132 return 0;
133 #ifdef CONFIG_UTF8
134 else if (utf8) {
135 unsigned char *scroll = fs->value + fs->vpos;
136 unsigned char *point = fs->value + fs->state;
138 if (fs->type == FC_PASSWORD)
139 return utf8_ptr2chars(scroll, point);
140 else
141 return utf8_ptr2cells(scroll, point);
143 #endif /* CONFIG_UTF8 */
144 else
145 return fs->state - fs->vpos;
147 case LINK_AREA:
148 fc = get_link_form_control(link);
149 fs = find_form_state(doc_view, fc);
150 #ifdef CONFIG_UTF8
151 return fs ? area_cursor(fc, fs, utf8) : 0;
152 #else
153 return fs ? area_cursor(fc, fs) : 0;
154 #endif /* CONFIG_UTF8 */
156 case LINK_HYPERTEXT:
157 case LINK_MAP:
158 case LINK_SELECT:
159 return 0;
162 return 0;
165 /** Initialise a static template character with the colour and attributes
166 * appropriate for an active link and return that character. */
167 static inline struct screen_char *
168 init_link_drawing(struct document_view *doc_view, struct link *link, int invert)
170 struct document_options *doc_opts;
171 static struct screen_char template;
172 enum color_flags color_flags;
173 enum color_mode color_mode;
174 struct color_pair colors;
176 template.attr = SCREEN_ATTR_STANDOUT;
178 doc_opts = &doc_view->document->options;
180 color_flags = (doc_opts->color_flags | COLOR_DECREASE_LIGHTNESS);
181 color_mode = doc_opts->color_mode;
183 if (doc_opts->active_link.underline)
184 template.attr |= SCREEN_ATTR_UNDERLINE;
186 if (doc_opts->active_link.bold)
187 template.attr |= SCREEN_ATTR_BOLD;
189 if (doc_opts->active_link.enable_color) {
190 colors.foreground = doc_opts->active_link.color.foreground;
191 colors.background = doc_opts->active_link.color.background;
192 } else {
193 colors.foreground = link->color.foreground;
194 colors.background = link->color.background;
197 if (invert && doc_opts->active_link.invert) {
198 swap_values(color_T, colors.foreground, colors.background);
200 /* Highlight text-input form-fields correctly if contrast
201 * correction is needed. */
202 if (link_is_textinput(link)) {
203 /* Make sure to use the options belonging to the
204 * current document when checking for fg and bg color
205 * usage. */
206 doc_opts = &doc_view->document->options;
208 /* Are we fallen angels who didn't want to believe that
209 * nothing /is/ nothing and so were born to lose our
210 * loved ones and dear friends one by one and finally
211 * our own life, to see it proved? --Kerouac */
213 /* Wipe out all default correction for 16 color mode */
214 color_flags = (color_flags & ~COLOR_INCREASE_CONTRAST);
215 /* Make contrast correction invert things properly */
216 color_flags |= COLOR_ENSURE_INVERTED_CONTRAST;
220 set_term_color(&template, &colors, color_flags, color_mode);
222 return &template;
225 /** Give the current link the appropriate colour and attributes. */
226 void
227 draw_current_link(struct session *ses, struct document_view *doc_view)
229 struct terminal *term = ses->tab->term;
230 struct screen_char *template;
231 struct link *link;
232 int cursor_offset;
233 int xpos, ypos;
234 int i;
236 assert(term && doc_view && doc_view->vs);
237 if_assert_failed return;
239 assert(ses->tab == get_current_tab(term));
240 if_assert_failed return;
242 link = get_current_link(doc_view);
243 if (!link) return;
245 i = !link_is_textinput(link) || ses->insert_mode == INSERT_MODE_OFF;
246 template = init_link_drawing(doc_view, link, i);
247 if (!template) return;
249 xpos = doc_view->box.x - doc_view->vs->x;
250 ypos = doc_view->box.y - doc_view->vs->y;
252 if (ses->insert_mode == INSERT_MODE_OFF
253 && ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
254 /* If we are navigating using cursor routing and not editing a
255 * text-input form-field never set the cursor. */
256 cursor_offset = -1;
257 } else {
258 cursor_offset = get_link_cursor_offset(doc_view, link);
261 for (i = 0; i < link->npoints; i++) {
262 int x = link->points[i].x + xpos;
263 int y = link->points[i].y + ypos;
264 struct screen_char *co;
266 if (!is_in_box(&doc_view->box, x, y)) {
267 continue;
270 co = get_char(term, x, y);
272 if (i == cursor_offset) {
273 int blockable = (!link_is_textinput(link)
274 && co->color != template->color);
276 set_cursor(term, x, y, blockable);
277 set_window_ptr(ses->tab, x, y);
280 template->data = co->data;
281 copy_screen_chars(co, template, 1);
282 set_screen_dirty(term->screen, y, y);
285 doc_view->vs->old_current_link = doc_view->vs->current_link;
288 static void
289 draw_link(struct terminal *term, struct document_view *doc_view,
290 struct link *link)
292 int xpos = doc_view->box.x - doc_view->vs->x;
293 int ypos = doc_view->box.y - doc_view->vs->y;
294 int i;
296 for (i = 0; i < link->npoints; ++i) {
297 int x = link->points[i].x;
298 int y = link->points[i].y;
300 if (is_in_box(&doc_view->box, x + xpos, y + ypos)){
301 struct screen_char *ch;
303 ch = get_char(term, x + xpos, y + ypos);
304 copy_struct(ch, &doc_view->document->data[y].chars[x]);
305 set_screen_dirty(term->screen, y + ypos, y + ypos);
310 /** Restore the colours and attributes that the active link had
311 * before it was selected. */
312 void
313 clear_link(struct terminal *term, struct document_view *doc_view)
315 struct link *link = get_current_link(doc_view);
316 struct link *last = get_old_current_link(doc_view);
318 if (last && last != link) {
319 draw_link(term, doc_view, last);
322 doc_view->vs->old_current_link = doc_view->vs->current_link;
325 void
326 highlight_links_with_prefixes_that_start_with_n(struct terminal *term,
327 struct document_view *doc_view,
328 int n)
330 struct color_pair *color = get_bfu_color(term, "searched");
331 int xoffset = doc_view->box.x - doc_view->vs->x;
332 int yoffset = doc_view->box.y - doc_view->vs->y;
333 struct document *document = doc_view->document;
334 int m;
336 for (m = n + 1; n <= document->nlinks; n *= 10, m *= 10) {
337 int linkn;
339 for (linkn = n; linkn < m; ++linkn) {
340 struct link *link = &document->links[linkn - 1];
341 int i;
343 if (linkn > document->nlinks) break;
345 for (i = 0; i < link->npoints; ++i) {
346 int x = link->points[i].x + xoffset;
347 int y = link->points[i].y + yoffset;
349 if (is_in_box(&doc_view->box, x, y))
350 draw_char_color(term, x, y, color);
356 struct link *
357 get_first_link(struct document_view *doc_view)
359 struct link *link, *undef;
360 struct document *document;
361 int height;
362 int i;
364 assert(doc_view && doc_view->document);
365 if_assert_failed return NULL;
367 document = doc_view->document;
369 if (!document->lines1) return NULL;
371 height = doc_view->vs->y + doc_view->box.height;
372 link = undef = document->links + document->nlinks;
374 for (i = int_max(0, doc_view->vs->y);
375 i < int_min(height, document->height);
376 i++) {
377 if (document->lines1[i]
378 && document->lines1[i] < link)
379 link = document->lines1[i];
382 return (link == undef) ? NULL : link;
385 struct link *
386 get_last_link(struct document_view *doc_view)
388 struct link *link = NULL;
389 struct document *document;
390 int height;
391 int i;
393 assert(doc_view && doc_view->document);
394 if_assert_failed return NULL;
396 document = doc_view->document;
398 if (!document->lines2) return NULL;
400 height = doc_view->vs->y + doc_view->box.height;
402 for (i = int_max(0, doc_view->vs->y);
403 i < int_min(height, document->height);
404 i++)
405 if (document->lines2[i] > link)
406 link = document->lines2[i];
408 return link;
411 static int
412 link_in_view_x(struct document_view *doc_view, struct link *link)
414 int i, dx, width;
416 assert(doc_view && link);
417 if_assert_failed return 0;
419 dx = doc_view->vs->x;
420 width = doc_view->box.width;
422 for (i = 0; i < link->npoints; i++) {
423 int x = link->points[i].x - dx;
425 if (x >= 0 && x < width)
426 return 1;
429 return 0;
432 static int
433 link_in_view_y(struct document_view *doc_view, struct link *link)
435 int i, dy, height;
437 assert(doc_view && link);
438 if_assert_failed return 0;
440 dy = doc_view->vs->y;
441 height = doc_view->box.height;
443 for (i = 0; i < link->npoints; i++) {
444 int y = link->points[i].y - dy;
446 if (y >= 0 && y < height)
447 return 1;
450 return 0;
453 static int
454 link_in_view(struct document_view *doc_view, struct link *link)
456 assert(doc_view && link);
457 if_assert_failed return 0;
458 return link_in_view_y(doc_view, link) && link_in_view_x(doc_view, link);
462 current_link_is_visible(struct document_view *doc_view)
464 struct link *link;
466 assert(doc_view && doc_view->vs);
467 if_assert_failed return 0;
469 link = get_current_link(doc_view);
470 return (link && link_in_view(doc_view, link));
473 /** Look for the first and the last link currently visible in our
474 * viewport. */
475 static void
476 get_visible_links_range(struct document_view *doc_view, int *first, int *last)
478 struct document *document = doc_view->document;
479 int height = int_min(doc_view->vs->y + doc_view->box.height,
480 document->height);
481 int y;
483 *first = document->nlinks - 1;
484 *last = 0;
486 for (y = int_max(0, doc_view->vs->y); y < height; y++) {
487 if (document->lines1[y])
488 int_upper_bound(first, document->lines1[y]
489 - document->links);
491 if (document->lines2[y])
492 int_lower_bound(last, document->lines2[y]
493 - document->links);
497 static int
498 next_link_in_view_(struct document_view *doc_view, int current, int direction,
499 int (*fn)(struct document_view *, struct link *),
500 void (*cntr)(struct document_view *, struct link *))
502 struct document *document;
503 struct view_state *vs;
504 int start, end;
506 assert(doc_view && doc_view->document && doc_view->vs && fn);
507 if_assert_failed return 0;
509 document = doc_view->document;
510 vs = doc_view->vs;
512 get_visible_links_range(doc_view, &start, &end);
514 current_link_blur(doc_view);
516 /* Go from the @current link in @direction until either
517 * fn() is happy or we would leave the current viewport. */
518 while (current >= start && current <= end) {
519 if (fn(doc_view, &document->links[current])) {
520 vs->current_link = current;
521 if (cntr) cntr(doc_view, &document->links[current]);
522 current_link_hover(doc_view);
523 return 1;
525 current += direction;
528 vs->current_link = -1;
529 return 0;
533 next_link_in_view(struct document_view *doc_view, int current, int direction)
535 return next_link_in_view_(doc_view, current, direction, link_in_view, NULL);
539 next_link_in_view_y(struct document_view *doc_view, int current, int direction)
541 return next_link_in_view_(doc_view, current, direction, link_in_view_y, set_pos_x);
544 /** Get the bounding columns of @a link at line @a y (or all lines if
545 * @a y == -1). */
546 void
547 get_link_x_bounds(struct link *link, int y, int *min_x, int *max_x)
549 int point;
551 if (min_x) *min_x = INT_MAX;
552 if (max_x) *max_x = 0;
554 for (point = 0; point < link->npoints; point++) {
555 if (y >= 0 && link->points[point].y != y)
556 continue;
557 if (min_x) int_upper_bound(min_x, link->points[point].x);
558 if (max_x) int_lower_bound(max_x, link->points[point].x);
562 /** Check whether there is any point between @a min_x and @a max_x at
563 * the line @a y in link @a link. */
564 static int
565 get_link_x_intersect(struct link *link, int y, int min_x, int max_x)
567 int point;
569 for (point = 0; point < link->npoints; point++) {
570 if (link->points[point].y != y)
571 continue;
572 if (link->points[point].x >= min_x
573 && link->points[point].x <= max_x)
574 return link->points[point].x + 1;
577 return 0;
580 /** Check whether there is any point between @a min_y and @a max_y in
581 * the column @a x in link @a link. */
582 static int
583 get_link_y_intersect(struct link *link, int x, int min_y, int max_y)
585 int point;
587 for (point = 0; point < link->npoints; point++) {
588 if (link->points[point].x != x)
589 continue;
590 if (link->points[point].y >= min_y
591 && link->points[point].y <= max_y)
592 return link->points[point].y + 1;
595 return 0;
599 next_link_in_dir(struct document_view *doc_view, int dir_x, int dir_y)
601 struct document *document;
602 struct view_state *vs;
603 struct link *link;
604 int min_x = INT_MAX, max_x = 0;
605 int min_y, max_y;
607 assert(doc_view && doc_view->document && doc_view->vs);
608 if_assert_failed return 0;
609 assert(dir_x || dir_y);
610 if_assert_failed return 0;
612 document = doc_view->document;
613 vs = doc_view->vs;
615 link = get_current_link(doc_view);
616 if (!link) return 0;
618 /* Find the link's "bounding box" coordinates. */
620 get_link_x_bounds(link, -1, &min_x, &max_x);
622 min_y = link->points[0].y;
623 max_y = link->points[link->npoints - 1].y;
625 /* Now go from the bounding box edge in the appropriate
626 * direction and find the nearest link. */
628 if (dir_y) {
629 /* Vertical movement */
631 /* The current line number */
632 int y = (dir_y > 0 ? max_y : min_y) + dir_y;
633 /* The bounding line numbers */
634 int top = int_max(0, doc_view->vs->y);
635 int bottom = int_min(doc_view->vs->y + doc_view->box.height,
636 document->height);
638 for (; dir_y > 0 ? y < bottom : y >= top; y += dir_y) {
639 /* @backup points to the nearest link from the left
640 * to the desired position. */
641 struct link *backup = NULL;
643 link = document->lines1[y];
644 if (!link) continue;
646 /* Go through all the links on line. */
647 for (; link <= document->lines2[y]; link++) {
648 int l_min_x, l_max_x;
650 /* Some links can be totally out of order here,
651 * ie. in tables or when using tabindex. */
652 if (y < link->points[0].y
653 || y > link->points[link->npoints - 1].y)
654 continue;
656 get_link_x_bounds(link, y, &l_min_x, &l_max_x);
657 if (l_min_x > max_x) {
658 /* This link is too at the right. */
659 if (!backup)
660 backup = link;
661 continue;
663 if (l_max_x < min_x) {
664 /* This link is too at the left. */
665 backup = link;
666 continue;
668 /* This link is aligned with the current one. */
669 goto chose_link;
672 if (backup) {
673 link = backup;
674 goto chose_link;
678 if (!y || y == document->height) {
679 /* We just stay at the same place, do not invalidate
680 * the link number. */
681 return 0;
684 } else {
685 /* Horizontal movement */
687 /* The current column number */
688 int x = (dir_x > 0 ? max_x : min_x) + dir_x;
689 /* How many lines are already past their last link */
690 int last = 0;
692 while ((last < max_y - min_y + 1) && (x += dir_x) >= 0) {
693 int y;
695 last = 0;
697 /* Go through all the lines */
698 for (y = min_y; y <= max_y; y++) {
699 link = document->lines1[y];
700 if (!link) continue;
702 /* Go through all the links on line. */
703 while (link <= document->lines2[y]) {
704 if (get_link_y_intersect(link, x,
705 min_y,
706 max_y))
707 goto chose_link;
708 link++;
711 /* Check if we already aren't past the last
712 * link on this line. */
713 if (!get_link_x_intersect(document->lines2[y],
714 y, x, INT_MAX))
715 last++;
719 /* We just stay */
720 return 0;
723 current_link_blur(doc_view);
724 vs->current_link = -1;
725 return 0;
727 chose_link:
728 /* The link is in bounds, take it. */
729 current_link_blur(doc_view);
730 vs->current_link = get_link_index(document, link);
731 set_pos_x(doc_view, link);
732 current_link_hover(doc_view);
733 return 1;
736 void
737 set_pos_x(struct document_view *doc_view, struct link *link)
739 int xm = 0;
740 int xl = INT_MAX;
741 int i;
743 assert(doc_view && link);
744 if_assert_failed return;
746 for (i = 0; i < link->npoints; i++) {
747 int y = link->points[i].y - doc_view->vs->y;
749 if (y >= 0 && y < doc_view->box.height) {
750 int_lower_bound(&xm, link->points[i].x + 1);
751 int_upper_bound(&xl, link->points[i].x);
755 if (xl != INT_MAX)
756 int_bounds(&doc_view->vs->x, xm - doc_view->box.width, xl);
759 void
760 set_pos_y(struct document_view *doc_view, struct link *link)
762 int ym = 0;
763 int height;
764 int i;
766 assert(doc_view && doc_view->document && doc_view->vs && link);
767 if_assert_failed return;
769 height = doc_view->document->height;
770 for (i = 0; i < link->npoints; i++) {
771 int_lower_bound(&ym, link->points[i].y + 1);
772 int_upper_bound(&height, link->points[i].y);
774 doc_view->vs->y = (ym + height - doc_view->box.height) / 2;
775 int_bounds(&doc_view->vs->y, 0,
776 doc_view->document->height - doc_view->box.height);
779 /** Focus the next link in the specified direction.
780 * @a direction == 1 -> DOWN;
781 * @a direction == -1 -> UP */
782 static void
783 find_link(struct document_view *doc_view, int direction, int page_mode)
785 struct link **line;
786 struct link *link = NULL;
787 int link_pos;
788 int y, ymin, ymax;
790 assert(doc_view && doc_view->document && doc_view->vs);
791 if_assert_failed return;
793 if (direction == -1) {
794 /* UP */
795 line = doc_view->document->lines2;
796 if (!line) goto nolink;
797 y = doc_view->vs->y + doc_view->box.height - 1;
798 int_upper_bound(&y, doc_view->document->height - 1);
799 if (y < 0) goto nolink;
800 } else {
801 /* DOWN */
802 line = doc_view->document->lines1;
803 if (!line) goto nolink;
804 y = doc_view->vs->y;
805 int_lower_bound(&y, 0);
806 if (y >= doc_view->document->height) goto nolink;
809 ymin = int_max(0, doc_view->vs->y);
810 ymax = int_min(doc_view->document->height,
811 doc_view->vs->y + doc_view->box.height);
813 if (direction == -1) {
814 /* UP */
815 do {
816 struct link *cur = line[y--];
818 if (cur && (!link || cur > link))
819 link = cur;
820 } while (y >= ymin && y < ymax);
822 } else {
823 /* DOWN */
824 do {
825 struct link *cur = line[y++];
827 if (cur && (!link || cur < link))
828 link = cur;
829 } while (y >= ymin && y < ymax);
832 if (!link) goto nolink;
834 link_pos = link - doc_view->document->links;
835 if (page_mode) {
836 /* PAGE */
837 next_link_in_view(doc_view, link_pos, direction);
838 return;
840 current_link_blur(doc_view);
841 doc_view->vs->current_link = link_pos;
842 set_pos_x(doc_view, link);
843 current_link_hover(doc_view);
844 return;
846 nolink:
847 current_link_blur(doc_view);
848 doc_view->vs->current_link = -1;
851 void
852 find_link_up(struct document_view *doc_view)
854 find_link(doc_view, -1, 0);
857 void
858 find_link_page_up(struct document_view *doc_view)
860 find_link(doc_view, -1, 1);
863 void
864 find_link_down(struct document_view *doc_view)
866 find_link(doc_view, 1, 0);
869 void
870 find_link_page_down(struct document_view *doc_view)
872 find_link(doc_view, 1, 1);
875 struct uri *
876 get_link_uri(struct session *ses, struct document_view *doc_view,
877 struct link *link)
879 assert(ses && doc_view && link);
880 if_assert_failed return NULL;
882 switch (link->type) {
883 case LINK_HYPERTEXT:
884 case LINK_MAP:
885 if (link->where) return get_uri(link->where, 0);
886 return get_uri(link->where_img, 0);
888 case LINK_BUTTON:
889 case LINK_FIELD:
890 return get_form_uri(ses, doc_view,
891 get_link_form_control(link));
893 default:
894 return NULL;
898 static int
899 call_onsubmit_and_submit(struct session *ses, struct document_view *doc_view,
900 struct form_control *fc, int do_reload)
902 struct uri *uri = NULL;
903 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD : CACHE_MODE_NORMAL;
905 assert(fc->form); /* regardless of whether there is a FORM element */
906 if_assert_failed return 0;
908 #ifdef CONFIG_ECMASCRIPT
909 /* If the form has multiple submit buttons, this does not
910 * explicitly tell the ECMAScript code which of them was
911 * pressed. W3C DOM Level 3 doesn't seem to include such a
912 * feature. */
913 if (fc->type != FC_RESET && fc->form->onsubmit) {
914 struct string code;
916 if (init_string(&code)) {
917 struct view_state *vs = doc_view->vs;
918 struct ecmascript_interpreter *interpreter;
919 int res;
921 if (vs->ecmascript_fragile)
922 ecmascript_reset_state(vs);
923 interpreter = vs->ecmascript;
924 /* If there is an onsubmit script and we want
925 * to run it, but the ECMAScript interpreter
926 * cannot be initialized, then don't submit. */
927 if (!interpreter) {
928 done_string(&code);
929 /* See the comment below for the
930 * return value. */
931 return 1;
934 add_to_string(&code, fc->form->onsubmit);
935 res = ecmascript_eval_boolback(interpreter, &code);
936 done_string(&code);
937 /* If the user presses Enter in a text field,
938 * and document.browse.forms.auto_submit is
939 * true, and the form has an onsubmit script
940 * that returns false, then insert mode should
941 * end, so return 1 here rather than 0. */
942 if (!res) return 1;
945 #endif /* CONFIG_ECMASCRIPT */
947 uri = get_form_uri(ses, doc_view, fc);
948 if (!uri) return 0;
949 goto_uri_frame(ses, uri, fc->form->target, mode);
950 done_uri(uri);
951 return 1;
954 struct link *
955 goto_link(struct session *ses, struct document_view *doc_view, struct link *link, int do_reload)
957 struct uri *uri;
959 assert(link && doc_view && ses);
960 if_assert_failed return NULL;
962 if (link_is_form(link)) {
963 struct form_control *fc = link->data.form_control;
965 if (fc->type != FC_BUTTON
966 && !call_onsubmit_and_submit(ses, doc_view, fc, do_reload))
967 return NULL;
968 else
969 return link;
970 } else
971 uri = get_link_uri(ses, doc_view, link);
973 if (!uri) return NULL;
975 if (link->type == LINK_MAP) {
976 /* TODO: Test reload? */
977 goto_imgmap(ses, uri, link->target);
979 } else {
980 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD
981 : CACHE_MODE_NORMAL;
983 goto_uri_frame(ses, uri, link->target, mode);
986 done_uri(uri);
987 return link;
990 struct link *
991 goto_current_link(struct session *ses, struct document_view *doc_view, int do_reload)
993 struct link *link;
995 assert(doc_view && ses);
996 if_assert_failed return NULL;
998 link = get_current_link(doc_view);
999 if (!link) return NULL;
1001 return goto_link(ses, doc_view, link, do_reload);
1004 static enum frame_event_status
1005 activate_link(struct session *ses, struct document_view *doc_view,
1006 struct link *link, int do_reload)
1008 struct form_control *link_fc;
1009 struct form_state *fs;
1010 struct form *form;
1012 switch (link->type) {
1013 case LINK_BUTTON:
1014 do_reload = 1;
1015 case LINK_HYPERTEXT:
1016 case LINK_MAP:
1017 case LINK_FIELD:
1018 case LINK_AREA:
1019 if (goto_link(ses, doc_view, link, do_reload))
1020 return FRAME_EVENT_OK;
1021 break;
1022 case LINK_CHECKBOX:
1023 link_fc = get_link_form_control(link);
1025 if (form_field_is_readonly(link_fc))
1026 return FRAME_EVENT_OK;
1028 fs = find_form_state(doc_view, link_fc);
1029 if (!fs) return FRAME_EVENT_OK;
1031 if (link_fc->type == FC_CHECKBOX) {
1032 fs->state = !fs->state;
1034 return FRAME_EVENT_REFRESH;
1037 /* @link_fc->type must be FC_RADIO, then. First turn
1038 * this one on, and then turn off all the other radio
1039 * buttons in the group. Do it in this order because
1040 * further @find_form_state calls may reallocate
1041 * @doc_view->vs->form_info[] and thereby make the @fs
1042 * pointer invalid. This also allows us to re-use
1043 * @fs in the loop. */
1044 fs->state = 1;
1045 foreach (form, doc_view->document->forms) {
1046 struct form_control *fc;
1048 if (form != link_fc->form)
1049 continue;
1051 foreach (fc, form->items) {
1052 if (fc->type == FC_RADIO
1053 && !xstrcmp(fc->name, link_fc->name)
1054 && fc != link_fc) {
1055 fs = find_form_state(doc_view, fc);
1056 if (fs) fs->state = 0;
1061 break;
1063 case LINK_SELECT:
1064 link_fc = get_link_form_control(link);
1066 if (form_field_is_readonly(link_fc))
1067 return FRAME_EVENT_OK;
1069 object_lock(doc_view->document);
1070 add_empty_window(ses->tab->term,
1071 (void (*)(void *)) release_document,
1072 doc_view->document);
1073 do_select_submenu(ses->tab->term, link_fc->menu, ses);
1075 break;
1077 default:
1078 INTERNAL("bad link type %d", link->type);
1081 return FRAME_EVENT_REFRESH;
1084 enum frame_event_status
1085 enter(struct session *ses, struct document_view *doc_view, int do_reload)
1087 struct link *link;
1089 assert(ses && doc_view && doc_view->vs && doc_view->document);
1090 if_assert_failed return FRAME_EVENT_REFRESH;
1092 link = get_current_link(doc_view);
1093 if (!link) return FRAME_EVENT_REFRESH;
1096 if (!current_link_evhook(doc_view, SEVHOOK_ONCLICK))
1097 return FRAME_EVENT_REFRESH;
1098 return activate_link(ses, doc_view, link, do_reload);
1101 /** Get the link at the coordinates @a x and @a y, or NULL if none.
1102 * The coordinates are relative to the document view; not to the
1103 * terminal, nor to the document. So (0, 0) means whatever part of
1104 * the document has been scrolled to the top left corner of the
1105 * document view. */
1106 struct link *
1107 get_link_at_coordinates(struct document_view *doc_view, int x, int y)
1109 struct link *l1, *l2, *link;
1110 int i, height;
1112 assert(doc_view && doc_view->vs && doc_view->document);
1113 if_assert_failed return NULL;
1115 /* If there are no links in in document, there is nothing to do. */
1116 if (!doc_view->document->nlinks) return NULL;
1118 /* If the coordinates are outside document view, no need to go further. */
1119 if (x < 0 || x >= doc_view->box.width) return NULL;
1120 if (y < 0 || y >= doc_view->box.height) return NULL;
1122 /* FIXME: This doesn't work. --Zas
1123 if (!check_mouse_position(ev, &doc_view->box))
1124 return NULL;
1127 /* Find link candidates. */
1128 l1 = doc_view->document->links + doc_view->document->nlinks;
1129 l2 = doc_view->document->links;
1130 height = int_min(doc_view->document->height,
1131 doc_view->vs->y + doc_view->box.height);
1133 for (i = doc_view->vs->y; i < height; i++) {
1134 if (doc_view->document->lines1[i]
1135 && doc_view->document->lines1[i] < l1)
1136 l1 = doc_view->document->lines1[i];
1138 if (doc_view->document->lines2[i]
1139 && doc_view->document->lines2[i] > l2)
1140 l2 = doc_view->document->lines2[i];
1143 /* Is there a link at the given coordinates? */
1144 x += doc_view->vs->x;
1145 y += doc_view->vs->y;
1147 for (link = l1; link <= l2; link++) {
1148 for (i = 0; i < link->npoints; i++)
1149 if (link->points[i].x == x
1150 && link->points[i].y == y)
1151 return link;
1154 return NULL;
1157 /** This is backend of the backend goto_link_number_do() below ;)). */
1158 void
1159 jump_to_link_number(struct session *ses, struct document_view *doc_view, int n)
1161 assert(ses && doc_view && doc_view->vs && doc_view->document);
1162 if_assert_failed return;
1164 if (n < 0 || n >= doc_view->document->nlinks) return;
1165 current_link_blur(doc_view);
1166 doc_view->vs->current_link = n;
1167 if (ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
1168 struct link *link = get_current_link(doc_view);
1169 int offset = get_link_cursor_offset(doc_view, link);
1171 if (link->npoints > offset) {
1172 int x = link->points[offset].x
1173 + doc_view->box.x - doc_view->vs->x;
1174 int y = link->points[offset].y
1175 + doc_view->box.y - doc_view->vs->y;
1177 move_cursor(ses, doc_view, x, y);
1180 check_vs(doc_view);
1181 current_link_hover(doc_view);
1184 /** This is common backend for goto_link_number() and try_document_key(). */
1185 static void
1186 goto_link_number_do(struct session *ses, struct document_view *doc_view, int n)
1188 struct link *link;
1190 assert(ses && doc_view && doc_view->document);
1191 if_assert_failed return;
1192 if (n < 0 || n >= doc_view->document->nlinks) return;
1193 jump_to_link_number(ses, doc_view, n);
1195 link = &doc_view->document->links[n];
1196 if (!link_is_textinput(link)
1197 && get_opt_bool("document.browse.accesskey.auto_follow", ses))
1198 enter(ses, doc_view, 0);
1201 void
1202 goto_link_number(struct session *ses, unsigned char *num)
1204 struct document_view *doc_view;
1206 assert(ses && num);
1207 if_assert_failed return;
1208 doc_view = current_frame(ses);
1209 assert(doc_view);
1210 if_assert_failed return;
1211 goto_link_number_do(ses, doc_view, atoi(num) - 1);
1214 /** See if this document is interested in the key user pressed. */
1215 enum frame_event_status
1216 try_document_key(struct session *ses, struct document_view *doc_view,
1217 struct term_event *ev)
1219 unicode_val_T key;
1220 int i; /* GOD I HATE C! --FF */ /* YEAH, BRAINFUCK RULEZ! --pasky */
1222 assert(ses && doc_view && doc_view->document && doc_view->vs && ev);
1223 if_assert_failed return FRAME_EVENT_IGNORED;
1225 if (!check_kbd_modifier(ev, KBD_MOD_ALT)
1226 || !is_kbd_character(get_kbd_key(ev))) {
1227 /* We accept only alt-character combos. */
1228 return FRAME_EVENT_IGNORED;
1231 /* The key is a character. Convert it to Unicode so that it
1232 * can be compared with link.accesskey. */
1233 #ifdef CONFIG_UTF8
1234 key = get_kbd_key(ev);
1235 #else /* !CONFIG_UTF8 */
1236 key = cp2u(get_terminal_codepage(ses->tab->term),
1237 get_kbd_key(ev));
1238 #endif /* !CONFIG_UTF8 */
1239 /* If @key now is 0 (which is used in link.accesskey if there
1240 * is no access key) or UCS_REPLACEMENT_CHARACTER, then the
1241 * results may be a little odd, but not really harmful. */
1243 /* Run through all the links and see if one of them is bound to the
1244 * key we test.. */
1246 i = doc_view->vs->current_link + 1;
1247 for (; i < doc_view->document->nlinks; i++) {
1248 struct link *link = &doc_view->document->links[i];
1250 if (key == link->accesskey) {
1251 set_kbd_repeat_count(ses, 0);
1252 goto_link_number_do(ses, doc_view, i);
1253 return FRAME_EVENT_REFRESH;
1256 for (i = 0; i <= doc_view->vs->current_link; i++) {
1257 struct link *link = &doc_view->document->links[i];
1259 if (key == link->accesskey) {
1260 set_kbd_repeat_count(ses, 0);
1261 goto_link_number_do(ses, doc_view, i);
1262 return FRAME_EVENT_REFRESH;
1266 return FRAME_EVENT_IGNORED;
1269 /** Open a contextual menu on a link, form or image element.
1270 * @todo TODO: This should be completely configurable. */
1271 void
1272 link_menu(struct terminal *term, void *xxx, void *ses_)
1274 struct session *ses = ses_;
1275 struct document_view *doc_view;
1276 struct link *link;
1277 struct menu_item *mi;
1278 struct form_control *fc;
1280 assert(term && ses);
1281 if_assert_failed return;
1283 doc_view = current_frame(ses);
1284 mi = new_menu(FREE_LIST);
1285 if (!mi) return;
1286 if (!doc_view) goto end;
1288 assert(doc_view->vs && doc_view->document);
1289 if_assert_failed return;
1291 link = get_current_link(doc_view);
1292 if (!link) goto end;
1294 if (link->where && !link_is_form(link)) {
1295 if (link->type == LINK_MAP) {
1296 /* [gettext_accelerator_context(link_menu.map)] */
1297 add_to_menu(&mi, N_("Display ~usemap"), NULL, ACT_MAIN_LINK_FOLLOW,
1298 NULL, NULL, SUBMENU);
1299 /* [gettext_accelerator_context()] */
1300 } else {
1301 /* [gettext_accelerator_context(link_menu.std)] */
1302 add_menu_action(&mi, N_("~Follow link"), ACT_MAIN_LINK_FOLLOW);
1304 add_menu_action(&mi, N_("Follow link and r~eload"), ACT_MAIN_LINK_FOLLOW_RELOAD);
1306 add_menu_action(&mi, N_("~Link info"), ACT_MAIN_LINK_INFO);
1308 add_menu_separator(&mi);
1310 add_new_win_to_menu(&mi, N_("Open in new ~window"), term);
1312 add_menu_action(&mi, N_("Open in new ~tab"), ACT_MAIN_OPEN_LINK_IN_NEW_TAB);
1314 add_menu_action(&mi, N_("Open in new tab in ~background"),
1315 ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND);
1317 if (!get_cmd_opt_bool("anonymous")) {
1318 add_menu_separator(&mi);
1319 add_menu_action(&mi, N_("~Download link"), ACT_MAIN_LINK_DOWNLOAD);
1321 #ifdef CONFIG_BOOKMARKS
1322 add_menu_action(&mi, N_("~Add link to bookmarks"),
1323 ACT_MAIN_ADD_BOOKMARK_LINK);
1324 #endif
1325 add_uri_command_to_menu(&mi, PASS_URI_LINK,
1326 N_("Pass link URI to e~xternal command"));
1328 /* [gettext_accelerator_context()] */
1332 fc = get_link_form_control(link);
1333 if (fc) {
1334 switch (fc->type) {
1335 case FC_RESET:
1336 /* [gettext_accelerator_context(link_menu.reset)] */
1337 add_menu_action(&mi, N_("~Reset form"), ACT_MAIN_RESET_FORM);
1338 /* [gettext_accelerator_context()] */
1339 break;
1341 case FC_TEXTAREA:
1342 /* [gettext_accelerator_context(link_menu.textarea)] */
1343 if (!form_field_is_readonly(fc)) {
1344 struct string keystroke;
1346 if (init_string(&keystroke))
1347 add_keystroke_action_to_string(
1348 &keystroke,
1349 ACT_EDIT_OPEN_EXTERNAL,
1350 KEYMAP_EDIT);
1352 add_to_menu(&mi, N_("Open in ~external editor"),
1353 keystroke.source, ACT_MAIN_NONE,
1354 menu_textarea_edit, NULL, FREE_RTEXT);
1356 /* [gettext_accelerator_context()] */
1357 /* Fall through */
1358 default:
1359 /* [gettext_accelerator_context(link_menu.textarea, link_menu.form)] */
1360 add_menu_action(&mi, N_("~Submit form"), ACT_MAIN_SUBMIT_FORM);
1361 add_menu_action(&mi, N_("Submit form and rel~oad"), ACT_MAIN_SUBMIT_FORM_RELOAD);
1363 assert(fc->form);
1364 if (fc->form->method == FORM_METHOD_GET) {
1365 add_new_win_to_menu(&mi, N_("Submit form and open in new ~window"), term);
1367 add_menu_action(&mi, N_("Submit form and open in new ~tab"),
1368 ACT_MAIN_OPEN_LINK_IN_NEW_TAB);
1370 add_menu_action(&mi, N_("Submit form and open in new tab in ~background"),
1371 ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND);
1374 if (!get_cmd_opt_bool("anonymous"))
1375 add_menu_action(&mi, N_("Submit form and ~download"), ACT_MAIN_LINK_DOWNLOAD);
1377 add_menu_action(&mi, N_("~Reset form"), ACT_MAIN_RESET_FORM);
1378 /* [gettext_accelerator_context()] */
1381 /* [gettext_accelerator_context(link_menu.reset, link_menu.textarea, link_menu.form)] */
1382 add_to_menu(&mi, N_("Form f~ields"), NULL, ACT_MAIN_LINK_FORM_MENU,
1383 NULL, NULL, SUBMENU);
1384 /* [gettext_accelerator_context()] */
1387 if (link->where_img) {
1388 /* [gettext_accelerator_context(link_menu.map, link_menu.std, link_menu.form)] */
1389 add_menu_action(&mi, N_("V~iew image"), ACT_MAIN_VIEW_IMAGE);
1390 if (!get_cmd_opt_bool("anonymous"))
1391 add_menu_action(&mi, N_("Download ima~ge"), ACT_MAIN_LINK_DOWNLOAD_IMAGE);
1392 /* [gettext_accelerator_context()] */
1395 /** @todo TODO: Make it possible to trigger any script event
1396 * hooks associated to the link. --pasky */
1398 end:
1399 if (!mi->text) {
1400 add_to_menu(&mi, N_("No link selected"), NULL, ACT_MAIN_NONE,
1401 NULL, NULL, NO_SELECT);
1404 do_menu(term, mi, ses, 1);
1407 /** Return current link's title. */
1408 unsigned char *
1409 get_current_link_title(struct document_view *doc_view)
1411 struct link *link;
1413 assert(doc_view && doc_view->document && doc_view->vs);
1414 if_assert_failed return NULL;
1416 if (doc_view->document->frame_desc)
1417 return NULL;
1419 link = get_current_link(doc_view);
1421 if (link && link->title && *link->title) {
1422 unsigned char *link_title, *src;
1423 struct conv_table *convert_table;
1425 convert_table = get_translation_table(doc_view->document->cp,
1426 doc_view->document->options.cp);
1428 /* CSM_NONE because any entities in the title have
1429 * already been decoded. */
1430 link_title = convert_string(convert_table, link->title,
1431 strlen(link->title),
1432 doc_view->document->options.cp,
1433 CSM_NONE, NULL, NULL, NULL);
1434 /* Remove illicit chars. */
1435 #ifdef CONFIG_UTF8
1436 if (link_title && !doc_view->document->options.utf8)
1437 #endif /* CONFIG_UTF8 */
1438 for (src = link_title; *src; src++)
1439 if (!isprint(*src) || iscntrl(*src))
1440 *src = '*';
1442 return link_title;
1445 return NULL;
1448 unsigned char *
1449 get_current_link_info(struct session *ses, struct document_view *doc_view)
1451 struct link *link;
1453 assert(ses && doc_view && doc_view->document && doc_view->vs);
1454 if_assert_failed return NULL;
1456 if (doc_view->document->frame_desc)
1457 return NULL;
1459 link = get_current_link(doc_view);
1460 if (!link) return NULL;
1462 /** @todo TODO: Provide info about script event hooks too. --pasky */
1464 if (!link_is_form(link)) {
1465 struct terminal *term = ses->tab->term;
1466 struct string str;
1467 unsigned char *uristring = link->where;
1469 if (!init_string(&str)) return NULL;
1471 if (!link->where && link->where_img) {
1472 add_to_string(&str, _("Image", term));
1473 add_char_to_string(&str, ' ');
1474 uristring = link->where_img;
1476 } else if (link->type == LINK_MAP) {
1477 add_to_string(&str, _("Usemap", term));
1478 add_char_to_string(&str, ' ');
1481 /* Add the uri with password and post info stripped */
1482 add_string_uri_to_string(&str, uristring, URI_PUBLIC);
1483 if (link->accesskey > 0
1484 && get_opt_bool("document.browse.accesskey.display",
1485 ses)) {
1486 add_to_string(&str, " (");
1487 add_accesskey_to_string(&str, link->accesskey);
1488 add_char_to_string(&str, ')');
1491 #ifdef CONFIG_UTF8
1492 if (term->utf8_cp)
1493 decode_uri_string(&str);
1494 else
1495 #endif /* CONFIG_UTF8 */
1496 decode_uri_string_for_display(&str);
1497 return str.source;
1500 if (!get_link_form_control(link)) return NULL;
1502 return get_form_info(ses, doc_view);