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_cp
;
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 @a link at line @a y (or all lines if
549 get_link_x_bounds(struct link
*link
, int y
, int *min_x
, int *max_x
)
553 if (min_x
) *min_x
= INT_MAX
;
554 if (max_x
) *max_x
= 0;
556 for (point
= 0; point
< link
->npoints
; point
++) {
557 if (y
>= 0 && link
->points
[point
].y
!= y
)
559 if (min_x
) int_upper_bound(min_x
, link
->points
[point
].x
);
560 if (max_x
) int_lower_bound(max_x
, link
->points
[point
].x
);
564 /** Check whether there is any point between @a min_x and @a max_x at
565 * the line @a y in link @a link. */
567 get_link_x_intersect(struct link
*link
, int y
, int min_x
, int max_x
)
571 for (point
= 0; point
< link
->npoints
; point
++) {
572 if (link
->points
[point
].y
!= y
)
574 if (link
->points
[point
].x
>= min_x
575 && link
->points
[point
].x
<= max_x
)
576 return link
->points
[point
].x
+ 1;
582 /** Check whether there is any point between @a min_y and @a max_y in
583 * the column @a x in link @a link. */
585 get_link_y_intersect(struct link
*link
, int x
, int min_y
, int max_y
)
589 for (point
= 0; point
< link
->npoints
; point
++) {
590 if (link
->points
[point
].x
!= x
)
592 if (link
->points
[point
].y
>= min_y
593 && link
->points
[point
].y
<= max_y
)
594 return link
->points
[point
].y
+ 1;
601 next_link_in_dir(struct document_view
*doc_view
, int dir_x
, int dir_y
)
603 struct document
*document
;
604 struct view_state
*vs
;
606 int min_x
= INT_MAX
, max_x
= 0;
609 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
);
610 if_assert_failed
return 0;
611 assert(dir_x
|| dir_y
);
612 if_assert_failed
return 0;
614 document
= doc_view
->document
;
617 link
= get_current_link(doc_view
);
620 /* Find the link's "bounding box" coordinates. */
622 get_link_x_bounds(link
, -1, &min_x
, &max_x
);
624 min_y
= link
->points
[0].y
;
625 max_y
= link
->points
[link
->npoints
- 1].y
;
627 /* Now go from the bounding box edge in the appropriate
628 * direction and find the nearest link. */
631 /* Vertical movement */
633 /* The current line number */
634 int y
= (dir_y
> 0 ? max_y
: min_y
) + dir_y
;
635 /* The bounding line numbers */
636 int top
= int_max(0, doc_view
->vs
->y
);
637 int bottom
= int_min(doc_view
->vs
->y
+ doc_view
->box
.height
,
640 for (; dir_y
> 0 ? y
< bottom
: y
>= top
; y
+= dir_y
) {
641 /* @backup points to the nearest link from the left
642 * to the desired position. */
643 struct link
*backup
= NULL
;
645 link
= document
->lines1
[y
];
648 /* Go through all the links on line. */
649 for (; link
<= document
->lines2
[y
]; link
++) {
650 int l_min_x
, l_max_x
;
652 /* Some links can be totally out of order here,
653 * ie. in tables or when using tabindex. */
654 if (y
< link
->points
[0].y
655 || y
> link
->points
[link
->npoints
- 1].y
)
658 get_link_x_bounds(link
, y
, &l_min_x
, &l_max_x
);
659 if (l_min_x
> max_x
) {
660 /* This link is too at the right. */
665 if (l_max_x
< min_x
) {
666 /* This link is too at the left. */
670 /* This link is aligned with the current one. */
680 if (!y
|| y
== document
->height
) {
681 /* We just stay at the same place, do not invalidate
682 * the link number. */
687 /* Horizontal movement */
689 /* The current column number */
690 int x
= (dir_x
> 0 ? max_x
: min_x
) + dir_x
;
691 /* How many lines are already past their last link */
694 while ((last
< max_y
- min_y
+ 1) && (x
+= dir_x
) >= 0) {
699 /* Go through all the lines */
700 for (y
= min_y
; y
<= max_y
; y
++) {
701 link
= document
->lines1
[y
];
704 /* Go through all the links on line. */
705 while (link
<= document
->lines2
[y
]) {
706 if (get_link_y_intersect(link
, x
,
713 /* Check if we already aren't past the last
714 * link on this line. */
715 if (!get_link_x_intersect(document
->lines2
[y
],
725 current_link_blur(doc_view
);
726 vs
->current_link
= -1;
730 /* The link is in bounds, take it. */
731 current_link_blur(doc_view
);
732 vs
->current_link
= get_link_index(document
, link
);
733 set_pos_x(doc_view
, link
);
734 current_link_hover(doc_view
);
739 set_pos_x(struct document_view
*doc_view
, struct link
*link
)
745 assert(doc_view
&& link
);
746 if_assert_failed
return;
748 for (i
= 0; i
< link
->npoints
; i
++) {
749 int y
= link
->points
[i
].y
- doc_view
->vs
->y
;
751 if (y
>= 0 && y
< doc_view
->box
.height
) {
752 int_lower_bound(&xm
, link
->points
[i
].x
+ 1);
753 int_upper_bound(&xl
, link
->points
[i
].x
);
758 int_bounds(&doc_view
->vs
->x
, xm
- doc_view
->box
.width
, xl
);
762 set_pos_y(struct document_view
*doc_view
, struct link
*link
)
768 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
&& link
);
769 if_assert_failed
return;
771 height
= doc_view
->document
->height
;
772 for (i
= 0; i
< link
->npoints
; i
++) {
773 int_lower_bound(&ym
, link
->points
[i
].y
+ 1);
774 int_upper_bound(&height
, link
->points
[i
].y
);
776 doc_view
->vs
->y
= (ym
+ height
- doc_view
->box
.height
) / 2;
777 int_bounds(&doc_view
->vs
->y
, 0,
778 doc_view
->document
->height
- doc_view
->box
.height
);
781 /** Focus the next link in the specified direction.
782 * @a direction == 1 -> DOWN;
783 * @a direction == -1 -> UP */
785 find_link(struct document_view
*doc_view
, int direction
, int page_mode
)
788 struct link
*link
= NULL
;
792 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
);
793 if_assert_failed
return;
795 if (direction
== -1) {
797 line
= doc_view
->document
->lines2
;
798 if (!line
) goto nolink
;
799 y
= doc_view
->vs
->y
+ doc_view
->box
.height
- 1;
800 int_upper_bound(&y
, doc_view
->document
->height
- 1);
801 if (y
< 0) goto nolink
;
804 line
= doc_view
->document
->lines1
;
805 if (!line
) goto nolink
;
807 int_lower_bound(&y
, 0);
808 if (y
>= doc_view
->document
->height
) goto nolink
;
811 ymin
= int_max(0, doc_view
->vs
->y
);
812 ymax
= int_min(doc_view
->document
->height
,
813 doc_view
->vs
->y
+ doc_view
->box
.height
);
815 if (direction
== -1) {
818 struct link
*cur
= line
[y
--];
820 if (cur
&& (!link
|| cur
> link
))
822 } while (y
>= ymin
&& y
< ymax
);
827 struct link
*cur
= line
[y
++];
829 if (cur
&& (!link
|| cur
< link
))
831 } while (y
>= ymin
&& y
< ymax
);
834 if (!link
) goto nolink
;
836 link_pos
= link
- doc_view
->document
->links
;
839 next_link_in_view(doc_view
, link_pos
, direction
);
842 current_link_blur(doc_view
);
843 doc_view
->vs
->current_link
= link_pos
;
844 set_pos_x(doc_view
, link
);
845 current_link_hover(doc_view
);
849 current_link_blur(doc_view
);
850 doc_view
->vs
->current_link
= -1;
854 find_link_up(struct document_view
*doc_view
)
856 find_link(doc_view
, -1, 0);
860 find_link_page_up(struct document_view
*doc_view
)
862 find_link(doc_view
, -1, 1);
866 find_link_down(struct document_view
*doc_view
)
868 find_link(doc_view
, 1, 0);
872 find_link_page_down(struct document_view
*doc_view
)
874 find_link(doc_view
, 1, 1);
878 get_link_uri(struct session
*ses
, struct document_view
*doc_view
,
881 assert(ses
&& doc_view
&& link
);
882 if_assert_failed
return NULL
;
884 switch (link
->type
) {
887 if (link
->where
) return get_uri(link
->where
, 0);
888 return get_uri(link
->where_img
, 0);
892 return get_form_uri(ses
, doc_view
,
893 get_link_form_control(link
));
901 call_onsubmit_and_submit(struct session
*ses
, struct document_view
*doc_view
,
902 struct form_control
*fc
, int do_reload
)
904 struct uri
*uri
= NULL
;
905 enum cache_mode mode
= do_reload
? CACHE_MODE_FORCE_RELOAD
: CACHE_MODE_NORMAL
;
907 assert(fc
->form
); /* regardless of whether there is a FORM element */
908 if_assert_failed
return 0;
910 #ifdef CONFIG_ECMASCRIPT
911 /* If the form has multiple submit buttons, this does not
912 * explicitly tell the ECMAScript code which of them was
913 * pressed. W3C DOM Level 3 doesn't seem to include such a
915 if (fc
->type
!= FC_RESET
&& fc
->form
->onsubmit
) {
918 if (init_string(&code
)) {
919 struct view_state
*vs
= doc_view
->vs
;
920 struct ecmascript_interpreter
*interpreter
;
923 if (vs
->ecmascript_fragile
)
924 ecmascript_reset_state(vs
);
925 interpreter
= vs
->ecmascript
;
928 add_to_string(&code
, fc
->form
->onsubmit
);
929 res
= ecmascript_eval_boolback(interpreter
, &code
);
931 /* If the user presses Enter in a text field,
932 * and document.browse.forms.auto_submit is
933 * true, and the form has an onsubmit script
934 * that returns false, then insert mode should
935 * end, so return 1 here rather than 0. */
939 #endif /* CONFIG_ECMASCRIPT */
941 uri
= get_form_uri(ses
, doc_view
, fc
);
943 goto_uri_frame(ses
, uri
, fc
->form
->target
, mode
);
949 goto_current_link(struct session
*ses
, struct document_view
*doc_view
, int do_reload
)
954 assert(doc_view
&& ses
);
955 if_assert_failed
return NULL
;
957 link
= get_current_link(doc_view
);
958 if (!link
) return NULL
;
960 if (link_is_form(link
)) {
961 struct form_control
*fc
= link
->data
.form_control
;
963 if (fc
->type
!= FC_BUTTON
964 && !call_onsubmit_and_submit(ses
, doc_view
, fc
, do_reload
))
969 uri
= get_link_uri(ses
, doc_view
, link
);
971 if (!uri
) return NULL
;
973 if (link
->type
== LINK_MAP
) {
974 /* TODO: Test reload? */
975 goto_imgmap(ses
, uri
, null_or_stracpy(link
->target
));
978 enum cache_mode mode
= do_reload
? CACHE_MODE_FORCE_RELOAD
981 goto_uri_frame(ses
, uri
, link
->target
, mode
);
988 static enum frame_event_status
989 activate_link(struct session
*ses
, struct document_view
*doc_view
,
990 struct link
*link
, int do_reload
)
992 struct form_control
*link_fc
;
993 struct form_state
*fs
;
996 switch (link
->type
) {
1002 if (goto_current_link(ses
, doc_view
, do_reload
))
1003 return FRAME_EVENT_OK
;
1006 link_fc
= get_link_form_control(link
);
1008 if (form_field_is_readonly(link_fc
))
1009 return FRAME_EVENT_OK
;
1011 fs
= find_form_state(doc_view
, link_fc
);
1012 if (!fs
) return FRAME_EVENT_OK
;
1014 if (link_fc
->type
== FC_CHECKBOX
) {
1015 fs
->state
= !fs
->state
;
1017 return FRAME_EVENT_REFRESH
;
1020 /* @link_fc->type must be FC_RADIO, then. First turn
1021 * this one on, and then turn off all the other radio
1022 * buttons in the group. Do it in this order because
1023 * further @find_form_state calls may reallocate
1024 * @doc_view->vs->form_info[] and thereby make the @fs
1025 * pointer invalid. This also allows us to re-use
1026 * @fs in the loop. */
1028 foreach (form
, doc_view
->document
->forms
) {
1029 struct form_control
*fc
;
1031 if (form
!= link_fc
->form
)
1034 foreach (fc
, form
->items
) {
1035 if (fc
->type
== FC_RADIO
1036 && !xstrcmp(fc
->name
, link_fc
->name
)
1038 fs
= find_form_state(doc_view
, fc
);
1039 if (fs
) fs
->state
= 0;
1047 link_fc
= get_link_form_control(link
);
1049 if (form_field_is_readonly(link_fc
))
1050 return FRAME_EVENT_OK
;
1052 object_lock(doc_view
->document
);
1053 add_empty_window(ses
->tab
->term
,
1054 (void (*)(void *)) release_document
,
1055 doc_view
->document
);
1056 do_select_submenu(ses
->tab
->term
, link_fc
->menu
, ses
);
1061 INTERNAL("bad link type %d", link
->type
);
1064 return FRAME_EVENT_REFRESH
;
1067 enum frame_event_status
1068 enter(struct session
*ses
, struct document_view
*doc_view
, int do_reload
)
1072 assert(ses
&& doc_view
&& doc_view
->vs
&& doc_view
->document
);
1073 if_assert_failed
return FRAME_EVENT_REFRESH
;
1075 link
= get_current_link(doc_view
);
1076 if (!link
) return FRAME_EVENT_REFRESH
;
1079 if (!current_link_evhook(doc_view
, SEVHOOK_ONCLICK
))
1080 return FRAME_EVENT_REFRESH
;
1081 return activate_link(ses
, doc_view
, link
, do_reload
);
1084 /** Get the link at the coordinates @a x and @a y, or NULL if none.
1085 * The coordinates are relative to the document view; not to the
1086 * terminal, nor to the document. So (0, 0) means whatever part of
1087 * the document has been scrolled to the top left corner of the
1090 get_link_at_coordinates(struct document_view
*doc_view
, int x
, int y
)
1092 struct link
*l1
, *l2
, *link
;
1095 assert(doc_view
&& doc_view
->vs
&& doc_view
->document
);
1096 if_assert_failed
return NULL
;
1098 /* If there are no links in in document, there is nothing to do. */
1099 if (!doc_view
->document
->nlinks
) return NULL
;
1101 /* If the coordinates are outside document view, no need to go further. */
1102 if (x
< 0 || x
>= doc_view
->box
.width
) return NULL
;
1103 if (y
< 0 || y
>= doc_view
->box
.height
) return NULL
;
1105 /* FIXME: This doesn't work. --Zas
1106 if (!check_mouse_position(ev, &doc_view->box))
1110 /* Find link candidates. */
1111 l1
= doc_view
->document
->links
+ doc_view
->document
->nlinks
;
1112 l2
= doc_view
->document
->links
;
1113 height
= int_min(doc_view
->document
->height
,
1114 doc_view
->vs
->y
+ doc_view
->box
.height
);
1116 for (i
= doc_view
->vs
->y
; i
< height
; i
++) {
1117 if (doc_view
->document
->lines1
[i
]
1118 && doc_view
->document
->lines1
[i
] < l1
)
1119 l1
= doc_view
->document
->lines1
[i
];
1121 if (doc_view
->document
->lines2
[i
]
1122 && doc_view
->document
->lines2
[i
] > l2
)
1123 l2
= doc_view
->document
->lines2
[i
];
1126 /* Is there a link at the given coordinates? */
1127 x
+= doc_view
->vs
->x
;
1128 y
+= doc_view
->vs
->y
;
1130 for (link
= l1
; link
<= l2
; link
++) {
1131 for (i
= 0; i
< link
->npoints
; i
++)
1132 if (link
->points
[i
].x
== x
1133 && link
->points
[i
].y
== y
)
1140 /** This is backend of the backend goto_link_number_do() below ;)). */
1142 jump_to_link_number(struct session
*ses
, struct document_view
*doc_view
, int n
)
1144 assert(ses
&& doc_view
&& doc_view
->vs
&& doc_view
->document
);
1145 if_assert_failed
return;
1147 if (n
< 0 || n
>= doc_view
->document
->nlinks
) return;
1148 current_link_blur(doc_view
);
1149 doc_view
->vs
->current_link
= n
;
1150 if (ses
->navigate_mode
== NAVIGATE_CURSOR_ROUTING
) {
1151 struct link
*link
= get_current_link(doc_view
);
1152 int offset
= get_link_cursor_offset(doc_view
, link
);
1154 if (link
->npoints
> offset
) {
1155 int x
= link
->points
[offset
].x
1156 + doc_view
->box
.x
- doc_view
->vs
->x
;
1157 int y
= link
->points
[offset
].y
1158 + doc_view
->box
.y
- doc_view
->vs
->y
;
1160 move_cursor(ses
, doc_view
, x
, y
);
1164 current_link_hover(doc_view
);
1167 /** This is common backend for goto_link_number() and try_document_key(). */
1169 goto_link_number_do(struct session
*ses
, struct document_view
*doc_view
, int n
)
1173 assert(ses
&& doc_view
&& doc_view
->document
);
1174 if_assert_failed
return;
1175 if (n
< 0 || n
>= doc_view
->document
->nlinks
) return;
1176 jump_to_link_number(ses
, doc_view
, n
);
1178 link
= &doc_view
->document
->links
[n
];
1179 if (!link_is_textinput(link
)
1180 && get_opt_bool("document.browse.accesskey.auto_follow"))
1181 enter(ses
, doc_view
, 0);
1185 goto_link_number(struct session
*ses
, unsigned char *num
)
1187 struct document_view
*doc_view
;
1190 if_assert_failed
return;
1191 doc_view
= current_frame(ses
);
1193 if_assert_failed
return;
1194 goto_link_number_do(ses
, doc_view
, atoi(num
) - 1);
1197 /** See if this document is interested in the key user pressed. */
1198 enum frame_event_status
1199 try_document_key(struct session
*ses
, struct document_view
*doc_view
,
1200 struct term_event
*ev
)
1203 int i
; /* GOD I HATE C! --FF */ /* YEAH, BRAINFUCK RULEZ! --pasky */
1205 assert(ses
&& doc_view
&& doc_view
->document
&& doc_view
->vs
&& ev
);
1206 if_assert_failed
return FRAME_EVENT_IGNORED
;
1208 if (!check_kbd_modifier(ev
, KBD_MOD_ALT
)
1209 || !is_kbd_character(get_kbd_key(ev
))) {
1210 /* We accept only alt-character combos. */
1211 return FRAME_EVENT_IGNORED
;
1214 /* The key is a character. Convert it to Unicode so that it
1215 * can be compared with link.accesskey. */
1217 key
= get_kbd_key(ev
);
1218 #else /* !CONFIG_UTF8 */
1219 key
= cp2u(get_terminal_codepage(ses
->tab
->term
),
1221 #endif /* !CONFIG_UTF8 */
1222 /* If @key now is 0 (which is used in link.accesskey if there
1223 * is no access key) or UCS_REPLACEMENT_CHARACTER, then the
1224 * results may be a little odd, but not really harmful. */
1226 /* Run through all the links and see if one of them is bound to the
1229 i
= doc_view
->vs
->current_link
+ 1;
1230 for (; i
< doc_view
->document
->nlinks
; i
++) {
1231 struct link
*link
= &doc_view
->document
->links
[i
];
1233 if (key
== link
->accesskey
) {
1234 ses
->kbdprefix
.repeat_count
= 0;
1235 goto_link_number_do(ses
, doc_view
, i
);
1236 return FRAME_EVENT_REFRESH
;
1239 for (i
= 0; i
<= doc_view
->vs
->current_link
; i
++) {
1240 struct link
*link
= &doc_view
->document
->links
[i
];
1242 if (key
== link
->accesskey
) {
1243 ses
->kbdprefix
.repeat_count
= 0;
1244 goto_link_number_do(ses
, doc_view
, i
);
1245 return FRAME_EVENT_REFRESH
;
1249 return FRAME_EVENT_IGNORED
;
1252 /** Open a contextual menu on a link, form or image element.
1253 * @todo TODO: This should be completely configurable. */
1255 link_menu(struct terminal
*term
, void *xxx
, void *ses_
)
1257 struct session
*ses
= ses_
;
1258 struct document_view
*doc_view
;
1260 struct menu_item
*mi
;
1261 struct form_control
*fc
;
1263 assert(term
&& ses
);
1264 if_assert_failed
return;
1266 doc_view
= current_frame(ses
);
1267 mi
= new_menu(FREE_LIST
);
1269 if (!doc_view
) goto end
;
1271 assert(doc_view
->vs
&& doc_view
->document
);
1272 if_assert_failed
return;
1274 link
= get_current_link(doc_view
);
1275 if (!link
) goto end
;
1277 if (link
->where
&& !link_is_form(link
)) {
1278 if (link
->type
== LINK_MAP
) {
1279 /* [gettext_accelerator_context(link_menu.map)] */
1280 add_to_menu(&mi
, N_("Display ~usemap"), NULL
, ACT_MAIN_LINK_FOLLOW
,
1281 NULL
, NULL
, SUBMENU
);
1282 /* [gettext_accelerator_context()] */
1284 /* [gettext_accelerator_context(link_menu.std)] */
1285 add_menu_action(&mi
, N_("~Follow link"), ACT_MAIN_LINK_FOLLOW
);
1287 add_menu_action(&mi
, N_("Follow link and r~eload"), ACT_MAIN_LINK_FOLLOW_RELOAD
);
1289 add_menu_separator(&mi
);
1291 add_new_win_to_menu(&mi
, N_("Open in new ~window"), term
);
1293 add_menu_action(&mi
, N_("Open in new ~tab"), ACT_MAIN_OPEN_LINK_IN_NEW_TAB
);
1295 add_menu_action(&mi
, N_("Open in new tab in ~background"),
1296 ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND
);
1298 if (!get_cmd_opt_bool("anonymous")) {
1299 add_menu_separator(&mi
);
1300 add_menu_action(&mi
, N_("~Download link"), ACT_MAIN_LINK_DOWNLOAD
);
1302 #ifdef CONFIG_BOOKMARKS
1303 add_menu_action(&mi
, N_("~Add link to bookmarks"),
1304 ACT_MAIN_ADD_BOOKMARK_LINK
);
1306 add_uri_command_to_menu(&mi
, PASS_URI_LINK
,
1307 N_("Pass link URI to e~xternal command"));
1309 /* [gettext_accelerator_context()] */
1313 fc
= get_link_form_control(link
);
1317 /* [gettext_accelerator_context(link_menu.reset)] */
1318 add_menu_action(&mi
, N_("~Reset form"), ACT_MAIN_RESET_FORM
);
1319 /* [gettext_accelerator_context()] */
1323 /* [gettext_accelerator_context(link_menu.textarea)] */
1324 if (!form_field_is_readonly(fc
)) {
1325 struct string keystroke
;
1327 if (init_string(&keystroke
))
1328 add_keystroke_action_to_string(
1330 ACT_EDIT_OPEN_EXTERNAL
,
1333 add_to_menu(&mi
, N_("Open in ~external editor"),
1334 keystroke
.source
, ACT_MAIN_NONE
,
1335 menu_textarea_edit
, NULL
, FREE_RTEXT
);
1337 /* [gettext_accelerator_context()] */
1340 /* [gettext_accelerator_context(link_menu.textarea, link_menu.form)] */
1341 add_menu_action(&mi
, N_("~Submit form"), ACT_MAIN_SUBMIT_FORM
);
1342 add_menu_action(&mi
, N_("Submit form and rel~oad"), ACT_MAIN_SUBMIT_FORM_RELOAD
);
1345 if (fc
->form
->method
== FORM_METHOD_GET
) {
1346 add_new_win_to_menu(&mi
, N_("Submit form and open in new ~window"), term
);
1348 add_menu_action(&mi
, N_("Submit form and open in new ~tab"),
1349 ACT_MAIN_OPEN_LINK_IN_NEW_TAB
);
1351 add_menu_action(&mi
, N_("Submit form and open in new tab in ~background"),
1352 ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND
);
1355 if (!get_cmd_opt_bool("anonymous"))
1356 add_menu_action(&mi
, N_("Submit form and ~download"), ACT_MAIN_LINK_DOWNLOAD
);
1358 add_menu_action(&mi
, N_("~Reset form"), ACT_MAIN_RESET_FORM
);
1359 /* [gettext_accelerator_context()] */
1362 /* [gettext_accelerator_context(link_menu.reset, link_menu.textarea, link_menu.form)] */
1363 add_to_menu(&mi
, N_("Form f~ields"), NULL
, ACT_MAIN_LINK_FORM_MENU
,
1364 NULL
, NULL
, SUBMENU
);
1365 /* [gettext_accelerator_context()] */
1368 if (link
->where_img
) {
1369 /* [gettext_accelerator_context(link_menu.map, link_menu.std, link_menu.form)] */
1370 add_menu_action(&mi
, N_("V~iew image"), ACT_MAIN_VIEW_IMAGE
);
1371 if (!get_cmd_opt_bool("anonymous"))
1372 add_menu_action(&mi
, N_("Download ima~ge"), ACT_MAIN_LINK_DOWNLOAD_IMAGE
);
1373 /* [gettext_accelerator_context()] */
1376 /** @todo TODO: Make it possible to trigger any script event
1377 * hooks associated to the link. --pasky */
1381 add_to_menu(&mi
, N_("No link selected"), NULL
, ACT_MAIN_NONE
,
1382 NULL
, NULL
, NO_SELECT
);
1385 do_menu(term
, mi
, ses
, 1);
1388 /** Return current link's title. */
1390 get_current_link_title(struct document_view
*doc_view
)
1394 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
);
1395 if_assert_failed
return NULL
;
1397 if (doc_view
->document
->frame_desc
)
1400 link
= get_current_link(doc_view
);
1402 if (link
&& link
->title
&& *link
->title
) {
1403 unsigned char *link_title
, *src
;
1404 struct conv_table
*convert_table
;
1406 convert_table
= get_translation_table(doc_view
->document
->cp
,
1407 doc_view
->document
->options
.cp
);
1409 link_title
= convert_string(convert_table
, link
->title
,
1410 strlen(link
->title
),
1411 doc_view
->document
->options
.cp
,
1412 CSM_DEFAULT
, NULL
, NULL
, NULL
);
1413 /* Remove illicit chars. */
1415 if (link_title
&& !doc_view
->document
->options
.utf8
)
1416 #endif /* CONFIG_UTF8 */
1417 for (src
= link_title
; *src
; src
++)
1418 if (!isprint(*src
) || iscntrl(*src
))
1428 get_current_link_info(struct session
*ses
, struct document_view
*doc_view
)
1432 assert(ses
&& doc_view
&& doc_view
->document
&& doc_view
->vs
);
1433 if_assert_failed
return NULL
;
1435 if (doc_view
->document
->frame_desc
)
1438 link
= get_current_link(doc_view
);
1439 if (!link
) return NULL
;
1441 /** @todo TODO: Provide info about script event hooks too. --pasky */
1443 if (!link_is_form(link
)) {
1444 struct terminal
*term
= ses
->tab
->term
;
1446 unsigned char *uristring
= link
->where
;
1448 if (!init_string(&str
)) return NULL
;
1450 if (!link
->where
&& link
->where_img
) {
1451 add_to_string(&str
, _("Image", term
));
1452 add_char_to_string(&str
, ' ');
1453 uristring
= link
->where_img
;
1455 } else if (link
->type
== LINK_MAP
) {
1456 add_to_string(&str
, _("Usemap", term
));
1457 add_char_to_string(&str
, ' ');
1460 /* Add the uri with password and post info stripped */
1461 add_string_uri_to_string(&str
, uristring
, URI_PUBLIC
);
1462 if (link
->accesskey
> 0
1463 && get_opt_bool("document.browse.accesskey.display")) {
1464 add_to_string(&str
, " (");
1465 add_accesskey_to_string(&str
, link
->accesskey
);
1466 add_char_to_string(&str
, ')');
1471 decode_uri_string(&str
);
1473 #endif /* CONFIG_UTF8 */
1474 decode_uri_string_for_display(&str
);
1478 if (!get_link_form_control(link
)) return NULL
;
1480 return get_form_info(ses
, doc_view
);