Handling onsubmit
[elinks.git] / src / viewer / text / link.c
blobf28e81e5e3e508c806da9a7fce96834877a06e22
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 "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"
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 struct string src = INIT_STRING(evhook->src, strlen(evhook->src));
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;
74 return 1;
75 #else
76 return -1;
77 #endif
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)
93 void
94 set_link(struct document_view *doc_view)
96 assert(doc_view);
97 if_assert_failed return;
99 if (current_link_is_visible(doc_view)) return;
101 find_link_page_down(doc_view);
104 static inline int
105 get_link_cursor_offset(struct document_view *doc_view, struct link *link)
107 struct form_control *fc;
108 struct form_state *fs;
110 switch (link->type) {
111 case LINK_CHECKBOX:
112 return 1;
114 case LINK_BUTTON:
115 return 2;
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;
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;
127 case LINK_HYPERTEXT:
128 case LINK_MAP:
129 case LINK_SELECT:
130 return 0;
133 return 0;
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 *
141 init_link_drawing(struct document_view *doc_view, struct link *link, int invert)
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;
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;
153 doc_view->link_bg_n = link->npoints;
155 /* Setup the template char. */
156 template = &doc_view->link_bg[link->npoints].c;
158 template->attr = SCREEN_ATTR_STANDOUT;
160 doc_opts = &doc_view->document->options;
162 color_flags = (doc_opts->color_flags | COLOR_DECREASE_LIGHTNESS);
163 color_mode = doc_opts->color_mode;
165 if (doc_opts->active_link.underline)
166 template->attr |= SCREEN_ATTR_UNDERLINE;
168 if (doc_opts->active_link.bold)
169 template->attr |= SCREEN_ATTR_BOLD;
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;
179 if (invert && doc_opts->active_link.invert) {
180 swap_values(color_T, colors.foreground, colors.background);
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;
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 */
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;
202 set_term_color(template, &colors, color_flags, color_mode);
204 return template;
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
210 draw_current_link(struct session *ses, struct document_view *doc_view)
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;
219 assert(term && doc_view && doc_view->vs);
220 if_assert_failed return;
222 assert(ses->tab == get_current_tab(term));
223 if_assert_failed return;
225 assertm(!doc_view->link_bg, "link background not empty");
226 if_assert_failed mem_free(doc_view->link_bg);
228 link = get_current_link(doc_view);
229 if (!link) return;
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;
235 xpos = doc_view->box.x - doc_view->vs->x;
236 ypos = doc_view->box.y - doc_view->vs->y;
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);
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;
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;
258 doc_view->link_bg[i].x = x;
259 doc_view->link_bg[i].y = y;
261 co = get_char(term, x, y);
262 copy_screen_chars(&doc_view->link_bg[i].c, co, 1);
264 if (i == cursor_offset) {
265 int blockable = (!link_is_textinput(link)
266 && co->color != template->color);
268 set_cursor(term, x, y, blockable);
269 set_window_ptr(ses->tab, x, y);
272 template->data = co->data;
273 copy_screen_chars(co, template, 1);
274 set_screen_dirty(term->screen, y, y);
278 void
279 free_link(struct document_view *doc_view)
281 assert(doc_view);
282 if_assert_failed return;
284 mem_free_set(&doc_view->link_bg, NULL);
285 doc_view->link_bg_n = 0;
288 /* Restore the colours and attributes that the active link had
289 * before it was selected. */
290 void
291 clear_link(struct terminal *term, struct document_view *doc_view)
293 assert(term && doc_view);
294 if_assert_failed return;
296 if (doc_view->link_bg) {
297 struct link_bg *link_bg = doc_view->link_bg;
298 int i;
300 for (i = doc_view->link_bg_n - 1; i >= 0; i--) {
301 struct link_bg *bgchar = &link_bg[i];
303 if (bgchar->x != -1 && bgchar->y != -1) {
304 struct terminal_screen *screen = term->screen;
305 struct screen_char *co;
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);
313 free_link(doc_view);
317 struct link *
318 get_first_link(struct document_view *doc_view)
320 struct link *link, *undef;
321 struct document *document;
322 int height;
323 int i;
325 assert(doc_view && doc_view->document);
326 if_assert_failed return NULL;
328 document = doc_view->document;
330 if (!document->lines1) return NULL;
332 height = doc_view->vs->y + doc_view->box.height;
333 link = undef = document->links + document->nlinks;
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];
343 return (link == undef) ? NULL : link;
346 struct link *
347 get_last_link(struct document_view *doc_view)
349 struct link *link = NULL;
350 struct document *document;
351 int height;
352 int i;
354 assert(doc_view && doc_view->document);
355 if_assert_failed return NULL;
357 document = doc_view->document;
359 if (!document->lines2) return NULL;
361 height = doc_view->vs->y + doc_view->box.height;
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];
369 return link;
372 static int
373 link_in_view_x(struct document_view *doc_view, struct link *link)
375 int i, dx, width;
377 assert(doc_view && link);
378 if_assert_failed return 0;
380 dx = doc_view->vs->x;
381 width = doc_view->box.width;
383 for (i = 0; i < link->npoints; i++) {
384 int x = link->points[i].x - dx;
386 if (x >= 0 && x < width)
387 return 1;
390 return 0;
393 static int
394 link_in_view_y(struct document_view *doc_view, struct link *link)
396 int i, dy, height;
398 assert(doc_view && link);
399 if_assert_failed return 0;
401 dy = doc_view->vs->y;
402 height = doc_view->box.height;
404 for (i = 0; i < link->npoints; i++) {
405 int y = link->points[i].y - dy;
407 if (y >= 0 && y < height)
408 return 1;
411 return 0;
414 static int
415 link_in_view(struct document_view *doc_view, struct link *link)
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);
423 current_link_is_visible(struct document_view *doc_view)
425 struct link *link;
427 assert(doc_view && doc_view->vs);
428 if_assert_failed return 0;
430 link = get_current_link(doc_view);
431 return (link && link_in_view(doc_view, link));
434 /* Look for the first and the last link currently visible in our
435 * viewport. */
436 static void
437 get_visible_links_range(struct document_view *doc_view, int *first, int *last)
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;
444 *first = document->nlinks - 1;
445 *last = 0;
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);
452 if (document->lines2[y])
453 int_lower_bound(last, document->lines2[y]
454 - document->links);
458 static int
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 *))
463 struct document *document;
464 struct view_state *vs;
465 int start, end;
467 assert(doc_view && doc_view->document && doc_view->vs && fn);
468 if_assert_failed return 0;
470 document = doc_view->document;
471 vs = doc_view->vs;
473 get_visible_links_range(doc_view, &start, &end);
475 current_link_blur(doc_view);
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;
486 current += direction;
489 vs->current_link = -1;
490 return 0;
494 next_link_in_view(struct document_view *doc_view, int current, int direction)
496 return next_link_in_view_(doc_view, current, direction, link_in_view, NULL);
500 next_link_in_view_y(struct document_view *doc_view, int current, int direction)
502 return next_link_in_view_(doc_view, current, direction, link_in_view_y, set_pos_x);
505 /* Get the bounding columns of @link at line @y (or all lines if @y == -1). */
506 static void
507 get_link_x_bounds(struct link *link, int y, int *min_x, int *max_x)
509 int point;
511 if (min_x) *min_x = INT_MAX;
512 if (max_x) *max_x = 0;
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);
522 /* Check whether there is any point between @min_x and @max_x at the line @y
523 * in link @link. */
524 static int
525 get_link_x_intersect(struct link *link, int y, int min_x, int max_x)
527 int point;
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;
537 return 0;
540 /* Check whether there is any point between @min_y and @max_y in the column @x
541 * in link @link. */
542 static int
543 get_link_y_intersect(struct link *link, int x, int min_y, int max_y)
545 int point;
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;
555 return 0;
559 next_link_in_dir(struct document_view *doc_view, int dir_x, int dir_y)
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;
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;
572 document = doc_view->document;
573 vs = doc_view->vs;
575 link = get_current_link(doc_view);
576 if (!link) return 0;
578 /* Find the link's "bounding box" coordinates. */
580 get_link_x_bounds(link, -1, &min_x, &max_x);
582 min_y = link->points[0].y;
583 max_y = link->points[link->npoints - 1].y;
585 /* Now go from the bounding box edge in the appropriate
586 * direction and find the nearest link. */
588 if (dir_y) {
589 /* Vertical movement */
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);
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;
603 link = document->lines1[y];
604 if (!link) continue;
606 /* Go through all the links on line. */
607 for (; link <= document->lines2[y]; link++) {
608 int l_min_x, l_max_x;
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;
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;
623 if (l_max_x < min_x) {
624 /* This link is too at the left. */
625 backup = link;
626 continue;
628 /* This link is aligned with the current one. */
629 goto chose_link;
632 if (backup) {
633 link = backup;
634 goto chose_link;
638 if (!y || y == document->height) {
639 /* We just stay at the same place, do not invalidate
640 * the link number. */
641 return 0;
644 } else {
645 /* Horizontal movement */
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;
652 while ((last < max_y - min_y + 1) && (x += dir_x) >= 0) {
653 int y;
655 last = 0;
657 /* Go through all the lines */
658 for (y = min_y; y <= max_y; y++) {
659 link = document->lines1[y];
660 if (!link) continue;
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++;
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++;
679 /* We just stay */
680 return 0;
683 current_link_blur(doc_view);
684 vs->current_link = -1;
685 return 0;
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;
696 void
697 set_pos_x(struct document_view *doc_view, struct link *link)
699 int xm = 0;
700 int xl = INT_MAX;
701 int i;
703 assert(doc_view && link);
704 if_assert_failed return;
706 for (i = 0; i < link->npoints; i++) {
707 int y = link->points[i].y - doc_view->vs->y;
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);
715 if (xl != INT_MAX)
716 int_bounds(&doc_view->vs->x, xm - doc_view->box.width, xl);
719 void
720 set_pos_y(struct document_view *doc_view, struct link *link)
722 int ym = 0;
723 int height;
724 int i;
726 assert(doc_view && doc_view->document && doc_view->vs && link);
727 if_assert_failed return;
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);
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);
739 /* direction == 1 -> DOWN
740 * direction == -1 -> UP */
741 static void
742 find_link(struct document_view *doc_view, int direction, int page_mode)
744 struct link **line;
745 struct link *link = NULL;
746 int link_pos;
747 int y, ymin, ymax;
749 assert(doc_view && doc_view->document && doc_view->vs);
750 if_assert_failed return;
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;
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);
772 if (direction == -1) {
773 /* UP */
774 do {
775 struct link *cur = line[y--];
777 if (cur && (!link || cur > link))
778 link = cur;
779 } while (y >= ymin && y < ymax);
781 } else {
782 /* DOWN */
783 do {
784 struct link *cur = line[y++];
786 if (cur && (!link || cur < link))
787 link = cur;
788 } while (y >= ymin && y < ymax);
791 if (!link) goto nolink;
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;
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;
805 nolink:
806 current_link_blur(doc_view);
807 doc_view->vs->current_link = -1;
810 void
811 find_link_up(struct document_view *doc_view)
813 find_link(doc_view, -1, 0);
816 void
817 find_link_page_up(struct document_view *doc_view)
819 find_link(doc_view, -1, 1);
822 void
823 find_link_down(struct document_view *doc_view)
825 find_link(doc_view, 1, 0);
828 void
829 find_link_page_down(struct document_view *doc_view)
831 find_link(doc_view, 1, 1);
834 struct uri *
835 get_link_uri(struct session *ses, struct document_view *doc_view,
836 struct link *link)
838 assert(ses && doc_view && link);
839 if_assert_failed return NULL;
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);
847 case LINK_BUTTON:
848 case LINK_FIELD:
849 return get_form_uri(ses, doc_view,
850 get_link_form_control(link));
852 default:
853 return NULL;
857 static void
858 try_submit_given_form(struct session *ses, struct document_view *doc_view,
859 struct form *form, int do_reload)
861 #ifdef CONFIG_ECMASCRIPT
862 if (form->onsubmit) {
863 struct string code;
865 if (init_string(&code)) {
866 struct view_state *vs = doc_view->vs;
867 struct ecmascript_interpreter *interpreter;
868 int res = 1;
869 unsigned char *ret = form->onsubmit;
871 if (vs->ecmascript_fragile)
872 ecmascript_reset_state(vs);
873 interpreter = vs->ecmascript;
874 assert(interpreter);
875 #ifdef CONFIG_ECMASCRIPT_SEE
876 /* SEE doesn't like return outside functions */
877 while ((ret = strstr(ret, "return "))) {
878 while (*ret != ' ') *ret++ = ' ';
880 #endif
881 add_to_string(&code, form->onsubmit);
882 res = ecmascript_eval_boolback(interpreter, &code);
883 done_string(&code);
884 if (!res) return;
887 #endif
888 submit_given_form(ses, doc_view, form, do_reload);
891 struct link *
892 goto_current_link(struct session *ses, struct document_view *doc_view, int do_reload)
894 struct link *link;
895 struct uri *uri;
897 assert(doc_view && ses);
898 if_assert_failed return NULL;
900 link = get_current_link(doc_view);
901 if (!link) return NULL;
903 if (link_is_form(link)) {
904 struct form_control *fc = link->data.form_control;
905 struct form *form = fc->form;
907 try_submit_given_form(ses, doc_view, form, do_reload);
908 return link;
909 } else
910 uri = get_link_uri(ses, doc_view, link);
912 if (!uri) return NULL;
914 if (link->type == LINK_MAP) {
915 /* TODO: Test reload? */
916 goto_imgmap(ses, uri, null_or_stracpy(link->target));
918 } else {
919 enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD
920 : CACHE_MODE_NORMAL;
922 goto_uri_frame(ses, uri, link->target, mode);
925 done_uri(uri);
926 return link;
929 static enum frame_event_status
930 activate_link(struct session *ses, struct document_view *doc_view,
931 struct link *link, int do_reload)
933 struct form_control *link_fc;
934 struct form_state *fs;
935 struct form *form;
937 switch (link->type) {
938 case LINK_HYPERTEXT:
939 case LINK_MAP:
940 case LINK_FIELD:
941 case LINK_AREA:
942 case LINK_BUTTON:
943 if (goto_current_link(ses, doc_view, do_reload))
944 return FRAME_EVENT_OK;
946 break;
948 case LINK_CHECKBOX:
949 link_fc = get_link_form_control(link);
951 if (form_field_is_readonly(link_fc))
952 return FRAME_EVENT_OK;
954 fs = find_form_state(doc_view, link_fc);
955 if (!fs) return FRAME_EVENT_OK;
957 if (link_fc->type == FC_CHECKBOX) {
958 fs->state = !fs->state;
960 return FRAME_EVENT_REFRESH;
963 foreach (form, doc_view->document->forms) {
964 struct form_control *fc;
966 if (form != link_fc->form)
967 continue;
969 foreach (fc, form->items) {
970 if (fc->type == FC_RADIO
971 && !xstrcmp(fc->name, link_fc->name)) {
972 struct form_state *frm_st;
974 frm_st = find_form_state(doc_view, fc);
975 if (frm_st) frm_st->state = 0;
979 fs->state = 1;
981 break;
983 case LINK_SELECT:
984 link_fc = get_link_form_control(link);
986 if (form_field_is_readonly(link_fc))
987 return FRAME_EVENT_OK;
989 object_lock(doc_view->document);
990 add_empty_window(ses->tab->term,
991 (void (*)(void *)) release_document,
992 doc_view->document);
993 do_select_submenu(ses->tab->term, link_fc->menu, ses);
995 break;
997 default:
998 INTERNAL("bad link type %d", link->type);
1001 return FRAME_EVENT_REFRESH;
1004 enum frame_event_status
1005 enter(struct session *ses, struct document_view *doc_view, int do_reload)
1007 enum frame_event_status ret;
1008 struct link *link;
1010 assert(ses && doc_view && doc_view->vs && doc_view->document);
1011 if_assert_failed return FRAME_EVENT_REFRESH;
1013 link = get_current_link(doc_view);
1014 if (!link) return FRAME_EVENT_REFRESH;
1016 ret = activate_link(ses, doc_view, link, do_reload);
1017 if (ret != FRAME_EVENT_IGNORED)
1018 if (!current_link_evhook(doc_view, SEVHOOK_ONCLICK))
1019 return FRAME_EVENT_REFRESH;
1021 return ret;
1024 struct link *
1025 get_link_at_coordinates(struct document_view *doc_view, int x, int y)
1027 struct link *l1, *l2, *link;
1028 int i, height;
1030 assert(doc_view && doc_view->vs && doc_view->document);
1031 if_assert_failed return NULL;
1033 /* If there are no links in in document, there is nothing to do. */
1034 if (!doc_view->document->nlinks) return NULL;
1036 /* If the coordinates are outside document view, no need to go further. */
1037 if (x < 0 || x >= doc_view->box.width) return NULL;
1038 if (y < 0 || y >= doc_view->box.height) return NULL;
1040 /* FIXME: This doesn't work. --Zas
1041 if (!check_mouse_position(ev, &doc_view->box))
1042 return NULL;
1045 /* Find link candidates. */
1046 l1 = doc_view->document->links + doc_view->document->nlinks;
1047 l2 = doc_view->document->links;
1048 height = int_min(doc_view->document->height,
1049 doc_view->vs->y + doc_view->box.height);
1051 for (i = doc_view->vs->y; i < height; i++) {
1052 if (doc_view->document->lines1[i]
1053 && doc_view->document->lines1[i] < l1)
1054 l1 = doc_view->document->lines1[i];
1056 if (doc_view->document->lines2[i]
1057 && doc_view->document->lines2[i] > l2)
1058 l2 = doc_view->document->lines2[i];
1061 /* Is there a link at the given coordinates? */
1062 x += doc_view->vs->x;
1063 y += doc_view->vs->y;
1065 for (link = l1; link <= l2; link++) {
1066 for (i = 0; i < link->npoints; i++)
1067 if (link->points[i].x == x
1068 && link->points[i].y == y)
1069 return link;
1072 return NULL;
1075 /* This is backend of the backend goto_link_number_do() below ;)). */
1076 void
1077 jump_to_link_number(struct session *ses, struct document_view *doc_view, int n)
1079 assert(ses && doc_view && doc_view->vs && doc_view->document);
1080 if_assert_failed return;
1082 if (n < 0 || n >= doc_view->document->nlinks) return;
1083 current_link_blur(doc_view);
1084 doc_view->vs->current_link = n;
1085 if (ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
1086 struct link *link = get_current_link(doc_view);
1087 int offset = get_link_cursor_offset(doc_view, link);
1089 if (link->npoints > offset) {
1090 int x = link->points[offset].x
1091 + doc_view->box.x - doc_view->vs->x;
1092 int y = link->points[offset].y
1093 + doc_view->box.y - doc_view->vs->y;
1095 move_cursor(ses, doc_view, x, y);
1098 check_vs(doc_view);
1099 current_link_hover(doc_view);
1102 /* This is common backend for goto_link_number() and try_document_key(). */
1103 static void
1104 goto_link_number_do(struct session *ses, struct document_view *doc_view, int n)
1106 struct link *link;
1108 assert(ses && doc_view && doc_view->document);
1109 if_assert_failed return;
1110 if (n < 0 || n >= doc_view->document->nlinks) return;
1111 jump_to_link_number(ses, doc_view, n);
1113 link = &doc_view->document->links[n];
1114 if (!link_is_textinput(link)
1115 && get_opt_bool("document.browse.accesskey.auto_follow"))
1116 enter(ses, doc_view, 0);
1119 void
1120 goto_link_number(struct session *ses, unsigned char *num)
1122 struct document_view *doc_view;
1124 assert(ses && num);
1125 if_assert_failed return;
1126 doc_view = current_frame(ses);
1127 assert(doc_view);
1128 if_assert_failed return;
1129 goto_link_number_do(ses, doc_view, atoi(num) - 1);
1132 /* See if this document is interested in the key user pressed. */
1133 enum frame_event_status
1134 try_document_key(struct session *ses, struct document_view *doc_view,
1135 struct term_event *ev)
1137 long key;
1138 int passed = -1;
1139 int i; /* GOD I HATE C! --FF */ /* YEAH, BRAINFUCK RULEZ! --pasky */
1141 assert(ses && doc_view && doc_view->document && doc_view->vs && ev);
1142 if_assert_failed return FRAME_EVENT_IGNORED;
1144 if (!check_kbd_modifier(ev, KBD_MOD_ALT)) {
1145 /* We accept those only in alt-combo. */
1146 return FRAME_EVENT_IGNORED;
1149 /* Run through all the links and see if one of them is bound to the
1150 * key we test.. */
1151 key = get_kbd_key(ev);
1153 for (i = 0; i < doc_view->document->nlinks; i++) {
1154 struct link *link = &doc_view->document->links[i];
1156 if (key == link->accesskey) { /* FIXME: key vs unicode ... */
1157 if (passed != i && i <= doc_view->vs->current_link) {
1158 /* This is here in order to rotate between
1159 * links with same accesskey. */
1160 if (passed < 0) passed = i;
1161 continue;
1163 ses->kbdprefix.repeat_count = 0;
1164 goto_link_number_do(ses, doc_view, i);
1165 return FRAME_EVENT_REFRESH;
1168 if (i == doc_view->document->nlinks - 1 && passed >= 0) {
1169 /* Return to the start. */
1170 i = passed - 1;
1174 return FRAME_EVENT_IGNORED;
1177 /* Open a contextual menu on a link, form or image element. */
1178 /* TODO: This should be completely configurable. */
1179 void
1180 link_menu(struct terminal *term, void *xxx, void *ses_)
1182 struct session *ses = ses_;
1183 struct document_view *doc_view;
1184 struct link *link;
1185 struct menu_item *mi;
1186 struct form_control *fc;
1188 assert(term && ses);
1189 if_assert_failed return;
1191 doc_view = current_frame(ses);
1192 mi = new_menu(FREE_LIST);
1193 if (!mi) return;
1194 if (!doc_view) goto end;
1196 assert(doc_view->vs && doc_view->document);
1197 if_assert_failed return;
1199 link = get_current_link(doc_view);
1200 if (!link) goto end;
1202 if (link->where && !link_is_form(link)) {
1203 if (link->type == LINK_MAP) {
1204 add_to_menu(&mi, N_("Display ~usemap"), NULL, ACT_MAIN_LINK_FOLLOW,
1205 NULL, NULL, SUBMENU);
1206 } else {
1207 add_menu_action(&mi, N_("~Follow link"), ACT_MAIN_LINK_FOLLOW);
1209 add_menu_action(&mi, N_("Follow link and r~eload"), ACT_MAIN_LINK_FOLLOW_RELOAD);
1211 add_menu_separator(&mi);
1213 add_new_win_to_menu(&mi, N_("Open in new ~window"), term);
1215 add_menu_action(&mi, N_("Open in new ~tab"), ACT_MAIN_OPEN_LINK_IN_NEW_TAB);
1217 add_menu_action(&mi, N_("Open in new tab in ~background"),
1218 ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND);
1220 if (!get_cmd_opt_bool("anonymous")) {
1221 add_menu_separator(&mi);
1222 add_menu_action(&mi, N_("~Download link"), ACT_MAIN_LINK_DOWNLOAD);
1224 #ifdef CONFIG_BOOKMARKS
1225 add_menu_action(&mi, N_("~Add link to bookmarks"),
1226 ACT_MAIN_ADD_BOOKMARK_LINK);
1227 #endif
1228 add_uri_command_to_menu(&mi, PASS_URI_LINK);
1233 fc = get_link_form_control(link);
1234 if (fc) {
1235 switch (fc->type) {
1236 case FC_RESET:
1237 add_menu_action(&mi, N_("~Reset form"), ACT_MAIN_RESET_FORM);
1238 break;
1240 case FC_TEXTAREA:
1241 if (!form_field_is_readonly(fc)) {
1242 struct string keystroke;
1244 if (init_string(&keystroke))
1245 add_keystroke_action_to_string(
1246 &keystroke,
1247 ACT_EDIT_OPEN_EXTERNAL,
1248 KEYMAP_EDIT);
1250 add_to_menu(&mi, N_("Open in ~external editor"),
1251 keystroke.source, ACT_MAIN_NONE,
1252 menu_textarea_edit, NULL, FREE_RTEXT);
1254 /* Fall through */
1255 default:
1256 add_menu_action(&mi, N_("~Submit form"), ACT_MAIN_SUBMIT_FORM);
1257 add_menu_action(&mi, N_("Submit form and rel~oad"), ACT_MAIN_SUBMIT_FORM_RELOAD);
1259 assert(fc->form);
1260 if (fc->form->method == FORM_METHOD_GET) {
1261 add_new_win_to_menu(&mi, N_("Submit form and open in new ~window"), term);
1263 add_menu_action(&mi, N_("Submit form and open in new ~tab"),
1264 ACT_MAIN_OPEN_LINK_IN_NEW_TAB);
1266 add_menu_action(&mi, N_("Submit form and open in new tab in ~background"),
1267 ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND);
1270 if (!get_cmd_opt_bool("anonymous"))
1271 add_menu_action(&mi, N_("Submit form and ~download"), ACT_MAIN_LINK_DOWNLOAD);
1273 add_menu_action(&mi, N_("~Reset form"), ACT_MAIN_RESET_FORM);
1276 add_to_menu(&mi, N_("Form f~ields"), NULL, ACT_MAIN_LINK_FORM_MENU,
1277 NULL, NULL, SUBMENU);
1280 if (link->where_img) {
1281 add_menu_action(&mi, N_("V~iew image"), ACT_MAIN_VIEW_IMAGE);
1282 if (!get_cmd_opt_bool("anonymous"))
1283 add_menu_action(&mi, N_("Download ima~ge"), ACT_MAIN_LINK_DOWNLOAD_IMAGE);
1286 /* TODO: Make it possible to trigger any script event hooks associated
1287 * to the link. --pasky */
1289 end:
1290 if (!mi->text) {
1291 add_to_menu(&mi, N_("No link selected"), NULL, ACT_MAIN_NONE,
1292 NULL, NULL, NO_SELECT);
1295 do_menu(term, mi, ses, 1);
1298 /* Return current link's title. Pretty trivial. */
1299 unsigned char *
1300 get_current_link_title(struct document_view *doc_view)
1302 struct link *link;
1304 assert(doc_view && doc_view->document && doc_view->vs);
1305 if_assert_failed return NULL;
1307 if (doc_view->document->frame_desc)
1308 return NULL;
1310 link = get_current_link(doc_view);
1312 return (link && link->title && *link->title) ? stracpy(link->title) : NULL;
1315 unsigned char *
1316 get_current_link_info(struct session *ses, struct document_view *doc_view)
1318 struct link *link;
1320 assert(ses && doc_view && doc_view->document && doc_view->vs);
1321 if_assert_failed return NULL;
1323 if (doc_view->document->frame_desc)
1324 return NULL;
1326 link = get_current_link(doc_view);
1327 if (!link) return NULL;
1329 /* TODO: Provide info about script event hooks too. --pasky */
1331 if (!link_is_form(link)) {
1332 struct terminal *term = ses->tab->term;
1333 struct string str;
1334 unsigned char *uristring = link->where;
1336 if (!init_string(&str)) return NULL;
1338 if (!link->where && link->where_img) {
1339 add_to_string(&str, _("Image", term));
1340 add_char_to_string(&str, ' ');
1341 uristring = link->where_img;
1343 } else if (link->type == LINK_MAP) {
1344 add_to_string(&str, _("Usemap", term));
1345 add_char_to_string(&str, ' ');
1348 /* Add the uri with password and post info stripped */
1349 add_string_uri_to_string(&str, uristring, URI_PUBLIC);
1350 if (link->accesskey > 0
1351 && get_opt_bool("document.browse.accesskey.display")) {
1352 add_to_string(&str, " (");
1353 add_accesskey_to_string(&str, link->accesskey);
1354 add_char_to_string(&str, ')');
1357 decode_uri_string_for_display(&str);
1358 return str.source;
1361 if (!get_link_form_control(link)) return NULL;
1363 return get_form_info(ses, doc_view);