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/options.h"
20 #include "document/view.h"
21 #include "ecmascript/ecmascript.h"
22 #include "intl/gettext/libintl.h"
23 #include "main/object.h"
24 #include "protocol/uri.h"
25 #include "session/session.h"
26 #include "session/task.h"
27 #include "terminal/color.h"
28 #include "terminal/draw.h"
29 #include "terminal/kbd.h"
30 #include "terminal/screen.h"
31 #include "terminal/tab.h"
32 #include "terminal/terminal.h"
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.
52 current_link_evhook(struct document_view
*doc_view
, enum script_event_hook_type type
)
54 #ifdef CONFIG_ECMASCRIPT
56 struct script_event_hook
*evhook
;
58 assert(doc_view
&& doc_view
->vs
);
59 link
= get_current_link(doc_view
);
61 if (!link
->event_hooks
) return -1;
63 if (!doc_view
->vs
->ecmascript
) return -1;
65 foreach (evhook
, *link
->event_hooks
) {
68 if (evhook
->type
!= type
) continue;
70 while ((ret
= strstr(ret
, "return ")))
71 while (*ret
!= ' ') *ret
++ = ' ';
73 struct string src
= INIT_STRING(evhook
->src
, strlen(evhook
->src
));
74 /* TODO: Some even handlers return a bool. */
75 if (!ecmascript_eval_boolback(doc_view
->vs
->ecmascript
, &src
))
86 #define current_link_hover(dv) \
88 current_link_evhook(dv, SEVHOOK_ONMOUSEOVER); \
89 current_link_evhook(dv, SEVHOOK_ONHOVER); \
90 current_link_evhook(dv, SEVHOOK_ONFOCUS); \
92 #define current_link_blur(dv) \
94 current_link_evhook(dv, SEVHOOK_ONMOUSEOUT); \
95 current_link_evhook(dv, SEVHOOK_ONBLUR); \
100 set_link(struct document_view
*doc_view
)
103 if_assert_failed
return;
105 if (current_link_is_visible(doc_view
)) return;
107 find_link_page_down(doc_view
);
111 get_link_cursor_offset(struct document_view
*doc_view
, struct link
*link
)
113 struct form_control
*fc
;
114 struct form_state
*fs
;
116 /* The encoding of form fields depends on the terminal,
117 * rather than on the document. */
118 int utf8
= doc_view
->session
->tab
->term
->utf8_cp
;
119 #endif /* CONFIG_UTF8 */
121 switch (link
->type
) {
129 fc
= get_link_form_control(link
);
130 fs
= find_form_state(doc_view
, fc
);
131 if (!fs
|| !fs
->value
)
135 unsigned char *scroll
= fs
->value
+ fs
->vpos
;
136 unsigned char *point
= fs
->value
+ fs
->state
;
138 if (fs
->type
== FC_PASSWORD
)
139 return utf8_ptr2chars(scroll
, point
);
141 return utf8_ptr2cells(scroll
, point
);
143 #endif /* CONFIG_UTF8 */
145 return fs
->state
- fs
->vpos
;
148 fc
= get_link_form_control(link
);
149 fs
= find_form_state(doc_view
, fc
);
151 return fs
? area_cursor(fc
, fs
, utf8
) : 0;
153 return fs
? area_cursor(fc
, fs
) : 0;
154 #endif /* CONFIG_UTF8 */
165 /** Initialise a static template character with the colour and attributes
166 * appropriate for an active link and return that character. */
167 static inline struct screen_char
*
168 init_link_drawing(struct document_view
*doc_view
, struct link
*link
, int invert
)
170 struct document_options
*doc_opts
;
171 static struct screen_char
template;
172 enum color_flags color_flags
;
173 enum color_mode color_mode
;
174 struct color_pair colors
;
176 template.attr
= SCREEN_ATTR_STANDOUT
;
178 doc_opts
= &doc_view
->document
->options
;
180 color_flags
= (doc_opts
->color_flags
| COLOR_DECREASE_LIGHTNESS
);
181 color_mode
= doc_opts
->color_mode
;
183 if (doc_opts
->active_link
.underline
)
184 template.attr
|= SCREEN_ATTR_UNDERLINE
;
186 if (doc_opts
->active_link
.bold
)
187 template.attr
|= SCREEN_ATTR_BOLD
;
189 if (doc_opts
->active_link
.enable_color
) {
190 colors
.foreground
= doc_opts
->active_link
.color
.foreground
;
191 colors
.background
= doc_opts
->active_link
.color
.background
;
193 colors
.foreground
= link
->color
.foreground
;
194 colors
.background
= link
->color
.background
;
197 if (invert
&& doc_opts
->active_link
.invert
) {
198 swap_values(color_T
, colors
.foreground
, colors
.background
);
200 /* Highlight text-input form-fields correctly if contrast
201 * correction is needed. */
202 if (link_is_textinput(link
)) {
203 /* Make sure to use the options belonging to the
204 * current document when checking for fg and bg color
206 doc_opts
= &doc_view
->document
->options
;
208 /* Are we fallen angels who didn't want to believe that
209 * nothing /is/ nothing and so were born to lose our
210 * loved ones and dear friends one by one and finally
211 * our own life, to see it proved? --Kerouac */
213 /* Wipe out all default correction for 16 color mode */
214 color_flags
= (color_flags
& ~COLOR_INCREASE_CONTRAST
);
215 /* Make contrast correction invert things properly */
216 color_flags
|= COLOR_ENSURE_INVERTED_CONTRAST
;
220 set_term_color(&template, &colors
, color_flags
, color_mode
);
225 /** Give the current link the appropriate colour and attributes. */
227 draw_current_link(struct session
*ses
, struct document_view
*doc_view
)
229 struct terminal
*term
= ses
->tab
->term
;
230 struct screen_char
*template;
236 assert(term
&& doc_view
&& doc_view
->vs
);
237 if_assert_failed
return;
239 assert(ses
->tab
== get_current_tab(term
));
240 if_assert_failed
return;
242 link
= get_current_link(doc_view
);
245 i
= !link_is_textinput(link
) || ses
->insert_mode
== INSERT_MODE_OFF
;
246 template = init_link_drawing(doc_view
, link
, i
);
247 if (!template) return;
249 xpos
= doc_view
->box
.x
- doc_view
->vs
->x
;
250 ypos
= doc_view
->box
.y
- doc_view
->vs
->y
;
252 if (ses
->insert_mode
== INSERT_MODE_OFF
253 && ses
->navigate_mode
== NAVIGATE_CURSOR_ROUTING
) {
254 /* If we are navigating using cursor routing and not editing a
255 * text-input form-field never set the cursor. */
258 cursor_offset
= get_link_cursor_offset(doc_view
, link
);
261 for (i
= 0; i
< link
->npoints
; i
++) {
262 int x
= link
->points
[i
].x
+ xpos
;
263 int y
= link
->points
[i
].y
+ ypos
;
264 struct screen_char
*co
;
266 if (!is_in_box(&doc_view
->box
, x
, y
)) {
270 co
= get_char(term
, x
, y
);
272 if (i
== cursor_offset
) {
273 int blockable
= (!link_is_textinput(link
)
274 && co
->color
!= template->color
);
276 set_cursor(term
, x
, y
, blockable
);
277 set_window_ptr(ses
->tab
, x
, y
);
280 template->data
= co
->data
;
281 copy_screen_chars(co
, template, 1);
282 set_screen_dirty(term
->screen
, y
, y
);
285 doc_view
->vs
->old_current_link
= doc_view
->vs
->current_link
;
289 draw_link(struct terminal
*term
, struct document_view
*doc_view
,
292 int xpos
= doc_view
->box
.x
- doc_view
->vs
->x
;
293 int ypos
= doc_view
->box
.y
- doc_view
->vs
->y
;
296 for (i
= 0; i
< link
->npoints
; ++i
) {
297 int x
= link
->points
[i
].x
;
298 int y
= link
->points
[i
].y
;
300 if (is_in_box(&doc_view
->box
, x
+ xpos
, y
+ ypos
)){
301 struct screen_char
*ch
;
303 ch
= get_char(term
, x
+ xpos
, y
+ ypos
);
304 copy_struct(ch
, &doc_view
->document
->data
[y
].chars
[x
]);
305 set_screen_dirty(term
->screen
, y
+ ypos
, y
+ ypos
);
310 /** Restore the colours and attributes that the active link had
311 * before it was selected. */
313 clear_link(struct terminal
*term
, struct document_view
*doc_view
)
315 struct link
*link
= get_current_link(doc_view
);
316 struct link
*last
= get_old_current_link(doc_view
);
318 if (last
&& last
!= link
) {
319 draw_link(term
, doc_view
, last
);
322 doc_view
->vs
->old_current_link
= doc_view
->vs
->current_link
;
326 highlight_links_with_prefixes_that_start_with_n(struct terminal
*term
,
327 struct document_view
*doc_view
,
330 struct color_pair
*color
= get_bfu_color(term
, "searched");
331 int xoffset
= doc_view
->box
.x
- doc_view
->vs
->x
;
332 int yoffset
= doc_view
->box
.y
- doc_view
->vs
->y
;
333 struct document
*document
= doc_view
->document
;
336 for (m
= n
+ 1; n
<= document
->nlinks
; n
*= 10, m
*= 10) {
339 for (linkn
= n
; linkn
< m
; ++linkn
) {
340 struct link
*link
= &document
->links
[linkn
- 1];
343 if (linkn
> document
->nlinks
) break;
345 for (i
= 0; i
< link
->npoints
; ++i
) {
346 int x
= link
->points
[i
].x
+ xoffset
;
347 int y
= link
->points
[i
].y
+ yoffset
;
349 if (is_in_box(&doc_view
->box
, x
, y
))
350 draw_char_color(term
, x
, y
, color
);
357 get_first_link(struct document_view
*doc_view
)
359 struct link
*link
, *undef
;
360 struct document
*document
;
364 assert(doc_view
&& doc_view
->document
);
365 if_assert_failed
return NULL
;
367 document
= doc_view
->document
;
369 if (!document
->lines1
) return NULL
;
371 height
= doc_view
->vs
->y
+ doc_view
->box
.height
;
372 link
= undef
= document
->links
+ document
->nlinks
;
374 for (i
= int_max(0, doc_view
->vs
->y
);
375 i
< int_min(height
, document
->height
);
377 if (document
->lines1
[i
]
378 && document
->lines1
[i
] < link
)
379 link
= document
->lines1
[i
];
382 return (link
== undef
) ? NULL
: link
;
386 get_last_link(struct document_view
*doc_view
)
388 struct link
*link
= NULL
;
389 struct document
*document
;
393 assert(doc_view
&& doc_view
->document
);
394 if_assert_failed
return NULL
;
396 document
= doc_view
->document
;
398 if (!document
->lines2
) return NULL
;
400 height
= doc_view
->vs
->y
+ doc_view
->box
.height
;
402 for (i
= int_max(0, doc_view
->vs
->y
);
403 i
< int_min(height
, document
->height
);
405 if (document
->lines2
[i
] > link
)
406 link
= document
->lines2
[i
];
412 link_in_view_x(struct document_view
*doc_view
, struct link
*link
)
416 assert(doc_view
&& link
);
417 if_assert_failed
return 0;
419 dx
= doc_view
->vs
->x
;
420 width
= doc_view
->box
.width
;
422 for (i
= 0; i
< link
->npoints
; i
++) {
423 int x
= link
->points
[i
].x
- dx
;
425 if (x
>= 0 && x
< width
)
433 link_in_view_y(struct document_view
*doc_view
, struct link
*link
)
437 assert(doc_view
&& link
);
438 if_assert_failed
return 0;
440 dy
= doc_view
->vs
->y
;
441 height
= doc_view
->box
.height
;
443 for (i
= 0; i
< link
->npoints
; i
++) {
444 int y
= link
->points
[i
].y
- dy
;
446 if (y
>= 0 && y
< height
)
454 link_in_view(struct document_view
*doc_view
, struct link
*link
)
456 assert(doc_view
&& link
);
457 if_assert_failed
return 0;
458 return link_in_view_y(doc_view
, link
) && link_in_view_x(doc_view
, link
);
462 current_link_is_visible(struct document_view
*doc_view
)
466 assert(doc_view
&& doc_view
->vs
);
467 if_assert_failed
return 0;
469 link
= get_current_link(doc_view
);
470 return (link
&& link_in_view(doc_view
, link
));
473 /** Look for the first and the last link currently visible in our
476 get_visible_links_range(struct document_view
*doc_view
, int *first
, int *last
)
478 struct document
*document
= doc_view
->document
;
479 int height
= int_min(doc_view
->vs
->y
+ doc_view
->box
.height
,
483 *first
= document
->nlinks
- 1;
486 for (y
= int_max(0, doc_view
->vs
->y
); y
< height
; y
++) {
487 if (document
->lines1
[y
])
488 int_upper_bound(first
, document
->lines1
[y
]
491 if (document
->lines2
[y
])
492 int_lower_bound(last
, document
->lines2
[y
]
498 next_link_in_view_(struct document_view
*doc_view
, int current
, int direction
,
499 int (*fn
)(struct document_view
*, struct link
*),
500 void (*cntr
)(struct document_view
*, struct link
*))
502 struct document
*document
;
503 struct view_state
*vs
;
506 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
&& fn
);
507 if_assert_failed
return 0;
509 document
= doc_view
->document
;
512 get_visible_links_range(doc_view
, &start
, &end
);
514 current_link_blur(doc_view
);
516 /* Go from the @current link in @direction until either
517 * fn() is happy or we would leave the current viewport. */
518 while (current
>= start
&& current
<= end
) {
519 if (fn(doc_view
, &document
->links
[current
])) {
520 vs
->current_link
= current
;
521 if (cntr
) cntr(doc_view
, &document
->links
[current
]);
522 current_link_hover(doc_view
);
525 current
+= direction
;
528 vs
->current_link
= -1;
533 next_link_in_view(struct document_view
*doc_view
, int current
, int direction
)
535 return next_link_in_view_(doc_view
, current
, direction
, link_in_view
, NULL
);
539 next_link_in_view_y(struct document_view
*doc_view
, int current
, int direction
)
541 return next_link_in_view_(doc_view
, current
, direction
, link_in_view_y
, set_pos_x
);
544 /** Get the bounding columns of @a link at line @a y (or all lines if
547 get_link_x_bounds(struct link
*link
, int y
, int *min_x
, int *max_x
)
551 if (min_x
) *min_x
= INT_MAX
;
552 if (max_x
) *max_x
= 0;
554 for (point
= 0; point
< link
->npoints
; point
++) {
555 if (y
>= 0 && link
->points
[point
].y
!= y
)
557 if (min_x
) int_upper_bound(min_x
, link
->points
[point
].x
);
558 if (max_x
) int_lower_bound(max_x
, link
->points
[point
].x
);
562 /** Check whether there is any point between @a min_x and @a max_x at
563 * the line @a y in link @a link. */
565 get_link_x_intersect(struct link
*link
, int y
, int min_x
, int max_x
)
569 for (point
= 0; point
< link
->npoints
; point
++) {
570 if (link
->points
[point
].y
!= y
)
572 if (link
->points
[point
].x
>= min_x
573 && link
->points
[point
].x
<= max_x
)
574 return link
->points
[point
].x
+ 1;
580 /** Check whether there is any point between @a min_y and @a max_y in
581 * the column @a x in link @a link. */
583 get_link_y_intersect(struct link
*link
, int x
, int min_y
, int max_y
)
587 for (point
= 0; point
< link
->npoints
; point
++) {
588 if (link
->points
[point
].x
!= x
)
590 if (link
->points
[point
].y
>= min_y
591 && link
->points
[point
].y
<= max_y
)
592 return link
->points
[point
].y
+ 1;
599 next_link_in_dir(struct document_view
*doc_view
, int dir_x
, int dir_y
)
601 struct document
*document
;
602 struct view_state
*vs
;
604 int min_x
= INT_MAX
, max_x
= 0;
607 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
);
608 if_assert_failed
return 0;
609 assert(dir_x
|| dir_y
);
610 if_assert_failed
return 0;
612 document
= doc_view
->document
;
615 link
= get_current_link(doc_view
);
618 /* Find the link's "bounding box" coordinates. */
620 get_link_x_bounds(link
, -1, &min_x
, &max_x
);
622 min_y
= link
->points
[0].y
;
623 max_y
= link
->points
[link
->npoints
- 1].y
;
625 /* Now go from the bounding box edge in the appropriate
626 * direction and find the nearest link. */
629 /* Vertical movement */
631 /* The current line number */
632 int y
= (dir_y
> 0 ? max_y
: min_y
) + dir_y
;
633 /* The bounding line numbers */
634 int top
= int_max(0, doc_view
->vs
->y
);
635 int bottom
= int_min(doc_view
->vs
->y
+ doc_view
->box
.height
,
638 for (; dir_y
> 0 ? y
< bottom
: y
>= top
; y
+= dir_y
) {
639 /* @backup points to the nearest link from the left
640 * to the desired position. */
641 struct link
*backup
= NULL
;
643 link
= document
->lines1
[y
];
646 /* Go through all the links on line. */
647 for (; link
<= document
->lines2
[y
]; link
++) {
648 int l_min_x
, l_max_x
;
650 /* Some links can be totally out of order here,
651 * ie. in tables or when using tabindex. */
652 if (y
< link
->points
[0].y
653 || y
> link
->points
[link
->npoints
- 1].y
)
656 get_link_x_bounds(link
, y
, &l_min_x
, &l_max_x
);
657 if (l_min_x
> max_x
) {
658 /* This link is too at the right. */
663 if (l_max_x
< min_x
) {
664 /* This link is too at the left. */
668 /* This link is aligned with the current one. */
678 if (!y
|| y
== document
->height
) {
679 /* We just stay at the same place, do not invalidate
680 * the link number. */
685 /* Horizontal movement */
687 /* The current column number */
688 int x
= (dir_x
> 0 ? max_x
: min_x
) + dir_x
;
689 /* How many lines are already past their last link */
692 while ((last
< max_y
- min_y
+ 1) && (x
+= dir_x
) >= 0) {
697 /* Go through all the lines */
698 for (y
= min_y
; y
<= max_y
; y
++) {
699 link
= document
->lines1
[y
];
702 /* Go through all the links on line. */
703 while (link
<= document
->lines2
[y
]) {
704 if (get_link_y_intersect(link
, x
,
711 /* Check if we already aren't past the last
712 * link on this line. */
713 if (!get_link_x_intersect(document
->lines2
[y
],
723 current_link_blur(doc_view
);
724 vs
->current_link
= -1;
728 /* The link is in bounds, take it. */
729 current_link_blur(doc_view
);
730 vs
->current_link
= get_link_index(document
, link
);
731 set_pos_x(doc_view
, link
);
732 current_link_hover(doc_view
);
737 set_pos_x(struct document_view
*doc_view
, struct link
*link
)
743 assert(doc_view
&& link
);
744 if_assert_failed
return;
746 for (i
= 0; i
< link
->npoints
; i
++) {
747 int y
= link
->points
[i
].y
- doc_view
->vs
->y
;
749 if (y
>= 0 && y
< doc_view
->box
.height
) {
750 int_lower_bound(&xm
, link
->points
[i
].x
+ 1);
751 int_upper_bound(&xl
, link
->points
[i
].x
);
756 int_bounds(&doc_view
->vs
->x
, xm
- doc_view
->box
.width
, xl
);
760 set_pos_y(struct document_view
*doc_view
, struct link
*link
)
766 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
&& link
);
767 if_assert_failed
return;
769 height
= doc_view
->document
->height
;
770 for (i
= 0; i
< link
->npoints
; i
++) {
771 int_lower_bound(&ym
, link
->points
[i
].y
+ 1);
772 int_upper_bound(&height
, link
->points
[i
].y
);
774 doc_view
->vs
->y
= (ym
+ height
- doc_view
->box
.height
) / 2;
775 int_bounds(&doc_view
->vs
->y
, 0,
776 doc_view
->document
->height
- doc_view
->box
.height
);
779 /** Focus the next link in the specified direction.
780 * @a direction == 1 -> DOWN;
781 * @a direction == -1 -> UP */
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
->type
!= FC_RESET
&& fc
->form
->onsubmit
) {
916 if (init_string(&code
)) {
917 struct view_state
*vs
= doc_view
->vs
;
918 struct ecmascript_interpreter
*interpreter
;
921 if (vs
->ecmascript_fragile
)
922 ecmascript_reset_state(vs
);
923 interpreter
= vs
->ecmascript
;
926 add_to_string(&code
, fc
->form
->onsubmit
);
927 res
= ecmascript_eval_boolback(interpreter
, &code
);
929 /* If the user presses Enter in a text field,
930 * and document.browse.forms.auto_submit is
931 * true, and the form has an onsubmit script
932 * that returns false, then insert mode should
933 * end, so return 1 here rather than 0. */
937 #endif /* CONFIG_ECMASCRIPT */
939 uri
= get_form_uri(ses
, doc_view
, fc
);
941 goto_uri_frame(ses
, uri
, fc
->form
->target
, mode
);
947 goto_current_link(struct session
*ses
, struct document_view
*doc_view
, int do_reload
)
952 assert(doc_view
&& ses
);
953 if_assert_failed
return NULL
;
955 link
= get_current_link(doc_view
);
956 if (!link
) return NULL
;
958 if (link_is_form(link
)) {
959 struct form_control
*fc
= link
->data
.form_control
;
961 if (fc
->type
!= FC_BUTTON
962 && !call_onsubmit_and_submit(ses
, doc_view
, fc
, do_reload
))
967 uri
= get_link_uri(ses
, doc_view
, link
);
969 if (!uri
) return NULL
;
971 if (link
->type
== LINK_MAP
) {
972 /* TODO: Test reload? */
973 goto_imgmap(ses
, uri
, link
->target
);
976 enum cache_mode mode
= do_reload
? CACHE_MODE_FORCE_RELOAD
979 goto_uri_frame(ses
, uri
, link
->target
, mode
);
986 static enum frame_event_status
987 activate_link(struct session
*ses
, struct document_view
*doc_view
,
988 struct link
*link
, int do_reload
)
990 struct form_control
*link_fc
;
991 struct form_state
*fs
;
994 switch (link
->type
) {
1000 if (goto_current_link(ses
, doc_view
, do_reload
))
1001 return FRAME_EVENT_OK
;
1004 link_fc
= get_link_form_control(link
);
1006 if (form_field_is_readonly(link_fc
))
1007 return FRAME_EVENT_OK
;
1009 fs
= find_form_state(doc_view
, link_fc
);
1010 if (!fs
) return FRAME_EVENT_OK
;
1012 if (link_fc
->type
== FC_CHECKBOX
) {
1013 fs
->state
= !fs
->state
;
1015 return FRAME_EVENT_REFRESH
;
1018 /* @link_fc->type must be FC_RADIO, then. First turn
1019 * this one on, and then turn off all the other radio
1020 * buttons in the group. Do it in this order because
1021 * further @find_form_state calls may reallocate
1022 * @doc_view->vs->form_info[] and thereby make the @fs
1023 * pointer invalid. This also allows us to re-use
1024 * @fs in the loop. */
1026 foreach (form
, doc_view
->document
->forms
) {
1027 struct form_control
*fc
;
1029 if (form
!= link_fc
->form
)
1032 foreach (fc
, form
->items
) {
1033 if (fc
->type
== FC_RADIO
1034 && !xstrcmp(fc
->name
, link_fc
->name
)
1036 fs
= find_form_state(doc_view
, fc
);
1037 if (fs
) fs
->state
= 0;
1045 link_fc
= get_link_form_control(link
);
1047 if (form_field_is_readonly(link_fc
))
1048 return FRAME_EVENT_OK
;
1050 object_lock(doc_view
->document
);
1051 add_empty_window(ses
->tab
->term
,
1052 (void (*)(void *)) release_document
,
1053 doc_view
->document
);
1054 do_select_submenu(ses
->tab
->term
, link_fc
->menu
, ses
);
1059 INTERNAL("bad link type %d", link
->type
);
1062 return FRAME_EVENT_REFRESH
;
1065 enum frame_event_status
1066 enter(struct session
*ses
, struct document_view
*doc_view
, int do_reload
)
1070 assert(ses
&& doc_view
&& doc_view
->vs
&& doc_view
->document
);
1071 if_assert_failed
return FRAME_EVENT_REFRESH
;
1073 link
= get_current_link(doc_view
);
1074 if (!link
) return FRAME_EVENT_REFRESH
;
1077 if (!current_link_evhook(doc_view
, SEVHOOK_ONCLICK
))
1078 return FRAME_EVENT_REFRESH
;
1079 return activate_link(ses
, doc_view
, link
, do_reload
);
1082 /** Get the link at the coordinates @a x and @a y, or NULL if none.
1083 * The coordinates are relative to the document view; not to the
1084 * terminal, nor to the document. So (0, 0) means whatever part of
1085 * the document has been scrolled to the top left corner of the
1088 get_link_at_coordinates(struct document_view
*doc_view
, int x
, int y
)
1090 struct link
*l1
, *l2
, *link
;
1093 assert(doc_view
&& doc_view
->vs
&& doc_view
->document
);
1094 if_assert_failed
return NULL
;
1096 /* If there are no links in in document, there is nothing to do. */
1097 if (!doc_view
->document
->nlinks
) return NULL
;
1099 /* If the coordinates are outside document view, no need to go further. */
1100 if (x
< 0 || x
>= doc_view
->box
.width
) return NULL
;
1101 if (y
< 0 || y
>= doc_view
->box
.height
) return NULL
;
1103 /* FIXME: This doesn't work. --Zas
1104 if (!check_mouse_position(ev, &doc_view->box))
1108 /* Find link candidates. */
1109 l1
= doc_view
->document
->links
+ doc_view
->document
->nlinks
;
1110 l2
= doc_view
->document
->links
;
1111 height
= int_min(doc_view
->document
->height
,
1112 doc_view
->vs
->y
+ doc_view
->box
.height
);
1114 for (i
= doc_view
->vs
->y
; i
< height
; i
++) {
1115 if (doc_view
->document
->lines1
[i
]
1116 && doc_view
->document
->lines1
[i
] < l1
)
1117 l1
= doc_view
->document
->lines1
[i
];
1119 if (doc_view
->document
->lines2
[i
]
1120 && doc_view
->document
->lines2
[i
] > l2
)
1121 l2
= doc_view
->document
->lines2
[i
];
1124 /* Is there a link at the given coordinates? */
1125 x
+= doc_view
->vs
->x
;
1126 y
+= doc_view
->vs
->y
;
1128 for (link
= l1
; link
<= l2
; link
++) {
1129 for (i
= 0; i
< link
->npoints
; i
++)
1130 if (link
->points
[i
].x
== x
1131 && link
->points
[i
].y
== y
)
1138 /** This is backend of the backend goto_link_number_do() below ;)). */
1140 jump_to_link_number(struct session
*ses
, struct document_view
*doc_view
, int n
)
1142 assert(ses
&& doc_view
&& doc_view
->vs
&& doc_view
->document
);
1143 if_assert_failed
return;
1145 if (n
< 0 || n
>= doc_view
->document
->nlinks
) return;
1146 current_link_blur(doc_view
);
1147 doc_view
->vs
->current_link
= n
;
1148 if (ses
->navigate_mode
== NAVIGATE_CURSOR_ROUTING
) {
1149 struct link
*link
= get_current_link(doc_view
);
1150 int offset
= get_link_cursor_offset(doc_view
, link
);
1152 if (link
->npoints
> offset
) {
1153 int x
= link
->points
[offset
].x
1154 + doc_view
->box
.x
- doc_view
->vs
->x
;
1155 int y
= link
->points
[offset
].y
1156 + doc_view
->box
.y
- doc_view
->vs
->y
;
1158 move_cursor(ses
, doc_view
, x
, y
);
1162 current_link_hover(doc_view
);
1165 /** This is common backend for goto_link_number() and try_document_key(). */
1167 goto_link_number_do(struct session
*ses
, struct document_view
*doc_view
, int n
)
1171 assert(ses
&& doc_view
&& doc_view
->document
);
1172 if_assert_failed
return;
1173 if (n
< 0 || n
>= doc_view
->document
->nlinks
) return;
1174 jump_to_link_number(ses
, doc_view
, n
);
1176 link
= &doc_view
->document
->links
[n
];
1177 if (!link_is_textinput(link
)
1178 && get_opt_bool("document.browse.accesskey.auto_follow", ses
))
1179 enter(ses
, doc_view
, 0);
1183 goto_link_number(struct session
*ses
, unsigned char *num
)
1185 struct document_view
*doc_view
;
1188 if_assert_failed
return;
1189 doc_view
= current_frame(ses
);
1191 if_assert_failed
return;
1192 goto_link_number_do(ses
, doc_view
, atoi(num
) - 1);
1195 /** See if this document is interested in the key user pressed. */
1196 enum frame_event_status
1197 try_document_key(struct session
*ses
, struct document_view
*doc_view
,
1198 struct term_event
*ev
)
1201 int i
; /* GOD I HATE C! --FF */ /* YEAH, BRAINFUCK RULEZ! --pasky */
1203 assert(ses
&& doc_view
&& doc_view
->document
&& doc_view
->vs
&& ev
);
1204 if_assert_failed
return FRAME_EVENT_IGNORED
;
1206 if (!check_kbd_modifier(ev
, KBD_MOD_ALT
)
1207 || !is_kbd_character(get_kbd_key(ev
))) {
1208 /* We accept only alt-character combos. */
1209 return FRAME_EVENT_IGNORED
;
1212 /* The key is a character. Convert it to Unicode so that it
1213 * can be compared with link.accesskey. */
1215 key
= get_kbd_key(ev
);
1216 #else /* !CONFIG_UTF8 */
1217 key
= cp2u(get_terminal_codepage(ses
->tab
->term
),
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 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_action(&mi
, N_("~Link info"), ACT_MAIN_LINK_INFO
);
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",
1465 add_to_string(&str
, " (");
1466 add_accesskey_to_string(&str
, link
->accesskey
);
1467 add_char_to_string(&str
, ')');
1472 decode_uri_string(&str
);
1474 #endif /* CONFIG_UTF8 */
1475 decode_uri_string_for_display(&str
);
1479 if (!get_link_form_control(link
)) return NULL
;
1481 return get_form_info(ses
, doc_view
);