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
;
924 /* If there is an onsubmit script and we want
925 * to run it, but the ECMAScript interpreter
926 * cannot be initialized, then don't submit. */
929 /* See the comment below for the
934 add_to_string(&code
, fc
->form
->onsubmit
);
935 res
= ecmascript_eval_boolback(interpreter
, &code
);
937 /* If the user presses Enter in a text field,
938 * and document.browse.forms.auto_submit is
939 * true, and the form has an onsubmit script
940 * that returns false, then insert mode should
941 * end, so return 1 here rather than 0. */
945 #endif /* CONFIG_ECMASCRIPT */
947 uri
= get_form_uri(ses
, doc_view
, fc
);
949 goto_uri_frame(ses
, uri
, fc
->form
->target
, mode
);
955 goto_link(struct session
*ses
, struct document_view
*doc_view
, struct link
*link
, int do_reload
)
959 assert(link
&& doc_view
&& ses
);
960 if_assert_failed
return NULL
;
962 if (link_is_form(link
)) {
963 struct form_control
*fc
= link
->data
.form_control
;
965 if (fc
->type
!= FC_BUTTON
966 && !call_onsubmit_and_submit(ses
, doc_view
, fc
, do_reload
))
971 uri
= get_link_uri(ses
, doc_view
, link
);
973 if (!uri
) return NULL
;
975 if (link
->type
== LINK_MAP
) {
976 /* TODO: Test reload? */
977 goto_imgmap(ses
, uri
, link
->target
);
980 enum cache_mode mode
= do_reload
? CACHE_MODE_FORCE_RELOAD
983 goto_uri_frame(ses
, uri
, link
->target
, mode
);
991 goto_current_link(struct session
*ses
, struct document_view
*doc_view
, int do_reload
)
995 assert(doc_view
&& ses
);
996 if_assert_failed
return NULL
;
998 link
= get_current_link(doc_view
);
999 if (!link
) return NULL
;
1001 return goto_link(ses
, doc_view
, link
, do_reload
);
1004 static enum frame_event_status
1005 activate_link(struct session
*ses
, struct document_view
*doc_view
,
1006 struct link
*link
, int do_reload
)
1008 struct form_control
*link_fc
;
1009 struct form_state
*fs
;
1012 switch (link
->type
) {
1015 case LINK_HYPERTEXT
:
1019 if (goto_link(ses
, doc_view
, link
, do_reload
))
1020 return FRAME_EVENT_OK
;
1023 link_fc
= get_link_form_control(link
);
1025 if (form_field_is_readonly(link_fc
))
1026 return FRAME_EVENT_OK
;
1028 fs
= find_form_state(doc_view
, link_fc
);
1029 if (!fs
) return FRAME_EVENT_OK
;
1031 if (link_fc
->type
== FC_CHECKBOX
) {
1032 fs
->state
= !fs
->state
;
1034 return FRAME_EVENT_REFRESH
;
1037 /* @link_fc->type must be FC_RADIO, then. First turn
1038 * this one on, and then turn off all the other radio
1039 * buttons in the group. Do it in this order because
1040 * further @find_form_state calls may reallocate
1041 * @doc_view->vs->form_info[] and thereby make the @fs
1042 * pointer invalid. This also allows us to re-use
1043 * @fs in the loop. */
1045 foreach (form
, doc_view
->document
->forms
) {
1046 struct form_control
*fc
;
1048 if (form
!= link_fc
->form
)
1051 foreach (fc
, form
->items
) {
1052 if (fc
->type
== FC_RADIO
1053 && !xstrcmp(fc
->name
, link_fc
->name
)
1055 fs
= find_form_state(doc_view
, fc
);
1056 if (fs
) fs
->state
= 0;
1064 link_fc
= get_link_form_control(link
);
1066 if (form_field_is_readonly(link_fc
))
1067 return FRAME_EVENT_OK
;
1069 object_lock(doc_view
->document
);
1070 add_empty_window(ses
->tab
->term
,
1071 (void (*)(void *)) release_document
,
1072 doc_view
->document
);
1073 do_select_submenu(ses
->tab
->term
, link_fc
->menu
, ses
);
1078 INTERNAL("bad link type %d", link
->type
);
1081 return FRAME_EVENT_REFRESH
;
1084 enum frame_event_status
1085 enter(struct session
*ses
, struct document_view
*doc_view
, int do_reload
)
1089 assert(ses
&& doc_view
&& doc_view
->vs
&& doc_view
->document
);
1090 if_assert_failed
return FRAME_EVENT_REFRESH
;
1092 link
= get_current_link(doc_view
);
1093 if (!link
) return FRAME_EVENT_REFRESH
;
1096 if (!current_link_evhook(doc_view
, SEVHOOK_ONCLICK
))
1097 return FRAME_EVENT_REFRESH
;
1098 return activate_link(ses
, doc_view
, link
, do_reload
);
1101 /** Get the link at the coordinates @a x and @a y, or NULL if none.
1102 * The coordinates are relative to the document view; not to the
1103 * terminal, nor to the document. So (0, 0) means whatever part of
1104 * the document has been scrolled to the top left corner of the
1107 get_link_at_coordinates(struct document_view
*doc_view
, int x
, int y
)
1109 struct link
*l1
, *l2
, *link
;
1112 assert(doc_view
&& doc_view
->vs
&& doc_view
->document
);
1113 if_assert_failed
return NULL
;
1115 /* If there are no links in in document, there is nothing to do. */
1116 if (!doc_view
->document
->nlinks
) return NULL
;
1118 /* If the coordinates are outside document view, no need to go further. */
1119 if (x
< 0 || x
>= doc_view
->box
.width
) return NULL
;
1120 if (y
< 0 || y
>= doc_view
->box
.height
) return NULL
;
1122 /* FIXME: This doesn't work. --Zas
1123 if (!check_mouse_position(ev, &doc_view->box))
1127 /* Find link candidates. */
1128 l1
= doc_view
->document
->links
+ doc_view
->document
->nlinks
;
1129 l2
= doc_view
->document
->links
;
1130 height
= int_min(doc_view
->document
->height
,
1131 doc_view
->vs
->y
+ doc_view
->box
.height
);
1133 for (i
= doc_view
->vs
->y
; i
< height
; i
++) {
1134 if (doc_view
->document
->lines1
[i
]
1135 && doc_view
->document
->lines1
[i
] < l1
)
1136 l1
= doc_view
->document
->lines1
[i
];
1138 if (doc_view
->document
->lines2
[i
]
1139 && doc_view
->document
->lines2
[i
] > l2
)
1140 l2
= doc_view
->document
->lines2
[i
];
1143 /* Is there a link at the given coordinates? */
1144 x
+= doc_view
->vs
->x
;
1145 y
+= doc_view
->vs
->y
;
1147 for (link
= l1
; link
<= l2
; link
++) {
1148 for (i
= 0; i
< link
->npoints
; i
++)
1149 if (link
->points
[i
].x
== x
1150 && link
->points
[i
].y
== y
)
1157 /** This is backend of the backend goto_link_number_do() below ;)). */
1159 jump_to_link_number(struct session
*ses
, struct document_view
*doc_view
, int n
)
1161 assert(ses
&& doc_view
&& doc_view
->vs
&& doc_view
->document
);
1162 if_assert_failed
return;
1164 if (n
< 0 || n
>= doc_view
->document
->nlinks
) return;
1165 current_link_blur(doc_view
);
1166 doc_view
->vs
->current_link
= n
;
1167 if (ses
->navigate_mode
== NAVIGATE_CURSOR_ROUTING
) {
1168 struct link
*link
= get_current_link(doc_view
);
1169 int offset
= get_link_cursor_offset(doc_view
, link
);
1171 if (link
->npoints
> offset
) {
1172 int x
= link
->points
[offset
].x
1173 + doc_view
->box
.x
- doc_view
->vs
->x
;
1174 int y
= link
->points
[offset
].y
1175 + doc_view
->box
.y
- doc_view
->vs
->y
;
1177 move_cursor(ses
, doc_view
, x
, y
);
1181 current_link_hover(doc_view
);
1184 /** This is common backend for goto_link_number() and try_document_key(). */
1186 goto_link_number_do(struct session
*ses
, struct document_view
*doc_view
, int n
)
1190 assert(ses
&& doc_view
&& doc_view
->document
);
1191 if_assert_failed
return;
1192 if (n
< 0 || n
>= doc_view
->document
->nlinks
) return;
1193 jump_to_link_number(ses
, doc_view
, n
);
1195 link
= &doc_view
->document
->links
[n
];
1196 if (!link_is_textinput(link
)
1197 && get_opt_bool("document.browse.accesskey.auto_follow", ses
))
1198 enter(ses
, doc_view
, 0);
1202 goto_link_number(struct session
*ses
, unsigned char *num
)
1204 struct document_view
*doc_view
;
1207 if_assert_failed
return;
1208 doc_view
= current_frame(ses
);
1210 if_assert_failed
return;
1211 goto_link_number_do(ses
, doc_view
, atoi(num
) - 1);
1214 /** See if this document is interested in the key user pressed. */
1215 enum frame_event_status
1216 try_document_key(struct session
*ses
, struct document_view
*doc_view
,
1217 struct term_event
*ev
)
1220 int i
; /* GOD I HATE C! --FF */ /* YEAH, BRAINFUCK RULEZ! --pasky */
1222 assert(ses
&& doc_view
&& doc_view
->document
&& doc_view
->vs
&& ev
);
1223 if_assert_failed
return FRAME_EVENT_IGNORED
;
1225 if (!check_kbd_modifier(ev
, KBD_MOD_ALT
)
1226 || !is_kbd_character(get_kbd_key(ev
))) {
1227 /* We accept only alt-character combos. */
1228 return FRAME_EVENT_IGNORED
;
1231 /* The key is a character. Convert it to Unicode so that it
1232 * can be compared with link.accesskey. */
1234 key
= get_kbd_key(ev
);
1235 #else /* !CONFIG_UTF8 */
1236 key
= cp2u(get_terminal_codepage(ses
->tab
->term
),
1238 #endif /* !CONFIG_UTF8 */
1239 /* If @key now is 0 (which is used in link.accesskey if there
1240 * is no access key) or UCS_REPLACEMENT_CHARACTER, then the
1241 * results may be a little odd, but not really harmful. */
1243 /* Run through all the links and see if one of them is bound to the
1246 i
= doc_view
->vs
->current_link
+ 1;
1247 for (; i
< doc_view
->document
->nlinks
; i
++) {
1248 struct link
*link
= &doc_view
->document
->links
[i
];
1250 if (key
== link
->accesskey
) {
1251 set_kbd_repeat_count(ses
, 0);
1252 goto_link_number_do(ses
, doc_view
, i
);
1253 return FRAME_EVENT_REFRESH
;
1256 for (i
= 0; i
<= doc_view
->vs
->current_link
; i
++) {
1257 struct link
*link
= &doc_view
->document
->links
[i
];
1259 if (key
== link
->accesskey
) {
1260 set_kbd_repeat_count(ses
, 0);
1261 goto_link_number_do(ses
, doc_view
, i
);
1262 return FRAME_EVENT_REFRESH
;
1266 return FRAME_EVENT_IGNORED
;
1269 /** Open a contextual menu on a link, form or image element.
1270 * @todo TODO: This should be completely configurable. */
1272 link_menu(struct terminal
*term
, void *xxx
, void *ses_
)
1274 struct session
*ses
= ses_
;
1275 struct document_view
*doc_view
;
1277 struct menu_item
*mi
;
1278 struct form_control
*fc
;
1280 assert(term
&& ses
);
1281 if_assert_failed
return;
1283 doc_view
= current_frame(ses
);
1284 mi
= new_menu(FREE_LIST
);
1286 if (!doc_view
) goto end
;
1288 assert(doc_view
->vs
&& doc_view
->document
);
1289 if_assert_failed
return;
1291 link
= get_current_link(doc_view
);
1292 if (!link
) goto end
;
1294 if (link
->where
&& !link_is_form(link
)) {
1295 if (link
->type
== LINK_MAP
) {
1296 /* [gettext_accelerator_context(link_menu.map)] */
1297 add_to_menu(&mi
, N_("Display ~usemap"), NULL
, ACT_MAIN_LINK_FOLLOW
,
1298 NULL
, NULL
, SUBMENU
);
1299 /* [gettext_accelerator_context()] */
1301 /* [gettext_accelerator_context(link_menu.std)] */
1302 add_menu_action(&mi
, N_("~Follow link"), ACT_MAIN_LINK_FOLLOW
);
1304 add_menu_action(&mi
, N_("Follow link and r~eload"), ACT_MAIN_LINK_FOLLOW_RELOAD
);
1306 add_menu_action(&mi
, N_("~Link info"), ACT_MAIN_LINK_INFO
);
1308 add_menu_separator(&mi
);
1310 add_new_win_to_menu(&mi
, N_("Open in new ~window"), term
);
1312 add_menu_action(&mi
, N_("Open in new ~tab"), ACT_MAIN_OPEN_LINK_IN_NEW_TAB
);
1314 add_menu_action(&mi
, N_("Open in new tab in ~background"),
1315 ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND
);
1317 if (!get_cmd_opt_bool("anonymous")) {
1318 add_menu_separator(&mi
);
1319 add_menu_action(&mi
, N_("~Download link"), ACT_MAIN_LINK_DOWNLOAD
);
1321 #ifdef CONFIG_BOOKMARKS
1322 add_menu_action(&mi
, N_("~Add link to bookmarks"),
1323 ACT_MAIN_ADD_BOOKMARK_LINK
);
1325 add_uri_command_to_menu(&mi
, PASS_URI_LINK
,
1326 N_("Pass link URI to e~xternal command"));
1328 /* [gettext_accelerator_context()] */
1332 fc
= get_link_form_control(link
);
1336 /* [gettext_accelerator_context(link_menu.reset)] */
1337 add_menu_action(&mi
, N_("~Reset form"), ACT_MAIN_RESET_FORM
);
1338 /* [gettext_accelerator_context()] */
1342 /* [gettext_accelerator_context(link_menu.textarea)] */
1343 if (!form_field_is_readonly(fc
)) {
1344 struct string keystroke
;
1346 if (init_string(&keystroke
))
1347 add_keystroke_action_to_string(
1349 ACT_EDIT_OPEN_EXTERNAL
,
1352 add_to_menu(&mi
, N_("Open in ~external editor"),
1353 keystroke
.source
, ACT_MAIN_NONE
,
1354 menu_textarea_edit
, NULL
, FREE_RTEXT
);
1356 /* [gettext_accelerator_context()] */
1359 /* [gettext_accelerator_context(link_menu.textarea, link_menu.form)] */
1360 add_menu_action(&mi
, N_("~Submit form"), ACT_MAIN_SUBMIT_FORM
);
1361 add_menu_action(&mi
, N_("Submit form and rel~oad"), ACT_MAIN_SUBMIT_FORM_RELOAD
);
1364 if (fc
->form
->method
== FORM_METHOD_GET
) {
1365 add_new_win_to_menu(&mi
, N_("Submit form and open in new ~window"), term
);
1367 add_menu_action(&mi
, N_("Submit form and open in new ~tab"),
1368 ACT_MAIN_OPEN_LINK_IN_NEW_TAB
);
1370 add_menu_action(&mi
, N_("Submit form and open in new tab in ~background"),
1371 ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND
);
1374 if (!get_cmd_opt_bool("anonymous"))
1375 add_menu_action(&mi
, N_("Submit form and ~download"), ACT_MAIN_LINK_DOWNLOAD
);
1377 add_menu_action(&mi
, N_("~Reset form"), ACT_MAIN_RESET_FORM
);
1378 /* [gettext_accelerator_context()] */
1381 /* [gettext_accelerator_context(link_menu.reset, link_menu.textarea, link_menu.form)] */
1382 add_to_menu(&mi
, N_("Form f~ields"), NULL
, ACT_MAIN_LINK_FORM_MENU
,
1383 NULL
, NULL
, SUBMENU
);
1384 /* [gettext_accelerator_context()] */
1387 if (link
->where_img
) {
1388 /* [gettext_accelerator_context(link_menu.map, link_menu.std, link_menu.form)] */
1389 add_menu_action(&mi
, N_("V~iew image"), ACT_MAIN_VIEW_IMAGE
);
1390 if (!get_cmd_opt_bool("anonymous"))
1391 add_menu_action(&mi
, N_("Download ima~ge"), ACT_MAIN_LINK_DOWNLOAD_IMAGE
);
1392 /* [gettext_accelerator_context()] */
1395 /** @todo TODO: Make it possible to trigger any script event
1396 * hooks associated to the link. --pasky */
1400 add_to_menu(&mi
, N_("No link selected"), NULL
, ACT_MAIN_NONE
,
1401 NULL
, NULL
, NO_SELECT
);
1404 do_menu(term
, mi
, ses
, 1);
1407 /** Return current link's title. */
1409 get_current_link_title(struct document_view
*doc_view
)
1413 assert(doc_view
&& doc_view
->document
&& doc_view
->vs
);
1414 if_assert_failed
return NULL
;
1416 if (doc_view
->document
->frame_desc
)
1419 link
= get_current_link(doc_view
);
1421 if (link
&& link
->title
&& *link
->title
) {
1422 unsigned char *link_title
, *src
;
1423 struct conv_table
*convert_table
;
1425 convert_table
= get_translation_table(doc_view
->document
->cp
,
1426 doc_view
->document
->options
.cp
);
1428 /* CSM_NONE because any entities in the title have
1429 * already been decoded. */
1430 link_title
= convert_string(convert_table
, link
->title
,
1431 strlen(link
->title
),
1432 doc_view
->document
->options
.cp
,
1433 CSM_NONE
, NULL
, NULL
, NULL
);
1434 /* Remove illicit chars. */
1436 if (link_title
&& !doc_view
->document
->options
.utf8
)
1437 #endif /* CONFIG_UTF8 */
1438 for (src
= link_title
; *src
; src
++)
1439 if (!isprint(*src
) || iscntrl(*src
))
1449 get_current_link_info(struct session
*ses
, struct document_view
*doc_view
)
1453 assert(ses
&& doc_view
&& doc_view
->document
&& doc_view
->vs
);
1454 if_assert_failed
return NULL
;
1456 if (doc_view
->document
->frame_desc
)
1459 link
= get_current_link(doc_view
);
1460 if (!link
) return NULL
;
1462 /** @todo TODO: Provide info about script event hooks too. --pasky */
1464 if (!link_is_form(link
)) {
1465 struct terminal
*term
= ses
->tab
->term
;
1467 unsigned char *uristring
= link
->where
;
1469 if (!init_string(&str
)) return NULL
;
1471 if (!link
->where
&& link
->where_img
) {
1472 add_to_string(&str
, _("Image", term
));
1473 add_char_to_string(&str
, ' ');
1474 uristring
= link
->where_img
;
1476 } else if (link
->type
== LINK_MAP
) {
1477 add_to_string(&str
, _("Usemap", term
));
1478 add_char_to_string(&str
, ' ');
1481 /* Add the uri with password and post info stripped */
1482 add_string_uri_to_string(&str
, uristring
, URI_PUBLIC
);
1483 if (link
->accesskey
> 0
1484 && get_opt_bool("document.browse.accesskey.display",
1486 add_to_string(&str
, " (");
1487 add_accesskey_to_string(&str
, link
->accesskey
);
1488 add_char_to_string(&str
, ')');
1493 decode_uri_string(&str
);
1495 #endif /* CONFIG_UTF8 */
1496 decode_uri_string_for_display(&str
);
1500 if (!get_link_form_control(link
)) return NULL
;
1502 return get_form_info(ses
, doc_view
);