1 /* Links viewing/manipulation handling */
12 #include "bfu/listmenu.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"
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.
54 current_link_evhook(struct document_view
*doc_view
, enum script_event_hook_type type
)
56 #ifdef CONFIG_ECMASCRIPT
58 struct script_event_hook
*evhook
;
60 assert(doc_view
&& doc_view
->vs
);
61 link
= get_current_link(doc_view
);
63 if (!link
->event_hooks
) return -1;
65 if (!doc_view
->vs
->ecmascript
) return -1;
67 foreach (evhook
, *link
->event_hooks
) {
70 if (evhook
->type
!= type
) continue;
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
))
88 #define current_link_hover(dv) \
90 current_link_evhook(dv, SEVHOOK_ONMOUSEOVER); \
91 current_link_evhook(dv, SEVHOOK_ONHOVER); \
92 current_link_evhook(dv, SEVHOOK_ONFOCUS); \
94 #define current_link_blur(dv) \
96 current_link_evhook(dv, SEVHOOK_ONMOUSEOUT); \
97 current_link_evhook(dv, SEVHOOK_ONBLUR); \
102 set_link(struct document_view
*doc_view
)
105 if_assert_failed
return;
107 if (current_link_is_visible(doc_view
)) return;
109 find_link_page_down(doc_view
);
113 get_link_cursor_offset(struct document_view
*doc_view
, struct link
*link
)
115 struct form_control
*fc
;
116 struct form_state
*fs
;
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
;
121 #endif /* CONFIG_UTF8 */
123 switch (link
->type
) {
131 fc
= get_link_form_control(link
);
132 fs
= find_form_state(doc_view
, fc
);
133 if (!fs
|| !fs
->value
)
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
);
143 return utf8_ptr2cells(scroll
, point
);
145 #endif /* CONFIG_UTF8 */
147 return fs
->state
- fs
->vpos
;
150 fc
= get_link_form_control(link
);
151 fs
= find_form_state(doc_view
, fc
);
153 return fs
? area_cursor(fc
, fs
, utf8
) : 0;
155 return fs
? area_cursor(fc
, fs
) : 0;
156 #endif /* CONFIG_UTF8 */
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
;
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
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
);
227 /* Give the current link the appropriate colour and attributes. */
229 draw_current_link(struct session
*ses
, struct document_view
*doc_view
)
231 struct terminal
*term
= ses
->tab
->term
;
232 struct screen_char
*template;
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
);
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. */
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
)) {
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
;
291 draw_link(struct terminal
*term
, struct document_view
*doc_view
,
294 int xpos
= doc_view
->box
.x
- doc_view
->vs
->x
;
295 int ypos
= doc_view
->box
.y
- doc_view
->vs
->y
;
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. */
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
;
328 highlight_links_with_prefixes_that_start_with_n(struct terminal
*term
,
329 struct document_view
*doc_view
,
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
;
338 for (m
= n
+ 1; n
<= document
->nlinks
; n
*= 10, m
*= 10) {
341 for (linkn
= n
; linkn
< m
; ++linkn
) {
342 struct link
*link
= &document
->links
[linkn
- 1];
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
);
359 get_first_link(struct document_view
*doc_view
)
361 struct link
*link
, *undef
;
362 struct document
*document
;
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
);
379 if (document
->lines1
[i
]
380 && document
->lines1
[i
] < link
)
381 link
= document
->lines1
[i
];
384 return (link
== undef
) ? NULL
: link
;
388 get_last_link(struct document_view
*doc_view
)
390 struct link
*link
= NULL
;
391 struct document
*document
;
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
);
407 if (document
->lines2
[i
] > link
)
408 link
= document
->lines2
[i
];
414 link_in_view_x(struct document_view
*doc_view
, struct link
*link
)
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
)
435 link_in_view_y(struct document_view
*doc_view
, struct link
*link
)
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
)
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
)
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
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
,
485 *first
= document
->nlinks
- 1;
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
]
493 if (document
->lines2
[y
])
494 int_lower_bound(last
, document
->lines2
[y
]
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
;
508 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
&& fn
);
509 if_assert_failed
return 0;
511 document
= doc_view
->document
;
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
);
527 current
+= direction
;
530 vs
->current_link
= -1;
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 @link at line @y (or all lines if @y == -1). */
548 get_link_x_bounds(struct link
*link
, int y
, int *min_x
, int *max_x
)
552 if (min_x
) *min_x
= INT_MAX
;
553 if (max_x
) *max_x
= 0;
555 for (point
= 0; point
< link
->npoints
; point
++) {
556 if (y
>= 0 && link
->points
[point
].y
!= y
)
558 if (min_x
) int_upper_bound(min_x
, link
->points
[point
].x
);
559 if (max_x
) int_lower_bound(max_x
, link
->points
[point
].x
);
563 /* Check whether there is any point between @min_x and @max_x at the line @y
566 get_link_x_intersect(struct link
*link
, int y
, int min_x
, int max_x
)
570 for (point
= 0; point
< link
->npoints
; point
++) {
571 if (link
->points
[point
].y
!= y
)
573 if (link
->points
[point
].x
>= min_x
574 && link
->points
[point
].x
<= max_x
)
575 return link
->points
[point
].x
+ 1;
581 /* Check whether there is any point between @min_y and @max_y in the column @x
584 get_link_y_intersect(struct link
*link
, int x
, int min_y
, int max_y
)
588 for (point
= 0; point
< link
->npoints
; point
++) {
589 if (link
->points
[point
].x
!= x
)
591 if (link
->points
[point
].y
>= min_y
592 && link
->points
[point
].y
<= max_y
)
593 return link
->points
[point
].y
+ 1;
600 next_link_in_dir(struct document_view
*doc_view
, int dir_x
, int dir_y
)
602 struct document
*document
;
603 struct view_state
*vs
;
605 int min_x
= INT_MAX
, max_x
= 0;
608 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
);
609 if_assert_failed
return 0;
610 assert(dir_x
|| dir_y
);
611 if_assert_failed
return 0;
613 document
= doc_view
->document
;
616 link
= get_current_link(doc_view
);
619 /* Find the link's "bounding box" coordinates. */
621 get_link_x_bounds(link
, -1, &min_x
, &max_x
);
623 min_y
= link
->points
[0].y
;
624 max_y
= link
->points
[link
->npoints
- 1].y
;
626 /* Now go from the bounding box edge in the appropriate
627 * direction and find the nearest link. */
630 /* Vertical movement */
632 /* The current line number */
633 int y
= (dir_y
> 0 ? max_y
: min_y
) + dir_y
;
634 /* The bounding line numbers */
635 int top
= int_max(0, doc_view
->vs
->y
);
636 int bottom
= int_min(doc_view
->vs
->y
+ doc_view
->box
.height
,
639 for (; dir_y
> 0 ? y
< bottom
: y
>= top
; y
+= dir_y
) {
640 /* @backup points to the nearest link from the left
641 * to the desired position. */
642 struct link
*backup
= NULL
;
644 link
= document
->lines1
[y
];
647 /* Go through all the links on line. */
648 for (; link
<= document
->lines2
[y
]; link
++) {
649 int l_min_x
, l_max_x
;
651 /* Some links can be totally out of order here,
652 * ie. in tables or when using tabindex. */
653 if (y
< link
->points
[0].y
654 || y
> link
->points
[link
->npoints
- 1].y
)
657 get_link_x_bounds(link
, y
, &l_min_x
, &l_max_x
);
658 if (l_min_x
> max_x
) {
659 /* This link is too at the right. */
664 if (l_max_x
< min_x
) {
665 /* This link is too at the left. */
669 /* This link is aligned with the current one. */
679 if (!y
|| y
== document
->height
) {
680 /* We just stay at the same place, do not invalidate
681 * the link number. */
686 /* Horizontal movement */
688 /* The current column number */
689 int x
= (dir_x
> 0 ? max_x
: min_x
) + dir_x
;
690 /* How many lines are already past their last link */
693 while ((last
< max_y
- min_y
+ 1) && (x
+= dir_x
) >= 0) {
698 /* Go through all the lines */
699 for (y
= min_y
; y
<= max_y
; y
++) {
700 link
= document
->lines1
[y
];
703 /* Go through all the links on line. */
704 while (link
<= document
->lines2
[y
]) {
705 if (get_link_y_intersect(link
, x
,
712 /* Check if we already aren't past the last
713 * link on this line. */
714 if (!get_link_x_intersect(document
->lines2
[y
],
724 current_link_blur(doc_view
);
725 vs
->current_link
= -1;
729 /* The link is in bounds, take it. */
730 current_link_blur(doc_view
);
731 vs
->current_link
= get_link_index(document
, link
);
732 set_pos_x(doc_view
, link
);
733 current_link_hover(doc_view
);
738 set_pos_x(struct document_view
*doc_view
, struct link
*link
)
744 assert(doc_view
&& link
);
745 if_assert_failed
return;
747 for (i
= 0; i
< link
->npoints
; i
++) {
748 int y
= link
->points
[i
].y
- doc_view
->vs
->y
;
750 if (y
>= 0 && y
< doc_view
->box
.height
) {
751 int_lower_bound(&xm
, link
->points
[i
].x
+ 1);
752 int_upper_bound(&xl
, link
->points
[i
].x
);
757 int_bounds(&doc_view
->vs
->x
, xm
- doc_view
->box
.width
, xl
);
761 set_pos_y(struct document_view
*doc_view
, struct link
*link
)
767 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
&& link
);
768 if_assert_failed
return;
770 height
= doc_view
->document
->height
;
771 for (i
= 0; i
< link
->npoints
; i
++) {
772 int_lower_bound(&ym
, link
->points
[i
].y
+ 1);
773 int_upper_bound(&height
, link
->points
[i
].y
);
775 doc_view
->vs
->y
= (ym
+ height
- doc_view
->box
.height
) / 2;
776 int_bounds(&doc_view
->vs
->y
, 0,
777 doc_view
->document
->height
- doc_view
->box
.height
);
780 /* direction == 1 -> DOWN
781 * direction == -1 -> UP */
783 find_link(struct document_view
*doc_view
, int direction
, int page_mode
)
786 struct link
*link
= NULL
;
790 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
);
791 if_assert_failed
return;
793 if (direction
== -1) {
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
;
802 line
= doc_view
->document
->lines1
;
803 if (!line
) goto nolink
;
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) {
816 struct link
*cur
= line
[y
--];
818 if (cur
&& (!link
|| cur
> link
))
820 } while (y
>= ymin
&& y
< ymax
);
825 struct link
*cur
= line
[y
++];
827 if (cur
&& (!link
|| cur
< link
))
829 } while (y
>= ymin
&& y
< ymax
);
832 if (!link
) goto nolink
;
834 link_pos
= link
- doc_view
->document
->links
;
837 next_link_in_view(doc_view
, link_pos
, direction
);
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
);
847 current_link_blur(doc_view
);
848 doc_view
->vs
->current_link
= -1;
852 find_link_up(struct document_view
*doc_view
)
854 find_link(doc_view
, -1, 0);
858 find_link_page_up(struct document_view
*doc_view
)
860 find_link(doc_view
, -1, 1);
864 find_link_down(struct document_view
*doc_view
)
866 find_link(doc_view
, 1, 0);
870 find_link_page_down(struct document_view
*doc_view
)
872 find_link(doc_view
, 1, 1);
876 get_link_uri(struct session
*ses
, struct document_view
*doc_view
,
879 assert(ses
&& doc_view
&& link
);
880 if_assert_failed
return NULL
;
882 switch (link
->type
) {
885 if (link
->where
) return get_uri(link
->where
, 0);
886 return get_uri(link
->where_img
, 0);
890 return get_form_uri(ses
, doc_view
,
891 get_link_form_control(link
));
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
913 if (fc
->form
->onsubmit
) {
916 if (init_string(&code
)) {
917 struct view_state
*vs
= doc_view
->vs
;
918 struct ecmascript_interpreter
*interpreter
;
919 unsigned char *ret
= fc
->form
->onsubmit
;
922 if (vs
->ecmascript_fragile
)
923 ecmascript_reset_state(vs
);
924 interpreter
= vs
->ecmascript
;
926 /* SEE and SpiderMonkey do not like return outside
928 while ((ret
= strstr(ret
, "return ")))
929 while (*ret
!= ' ') *ret
++ = ' ';
931 add_to_string(&code
, fc
->form
->onsubmit
);
932 res
= ecmascript_eval_boolback(interpreter
, &code
);
934 /* If the user presses Enter in a text field,
935 * and document.browse.forms.auto_submit is
936 * true, and the form has an onsubmit script
937 * that returns false, then insert mode should
938 * end, so return 1 here rather than 0. */
942 #endif /* CONFIG_ECMASCRIPT */
944 uri
= get_form_uri(ses
, doc_view
, fc
);
946 goto_uri_frame(ses
, uri
, fc
->form
->target
, mode
);
952 goto_current_link(struct session
*ses
, struct document_view
*doc_view
, int do_reload
)
957 assert(doc_view
&& ses
);
958 if_assert_failed
return NULL
;
960 link
= get_current_link(doc_view
);
961 if (!link
) return NULL
;
963 if (link_is_form(link
)) {
964 struct form_control
*fc
= link
->data
.form_control
;
966 if (!call_onsubmit_and_submit(ses
, doc_view
, fc
, do_reload
))
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
, null_or_stracpy(link
->target
));
980 enum cache_mode mode
= do_reload
? CACHE_MODE_FORCE_RELOAD
983 goto_uri_frame(ses
, uri
, link
->target
, mode
);
990 static enum frame_event_status
991 activate_link(struct session
*ses
, struct document_view
*doc_view
,
992 struct link
*link
, int do_reload
)
994 struct form_control
*link_fc
;
995 struct form_state
*fs
;
998 switch (link
->type
) {
1004 if (goto_current_link(ses
, doc_view
, do_reload
))
1005 return FRAME_EVENT_OK
;
1008 link_fc
= get_link_form_control(link
);
1010 if (form_field_is_readonly(link_fc
))
1011 return FRAME_EVENT_OK
;
1013 fs
= find_form_state(doc_view
, link_fc
);
1014 if (!fs
) return FRAME_EVENT_OK
;
1016 if (link_fc
->type
== FC_CHECKBOX
) {
1017 fs
->state
= !fs
->state
;
1019 return FRAME_EVENT_REFRESH
;
1022 /* @link_fc->type must be FC_RADIO, then. First turn
1023 * this one on, and then turn off all the other radio
1024 * buttons in the group. Do it in this order because
1025 * further @find_form_state calls may reallocate
1026 * @doc_view->vs->form_info[] and thereby make the @fs
1027 * pointer invalid. This also allows us to re-use
1028 * @fs in the loop. */
1030 foreach (form
, doc_view
->document
->forms
) {
1031 struct form_control
*fc
;
1033 if (form
!= link_fc
->form
)
1036 foreach (fc
, form
->items
) {
1037 if (fc
->type
== FC_RADIO
1038 && !xstrcmp(fc
->name
, link_fc
->name
)
1040 fs
= find_form_state(doc_view
, fc
);
1041 if (fs
) fs
->state
= 0;
1049 link_fc
= get_link_form_control(link
);
1051 if (form_field_is_readonly(link_fc
))
1052 return FRAME_EVENT_OK
;
1054 object_lock(doc_view
->document
);
1055 add_empty_window(ses
->tab
->term
,
1056 (void (*)(void *)) release_document
,
1057 doc_view
->document
);
1058 do_select_submenu(ses
->tab
->term
, link_fc
->menu
, ses
);
1063 INTERNAL("bad link type %d", link
->type
);
1066 return FRAME_EVENT_REFRESH
;
1069 enum frame_event_status
1070 enter(struct session
*ses
, struct document_view
*doc_view
, int do_reload
)
1074 assert(ses
&& doc_view
&& doc_view
->vs
&& doc_view
->document
);
1075 if_assert_failed
return FRAME_EVENT_REFRESH
;
1077 link
= get_current_link(doc_view
);
1078 if (!link
) return FRAME_EVENT_REFRESH
;
1081 if (!current_link_evhook(doc_view
, SEVHOOK_ONCLICK
))
1082 return FRAME_EVENT_REFRESH
;
1083 return activate_link(ses
, doc_view
, link
, do_reload
);
1087 get_link_at_coordinates(struct document_view
*doc_view
, int x
, int y
)
1089 struct link
*l1
, *l2
, *link
;
1092 assert(doc_view
&& doc_view
->vs
&& doc_view
->document
);
1093 if_assert_failed
return NULL
;
1095 /* If there are no links in in document, there is nothing to do. */
1096 if (!doc_view
->document
->nlinks
) return NULL
;
1098 /* If the coordinates are outside document view, no need to go further. */
1099 if (x
< 0 || x
>= doc_view
->box
.width
) return NULL
;
1100 if (y
< 0 || y
>= doc_view
->box
.height
) return NULL
;
1102 /* FIXME: This doesn't work. --Zas
1103 if (!check_mouse_position(ev, &doc_view->box))
1107 /* Find link candidates. */
1108 l1
= doc_view
->document
->links
+ doc_view
->document
->nlinks
;
1109 l2
= doc_view
->document
->links
;
1110 height
= int_min(doc_view
->document
->height
,
1111 doc_view
->vs
->y
+ doc_view
->box
.height
);
1113 for (i
= doc_view
->vs
->y
; i
< height
; i
++) {
1114 if (doc_view
->document
->lines1
[i
]
1115 && doc_view
->document
->lines1
[i
] < l1
)
1116 l1
= doc_view
->document
->lines1
[i
];
1118 if (doc_view
->document
->lines2
[i
]
1119 && doc_view
->document
->lines2
[i
] > l2
)
1120 l2
= doc_view
->document
->lines2
[i
];
1123 /* Is there a link at the given coordinates? */
1124 x
+= doc_view
->vs
->x
;
1125 y
+= doc_view
->vs
->y
;
1127 for (link
= l1
; link
<= l2
; link
++) {
1128 for (i
= 0; i
< link
->npoints
; i
++)
1129 if (link
->points
[i
].x
== x
1130 && link
->points
[i
].y
== y
)
1137 /* This is backend of the backend goto_link_number_do() below ;)). */
1139 jump_to_link_number(struct session
*ses
, struct document_view
*doc_view
, int n
)
1141 assert(ses
&& doc_view
&& doc_view
->vs
&& doc_view
->document
);
1142 if_assert_failed
return;
1144 if (n
< 0 || n
>= doc_view
->document
->nlinks
) return;
1145 current_link_blur(doc_view
);
1146 doc_view
->vs
->current_link
= n
;
1147 if (ses
->navigate_mode
== NAVIGATE_CURSOR_ROUTING
) {
1148 struct link
*link
= get_current_link(doc_view
);
1149 int offset
= get_link_cursor_offset(doc_view
, link
);
1151 if (link
->npoints
> offset
) {
1152 int x
= link
->points
[offset
].x
1153 + doc_view
->box
.x
- doc_view
->vs
->x
;
1154 int y
= link
->points
[offset
].y
1155 + doc_view
->box
.y
- doc_view
->vs
->y
;
1157 move_cursor(ses
, doc_view
, x
, y
);
1161 current_link_hover(doc_view
);
1164 /* This is common backend for goto_link_number() and try_document_key(). */
1166 goto_link_number_do(struct session
*ses
, struct document_view
*doc_view
, int n
)
1170 assert(ses
&& doc_view
&& doc_view
->document
);
1171 if_assert_failed
return;
1172 if (n
< 0 || n
>= doc_view
->document
->nlinks
) return;
1173 jump_to_link_number(ses
, doc_view
, n
);
1175 link
= &doc_view
->document
->links
[n
];
1176 if (!link_is_textinput(link
)
1177 && get_opt_bool("document.browse.accesskey.auto_follow"))
1178 enter(ses
, doc_view
, 0);
1182 goto_link_number(struct session
*ses
, unsigned char *num
)
1184 struct document_view
*doc_view
;
1187 if_assert_failed
return;
1188 doc_view
= current_frame(ses
);
1190 if_assert_failed
return;
1191 goto_link_number_do(ses
, doc_view
, atoi(num
) - 1);
1194 /* See if this document is interested in the key user pressed. */
1195 enum frame_event_status
1196 try_document_key(struct session
*ses
, struct document_view
*doc_view
,
1197 struct term_event
*ev
)
1200 int i
; /* GOD I HATE C! --FF */ /* YEAH, BRAINFUCK RULEZ! --pasky */
1202 assert(ses
&& doc_view
&& doc_view
->document
&& doc_view
->vs
&& ev
);
1203 if_assert_failed
return FRAME_EVENT_IGNORED
;
1205 if (!check_kbd_modifier(ev
, KBD_MOD_ALT
)
1206 || !is_kbd_character(get_kbd_key(ev
))) {
1207 /* We accept only alt-character combos. */
1208 return FRAME_EVENT_IGNORED
;
1211 /* The key is a character. Convert it to Unicode so that it
1212 * can be compared with link.accesskey. */
1214 key
= get_kbd_key(ev
);
1215 #else /* !CONFIG_UTF8 */
1216 key
= cp2u(get_opt_codepage_tree(ses
->tab
->term
->spec
,
1219 #endif /* !CONFIG_UTF8 */
1220 /* If @key now is 0 (which is used in link.accesskey if there
1221 * is no access key) or UCS_REPLACEMENT_CHARACTER, then the
1222 * results may be a little odd, but not really harmful. */
1224 /* Run through all the links and see if one of them is bound to the
1227 i
= doc_view
->vs
->current_link
+ 1;
1228 for (; i
< doc_view
->document
->nlinks
; i
++) {
1229 struct link
*link
= &doc_view
->document
->links
[i
];
1231 if (key
== link
->accesskey
) {
1232 ses
->kbdprefix
.repeat_count
= 0;
1233 goto_link_number_do(ses
, doc_view
, i
);
1234 return FRAME_EVENT_REFRESH
;
1237 for (i
= 0; i
<= doc_view
->vs
->current_link
; i
++) {
1238 struct link
*link
= &doc_view
->document
->links
[i
];
1240 if (key
== link
->accesskey
) {
1241 ses
->kbdprefix
.repeat_count
= 0;
1242 goto_link_number_do(ses
, doc_view
, i
);
1243 return FRAME_EVENT_REFRESH
;
1247 return FRAME_EVENT_IGNORED
;
1250 /* Open a contextual menu on a link, form or image element. */
1251 /* TODO: This should be completely configurable. */
1253 link_menu(struct terminal
*term
, void *xxx
, void *ses_
)
1255 struct session
*ses
= ses_
;
1256 struct document_view
*doc_view
;
1258 struct menu_item
*mi
;
1259 struct form_control
*fc
;
1261 assert(term
&& ses
);
1262 if_assert_failed
return;
1264 doc_view
= current_frame(ses
);
1265 mi
= new_menu(FREE_LIST
);
1267 if (!doc_view
) goto end
;
1269 assert(doc_view
->vs
&& doc_view
->document
);
1270 if_assert_failed
return;
1272 link
= get_current_link(doc_view
);
1273 if (!link
) goto end
;
1275 if (link
->where
&& !link_is_form(link
)) {
1276 if (link
->type
== LINK_MAP
) {
1277 /* [gettext_accelerator_context(link_menu.map)] */
1278 add_to_menu(&mi
, N_("Display ~usemap"), NULL
, ACT_MAIN_LINK_FOLLOW
,
1279 NULL
, NULL
, SUBMENU
);
1280 /* [gettext_accelerator_context()] */
1282 /* [gettext_accelerator_context(link_menu.std)] */
1283 add_menu_action(&mi
, N_("~Follow link"), ACT_MAIN_LINK_FOLLOW
);
1285 add_menu_action(&mi
, N_("Follow link and r~eload"), ACT_MAIN_LINK_FOLLOW_RELOAD
);
1287 add_menu_separator(&mi
);
1289 add_new_win_to_menu(&mi
, N_("Open in new ~window"), term
);
1291 add_menu_action(&mi
, N_("Open in new ~tab"), ACT_MAIN_OPEN_LINK_IN_NEW_TAB
);
1293 add_menu_action(&mi
, N_("Open in new tab in ~background"),
1294 ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND
);
1296 if (!get_cmd_opt_bool("anonymous")) {
1297 add_menu_separator(&mi
);
1298 add_menu_action(&mi
, N_("~Download link"), ACT_MAIN_LINK_DOWNLOAD
);
1300 #ifdef CONFIG_BOOKMARKS
1301 add_menu_action(&mi
, N_("~Add link to bookmarks"),
1302 ACT_MAIN_ADD_BOOKMARK_LINK
);
1304 add_uri_command_to_menu(&mi
, PASS_URI_LINK
,
1305 N_("Pass link URI to e~xternal command"));
1307 /* [gettext_accelerator_context()] */
1311 fc
= get_link_form_control(link
);
1315 /* [gettext_accelerator_context(link_menu.reset)] */
1316 add_menu_action(&mi
, N_("~Reset form"), ACT_MAIN_RESET_FORM
);
1317 /* [gettext_accelerator_context()] */
1321 /* [gettext_accelerator_context(link_menu.textarea)] */
1322 if (!form_field_is_readonly(fc
)) {
1323 struct string keystroke
;
1325 if (init_string(&keystroke
))
1326 add_keystroke_action_to_string(
1328 ACT_EDIT_OPEN_EXTERNAL
,
1331 add_to_menu(&mi
, N_("Open in ~external editor"),
1332 keystroke
.source
, ACT_MAIN_NONE
,
1333 menu_textarea_edit
, NULL
, FREE_RTEXT
);
1335 /* [gettext_accelerator_context()] */
1338 /* [gettext_accelerator_context(link_menu.textarea, link_menu.form)] */
1339 add_menu_action(&mi
, N_("~Submit form"), ACT_MAIN_SUBMIT_FORM
);
1340 add_menu_action(&mi
, N_("Submit form and rel~oad"), ACT_MAIN_SUBMIT_FORM_RELOAD
);
1343 if (fc
->form
->method
== FORM_METHOD_GET
) {
1344 add_new_win_to_menu(&mi
, N_("Submit form and open in new ~window"), term
);
1346 add_menu_action(&mi
, N_("Submit form and open in new ~tab"),
1347 ACT_MAIN_OPEN_LINK_IN_NEW_TAB
);
1349 add_menu_action(&mi
, N_("Submit form and open in new tab in ~background"),
1350 ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND
);
1353 if (!get_cmd_opt_bool("anonymous"))
1354 add_menu_action(&mi
, N_("Submit form and ~download"), ACT_MAIN_LINK_DOWNLOAD
);
1356 add_menu_action(&mi
, N_("~Reset form"), ACT_MAIN_RESET_FORM
);
1357 /* [gettext_accelerator_context()] */
1360 /* [gettext_accelerator_context(link_menu.reset, link_menu.textarea, link_menu.form)] */
1361 add_to_menu(&mi
, N_("Form f~ields"), NULL
, ACT_MAIN_LINK_FORM_MENU
,
1362 NULL
, NULL
, SUBMENU
);
1363 /* [gettext_accelerator_context()] */
1366 if (link
->where_img
) {
1367 /* [gettext_accelerator_context(link_menu.map, link_menu.std, link_menu.form)] */
1368 add_menu_action(&mi
, N_("V~iew image"), ACT_MAIN_VIEW_IMAGE
);
1369 if (!get_cmd_opt_bool("anonymous"))
1370 add_menu_action(&mi
, N_("Download ima~ge"), ACT_MAIN_LINK_DOWNLOAD_IMAGE
);
1371 /* [gettext_accelerator_context()] */
1374 /* TODO: Make it possible to trigger any script event hooks associated
1375 * to the link. --pasky */
1379 add_to_menu(&mi
, N_("No link selected"), NULL
, ACT_MAIN_NONE
,
1380 NULL
, NULL
, NO_SELECT
);
1383 do_menu(term
, mi
, ses
, 1);
1386 /* Return current link's title. */
1388 get_current_link_title(struct document_view
*doc_view
)
1392 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
);
1393 if_assert_failed
return NULL
;
1395 if (doc_view
->document
->frame_desc
)
1398 link
= get_current_link(doc_view
);
1400 if (link
&& link
->title
&& *link
->title
) {
1401 unsigned char *link_title
, *src
;
1402 struct conv_table
*convert_table
;
1404 convert_table
= get_translation_table(doc_view
->document
->cp
,
1405 doc_view
->document
->options
.cp
);
1407 link_title
= convert_string(convert_table
, link
->title
,
1408 strlen(link
->title
),
1409 doc_view
->document
->options
.cp
,
1410 CSM_DEFAULT
, NULL
, NULL
, NULL
);
1411 /* Remove illicit chars. */
1413 if (link_title
&& !doc_view
->document
->options
.utf8
)
1414 #endif /* CONFIG_UTF8 */
1415 for (src
= link_title
; *src
; src
++)
1416 if (!isprint(*src
) || iscntrl(*src
))
1426 get_current_link_info(struct session
*ses
, struct document_view
*doc_view
)
1430 assert(ses
&& doc_view
&& doc_view
->document
&& doc_view
->vs
);
1431 if_assert_failed
return NULL
;
1433 if (doc_view
->document
->frame_desc
)
1436 link
= get_current_link(doc_view
);
1437 if (!link
) return NULL
;
1439 /* TODO: Provide info about script event hooks too. --pasky */
1441 if (!link_is_form(link
)) {
1442 struct terminal
*term
= ses
->tab
->term
;
1444 unsigned char *uristring
= link
->where
;
1446 if (!init_string(&str
)) return NULL
;
1448 if (!link
->where
&& link
->where_img
) {
1449 add_to_string(&str
, _("Image", term
));
1450 add_char_to_string(&str
, ' ');
1451 uristring
= link
->where_img
;
1453 } else if (link
->type
== LINK_MAP
) {
1454 add_to_string(&str
, _("Usemap", term
));
1455 add_char_to_string(&str
, ' ');
1458 /* Add the uri with password and post info stripped */
1459 add_string_uri_to_string(&str
, uristring
, URI_PUBLIC
);
1460 if (link
->accesskey
> 0
1461 && get_opt_bool("document.browse.accesskey.display")) {
1462 add_to_string(&str
, " (");
1463 add_accesskey_to_string(&str
, link
->accesskey
);
1464 add_char_to_string(&str
, ')');
1469 decode_uri_string(&str
);
1471 #endif /* CONFIG_UTF8 */
1472 decode_uri_string_for_display(&str
);
1476 if (!get_link_form_control(link
)) return NULL
;
1478 return get_form_info(ses
, doc_view
);