1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-12-04 14:46:47 -05:00
elinks/src/viewer/text/link.c
2022-03-19 10:11:40 +01:00

1543 lines
39 KiB
C

/* Links viewing/manipulation handling */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include "elinks.h"
#include "bfu/listmenu.h"
#include "bfu/menu.h"
#include "bfu/style.h"
#include "dialogs/menu.h"
#include "dialogs/status.h"
#include "document/document.h"
#include "document/forms.h"
#include "document/html/renderer.h"
#include "document/options.h"
#include "document/view.h"
#include "ecmascript/ecmascript.h"
#include "intl/libintl.h"
#include "main/object.h"
#include "protocol/uri.h"
#include "session/session.h"
#include "session/task.h"
#include "terminal/color.h"
#include "terminal/draw.h"
#include "terminal/kbd.h"
#include "terminal/screen.h"
#include "terminal/tab.h"
#include "terminal/terminal.h"
#include "util/box.h"
#include "util/conv.h"
#include "util/error.h"
#include "util/memory.h"
#include "util/string.h"
#include "viewer/action.h"
#include "viewer/text/form.h"
#include "viewer/text/link.h"
#include "viewer/text/search.h"
#include "viewer/text/textarea.h"
#include "viewer/text/view.h"
#include "viewer/text/vs.h"
/* Perhaps some of these would be more fun to have in viewer/common/, dunno.
* --pasky */
static int
current_link_evhook(struct document_view *doc_view, enum script_event_hook_type type)
{
#if defined(CONFIG_ECMASCRIPT_SMJS) || defined(CONFIG_QUICKJS)
struct link *link;
struct script_event_hook *evhook;
assert(doc_view && doc_view->vs);
link = get_current_link(doc_view);
if (!link) return -1;
if (!link->event_hooks) return -1;
if (!doc_view->vs->ecmascript) return -1;
foreach (evhook, *link->event_hooks) {
char *ret;
if (evhook->type != type) continue;
ret = evhook->src;
while ((ret = strstr(ret, "return ")))
while (*ret != ' ') *ret++ = ' ';
{
struct string src = INIT_STRING(evhook->src, (int)strlen(evhook->src));
/* TODO: Some even handlers return a bool. */
if (!ecmascript_eval_boolback(doc_view->vs->ecmascript, &src))
return 0;
}
}
return 1;
#else
return -1;
#endif
}
#define current_link_hover(dv) \
do { \
current_link_evhook(dv, SEVHOOK_ONMOUSEOVER); \
current_link_evhook(dv, SEVHOOK_ONHOVER); \
current_link_evhook(dv, SEVHOOK_ONFOCUS); \
} while (0)
#define current_link_blur(dv) \
do { \
current_link_evhook(dv, SEVHOOK_ONMOUSEOUT); \
current_link_evhook(dv, SEVHOOK_ONBLUR); \
} while (0)
void
set_link(struct document_view *doc_view)
{
assert(doc_view);
if_assert_failed return;
if (current_link_is_visible(doc_view)) return;
find_link_page_down(doc_view);
}
static inline int
get_link_cursor_offset(struct document_view *doc_view, struct link *link)
{
struct el_form_control *fc;
struct form_state *fs;
#ifdef CONFIG_UTF8
/* The encoding of form fields depends on the terminal,
* rather than on the document. */
int utf8 = doc_view->session->tab->term->utf8_cp;
#endif /* CONFIG_UTF8 */
switch (link->type) {
case LINK_CHECKBOX:
return 1;
case LINK_SELECT:
case LINK_BUTTON:
return 2;
case LINK_FIELD:
fc = get_link_form_control(link);
fs = find_form_state(doc_view, fc);
if (!fs || !fs->value)
return 0;
#ifdef CONFIG_UTF8
else if (utf8) {
char *scroll = fs->value + fs->vpos;
char *point = fs->value + fs->state;
if (fs->type == FC_PASSWORD)
return utf8_ptr2chars(scroll, point);
else
return utf8_ptr2cells(scroll, point);
}
#endif /* CONFIG_UTF8 */
else
return fs->state - fs->vpos;
case LINK_AREA:
fc = get_link_form_control(link);
fs = find_form_state(doc_view, fc);
#ifdef CONFIG_UTF8
return fs ? area_cursor(fc, fs, utf8) : 0;
#else
return fs ? area_cursor(fc, fs) : 0;
#endif /* CONFIG_UTF8 */
case LINK_HYPERTEXT:
case LINK_MAP:
return 0;
}
return 0;
}
/** Initialise a static template character with the colour and attributes
* appropriate for an active link and return that character. */
static inline struct screen_char *
init_link_drawing(struct document_view *doc_view, struct link *link, int invert, int input)
{
struct document_options *doc_opts;
static struct screen_char template_;
color_flags_T color_flags;
color_mode_T color_mode;
struct color_pair colors;
template_.attr = SCREEN_ATTR_STANDOUT;
doc_opts = &doc_view->document->options;
color_flags = (doc_opts->color_flags | COLOR_DECREASE_LIGHTNESS);
color_mode = doc_opts->color_mode;
/* avoid underlining text input areas which has underscores */
if (doc_opts->active_link.underline && link->type != LINK_FIELD)
template_.attr |= SCREEN_ATTR_UNDERLINE;
/* keep forms in bold */
if (doc_opts->active_link.bold || link->type != LINK_HYPERTEXT)
template_.attr |= SCREEN_ATTR_BOLD;
if (doc_opts->active_link.enable_color) {
if (input) {
colors.foreground = doc_opts->active_link.insert_mode_color.foreground;
colors.background = doc_opts->active_link.insert_mode_color.background;
} else {
colors.foreground = doc_opts->active_link.color.foreground;
colors.background = doc_opts->active_link.color.background;
}
} else {
colors.foreground = link->color.foreground;
colors.background = link->color.background;
}
if (invert && doc_opts->active_link.invert) {
swap_values(color_T, colors.foreground, colors.background);
/* Highlight text-input form-fields correctly if contrast
* correction is needed. */
if (link_is_textinput(link)) {
/* Make sure to use the options belonging to the
* current document when checking for fg and bg color
* usage. */
doc_opts = &doc_view->document->options;
/* Are we fallen angels who didn't want to believe that
* nothing /is/ nothing and so were born to lose our
* loved ones and dear friends one by one and finally
* our own life, to see it proved? --Kerouac */
/* Wipe out all default correction for 16 color mode */
color_flags = (color_flags & ~COLOR_INCREASE_CONTRAST);
/* Make contrast correction invert things properly */
color_flags |= COLOR_ENSURE_INVERTED_CONTRAST;
}
}
set_term_color(&template_, &colors, color_flags, color_mode);
return &template_;
}
/** Give the current link the appropriate colour and attributes. */
void
draw_current_link(struct session *ses, struct document_view *doc_view)
{
struct terminal *term = ses->tab->term;
struct screen_char *template_;
struct link *link;
int cursor_offset;
int xpos, ypos;
int invert, input;
int i;
assert(term && doc_view && doc_view->vs);
if_assert_failed return;
assert(ses->tab == get_current_tab(term));
if_assert_failed return;
link = get_current_link(doc_view);
if (!link) return;
invert = !link_is_textinput(link) || ses->insert_mode == INSERT_MODE_OFF;
input = ses->insert_mode == INSERT_MODE_ON;
template_ = init_link_drawing(doc_view, link, invert, input);
if (!template_) return;
xpos = doc_view->box.x - doc_view->vs->x;
ypos = doc_view->box.y - doc_view->vs->y;
if (ses->insert_mode == INSERT_MODE_OFF
&& ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
/* If we are navigating using cursor routing and not editing a
* text-input form-field never set the cursor. */
cursor_offset = -1;
} else {
cursor_offset = get_link_cursor_offset(doc_view, link);
}
for (i = 0; i < link->npoints; i++) {
int x = link->points[i].x + xpos;
int y = link->points[i].y + ypos;
struct screen_char *co;
if (!is_in_box(&doc_view->box, x, y)) {
continue;
}
co = get_char(term, x, y);
if (i == cursor_offset) {
int blockable = (!link_is_textinput(link)
&& co->c.color != template_->c.color);
set_cursor(term, x, y, blockable);
set_window_ptr(ses->tab, x, y);
}
template_->data = co->data;
copy_screen_chars(co, template_, 1);
set_screen_dirty(term->screen, y, y);
}
doc_view->vs->old_current_link = doc_view->vs->current_link;
}
static void
draw_link(struct terminal *term, struct document_view *doc_view,
struct link *link)
{
int xpos = doc_view->box.x - doc_view->vs->x;
int ypos = doc_view->box.y - doc_view->vs->y;
int i;
for (i = 0; i < link->npoints; ++i) {
int x = link->points[i].x;
int y = link->points[i].y;
if (is_in_box(&doc_view->box, x + xpos, y + ypos)){
struct screen_char *ch;
ch = get_char(term, x + xpos, y + ypos);
copy_struct(ch, &doc_view->document->data[y].chars[x]);
set_screen_dirty(term->screen, y + ypos, y + ypos);
}
}
}
/** Restore the colours and attributes that the active link had
* before it was selected. */
void
clear_link(struct terminal *term, struct document_view *doc_view)
{
struct link *link = get_current_link(doc_view);
struct link *last = get_old_current_link(doc_view);
if (last && last != link) {
draw_link(term, doc_view, last);
}
doc_view->vs->old_current_link = doc_view->vs->current_link;
}
void
highlight_links_with_prefixes_that_start_with_n(struct terminal *term,
struct document_view *doc_view,
int n)
{
unsigned int node_number = get_bfu_color_node(term, "searched");
int xoffset = doc_view->box.x - doc_view->vs->x;
int yoffset = doc_view->box.y - doc_view->vs->y;
struct document *document = doc_view->document;
int m;
for (m = n + 1; n <= document->nlinks; n *= 10, m *= 10) {
int linkn;
for (linkn = n; linkn < m; ++linkn) {
struct link *link = &document->links[linkn - 1];
int i;
if (linkn > document->nlinks) break;
for (i = 0; i < link->npoints; ++i) {
int x = link->points[i].x + xoffset;
int y = link->points[i].y + yoffset;
if (is_in_box(&doc_view->box, x, y)) {
draw_char_color_node(term, x, y, node_number);
}
}
}
}
}
struct link *
get_first_link(struct document_view *doc_view)
{
struct link *link, *undef;
struct document *document;
int height;
int i;
assert(doc_view && doc_view->document);
if_assert_failed return NULL;
document = doc_view->document;
if (!document->lines1) return NULL;
height = doc_view->vs->y + doc_view->box.height;
link = undef = document->links + document->nlinks;
for (i = int_max(0, doc_view->vs->y);
i < int_min(height, document->height);
i++) {
if (document->lines1[i]
&& document->lines1[i] < link)
link = document->lines1[i];
}
return (link == undef) ? NULL : link;
}
struct link *
get_last_link(struct document_view *doc_view)
{
struct link *link = NULL;
struct document *document;
int height;
int i;
assert(doc_view && doc_view->document);
if_assert_failed return NULL;
document = doc_view->document;
if (!document->lines2) return NULL;
height = doc_view->vs->y + doc_view->box.height;
for (i = int_max(0, doc_view->vs->y);
i < int_min(height, document->height);
i++)
if (document->lines2[i] > link)
link = document->lines2[i];
return link;
}
static int
link_in_view_x(struct document_view *doc_view, struct link *link)
{
int i, dx, width;
assert(doc_view && link);
if_assert_failed return 0;
dx = doc_view->vs->x;
width = doc_view->box.width;
for (i = 0; i < link->npoints; i++) {
int x = link->points[i].x - dx;
if (x >= 0 && x < width)
return 1;
}
return 0;
}
static int
link_in_view_y(struct document_view *doc_view, struct link *link)
{
int i, dy, height;
assert(doc_view && link);
if_assert_failed return 0;
dy = doc_view->vs->y;
height = doc_view->box.height;
for (i = 0; i < link->npoints; i++) {
int y = link->points[i].y - dy;
if (y >= 0 && y < height)
return 1;
}
return 0;
}
static int
link_in_view(struct document_view *doc_view, struct link *link)
{
assert(doc_view && link);
if_assert_failed return 0;
return link_in_view_y(doc_view, link) && link_in_view_x(doc_view, link);
}
int
current_link_is_visible(struct document_view *doc_view)
{
struct link *link;
assert(doc_view && doc_view->vs);
if_assert_failed return 0;
link = get_current_link(doc_view);
return (link && link_in_view(doc_view, link));
}
/** Look for the first and the last link currently visible in our
* viewport. */
static void
get_visible_links_range(struct document_view *doc_view, int *first, int *last)
{
struct document *document = doc_view->document;
int height = int_min(doc_view->vs->y + doc_view->box.height,
document->height);
int y;
*first = document->nlinks - 1;
*last = 0;
for (y = int_max(0, doc_view->vs->y); y < height; y++) {
if (document->lines1[y])
int_upper_bound(first, document->lines1[y]
- document->links);
if (document->lines2[y])
int_lower_bound(last, document->lines2[y]
- document->links);
}
}
static int
next_link_in_view_(struct document_view *doc_view, int current, int direction,
int (*fn)(struct document_view *, struct link *),
void (*cntr)(struct document_view *, struct link *))
{
struct document *document;
struct view_state *vs;
int start, end;
assert(doc_view && doc_view->document && doc_view->vs && fn);
if_assert_failed return 0;
document = doc_view->document;
vs = doc_view->vs;
get_visible_links_range(doc_view, &start, &end);
current_link_blur(doc_view);
/* Go from the @current link in @direction until either
* fn() is happy or we would leave the current viewport. */
while (current >= start && current <= end) {
if (fn(doc_view, &document->links[current])) {
vs->current_link = current;
if (cntr) cntr(doc_view, &document->links[current]);
current_link_hover(doc_view);
return 1;
}
current += direction;
}
vs->current_link = -1;
return 0;
}
int
next_link_in_view(struct document_view *doc_view, int current, int direction)
{
return next_link_in_view_(doc_view, current, direction, link_in_view, NULL);
}
int
next_link_in_view_y(struct document_view *doc_view, int current, int direction)
{
return next_link_in_view_(doc_view, current, direction, link_in_view_y, set_pos_x);
}
/** Get the bounding columns of @a link at line @a y (or all lines if
* @a y == -1). */
void
get_link_x_bounds(struct link *link, int y, int *min_x, int *max_x)
{
int point;
if (min_x) *min_x = INT_MAX;
if (max_x) *max_x = 0;
for (point = 0; point < link->npoints; point++) {
if (y >= 0 && link->points[point].y != y)
continue;
if (min_x) int_upper_bound(min_x, link->points[point].x);
if (max_x) int_lower_bound(max_x, link->points[point].x);
}
}
/** Check whether there is any point between @a min_x and @a max_x at
* the line @a y in link @a link. */
static int
get_link_x_intersect(struct link *link, int y, int min_x, int max_x)
{
int point;
for (point = 0; point < link->npoints; point++) {
if (link->points[point].y != y)
continue;
if (link->points[point].x >= min_x
&& link->points[point].x <= max_x)
return link->points[point].x + 1;
}
return 0;
}
/** Check whether there is any point between @a min_y and @a max_y in
* the column @a x in link @a link. */
static int
get_link_y_intersect(struct link *link, int x, int min_y, int max_y)
{
int point;
for (point = 0; point < link->npoints; point++) {
if (link->points[point].x != x)
continue;
if (link->points[point].y >= min_y
&& link->points[point].y <= max_y)
return link->points[point].y + 1;
}
return 0;
}
int
next_link_in_dir(struct document_view *doc_view, int dir_x, int dir_y)
{
struct document *document;
struct view_state *vs;
struct link *link;
int min_x = INT_MAX, max_x = 0;
int min_y, max_y;
assert(doc_view && doc_view->document && doc_view->vs);
if_assert_failed return 0;
assert(dir_x || dir_y);
if_assert_failed return 0;
document = doc_view->document;
vs = doc_view->vs;
link = get_current_link(doc_view);
if (!link) return 0;
/* Find the link's "bounding box" coordinates. */
get_link_x_bounds(link, -1, &min_x, &max_x);
min_y = link->points[0].y;
max_y = link->points[link->npoints - 1].y;
/* Now go from the bounding box edge in the appropriate
* direction and find the nearest link. */
if (dir_y) {
/* Vertical movement */
/* The current line number */
int y = (dir_y > 0 ? max_y : min_y) + dir_y;
/* The bounding line numbers */
int top = int_max(0, doc_view->vs->y);
int bottom = int_min(doc_view->vs->y + doc_view->box.height,
document->height);
for (; dir_y > 0 ? y < bottom : y >= top; y += dir_y) {
/* @backup points to the nearest link from the left
* to the desired position. */
struct link *backup = NULL;
link = document->lines1[y];
if (!link) continue;
/* Go through all the links on line. */
for (; link <= document->lines2[y]; link++) {
int l_min_x, l_max_x;
/* Some links can be totally out of order here,
* ie. in tables or when using tabindex. */
if (y < link->points[0].y
|| y > link->points[link->npoints - 1].y)
continue;
get_link_x_bounds(link, y, &l_min_x, &l_max_x);
if (l_min_x > max_x) {
/* This link is too at the right. */
if (!backup)
backup = link;
continue;
}
if (l_max_x < min_x) {
/* This link is too at the left. */
backup = link;
continue;
}
/* This link is aligned with the current one. */
goto chose_link;
}
if (backup) {
link = backup;
goto chose_link;
}
}
if (!y || y == document->height) {
/* We just stay at the same place, do not invalidate
* the link number. */
return 0;
}
} else {
/* Horizontal movement */
/* The current column number */
int x = (dir_x > 0 ? max_x : min_x) + dir_x;
/* How many lines are already past their last link */
int last = 0;
while ((last < max_y - min_y + 1) && (x += dir_x) >= 0) {
int y;
last = 0;
/* Go through all the lines */
for (y = min_y; y <= max_y; y++) {
link = document->lines1[y];
if (!link) continue;
/* Go through all the links on line. */
while (link <= document->lines2[y]) {
if (get_link_y_intersect(link, x,
min_y,
max_y))
goto chose_link;
link++;
}
/* Check if we already aren't past the last
* link on this line. */
if (!get_link_x_intersect(document->lines2[y],
y, x, INT_MAX))
last++;
}
}
/* We just stay */
return 0;
}
current_link_blur(doc_view);
vs->current_link = -1;
return 0;
chose_link:
/* The link is in bounds, take it. */
current_link_blur(doc_view);
vs->current_link = get_link_index(document, link);
set_pos_x(doc_view, link);
current_link_hover(doc_view);
return 1;
}
void
set_pos_x(struct document_view *doc_view, struct link *link)
{
int xm = 0;
int xl = INT_MAX;
int i;
assert(doc_view && link);
if_assert_failed return;
for (i = 0; i < link->npoints; i++) {
int y = link->points[i].y - doc_view->vs->y;
if (y >= 0 && y < doc_view->box.height) {
int_lower_bound(&xm, link->points[i].x + 1);
int_upper_bound(&xl, link->points[i].x);
}
}
if (xl != INT_MAX)
int_bounds(&doc_view->vs->x, xm - doc_view->box.width, xl);
}
void
set_pos_y(struct document_view *doc_view, struct link *link)
{
int ym = 0;
int height;
int i;
assert(doc_view && doc_view->document && doc_view->vs && link);
if_assert_failed return;
height = doc_view->document->height;
for (i = 0; i < link->npoints; i++) {
int_lower_bound(&ym, link->points[i].y + 1);
int_upper_bound(&height, link->points[i].y);
}
doc_view->vs->y = (ym + height - doc_view->box.height) / 2;
int_bounds(&doc_view->vs->y, 0,
doc_view->document->height - doc_view->box.height);
}
/** Focus the next link in the specified direction.
* @a direction == 1 -> DOWN;
* @a direction == -1 -> UP */
static void
find_link(struct document_view *doc_view, int direction, int page_mode)
{
struct link **line;
struct link *link = NULL;
int link_pos;
int y, ymin, ymax;
assert(doc_view && doc_view->document && doc_view->vs);
if_assert_failed return;
if (direction == -1) {
/* UP */
line = doc_view->document->lines2;
if (!line) goto nolink;
y = doc_view->vs->y + doc_view->box.height - 1;
int_upper_bound(&y, doc_view->document->height - 1);
if (y < 0) goto nolink;
} else {
/* DOWN */
line = doc_view->document->lines1;
if (!line) goto nolink;
y = doc_view->vs->y;
int_lower_bound(&y, 0);
if (y >= doc_view->document->height) goto nolink;
}
ymin = int_max(0, doc_view->vs->y);
ymax = int_min(doc_view->document->height,
doc_view->vs->y + doc_view->box.height);
if (direction == -1) {
/* UP */
do {
struct link *cur = line[y--];
if (cur && (!link || cur > link))
link = cur;
} while (y >= ymin && y < ymax);
} else {
/* DOWN */
do {
struct link *cur = line[y++];
if (cur && (!link || cur < link))
link = cur;
} while (y >= ymin && y < ymax);
}
if (!link) goto nolink;
link_pos = link - doc_view->document->links;
if (page_mode) {
/* PAGE */
next_link_in_view(doc_view, link_pos, direction);
return;
}
current_link_blur(doc_view);
doc_view->vs->current_link = link_pos;
set_pos_x(doc_view, link);
current_link_hover(doc_view);
return;
nolink:
current_link_blur(doc_view);
doc_view->vs->current_link = -1;
}
void
find_link_up(struct document_view *doc_view)
{
find_link(doc_view, -1, 0);
}
void
find_link_page_up(struct document_view *doc_view)
{
find_link(doc_view, -1, 1);
}
void
find_link_down(struct document_view *doc_view)
{
find_link(doc_view, 1, 0);
}
void
find_link_page_down(struct document_view *doc_view)
{
find_link(doc_view, 1, 1);
}
struct uri *
get_link_uri(struct session *ses, struct document_view *doc_view,
struct link *link)
{
assert(ses && doc_view && link);
if_assert_failed return NULL;
switch (link->type) {
case LINK_HYPERTEXT:
case LINK_MAP:
if (link->where) return get_uri(link->where, URI_NONE);
return get_uri(link->where_img, URI_NONE);
case LINK_BUTTON:
case LINK_FIELD:
return get_form_uri(ses, doc_view,
get_link_form_control(link));
default:
return NULL;
}
}
static int
call_onsubmit_and_submit(struct session *ses, struct document_view *doc_view,
struct el_form_control *fc, int do_reload)
{
struct uri *uri = NULL;
cache_mode_T mode = do_reload ? CACHE_MODE_FORCE_RELOAD : CACHE_MODE_NORMAL;
assert(fc->form); /* regardless of whether there is a FORM element */
if_assert_failed return 0;
#if defined(CONFIG_ECMASCRIPT_SMJS) || defined(CONFIG_QUICKJS)
/* If the form has multiple submit buttons, this does not
* explicitly tell the ECMAScript code which of them was
* pressed. W3C DOM Level 3 doesn't seem to include such a
* feature. */
if (fc->type != FC_RESET && fc->form->onsubmit) {
struct string code;
if (init_string(&code)) {
struct view_state *vs = doc_view->vs;
struct ecmascript_interpreter *interpreter;
int res;
if (vs->ecmascript_fragile)
ecmascript_reset_state(vs);
interpreter = vs->ecmascript;
/* If there is an onsubmit script and we want
* to run it, but the ECMAScript interpreter
* cannot be initialized, then don't submit. */
if (!interpreter) {
done_string(&code);
/* See the comment below for the
* return value. */
return 1;
}
add_to_string(&code, fc->form->onsubmit);
res = ecmascript_eval_boolback(interpreter, &code);
done_string(&code);
/* If the user presses Enter in a text field,
* and document.browse.forms.auto_submit is
* true, and the form has an onsubmit script
* that returns false, then insert mode should
* end, so return 1 here rather than 0. */
if (!res) return 1;
}
}
#endif /* CONFIG_ECMASCRIPT_SMJS */
uri = get_form_uri(ses, doc_view, fc);
if (!uri) return 0;
goto_uri_frame(ses, uri, fc->form->target, mode);
done_uri(uri);
return 1;
}
struct link *
goto_link(struct session *ses, struct document_view *doc_view, struct link *link, int do_reload)
{
struct uri *uri;
assert(link && doc_view && ses);
if_assert_failed return NULL;
if (doc_view->vs) {
doc_view->prev_y = doc_view->vs->y;
} else {
doc_view->prev_y = 0;
}
if (link_is_form(link)) {
struct el_form_control *fc = link->data.form_control;
if (fc->type != FC_BUTTON
&& !call_onsubmit_and_submit(ses, doc_view, fc, do_reload))
return NULL;
else
return link;
} else
uri = get_link_uri(ses, doc_view, link);
if (!uri) return NULL;
if (link->type == LINK_MAP) {
/* TODO: Test reload? */
goto_imgmap(ses, uri, link->target);
} else {
cache_mode_T mode = do_reload ? CACHE_MODE_FORCE_RELOAD
: CACHE_MODE_NORMAL;
goto_uri_frame(ses, uri, link->target, mode);
}
done_uri(uri);
return link;
}
struct link *
goto_current_link(struct session *ses, struct document_view *doc_view, int do_reload)
{
struct link *link;
assert(doc_view && ses);
if_assert_failed return NULL;
link = get_current_link(doc_view);
if (!link) return NULL;
return goto_link(ses, doc_view, link, do_reload);
}
static enum frame_event_status
activate_link(struct session *ses, struct document_view *doc_view,
struct link *link, int do_reload)
{
struct el_form_control *link_fc;
struct form_state *fs;
struct form *form;
switch (link->type) {
case LINK_BUTTON:
do_reload = 1;
case LINK_HYPERTEXT:
case LINK_MAP:
case LINK_FIELD:
case LINK_AREA:
if (goto_link(ses, doc_view, link, do_reload))
return FRAME_EVENT_OK;
break;
case LINK_CHECKBOX:
link_fc = get_link_form_control(link);
if (form_field_is_readonly(link_fc))
return FRAME_EVENT_OK;
fs = find_form_state(doc_view, link_fc);
if (!fs) return FRAME_EVENT_OK;
if (link_fc->type == FC_CHECKBOX) {
fs->state = !fs->state;
return FRAME_EVENT_REFRESH;
}
/* @link_fc->type must be FC_RADIO, then. First turn
* this one on, and then turn off all the other radio
* buttons in the group. Do it in this order because
* further @find_form_state calls may reallocate
* @doc_view->vs->form_info[] and thereby make the @fs
* pointer invalid. This also allows us to re-use
* @fs in the loop. */
fs->state = 1;
foreach (form, doc_view->document->forms) {
struct el_form_control *fc;
if (form != link_fc->form)
continue;
foreach (fc, form->items) {
if (fc->type == FC_RADIO
&& !xstrcmp(fc->name, link_fc->name)
&& fc != link_fc) {
fs = find_form_state(doc_view, fc);
if (fs) fs->state = 0;
}
}
}
break;
case LINK_SELECT:
link_fc = get_link_form_control(link);
if (form_field_is_readonly(link_fc))
return FRAME_EVENT_OK;
object_lock(doc_view->document);
add_empty_window(ses->tab->term,
(void (*)(void *)) release_document,
doc_view->document);
do_select_submenu(ses->tab->term, link_fc->menu, ses);
break;
default:
INTERNAL("bad link type %d", link->type);
}
return FRAME_EVENT_REFRESH;
}
enum frame_event_status
enter(struct session *ses, struct document_view *doc_view, int do_reload)
{
struct link *link;
assert(ses && doc_view && doc_view->vs && doc_view->document);
if_assert_failed return FRAME_EVENT_REFRESH;
link = get_current_link(doc_view);
if (!link) return FRAME_EVENT_REFRESH;
if (link_is_textinput(link)
&& ses->insert_mode == INSERT_MODE_OFF) {
ses->insert_mode = INSERT_MODE_ON;
return FRAME_EVENT_REFRESH;
}
if (!current_link_evhook(doc_view, SEVHOOK_ONCLICK))
return FRAME_EVENT_REFRESH;
return activate_link(ses, doc_view, link, do_reload);
}
/** Get the link at the coordinates @a x and @a y, or NULL if none.
* The coordinates are relative to the document view; not to the
* terminal, nor to the document. So (0, 0) means whatever part of
* the document has been scrolled to the top left corner of the
* document view. */
struct link *
get_link_at_coordinates(struct document_view *doc_view, int x, int y)
{
struct link *l1, *l2, *link;
int i, height;
assert(doc_view && doc_view->vs && doc_view->document);
if_assert_failed return NULL;
/* If there are no links in in document, there is nothing to do. */
if (!doc_view->document->nlinks) return NULL;
/* If the coordinates are outside document view, no need to go further. */
if (x < 0 || x >= doc_view->box.width) return NULL;
if (y < 0 || y >= doc_view->box.height) return NULL;
/* FIXME: This doesn't work. --Zas
if (!check_mouse_position(ev, &doc_view->box))
return NULL;
*/
/* Find link candidates. */
l1 = doc_view->document->links + doc_view->document->nlinks;
l2 = doc_view->document->links;
height = int_min(doc_view->document->height,
doc_view->vs->y + doc_view->box.height);
for (i = doc_view->vs->y; i < height; i++) {
if (doc_view->document->lines1[i]
&& doc_view->document->lines1[i] < l1)
l1 = doc_view->document->lines1[i];
if (doc_view->document->lines2[i]
&& doc_view->document->lines2[i] > l2)
l2 = doc_view->document->lines2[i];
}
/* Is there a link at the given coordinates? */
x += doc_view->vs->x;
y += doc_view->vs->y;
for (link = l1; link <= l2; link++) {
for (i = 0; i < link->npoints; i++)
if (link->points[i].x == x
&& link->points[i].y == y)
return link;
}
return NULL;
}
/** This is backend of the backend goto_link_number_do() below ;)). */
void
jump_to_link_number(struct session *ses, struct document_view *doc_view, int n)
{
assert(ses && doc_view && doc_view->vs && doc_view->document);
if_assert_failed return;
if (n < 0 || n >= doc_view->document->nlinks) return;
current_link_blur(doc_view);
doc_view->vs->current_link = n;
if (ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
struct link *link = get_current_link(doc_view);
int offset = get_link_cursor_offset(doc_view, link);
if (link->npoints > offset) {
int x = link->points[offset].x
+ doc_view->box.x - doc_view->vs->x;
int y = link->points[offset].y
+ doc_view->box.y - doc_view->vs->y;
move_cursor(ses, doc_view, x, y);
}
}
check_vs(doc_view);
current_link_hover(doc_view);
}
/** This is common backend for goto_link_number() and try_document_key(). */
static void
goto_link_number_do(struct session *ses, struct document_view *doc_view, int n)
{
struct link *link;
assert(ses && doc_view && doc_view->document);
if_assert_failed return;
if (n < 0 || n >= doc_view->document->nlinks) return;
jump_to_link_number(ses, doc_view, n);
link = &doc_view->document->links[n];
if (!link_is_textinput(link)
&& get_opt_bool("document.browse.accesskey.auto_follow", ses))
enter(ses, doc_view, 0);
}
void
goto_link_number(struct session *ses, char *num)
{
struct document_view *doc_view;
assert(ses && num);
if_assert_failed return;
doc_view = current_frame(ses);
assert(doc_view);
if_assert_failed return;
goto_link_number_do(ses, doc_view, atoi(num) - 1);
}
void
goto_link_symbol(struct session *ses, char *sym)
{
char *symkey = get_opt_str("document.browse.links.label_key", ses);
struct document_view *doc_view;
int num;
int base = strlen(symkey);
assert(ses && sym);
if_assert_failed return;
doc_view = current_frame(ses);
assert(doc_view);
if_assert_failed return;
num = qwerty2dec(sym, symkey, base);
goto_link_number_do(ses, doc_view, num - 1);
}
/** See if this document is interested in the key user pressed. */
enum frame_event_status
try_document_key(struct session *ses, struct document_view *doc_view,
struct term_event *ev)
{
unicode_val_T key;
int i; /* GOD I HATE C! --FF */ /* YEAH, BRAINFUCK RULEZ! --pasky */
assert(ses && doc_view && doc_view->document && doc_view->vs && ev);
if_assert_failed return FRAME_EVENT_IGNORED;
if (!check_kbd_modifier(ev, KBD_MOD_ALT)
|| !is_kbd_character(get_kbd_key(ev))) {
/* We accept only alt-character combos. */
return FRAME_EVENT_IGNORED;
}
/* The key is a character. Convert it to Unicode so that it
* can be compared with link.accesskey. */
#ifdef CONFIG_UTF8
key = get_kbd_key(ev);
#else /* !CONFIG_UTF8 */
key = cp2u(get_terminal_codepage(ses->tab->term),
get_kbd_key(ev));
#endif /* !CONFIG_UTF8 */
/* If @key now is 0 (which is used in link.accesskey if there
* is no access key) or UCS_REPLACEMENT_CHARACTER, then the
* results may be a little odd, but not really harmful. */
/* Run through all the links and see if one of them is bound to the
* key we test.. */
i = doc_view->vs->current_link + 1;
for (; i < doc_view->document->nlinks; i++) {
struct link *link = &doc_view->document->links[i];
if (key == link->accesskey) {
set_kbd_repeat_count(ses, 0);
goto_link_number_do(ses, doc_view, i);
return FRAME_EVENT_REFRESH;
}
}
for (i = 0; i <= doc_view->vs->current_link; i++) {
struct link *link = &doc_view->document->links[i];
if (key == link->accesskey) {
set_kbd_repeat_count(ses, 0);
goto_link_number_do(ses, doc_view, i);
return FRAME_EVENT_REFRESH;
}
}
return FRAME_EVENT_IGNORED;
}
/** Open a contextual menu on a link, form or image element.
* @todo TODO: This should be completely configurable. */
void
link_menu(struct terminal *term, void *xxx, void *ses_)
{
struct session *ses = (struct session *)ses_;
struct document_view *doc_view;
struct link *link;
struct menu_item *mi;
struct el_form_control *fc;
assert(term && ses);
if_assert_failed return;
doc_view = current_frame(ses);
mi = new_menu(FREE_LIST);
if (!mi) return;
if (!doc_view) goto end;
assert(doc_view->vs && doc_view->document);
if_assert_failed return;
link = get_current_link(doc_view);
if (!link) goto end;
if (link->where && !link_is_form(link)) {
if (link->type == LINK_MAP) {
/* [gettext_accelerator_context(link_menu.map)] */
add_to_menu(&mi, N_("Display ~usemap"), NULL, ACT_MAIN_LINK_FOLLOW,
NULL, NULL, SUBMENU);
/* [gettext_accelerator_context()] */
} else {
/* [gettext_accelerator_context(link_menu.std)] */
add_menu_action(&mi, N_("~Follow link"), ACT_MAIN_LINK_FOLLOW);
add_menu_action(&mi, N_("Follow link and r~eload"), ACT_MAIN_LINK_FOLLOW_RELOAD);
add_menu_action(&mi, N_("~Link info"), ACT_MAIN_LINK_INFO);
add_menu_separator(&mi);
add_new_win_to_menu(&mi, N_("Open in new ~window"), term);
add_menu_action(&mi, N_("Open in new ~tab"), ACT_MAIN_OPEN_LINK_IN_NEW_TAB);
add_menu_action(&mi, N_("Open in new tab in ~background"),
ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND);
if (!get_cmd_opt_bool("anonymous")) {
add_menu_separator(&mi);
add_menu_action(&mi, N_("~Download link"), ACT_MAIN_LINK_DOWNLOAD);
#ifdef CONFIG_BOOKMARKS
add_menu_action(&mi, N_("~Add link to bookmarks"),
ACT_MAIN_ADD_BOOKMARK_LINK);
#endif
add_uri_command_to_menu(&mi, PASS_URI_LINK,
N_("Pass link URI to e~xternal command"));
}
/* [gettext_accelerator_context()] */
}
}
fc = get_link_form_control(link);
if (fc) {
switch (fc->type) {
case FC_RESET:
/* [gettext_accelerator_context(link_menu.reset)] */
add_menu_action(&mi, N_("~Reset form"), ACT_MAIN_RESET_FORM);
/* [gettext_accelerator_context()] */
break;
case FC_TEXTAREA:
/* [gettext_accelerator_context(link_menu.textarea)] */
if (!form_field_is_readonly(fc)) {
struct string keystroke;
if (init_string(&keystroke))
add_keystroke_action_to_string(
&keystroke,
ACT_EDIT_OPEN_EXTERNAL,
KEYMAP_EDIT);
add_to_menu(&mi, N_("Open in ~external editor"),
keystroke.source, ACT_MAIN_NONE,
menu_textarea_edit, NULL, FREE_RTEXT);
}
/* [gettext_accelerator_context()] */
/* Fall through */
default:
/* [gettext_accelerator_context(link_menu.textarea, link_menu.form)] */
add_menu_action(&mi, N_("~Submit form"), ACT_MAIN_SUBMIT_FORM);
add_menu_action(&mi, N_("Submit form and rel~oad"), ACT_MAIN_SUBMIT_FORM_RELOAD);
assert(fc->form);
if (fc->form->method == FORM_METHOD_GET) {
add_new_win_to_menu(&mi, N_("Submit form and open in new ~window"), term);
add_menu_action(&mi, N_("Submit form and open in new ~tab"),
ACT_MAIN_OPEN_LINK_IN_NEW_TAB);
add_menu_action(&mi, N_("Submit form and open in new tab in ~background"),
ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND);
}
if (!get_cmd_opt_bool("anonymous"))
add_menu_action(&mi, N_("Submit form and ~download"), ACT_MAIN_LINK_DOWNLOAD);
add_menu_action(&mi, N_("~Reset form"), ACT_MAIN_RESET_FORM);
/* [gettext_accelerator_context()] */
}
/* [gettext_accelerator_context(link_menu.reset, link_menu.textarea, link_menu.form)] */
add_to_menu(&mi, N_("Form f~ields"), NULL, ACT_MAIN_LINK_FORM_MENU,
NULL, NULL, SUBMENU);
/* [gettext_accelerator_context()] */
}
if (link->where_img) {
/* [gettext_accelerator_context(link_menu.map, link_menu.std, link_menu.form)] */
add_menu_action(&mi, N_("V~iew image"), ACT_MAIN_VIEW_IMAGE);
if (!get_cmd_opt_bool("anonymous"))
add_menu_action(&mi, N_("Download ima~ge"), ACT_MAIN_LINK_DOWNLOAD_IMAGE);
/* [gettext_accelerator_context()] */
}
/** @todo TODO: Make it possible to trigger any script event
* hooks associated to the link. --pasky */
end:
if (!mi->text) {
add_to_menu(&mi, N_("No link selected"), NULL, ACT_MAIN_NONE,
NULL, NULL, NO_SELECT);
}
do_menu(term, mi, ses, 1);
}
/** Return current link's title. */
char *
get_current_link_title(struct document_view *doc_view)
{
struct link *link;
assert(doc_view && doc_view->document && doc_view->vs);
if_assert_failed return NULL;
if (doc_view->document->frame_desc)
return NULL;
link = get_current_link(doc_view);
if (link && link->title && *link->title) {
char *link_title, *src;
struct conv_table *convert_table;
convert_table = get_translation_table(doc_view->document->cp,
doc_view->document->options.cp);
/* CSM_NONE because any entities in the title have
* already been decoded. */
link_title = convert_string(convert_table, link->title,
strlen(link->title),
doc_view->document->options.cp,
CSM_NONE, NULL, NULL, NULL);
/* Remove illicit chars. */
#ifdef CONFIG_UTF8
if (link_title && !doc_view->document->options.utf8)
#endif /* CONFIG_UTF8 */
for (src = link_title; *src; src++)
if (!isprint(*src) || iscntrl(*src))
*src = '*';
return link_title;
}
return NULL;
}
char *
get_current_link_info(struct session *ses, struct document_view *doc_view)
{
struct link *link;
assert(ses && doc_view && doc_view->document && doc_view->vs);
if_assert_failed return NULL;
if (doc_view->document->frame_desc)
return NULL;
link = get_current_link(doc_view);
if (!link) return NULL;
/** @todo TODO: Provide info about script event hooks too. --pasky */
if (!link_is_form(link)) {
struct terminal *term = ses->tab->term;
struct string str;
char *uristring = link->where;
if (!init_string(&str)) return NULL;
if (!link->where && link->where_img) {
add_to_string(&str, _("Image", term));
add_char_to_string(&str, ' ');
uristring = link->where_img;
} else if (link->type == LINK_MAP) {
add_to_string(&str, _("Usemap", term));
add_char_to_string(&str, ' ');
}
/* Add the uri with password and post info stripped */
add_string_uri_to_string(&str, uristring, URI_PUBLIC);
if (link->accesskey > 0
&& get_opt_bool("document.browse.accesskey.display",
ses)) {
add_to_string(&str, " (");
add_accesskey_to_string(&str, link->accesskey);
add_char_to_string(&str, ')');
}
#ifdef CONFIG_UTF8
if (term->utf8_cp)
decode_uri_string(&str);
else
#endif /* CONFIG_UTF8 */
decode_uri_string_for_display(&str);
return str.source;
}
if (!get_link_form_control(link)) return NULL;
return get_form_info(ses, doc_view);
}