move-link-up-line: segfault when cursor was below last line.
[elinks.git] / src / viewer / text / view.c
blobb32b87ffa1550c05ad236cef9697619db2355876
1 /** HTML viewer (and much more)
2 * @file */
4 #ifdef HAVE_CONFIG_H
5 #include "config.h"
6 #endif
8 #include <stdlib.h>
9 #include <string.h>
10 #ifdef HAVE_UNISTD_H
11 #include <unistd.h>
12 #endif
14 #include "elinks.h"
16 #include "bfu/leds.h"
17 #include "bfu/menu.h"
18 #include "bfu/dialog.h"
19 #include "config/kbdbind.h"
20 #include "config/options.h"
21 #include "dialogs/document.h"
22 #include "dialogs/menu.h"
23 #include "dialogs/options.h"
24 #include "dialogs/status.h"
25 #include "document/document.h"
26 #include "document/html/frames.h"
27 #include "document/options.h"
28 #include "document/renderer.h"
29 #include "document/view.h"
30 #include "intl/charsets.h"
31 #include "intl/gettext/libintl.h"
32 #include "main/event.h"
33 #include "osdep/osdep.h"
34 #include "protocol/uri.h"
35 #include "session/download.h"
36 #include "session/location.h"
37 #include "session/session.h"
38 #include "session/task.h"
39 #include "terminal/draw.h"
40 #include "terminal/event.h"
41 #include "terminal/kbd.h"
42 #include "terminal/mouse.h"
43 #include "terminal/tab.h"
44 #include "terminal/terminal.h"
45 #include "terminal/window.h"
46 #include "util/color.h"
47 #include "util/conv.h"
48 #include "util/error.h"
49 #include "util/memory.h"
50 #include "util/snprintf.h"
51 #include "util/string.h"
52 #include "viewer/action.h"
53 #include "viewer/dump/dump.h"
54 #include "viewer/text/draw.h"
55 #include "viewer/text/form.h"
56 #include "viewer/text/link.h"
57 #include "viewer/text/marks.h"
58 #include "viewer/text/search.h"
59 #include "viewer/text/textarea.h"
60 #include "viewer/text/view.h"
61 #include "viewer/text/vs.h"
63 void
64 detach_formatted(struct document_view *doc_view)
66 assert(doc_view);
67 if_assert_failed return;
69 #ifdef CONFIG_ECMASCRIPT
70 if (doc_view->session) {
71 mem_free_set(&doc_view->session->status.window_status, NULL);
73 #endif
74 if (doc_view->document) {
75 release_document(doc_view->document);
76 doc_view->document = NULL;
78 if (doc_view->vs) {
79 doc_view->vs->doc_view = NULL;
80 doc_view->vs = NULL;
82 mem_free_set(&doc_view->name, NULL);
85 /*! @a type == 0 -> PAGE_DOWN;
86 * @a type == 1 -> DOWN */
87 static void
88 move_down(struct session *ses, struct document_view *doc_view, int type)
90 int newpos;
92 assert(ses && doc_view && doc_view->vs);
93 if_assert_failed return;
95 assert(ses->navigate_mode == NAVIGATE_LINKWISE); /* XXX: drop it at some time. --Zas */
97 newpos = doc_view->vs->y + doc_view->box.height;
98 if (newpos < doc_view->document->height)
99 doc_view->vs->y = newpos;
101 if (current_link_is_visible(doc_view))
102 return;
104 if (type)
105 find_link_down(doc_view);
106 else
107 find_link_page_down(doc_view);
109 return;
112 enum frame_event_status
113 move_page_down(struct session *ses, struct document_view *doc_view)
115 int oldy = doc_view->vs->y;
116 int count = eat_kbd_repeat_count(ses);
118 ses->navigate_mode = NAVIGATE_LINKWISE;
120 do move_down(ses, doc_view, 0); while (--count > 0);
122 return doc_view->vs->y == oldy ? FRAME_EVENT_OK : FRAME_EVENT_REFRESH;
125 /*! @a type == 0 -> PAGE_UP;
126 * @a type == 1 -> UP */
127 static void
128 move_up(struct session *ses, struct document_view *doc_view, int type)
130 assert(ses && doc_view && doc_view->vs);
131 if_assert_failed return;
133 assert(ses->navigate_mode == NAVIGATE_LINKWISE); /* XXX: drop it at some time. --Zas */
135 if (doc_view->vs->y == 0) return;
137 doc_view->vs->y -= doc_view->box.height;
138 int_lower_bound(&doc_view->vs->y, 0);
140 if (current_link_is_visible(doc_view))
141 return;
143 if (type)
144 find_link_up(doc_view);
145 else
146 find_link_page_up(doc_view);
148 return;
151 enum frame_event_status
152 move_page_up(struct session *ses, struct document_view *doc_view)
154 int oldy = doc_view->vs->y;
155 int count = eat_kbd_repeat_count(ses);
157 ses->navigate_mode = NAVIGATE_LINKWISE;
159 do move_up(ses, doc_view, 0); while (--count > 0);
161 return doc_view->vs->y == oldy ? FRAME_EVENT_OK : FRAME_EVENT_REFRESH;
165 enum frame_event_status
166 move_link(struct session *ses, struct document_view *doc_view, int direction,
167 int wraparound_bound, int wraparound_link)
169 int wraparound = 0;
170 int count;
172 assert(ses && doc_view && doc_view->vs && doc_view->document);
173 if_assert_failed return FRAME_EVENT_OK;
175 ses->navigate_mode = NAVIGATE_LINKWISE;
177 if (doc_view->document->nlinks < 2) {
178 /* Wraparound only makes sense with more than one link. */
179 wraparound_bound = -1;
180 } else {
181 /* We only bother this option if there's some links
182 * in document. */
183 wraparound = get_opt_bool("document.browse.links.wraparound");
186 count = eat_kbd_repeat_count(ses);
188 do {
189 int current_link = doc_view->vs->current_link;
191 if (current_link == wraparound_bound) {
192 if (wraparound) {
193 jump_to_link_number(ses, doc_view, wraparound_link);
194 /* FIXME: This needs further work, we should call
195 * page_down() and set_textarea() under some conditions
196 * as well. --pasky */
197 continue;
200 } else {
201 if (next_link_in_view_y(doc_view, current_link + direction,
202 direction))
203 continue;
206 /* This is a work around for the case where the index of
207 * @wraparound_bound is not necessarily the index of the first
208 * or the last link in the view. It means that the link moving
209 * could end up calling next_link_in_view() in the condition
210 * above. This is bad because next_link_in_view() will then
211 * 'reset' doc_view->vs->current_link to -1 and the effect will
212 * be that the current link will 'wrap around'. By restoring
213 * the index of the @current_link nothing will be wrapped
214 * around and move_{up,down} will take care of finding the next
215 * link. */
216 doc_view->vs->current_link = current_link;
218 if (direction > 0) {
219 move_down(ses, doc_view, 1);
220 } else {
221 move_up(ses, doc_view, 1);
224 if (current_link != wraparound_bound
225 && current_link != doc_view->vs->current_link) {
226 set_textarea(doc_view, -direction);
228 } while (--count > 0);
230 return FRAME_EVENT_REFRESH;
233 enum frame_event_status
234 move_link_dir(struct session *ses, struct document_view *doc_view, int dir_x, int dir_y)
236 int count;
238 assert(ses && doc_view && doc_view->vs && doc_view->document);
239 if_assert_failed return FRAME_EVENT_OK;
241 ses->navigate_mode = NAVIGATE_LINKWISE;
243 count = eat_kbd_repeat_count(ses);
245 do {
246 int current_link = doc_view->vs->current_link;
248 if (next_link_in_dir(doc_view, dir_x, dir_y))
249 continue;
251 /* FIXME: This won't preserve the column! */
252 if (dir_y > 0)
253 move_down(ses, doc_view, 1);
254 else if (dir_y < 0)
255 move_up(ses, doc_view, 1);
257 if (dir_y && current_link != doc_view->vs->current_link) {
258 set_textarea(doc_view, -dir_y);
260 } while (--count > 0);
262 return FRAME_EVENT_REFRESH;
265 /*! @a steps > 0 -> down */
266 static enum frame_event_status
267 vertical_scroll(struct session *ses, struct document_view *doc_view, int steps)
269 int y;
271 assert(ses && doc_view && doc_view->vs && doc_view->document);
272 if_assert_failed return FRAME_EVENT_OK;
274 y = doc_view->vs->y + steps;
275 if (steps > 0) {
276 /* DOWN */
277 int max_height = doc_view->document->height - doc_view->box.height;
279 if (doc_view->vs->y >= max_height) return FRAME_EVENT_OK;
280 int_upper_bound(&y, max_height);
283 int_lower_bound(&y, 0);
285 if (doc_view->vs->y == y) return FRAME_EVENT_OK;
287 doc_view->vs->y = y;
289 if (current_link_is_visible(doc_view))
290 return FRAME_EVENT_REFRESH;
292 if (steps > 0)
293 find_link_page_down(doc_view);
294 else
295 find_link_page_up(doc_view);
297 return FRAME_EVENT_REFRESH;
300 /*! @a steps > 0 -> right */
301 static enum frame_event_status
302 horizontal_scroll(struct session *ses, struct document_view *doc_view, int steps)
304 int x, max;
306 assert(ses && doc_view && doc_view->vs && doc_view->document);
307 if_assert_failed return FRAME_EVENT_OK;
309 x = doc_view->vs->x + steps;
311 if (get_opt_bool("document.browse.scrolling.horizontal_extended")) {
312 max = doc_view->document->width - 1;
313 } else {
314 max = int_max(doc_view->vs->x,
315 doc_view->document->width - doc_view->box.width);
318 int_bounds(&x, 0, max);
319 if (doc_view->vs->x == x) return FRAME_EVENT_OK;
321 doc_view->vs->x = x;
323 if (current_link_is_visible(doc_view))
324 return FRAME_EVENT_REFRESH;
326 find_link_page_down(doc_view);
328 return FRAME_EVENT_REFRESH;
331 enum frame_event_status
332 scroll_up(struct session *ses, struct document_view *doc_view)
334 int steps = eat_kbd_repeat_count(ses);
336 if (!steps)
337 steps = get_opt_int("document.browse.scrolling.vertical_step");
339 return vertical_scroll(ses, doc_view, -steps);
342 enum frame_event_status
343 scroll_down(struct session *ses, struct document_view *doc_view)
345 int steps = eat_kbd_repeat_count(ses);
347 if (!steps)
348 steps = get_opt_int("document.browse.scrolling.vertical_step");
350 return vertical_scroll(ses, doc_view, steps);
353 enum frame_event_status
354 scroll_left(struct session *ses, struct document_view *doc_view)
356 int steps = eat_kbd_repeat_count(ses);
358 if (!steps)
359 steps = get_opt_int("document.browse.scrolling.horizontal_step");
361 return horizontal_scroll(ses, doc_view, -steps);
364 enum frame_event_status
365 scroll_right(struct session *ses, struct document_view *doc_view)
367 int steps = eat_kbd_repeat_count(ses);
369 if (!steps)
370 steps = get_opt_int("document.browse.scrolling.horizontal_step");
372 return horizontal_scroll(ses, doc_view, steps);
375 #ifdef CONFIG_MOUSE
376 static enum frame_event_status
377 scroll_mouse_up(struct session *ses, struct document_view *doc_view)
379 int steps = get_opt_int("document.browse.scrolling.vertical_step");
381 return vertical_scroll(ses, doc_view, -steps);
384 static enum frame_event_status
385 scroll_mouse_down(struct session *ses, struct document_view *doc_view)
387 int steps = get_opt_int("document.browse.scrolling.vertical_step");
389 return vertical_scroll(ses, doc_view, steps);
392 static enum frame_event_status
393 scroll_mouse_left(struct session *ses, struct document_view *doc_view)
395 int steps = get_opt_int("document.browse.scrolling.horizontal_step");
397 return horizontal_scroll(ses, doc_view, -steps);
400 static enum frame_event_status
401 scroll_mouse_right(struct session *ses, struct document_view *doc_view)
403 int steps = get_opt_int("document.browse.scrolling.horizontal_step");
405 return horizontal_scroll(ses, doc_view, steps);
407 #endif /* CONFIG_MOUSE */
409 enum frame_event_status
410 move_document_start(struct session *ses, struct document_view *doc_view)
412 assert(ses && doc_view && doc_view->vs);
413 if_assert_failed return FRAME_EVENT_OK;
415 doc_view->vs->y = doc_view->vs->x = 0;
417 if (ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
418 /* Move to the first line and the first column. */
419 move_cursor(ses, doc_view, doc_view->box.x, doc_view->box.y);
420 } else {
421 find_link_page_down(doc_view);
424 return FRAME_EVENT_REFRESH;
427 enum frame_event_status
428 move_document_end(struct session *ses, struct document_view *doc_view)
430 int max_height;
432 assert(ses && doc_view && doc_view->vs && doc_view->document);
433 if_assert_failed return FRAME_EVENT_OK;
435 max_height = doc_view->document->height - doc_view->box.height;
436 doc_view->vs->x = 0;
437 int_lower_bound(&doc_view->vs->y, int_max(0, max_height));
439 if (ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
440 /* Move to the last line of the document,
441 * but preserve the column. This is done to avoid
442 * moving the cursor backwards if it is already
443 * on the last line but is not on the first column. */
444 move_cursor(ses, doc_view, ses->tab->x,
445 doc_view->document->height - doc_view->vs->y);
446 } else {
447 find_link_page_up(doc_view);
450 return FRAME_EVENT_REFRESH;
453 enum frame_event_status
454 set_frame(struct session *ses, struct document_view *doc_view, int xxxx)
456 assert(ses && ses->doc_view && doc_view && doc_view->vs);
457 if_assert_failed return FRAME_EVENT_OK;
459 if (doc_view == ses->doc_view) return FRAME_EVENT_OK;
460 goto_uri(ses, doc_view->vs->uri);
461 ses->navigate_mode = NAVIGATE_LINKWISE;
463 return FRAME_EVENT_OK;
467 void
468 toggle_plain_html(struct session *ses, struct document_view *doc_view, int xxxx)
470 assert(ses && doc_view && ses->tab && ses->tab->term);
471 if_assert_failed return;
473 if (!doc_view->vs) {
474 nowhere_box(ses->tab->term, NULL);
475 return;
478 doc_view->vs->plain = !doc_view->vs->plain;
479 draw_formatted(ses, 1);
482 void
483 toggle_wrap_text(struct session *ses, struct document_view *doc_view, int xxxx)
485 assert(ses && doc_view && ses->tab && ses->tab->term);
486 if_assert_failed return;
488 if (!doc_view->vs) {
489 nowhere_box(ses->tab->term, NULL);
490 return;
493 doc_view->vs->wrap = !doc_view->vs->wrap;
494 draw_formatted(ses, 1);
497 /** Move the cursor to the document view co-ordinates provided
498 * as @a x and @a y, scroll the document if necessary, put us in
499 * cursor-routing navigation mode if that is not the current mode,
500 * and select any link under the cursor. */
501 enum frame_event_status
502 move_cursor(struct session *ses, struct document_view *doc_view, int x, int y)
504 enum frame_event_status status = FRAME_EVENT_REFRESH;
505 struct terminal *term = ses->tab->term;
506 struct box *box = &doc_view->box;
507 struct link *link;
509 /* If cursor was moved outside the document view scroll it, but only
510 * within the document canvas */
511 if (!is_in_box(box, x, y)) {
512 int max_height = doc_view->document->height - doc_view->vs->y;
513 int max_width = doc_view->document->width - doc_view->vs->x;
515 if (y < box->y) {
516 status = vertical_scroll(ses, doc_view, y - box->y);
518 } else if (y >= box->y + box->height && y <= max_height) {
519 status = vertical_scroll(ses, doc_view,
520 y - (box->y + box->height - 1));
522 } else if (x < box->x) {
523 status = horizontal_scroll(ses, doc_view, x - box->x);
525 } else if (x >= box->x + box->width && x <= max_width) {
526 status = horizontal_scroll(ses, doc_view,
527 x - (box->x + box->width - 1));
530 /* If the view was not scrolled there's nothing more to do */
531 if (status != FRAME_EVENT_REFRESH)
532 return status;
534 /* Restrict the cursor position within the current view */
535 int_bounds(&x, box->x, box->x + box->width - 1);
536 int_bounds(&y, box->y, box->y + box->height - 1);
539 /* Scrolling could have changed the navigation mode */
540 ses->navigate_mode = NAVIGATE_CURSOR_ROUTING;
542 link = get_link_at_coordinates(doc_view, x - box->x, y - box->y);
543 if (link) {
544 doc_view->vs->current_link = link - doc_view->document->links;
545 } else {
546 doc_view->vs->current_link = -1;
549 /* Set the unblockable cursor position and update the window pointer so
550 * stuff like the link menu will be drawn relative to the cursor. */
551 set_cursor(term, x, y, 0);
552 set_window_ptr(ses->tab, x, y);
554 return status;
557 static enum frame_event_status
558 move_cursor_rel(struct session *ses, struct document_view *view,
559 int rx, int ry)
561 int count = eat_kbd_repeat_count(ses);
562 int x, y;
564 int_lower_bound(&count, 1);
566 x = ses->tab->x + rx*count;
567 y = ses->tab->y + ry*count;
568 return move_cursor(ses, view, x, y);
571 enum frame_event_status
572 move_cursor_left(struct session *ses, struct document_view *view)
574 return move_cursor_rel(ses, view, -1, 0);
577 enum frame_event_status
578 move_cursor_right(struct session *ses, struct document_view *view)
580 return move_cursor_rel(ses, view, 1, 0);
583 enum frame_event_status
584 move_cursor_up(struct session *ses, struct document_view *view)
586 return move_cursor_rel(ses, view, 0, -1);
589 enum frame_event_status
590 move_cursor_down(struct session *ses, struct document_view *view)
592 return move_cursor_rel(ses, view, 0, 1);
595 enum frame_event_status
596 move_link_up_line(struct session *ses, struct document_view *doc_view)
598 struct document *document;
599 struct view_state *vs;
600 struct box *box;
601 int min_y, y, y1;
603 assert(ses && doc_view && doc_view->vs && doc_view->document);
604 if_assert_failed return FRAME_EVENT_OK;
605 vs = doc_view->vs;
606 document = doc_view->document;
607 box = &doc_view->box;
608 if (!document->lines1) {
609 if (vs->y) {
610 vs->y -= box->height;
611 int_lower_bound(&vs->y, 0);
612 return FRAME_EVENT_REFRESH;
614 return FRAME_EVENT_OK;
616 min_y = vs->y - box->height;
617 int_lower_bound(&min_y, 0);
618 y1 = y = vs->y + ses->tab->y - box->y;
619 int_upper_bound(&y, document->height - 1);
620 for (y--; y >= min_y; y--) {
621 struct link *link = document->lines1[y];
623 if (!link) continue;
624 for (; link <= document->lines2[y]; link++) {
625 enum frame_event_status status;
627 if (link->points[0].y != y) continue;
628 if (y < vs->y) {
629 int mini = int_min(box->height, vs->y);
631 vs->y -= mini;
632 y += mini;
634 status = move_cursor_rel(ses, doc_view, 0, y - y1);
635 if (link == get_current_link(doc_view))
636 ses->navigate_mode = NAVIGATE_LINKWISE;
637 return status;
640 if (vs->y) {
641 vs->y -= box->height;
642 int_lower_bound(&vs->y, 0);
643 return FRAME_EVENT_REFRESH;
645 return FRAME_EVENT_OK;
648 enum frame_event_status
649 move_link_down_line(struct session *ses, struct document_view *doc_view)
651 struct document *document;
652 struct view_state *vs;
653 struct box *box;
654 int max_y, y, y1;
656 assert(ses && doc_view && doc_view->vs && doc_view->document);
657 if_assert_failed return FRAME_EVENT_OK;
658 vs = doc_view->vs;
659 document = doc_view->document;
660 box = &doc_view->box;
661 if (!document->lines1) {
662 if (vs->y + box->height < document->height) {
663 vs->y += box->height;
664 return FRAME_EVENT_REFRESH;
666 return FRAME_EVENT_OK;
668 max_y = vs->y + box->height * 2 - 1;
669 int_upper_bound(&max_y, document->height - 1);
670 y1 = y = vs->y + ses->tab->y - box->y;
671 for (y++; y <= max_y; y++) {
672 struct link *link = document->lines1[y];
674 if (!link) continue;
675 for (; link <= document->lines2[y]; link++) {
676 enum frame_event_status status;
678 if (link->points[0].y != y) continue;
679 if (y >= vs->y + box->height) {
680 int mini = int_min(box->height, document->height - vs->y - 1);
682 vs->y += mini;
683 y -= mini;
685 status = move_cursor_rel(ses, doc_view, 0, y - y1);
686 if (link == get_current_link(doc_view))
687 ses->navigate_mode = NAVIGATE_LINKWISE;
688 return status;
691 if (vs->y + box->height < document->height) {
692 vs->y += box->height;
693 return FRAME_EVENT_REFRESH;
695 return FRAME_EVENT_OK;
698 enum frame_event_status
699 move_link_prev_line(struct session *ses, struct document_view *doc_view)
701 struct view_state *vs;
702 struct document *document;
703 struct box *box;
704 struct link *link, *last = NULL;
705 int y1, y, min_y, min_x, max_x, x1;
707 assert(ses && doc_view && doc_view->vs && doc_view->document);
708 if_assert_failed return FRAME_EVENT_OK;
710 vs = doc_view->vs;
711 document = doc_view->document;
712 box = &doc_view->box;
713 if (!document->lines1) {
714 if (vs->y) {
715 vs->y -= box->height;
716 int_lower_bound(&vs->y, 0);
717 return FRAME_EVENT_REFRESH;
719 return FRAME_EVENT_OK;
721 y = y1 = vs->y + ses->tab->y - box->y;
722 x1 = vs->x + ses->tab->x - box->x;
724 link = get_current_link(doc_view);
725 if (link) {
726 get_link_x_bounds(link, y1, &min_x, &max_x);
727 } else {
728 min_x = max_x = x1;
730 int_upper_bound(&y, document->height - 1);
731 min_y = int_max(0, vs->y - box->height);
733 for (; y >= min_y; y--, min_x = INT_MAX) {
734 link = document->lines1[y];
735 if (!link) continue;
736 for (; link <= document->lines2[y]; link++) {
737 if (link->points[0].y != y) continue;
738 if (link->points[0].x >= min_x) continue;
739 if (!last) last = link;
740 else if (link->points[0].x > last->points[0].x) last = link;
742 if (last) {
743 enum frame_event_status status;
745 y = last->points[0].y;
746 if (y < vs->y) {
747 int mini = int_min(box->height, vs->y);
749 vs->y -= mini;
750 y += mini;
752 status = move_cursor_rel(ses, doc_view, last->points[0].x - x1, y - y1);
753 if (link == get_current_link(doc_view))
754 ses->navigate_mode = NAVIGATE_LINKWISE;
755 return status;
758 if (vs->y) {
759 vs->y -= box->height;
760 int_lower_bound(&vs->y, 0);
761 return FRAME_EVENT_REFRESH;
763 return FRAME_EVENT_OK;
766 enum frame_event_status
767 move_link_next_line(struct session *ses, struct document_view *doc_view)
769 struct view_state *vs;
770 struct document *document;
771 struct box *box;
772 struct link *link, *last = NULL;
773 int y1, y, max_y, min_x, max_x, x1;
775 assert(ses && doc_view && doc_view->vs && doc_view->document);
776 if_assert_failed return FRAME_EVENT_OK;
778 vs = doc_view->vs;
779 document = doc_view->document;
780 box = &doc_view->box;
781 if (!document->lines1) {
782 if (vs->y + box->height < document->height) {
783 vs->y += box->height;
784 return FRAME_EVENT_REFRESH;
786 return FRAME_EVENT_OK;
788 y = y1 = vs->y + ses->tab->y - box->y;
789 x1 = vs->x + ses->tab->x - box->x;
791 link = get_current_link(doc_view);
792 if (link) {
793 get_link_x_bounds(link, y1, &min_x, &max_x);
794 } else {
795 min_x = max_x = x1;
797 int_upper_bound(&y, document->height - 1);
798 max_y = int_min(vs->y + 2 * box->height - 1, document->height - 1);
800 for (; y <= max_y; y++, min_x = -1) {
801 link = document->lines1[y];
802 if (!link) continue;
803 for (; link <= document->lines2[y]; link++) {
804 if (link->points[0].y != y) continue;
805 if (link->points[0].x <= min_x) continue;
806 if (!last) last = link;
807 else if (link->points[0].x < last->points[0].x) last = link;
809 if (last) {
810 enum frame_event_status status;
812 y = last->points[0].y;
813 if (y >= vs->y + box->height) {
814 int mini = int_min(box->height, document->height - vs->y - 1);
816 vs->y += mini;
817 y -= mini;
819 status = move_cursor_rel(ses, doc_view, last->points[0].x - x1, y - y1);
820 if (link == get_current_link(doc_view))
821 ses->navigate_mode = NAVIGATE_LINKWISE;
822 return status;
825 if (vs->y + box->height < document->height) {
826 vs->y += box->height;
827 return FRAME_EVENT_REFRESH;
829 return FRAME_EVENT_OK;
832 enum frame_event_status
833 copy_current_link_to_clipboard(struct session *ses,
834 struct document_view *doc_view,
835 int xxx)
837 struct link *link;
838 struct uri *uri;
839 unsigned char *uristring;
841 link = get_current_link(doc_view);
842 if (!link) return FRAME_EVENT_OK;
844 uri = get_link_uri(ses, doc_view, link);
845 if (!uri) return FRAME_EVENT_OK;
847 uristring = get_uri_string(uri, URI_ORIGINAL);
848 done_uri(uri);
850 if (uristring) {
851 set_clipboard_text(uristring);
852 mem_free(uristring);
855 return FRAME_EVENT_OK;
860 try_jump_to_link_number(struct session *ses, struct document_view *doc_view)
862 int link_number = eat_kbd_repeat_count(ses) - 1;
864 if (link_number < 0) return 1;
866 if (!doc_view) return 0;
868 if (link_number >= doc_view->document->nlinks)
869 return 0;
871 jump_to_link_number(ses, doc_view, link_number);
872 refresh_view(ses, doc_view, 0);
874 return 1;
877 #ifdef CONFIG_MARKS
878 enum frame_event_status
879 try_mark_key(struct session *ses, struct document_view *doc_view,
880 struct term_event *ev)
882 term_event_key_T key = get_kbd_key(ev);
883 unsigned char mark;
885 /* set_mark and goto_mark allow only a subset of the ASCII
886 * character repertoire as mark characters. If get_kbd_key(ev)
887 * is something else (i.e. a special key or a non-ASCII
888 * character), map it to an ASCII character that the functions
889 * will not accept, so the results are consistent.
890 * When CONFIG_UTF8 is not defined, this assumes that codes
891 * 0 to 0x7F in all codepages match ASCII. */
892 if (key >= 0 && key <= 0x7F)
893 mark = (unsigned char) key;
894 else
895 mark = 0;
897 switch (ses->kbdprefix.mark) {
898 case KP_MARK_NOTHING:
899 return FRAME_EVENT_IGNORED;
901 case KP_MARK_SET:
902 /* It is intentional to set the mark
903 * to NULL if !doc_view->vs. */
904 set_mark(mark, doc_view->vs);
905 break;
907 case KP_MARK_GOTO:
908 goto_mark(mark, doc_view->vs);
909 break;
912 ses->kbdprefix.repeat_count = 0;
913 ses->kbdprefix.mark = KP_MARK_NOTHING;
915 return FRAME_EVENT_REFRESH;
917 #endif
919 static enum frame_event_status
920 try_prefix_key(struct session *ses, struct document_view *doc_view,
921 struct term_event *ev)
923 struct document *document = doc_view->document;
924 struct document_options *doc_opts = &document->options;
925 int digit = get_kbd_key(ev) - '0';
927 if (digit < 0 || digit > 9)
928 return FRAME_EVENT_IGNORED;
930 if (get_kbd_modifier(ev)
931 || ses->kbdprefix.repeat_count /* The user has already begun
932 * entering a prefix. */
933 || !doc_opts->num_links_key
934 || (doc_opts->num_links_key == 1 && !doc_opts->links_numbering)) {
935 /* Repeat count.
936 * ses->kbdprefix.repeat_count is initialized to zero
937 * the first time by init_session() calloc() call.
938 * When used, it has to be reset to zero. */
940 /* Clear the highlighting for the previous partial prefix. */
941 if (ses->kbdprefix.repeat_count) draw_formatted(ses, 0);
943 ses->kbdprefix.repeat_count *= 10;
944 ses->kbdprefix.repeat_count += digit;
946 /* If too big, just restart from zero, so pressing
947 * '0' six times or more will reset the count. */
948 if (ses->kbdprefix.repeat_count > 99999)
949 ses->kbdprefix.repeat_count = 0;
950 else if (ses->kbdprefix.repeat_count)
951 highlight_links_with_prefixes_that_start_with_n(
952 ses->tab->term, doc_view,
953 ses->kbdprefix.repeat_count);
955 return FRAME_EVENT_OK;
958 if (digit >= 1 && !get_kbd_modifier(ev)) {
959 int nlinks = document->nlinks, length;
960 unsigned char d[2] = { get_kbd_key(ev), 0 };
962 ses->kbdprefix.repeat_count = 0;
964 if (!nlinks) return FRAME_EVENT_OK;
966 for (length = 1; nlinks; nlinks /= 10)
967 length++;
969 input_dialog(ses->tab->term, NULL,
970 N_("Go to link"), N_("Enter link number"),
971 ses, NULL,
972 length, d, 1, document->nlinks, check_number,
973 (void (*)(void *, unsigned char *)) goto_link_number, NULL);
975 return FRAME_EVENT_OK;
978 return FRAME_EVENT_IGNORED;
981 static enum frame_event_status
982 try_form_insert_mode(struct session *ses, struct document_view *doc_view,
983 struct link *link, struct term_event *ev)
985 enum frame_event_status status = FRAME_EVENT_IGNORED;
986 enum edit_action action_id;
988 if (!link_is_textinput(link))
989 return FRAME_EVENT_IGNORED;
991 action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
993 if (ses->insert_mode == INSERT_MODE_OFF) {
994 if (action_id == ACT_EDIT_ENTER) {
995 ses->insert_mode = INSERT_MODE_ON;
996 status = FRAME_EVENT_REFRESH;
1000 return status;
1003 static enum frame_event_status
1004 try_form_action(struct session *ses, struct document_view *doc_view,
1005 struct link *link, struct term_event *ev)
1007 enum frame_event_status status;
1009 assert(link);
1011 if (!link_is_textinput(link))
1012 return FRAME_EVENT_IGNORED;
1014 status = field_op(ses, doc_view, link, ev);
1016 if (status != FRAME_EVENT_IGNORED
1017 && ses->insert_mode == INSERT_MODE_ON) {
1018 assert(link == get_current_link(doc_view));
1021 return status;
1024 static enum frame_event_status
1025 frame_ev_kbd(struct session *ses, struct document_view *doc_view, struct term_event *ev)
1027 enum frame_event_status status = FRAME_EVENT_IGNORED;
1028 int accesskey_priority;
1029 struct link *link = get_current_link(doc_view);
1031 if (link) {
1032 status = try_form_insert_mode(ses, doc_view, link, ev);
1033 if (status != FRAME_EVENT_IGNORED)
1034 return status;
1036 status = try_form_action(ses, doc_view, link, ev);
1037 if (status != FRAME_EVENT_IGNORED)
1038 return status;
1041 #ifdef CONFIG_MARKS
1042 status = try_mark_key(ses, doc_view, ev);
1043 if (status != FRAME_EVENT_IGNORED)
1044 return status;
1045 #endif
1046 accesskey_priority = get_opt_int("document.browse.accesskey.priority");
1048 if (accesskey_priority >= 2) {
1049 status = try_document_key(ses, doc_view, ev);
1051 if (status != FRAME_EVENT_IGNORED) {
1052 /* The document ate the key! */
1053 return status;
1057 status = try_prefix_key(ses, doc_view, ev);
1058 if (status != FRAME_EVENT_IGNORED)
1059 return status;
1061 if (accesskey_priority == 1) {
1062 status = try_document_key(ses, doc_view, ev);
1064 if (status != FRAME_EVENT_IGNORED) {
1065 /* The document ate the key! */
1066 return status;
1070 return FRAME_EVENT_IGNORED;
1073 #ifdef CONFIG_MOUSE
1074 static enum frame_event_status
1075 frame_ev_mouse(struct session *ses, struct document_view *doc_view, struct term_event *ev)
1077 int x = ev->info.mouse.x;
1078 int y = ev->info.mouse.y;
1079 struct link *link;
1081 if (check_mouse_wheel(ev)) {
1082 if (!check_mouse_action(ev, B_DOWN)) {
1083 /* We handle only B_DOWN case... */
1084 } else if (check_mouse_button(ev, B_WHEEL_UP)) {
1085 return scroll_mouse_up(ses, doc_view);
1086 } else if (check_mouse_button(ev, B_WHEEL_DOWN)) {
1087 return scroll_mouse_down(ses, doc_view);
1090 return FRAME_EVENT_OK;
1093 link = get_link_at_coordinates(doc_view, x, y);
1094 if (link) {
1095 enum frame_event_status status = FRAME_EVENT_REFRESH;
1097 doc_view->vs->current_link = link - doc_view->document->links;
1099 if (!link_is_textinput(link)) {
1101 status = FRAME_EVENT_OK;
1103 refresh_view(ses, doc_view, 0);
1105 if (check_mouse_button(ev, B_LEFT)
1106 || check_mouse_button(ev, B_MIDDLE)) {
1107 if (check_mouse_action(ev, B_DOWN))
1108 do_not_ignore_next_mouse_event(ses->tab->term);
1109 else if (check_mouse_button(ev, B_LEFT))
1110 status = enter(ses, doc_view, 0);
1111 else if (check_mouse_button(ev, B_MIDDLE))
1112 open_current_link_in_new_tab(ses, 1);
1113 } else {
1114 link_menu(ses->tab->term, NULL, ses);
1118 return status;
1121 if (check_mouse_button(ev, B_LEFT)) {
1122 /* Clicking the edge of screen will scroll the document. */
1124 int scrollmargin = get_opt_int("document.browse.scrolling.margin");
1126 /* XXX: This is code duplication with kbd handlers. But
1127 * repeatcount-free here. */
1129 if (y < scrollmargin) {
1130 return scroll_mouse_up(ses, doc_view);
1132 if (y >= doc_view->box.height - scrollmargin) {
1133 return scroll_mouse_down(ses, doc_view);
1136 if (x < scrollmargin * 2) {
1137 return scroll_mouse_left(ses, doc_view);
1139 if (x >= doc_view->box.width - scrollmargin * 2) {
1140 return scroll_mouse_right(ses, doc_view);
1143 return FRAME_EVENT_OK;
1146 return FRAME_EVENT_IGNORED;
1148 #endif /* CONFIG_MOUSE */
1150 static enum frame_event_status
1151 frame_ev(struct session *ses, struct document_view *doc_view, struct term_event *ev)
1153 assertm(doc_view && doc_view->document, "document not formatted");
1154 if_assert_failed return FRAME_EVENT_IGNORED;
1156 assert(ses && ev);
1157 if_assert_failed return FRAME_EVENT_IGNORED;
1159 /* When changing frame, vs may be NULL. See bug 525. */
1160 if (!doc_view->vs) return FRAME_EVENT_IGNORED;
1162 switch (ev->ev) {
1163 case EVENT_KBD:
1164 return frame_ev_kbd(ses, doc_view, ev);
1165 #ifdef CONFIG_MOUSE
1166 case EVENT_MOUSE:
1167 return frame_ev_mouse(ses, doc_view, ev);
1168 #endif /* CONFIG_MOUSE */
1169 default:
1170 return FRAME_EVENT_IGNORED;
1174 struct document_view *
1175 current_frame(struct session *ses)
1177 struct document_view *doc_view = NULL;
1178 int current_frame_number;
1180 assert(ses);
1181 if_assert_failed return NULL;
1183 if (!have_location(ses)) return NULL;
1185 current_frame_number = cur_loc(ses)->vs.current_link;
1186 if (current_frame_number == -1) current_frame_number = 0;
1188 foreach (doc_view, ses->scrn_frames) {
1189 if (document_has_frames(doc_view->document)) continue;
1190 if (!current_frame_number--) return doc_view;
1193 doc_view = ses->doc_view;
1195 assert(doc_view && doc_view->document);
1196 if_assert_failed return NULL;
1198 if (document_has_frames(doc_view->document)) return NULL;
1199 return doc_view;
1202 static enum frame_event_status
1203 send_to_frame(struct session *ses, struct document_view *doc_view,
1204 struct term_event *ev)
1206 enum frame_event_status status;
1208 assert(ses && ses->tab && ses->tab->term && ev);
1209 if_assert_failed return FRAME_EVENT_IGNORED;
1211 status = frame_ev(ses, doc_view, ev);
1213 if (status == FRAME_EVENT_REFRESH)
1214 refresh_view(ses, doc_view, 0);
1215 else
1216 print_screen_status(ses);
1218 return status;
1221 #ifdef CONFIG_MOUSE
1222 static int
1223 do_mouse_event(struct session *ses, struct term_event *ev,
1224 struct document_view *doc_view)
1226 struct term_event evv;
1227 struct document_view *matched = NULL, *first = doc_view;
1229 assert(ses && ev);
1230 if_assert_failed return 0;
1232 if (!doc_view) return 0;
1234 do {
1235 assert(doc_view && doc_view->document);
1236 if_assert_failed return 0;
1238 assertm(doc_view->document->options.box.x == doc_view->box.x
1239 && doc_view->document->options.box.y == doc_view->box.y,
1240 "Jonas' 1.565 -> 1.566 patch sucks");
1241 if_assert_failed return 0;
1243 if (check_mouse_position(ev, &doc_view->box)) {
1244 matched = doc_view;
1245 break;
1248 next_frame(ses, 1);
1249 doc_view = current_frame(ses);
1251 } while (doc_view != first);
1253 if (!matched) return 0;
1255 if (doc_view != first) draw_formatted(ses, 0);
1257 set_mouse_term_event(&evv,
1258 ev->info.mouse.x - doc_view->box.x,
1259 ev->info.mouse.y - doc_view->box.y,
1260 ev->info.mouse.button);
1262 return send_to_frame(ses, doc_view, &evv);
1265 static int
1266 is_mouse_on_tab_bar(struct session *ses, struct term_event_mouse *mouse)
1268 struct terminal *term = ses->tab->term;
1269 int y;
1271 if (ses->status.show_tabs_bar_at_top) y = ses->status.show_title_bar;
1272 else y = term->height - 1 - !!ses->status.show_status_bar;
1274 return mouse->y == y;
1276 /** @returns the session if event cleanup should be done or NULL if no
1277 * cleanup is needed. */
1278 static struct session *
1279 send_mouse_event(struct session *ses, struct document_view *doc_view,
1280 struct term_event *ev)
1282 struct terminal *term = ses->tab->term;
1283 struct term_event_mouse *mouse = &ev->info.mouse;
1285 if (mouse->y == 0
1286 && check_mouse_action(ev, B_DOWN)
1287 && !check_mouse_wheel(ev)) {
1288 struct window *m;
1290 activate_bfu_technology(ses, -1);
1291 m = term->windows.next;
1292 m->handler(m, ev);
1294 return ses;
1297 /* Handle tabs navigation if tabs bar is displayed. */
1298 if (ses->status.show_tabs_bar && is_mouse_on_tab_bar(ses, mouse)) {
1299 int tab_num = get_tab_number_by_xpos(term, mouse->x);
1300 struct window *current_tab = get_current_tab(term);
1302 if (check_mouse_action(ev, B_UP)) {
1303 if (check_mouse_button(ev, B_MIDDLE)
1304 && term->current_tab == tab_num
1305 && mouse->y == term->prev_mouse_event.y) {
1306 if (current_tab->data == ses) ses = NULL;
1308 close_tab(term, current_tab->data);
1311 return ses;
1314 if (check_mouse_button(ev, B_WHEEL_UP)) {
1315 switch_current_tab(ses, -1);
1317 } else if (check_mouse_button(ev, B_WHEEL_DOWN)) {
1318 switch_current_tab(ses, 1);
1320 } else if (tab_num != -1) {
1321 switch_to_tab(term, tab_num, -1);
1323 if (check_mouse_button(ev, B_MIDDLE)) {
1324 do_not_ignore_next_mouse_event(term);
1325 } else if (check_mouse_button(ev, B_RIGHT)) {
1326 tab_menu(current_tab->data, mouse->x, mouse->y, 1);
1330 return ses;
1333 if (!do_mouse_event(ses, ev, doc_view)
1334 && check_mouse_button(ev, B_RIGHT)) {
1335 tab_menu(ses, mouse->x, mouse->y, 0);
1336 return NULL;
1339 #ifdef CONFIG_LEDS
1340 if (ses->status.show_leds
1341 && mouse->y == term->height - 1
1342 && mouse->x >= term->width - LEDS_COUNT - 3) {
1343 menu_leds_info(term, NULL, NULL);
1344 return NULL;
1346 #endif
1348 return NULL;
1350 #endif /* CONFIG_MOUSE */
1352 static void
1353 try_typeahead(struct session *ses, struct document_view *doc_view,
1354 struct term_event *ev, enum main_action action_id)
1356 switch (get_opt_int("document.browse.search.typeahead")) {
1357 case 0:
1358 return;
1359 case 1:
1360 action_id = ACT_MAIN_SEARCH_TYPEAHEAD_LINK;
1361 break;
1362 case 2:
1363 action_id = ACT_MAIN_SEARCH_TYPEAHEAD_TEXT;
1364 break;
1365 default:
1366 INTERNAL("invalid value for document.browse.search.typeahead");
1369 search_typeahead(ses, doc_view, action_id);
1371 /* Cross your fingers -- I'm just asking
1372 * for an infinite loop! -- Miciah */
1373 term_send_event(ses->tab->term, ev);
1376 /** @returns the session if event cleanup should be done or NULL if no
1377 * cleanup is needed. */
1378 static struct session *
1379 send_kbd_event(struct session *ses, struct document_view *doc_view,
1380 struct term_event *ev)
1382 int event;
1383 enum main_action action_id;
1385 if (doc_view && send_to_frame(ses, doc_view, ev) != FRAME_EVENT_IGNORED)
1386 return NULL;
1388 action_id = kbd_action(KEYMAP_MAIN, ev, &event);
1390 if (action_id == ACT_MAIN_QUIT) {
1391 if (check_kbd_key(ev, KBD_CTRL_C))
1392 quit:
1393 action_id = ACT_MAIN_REALLY_QUIT;
1396 switch (do_action(ses, action_id, 0)) {
1397 case FRAME_EVENT_SESSION_DESTROYED:
1398 return NULL;
1399 case FRAME_EVENT_IGNORED:
1400 break;
1401 case FRAME_EVENT_OK:
1402 case FRAME_EVENT_REFRESH:
1403 return ses;
1406 if (action_id == ACT_MAIN_SCRIPTING_FUNCTION) {
1407 #ifdef CONFIG_SCRIPTING
1408 trigger_event(event, ses);
1409 #endif
1410 return NULL;
1413 if (check_kbd_key(ev, KBD_CTRL_C)) goto quit;
1415 /* Ctrl-Alt-F should not open the File menu like Alt-f does. */
1416 if (check_kbd_modifier(ev, KBD_MOD_ALT)) {
1417 struct window *win;
1419 get_kbd_modifier(ev) &= ~KBD_MOD_ALT;
1420 activate_bfu_technology(ses, -1);
1421 win = ses->tab->term->windows.next;
1422 win->handler(win, ev);
1423 if (ses->tab->term->windows.next == win) {
1424 deselect_mainmenu(win->term, win->data);
1425 print_screen_status(ses);
1427 if (ses->tab != ses->tab->term->windows.next)
1428 return NULL;
1429 get_kbd_modifier(ev) |= KBD_MOD_ALT;
1431 if (doc_view
1432 && get_opt_int("document.browse.accesskey"
1433 ".priority") <= 0
1434 && try_document_key(ses, doc_view, ev)
1435 == FRAME_EVENT_REFRESH) {
1436 /* The document ate the key! */
1437 refresh_view(ses, doc_view, 0);
1439 return NULL;
1442 return ses;
1445 if (!(get_kbd_modifier(ev) & KBD_MOD_CTRL)) {
1446 if (doc_view) try_typeahead(ses, doc_view, ev, action_id);
1449 return NULL;
1452 void
1453 send_event(struct session *ses, struct term_event *ev)
1455 assert(ses && ev);
1456 if_assert_failed return;
1458 if (ev->ev == EVENT_KBD) {
1459 struct document_view *doc_view = current_frame(ses);
1461 ses = send_kbd_event(ses, doc_view, ev);
1463 #ifdef CONFIG_MOUSE
1464 else if (ev->ev == EVENT_MOUSE) {
1465 struct document_view *doc_view = current_frame(ses);
1467 ses = send_mouse_event(ses, doc_view, ev);
1469 #endif /* CONFIG_MOUSE */
1471 /* @ses may disappear ie. in close_tab() */
1472 if (ses) ses->kbdprefix.repeat_count = 0;
1475 enum frame_event_status
1476 download_link(struct session *ses, struct document_view *doc_view,
1477 action_id_T action_id)
1479 struct link *link = get_current_link(doc_view);
1480 void (*download)(void *ses, unsigned char *file) = start_download;
1482 if (!link) return FRAME_EVENT_OK;
1484 if (ses->download_uri) {
1485 done_uri(ses->download_uri);
1486 ses->download_uri = NULL;
1489 switch (action_id) {
1490 case ACT_MAIN_LINK_DOWNLOAD_RESUME:
1491 download = resume_download;
1492 case ACT_MAIN_LINK_DOWNLOAD:
1493 ses->download_uri = get_link_uri(ses, doc_view, link);
1494 break;
1496 case ACT_MAIN_LINK_DOWNLOAD_IMAGE:
1497 if (!link->where_img) break;
1498 ses->download_uri = get_uri(link->where_img, 0);
1499 break;
1501 default:
1502 INTERNAL("I think you forgot to take your medication, mental boy!");
1503 return FRAME_EVENT_OK;
1506 if (!ses->download_uri) return FRAME_EVENT_OK;
1508 set_session_referrer(ses, doc_view->document->uri);
1509 query_file(ses, ses->download_uri, ses, download, NULL, 1);
1511 return FRAME_EVENT_OK;
1514 enum frame_event_status
1515 view_image(struct session *ses, struct document_view *doc_view, int xxxx)
1517 struct link *link = get_current_link(doc_view);
1519 if (link && link->where_img)
1520 goto_url(ses, link->where_img);
1522 return FRAME_EVENT_OK;
1525 enum frame_event_status
1526 save_as(struct session *ses, struct document_view *doc_view, int magic)
1528 assert(ses);
1529 if_assert_failed return FRAME_EVENT_OK;
1531 if (!have_location(ses)) return FRAME_EVENT_OK;
1533 if (ses->download_uri) done_uri(ses->download_uri);
1534 ses->download_uri = get_uri_reference(cur_loc(ses)->vs.uri);
1536 assert(doc_view && doc_view->document && doc_view->document->uri);
1537 if_assert_failed return FRAME_EVENT_OK;
1539 set_session_referrer(ses, doc_view->document->uri);
1540 query_file(ses, ses->download_uri, ses, start_download, NULL, 1);
1542 return FRAME_EVENT_OK;
1545 static void
1546 save_formatted_finish(struct terminal *term, int h, void *data, int resume)
1548 struct document *document = data;
1550 assert(term && document);
1551 if_assert_failed return;
1553 if (h == -1) return;
1554 if (dump_to_file(document, h)) {
1555 info_box(term, 0, N_("Save error"), ALIGN_CENTER,
1556 N_("Error writing to file"));
1558 close(h);
1561 static void
1562 save_formatted(void *data, unsigned char *file)
1564 struct session *ses = data;
1565 struct document_view *doc_view;
1567 assert(ses && ses->tab && ses->tab->term && file);
1568 if_assert_failed return;
1569 doc_view = current_frame(ses);
1570 assert(doc_view && doc_view->document);
1571 if_assert_failed return;
1573 create_download_file(ses->tab->term, file, NULL, 0, 0,
1574 save_formatted_finish, doc_view->document);
1577 enum frame_event_status
1578 save_formatted_dlg(struct session *ses, struct document_view *doc_view, int xxxx)
1580 query_file(ses, doc_view->vs->uri, ses, save_formatted, NULL, 1);
1581 return FRAME_EVENT_OK;