Color trailing spaces with color of last non-space cell
[elinks.git] / src / viewer / text / draw.c
blobfe3dd2dda6b7469351f4d96a7c15ae8c326a4270
1 /** Text mode drawing functions
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/dialog.h"
17 #include "cache/cache.h"
18 #include "document/document.h"
19 #include "document/html/frames.h"
20 #include "document/options.h"
21 #include "document/refresh.h"
22 #include "document/renderer.h"
23 #include "document/view.h"
24 #include "dialogs/status.h" /* print_screen_status() */
25 #include "intl/charsets.h"
26 #include "intl/gettext/libintl.h"
27 #include "protocol/uri.h"
28 #include "session/location.h"
29 #include "session/session.h"
30 #include "terminal/draw.h"
31 #include "terminal/tab.h"
32 #include "terminal/terminal.h"
33 #include "util/error.h"
34 #include "util/lists.h"
35 #include "util/memory.h"
36 #include "util/string.h"
37 #include "viewer/text/draw.h"
38 #include "viewer/text/form.h"
39 #include "viewer/text/link.h"
40 #include "viewer/text/search.h"
41 #include "viewer/text/view.h" /* current_frame() */
42 #include "viewer/text/vs.h"
45 static inline int
46 check_document_fragment(struct session *ses, struct document_view *doc_view)
48 struct document *document = doc_view->document;
49 struct uri *uri = doc_view->vs->uri;
50 int vy;
51 struct string fragment;
53 assert(uri->fragmentlen);
55 if (!init_string(&fragment)) return -2;
56 if (!add_uri_to_string(&fragment, uri, URI_FRAGMENT)) {
57 done_string(&fragment);
58 return -2;
60 decode_uri_string(&fragment);
61 assert(fragment.length);
62 assert(*fragment.source);
64 /* Omit the leading '#' when calling find_tag. */
65 vy = find_tag(document, fragment.source + 1, fragment.length - 1);
66 if (vy == -1) {
67 struct cache_entry *cached = document->cached;
69 assert(cached);
70 if (cached->incomplete || cached->cache_id != document->cache_id) {
71 done_string(&fragment);
72 return -2;
75 if (get_opt_bool("document.browse.links.missing_fragment",
76 ses)) {
77 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
78 N_("Missing fragment"), ALIGN_CENTER,
79 msg_text(ses->tab->term, N_("The requested fragment "
80 "\"%s\" doesn't exist."),
81 fragment.source));
83 } else {
84 int_bounds(&vy, 0, document->height - 1);
87 done_string(&fragment);
89 return vy;
92 static void
93 draw_frame_lines(struct terminal *term, struct frameset_desc *frameset_desc,
94 int xp, int yp, struct color_pair *colors)
96 int y, j;
98 assert(term && frameset_desc && frameset_desc->frame_desc);
99 if_assert_failed return;
101 y = yp - 1;
102 for (j = 0; j < frameset_desc->box.height; j++) {
103 int x, i;
104 int height = frameset_desc->frame_desc[j * frameset_desc->box.width].height;
106 x = xp - 1;
107 for (i = 0; i < frameset_desc->box.width; i++) {
108 int width = frameset_desc->frame_desc[i].width;
110 if (i) {
111 struct box box;
113 set_box(&box, x, y + 1, 1, height);
114 draw_box(term, &box, BORDER_SVLINE, SCREEN_ATTR_FRAME, colors);
116 if (j == frameset_desc->box.height - 1)
117 draw_border_cross(term, x, y + height + 1,
118 BORDER_X_UP, colors);
119 } else if (j) {
120 if (x >= 0)
121 draw_border_cross(term, x, y,
122 BORDER_X_RIGHT, colors);
125 if (j) {
126 struct box box;
128 set_box(&box, x + 1, y, width, 1);
129 draw_box(term, &box, BORDER_SHLINE, SCREEN_ATTR_FRAME, colors);
131 if (i == frameset_desc->box.width - 1
132 && x + width + 1 < term->width)
133 draw_border_cross(term, x + width + 1, y,
134 BORDER_X_LEFT, colors);
135 } else if (i) {
136 draw_border_cross(term, x, y, BORDER_X_DOWN, colors);
139 if (i && j)
140 draw_border_char(term, x, y, BORDER_SCROSS, colors);
142 x += width + 1;
144 y += height + 1;
147 y = yp - 1;
148 for (j = 0; j < frameset_desc->box.height; j++) {
149 int x, i;
150 int pj = j * frameset_desc->box.width;
151 int height = frameset_desc->frame_desc[pj].height;
153 x = xp - 1;
154 for (i = 0; i < frameset_desc->box.width; i++) {
155 int width = frameset_desc->frame_desc[i].width;
156 int p = pj + i;
158 if (frameset_desc->frame_desc[p].subframe) {
159 draw_frame_lines(term, frameset_desc->frame_desc[p].subframe,
160 x + 1, y + 1, colors);
162 x += width + 1;
164 y += height + 1;
168 static void
169 draw_view_status(struct session *ses, struct document_view *doc_view, int active)
171 struct terminal *term = ses->tab->term;
173 draw_forms(term, doc_view);
174 if (active) {
175 draw_searched(term, doc_view);
176 draw_current_link(ses, doc_view);
180 /** Checks if there is a link under the cursor so it can become the current
181 * highlighted link. */
182 static void
183 check_link_under_cursor(struct session *ses, struct document_view *doc_view)
185 int x = ses->tab->x;
186 int y = ses->tab->y;
187 struct box *box = &doc_view->box;
188 struct link *link;
190 link = get_link_at_coordinates(doc_view, x - box->x, y - box->y);
191 if (link && link != get_current_link(doc_view)) {
192 doc_view->vs->current_link = link - doc_view->document->links;
196 /** Puts the formatted document on the given terminal's screen.
197 * @a active indicates whether the document is focused -- i.e.,
198 * whether it is displayed in the selected frame or document. */
199 static void
200 draw_doc(struct session *ses, struct document_view *doc_view, int active)
202 struct color_pair color;
203 struct view_state *vs;
204 struct terminal *term;
205 struct box *box;
206 struct screen_char *last = NULL;
208 int vx, vy;
209 int y;
211 assert(ses && ses->tab && ses->tab->term && doc_view);
212 if_assert_failed return;
214 box = &doc_view->box;
215 term = ses->tab->term;
217 /* The code in this function assumes that both width and height are
218 * bigger than 1 so we have to bail out here. */
219 if (box->width < 2 || box->height < 2) return;
221 if (active) {
222 /* When redrawing the document after things like link menu we
223 * have to reset the cursor routing state. */
224 if (ses->navigate_mode == NAVIGATE_CURSOR_ROUTING) {
225 set_cursor(term, ses->tab->x, ses->tab->y, 0);
226 } else {
227 set_cursor(term, box->x + box->width - 1, box->y + box->height - 1, 1);
228 set_window_ptr(ses->tab, box->x, box->y);
232 color.foreground = get_opt_color("document.colors.text", ses);
233 color.background = doc_view->document->height
234 ? doc_view->document->color.background
235 : get_opt_color("document.colors.background", ses);
237 vs = doc_view->vs;
238 if (!vs) {
239 draw_box(term, box, ' ', 0, &color);
240 return;
243 if (document_has_frames(doc_view->document)) {
244 draw_box(term, box, ' ', 0, &color);
245 draw_frame_lines(term, doc_view->document->frame_desc, box->x, box->y, &color);
246 if (vs->current_link == -1)
247 vs->current_link = 0;
248 return;
251 if (ses->navigate_mode == NAVIGATE_LINKWISE) {
252 check_vs(doc_view);
254 } else {
255 check_link_under_cursor(ses, doc_view);
258 if (!vs->did_fragment) {
259 vy = check_document_fragment(ses, doc_view);
261 if (vy != -2) vs->did_fragment = 1;
262 if (vy >= 0) {
263 doc_view->vs->y = vy;
264 set_link(doc_view);
267 vx = vs->x;
268 vy = vs->y;
269 if (doc_view->last_x != -1
270 && doc_view->last_x == vx
271 && doc_view->last_y == vy
272 && !has_search_word(doc_view)) {
273 clear_link(term, doc_view);
274 draw_view_status(ses, doc_view, active);
275 return;
277 doc_view->last_x = vx;
278 doc_view->last_y = vy;
279 draw_box(term, box, ' ', 0, &color);
280 if (!doc_view->document->height) return;
282 while (vs->y >= doc_view->document->height) vs->y -= box->height;
283 int_lower_bound(&vs->y, 0);
284 if (vy != vs->y) {
285 vy = vs->y;
286 if (ses->navigate_mode == NAVIGATE_LINKWISE)
287 check_vs(doc_view);
289 for (y = int_max(vy, 0);
290 y < int_min(doc_view->document->height, box->height + vy);
291 y++) {
292 struct screen_char *first = NULL;
293 int i, j;
294 int last_index = 0;
295 int st = int_max(vx, 0);
296 int en = int_min(doc_view->document->data[y].length,
297 box->width + vx);
298 int max = int_min(en, st + 200);
300 if (en - st > 0) {
301 draw_line(term, box->x + st - vx, box->y + y - vy,
302 en - st,
303 &doc_view->document->data[y].chars[st]);
305 for (i = en - 1; i > 0; --i) {
306 if (doc_view->document->data[y].chars[i].data != ' ') {
307 last = &doc_view->document->data[y].chars[i];
308 last_index = i + 1;
309 break;
313 for (i = st; i < max; i++) {
314 if (doc_view->document->data[y].chars[i].data != ' ') {
315 first = &doc_view->document->data[y].chars[i];
316 break;
320 for (j = st; j < i; j++) {
321 draw_space(term, box->x + j - vx, box->y + y - vy,
322 first);
325 for (i = last_index; i < box->width + vx; i++) {
326 draw_space(term, box->x + i - vx, box->y + y - vy,
327 last);
330 draw_view_status(ses, doc_view, active);
331 if (has_search_word(doc_view))
332 doc_view->last_x = doc_view->last_y = -1;
335 static void
336 draw_frames(struct session *ses)
338 struct document_view *doc_view, *current_doc_view;
339 int *l;
340 int n, d;
342 assert(ses && ses->doc_view && ses->doc_view->document);
343 if_assert_failed return;
345 if (!document_has_frames(ses->doc_view->document)) return;
347 n = 0;
348 foreach (doc_view, ses->scrn_frames) {
349 doc_view->last_x = doc_view->last_y = -1;
350 n++;
352 l = &cur_loc(ses)->vs.current_link;
353 *l = int_max(*l, 0) % int_max(n, 1);
355 current_doc_view = current_frame(ses);
356 d = 0;
357 while (1) {
358 int more = 0;
360 foreach (doc_view, ses->scrn_frames) {
361 if (doc_view->depth == d)
362 draw_doc(ses, doc_view, doc_view == current_doc_view);
363 else if (doc_view->depth > d)
364 more = 1;
367 if (!more) break;
368 d++;
372 /** @todo @a rerender is ridiciously wound-up. */
373 void
374 draw_formatted(struct session *ses, int rerender)
376 assert(ses && ses->tab);
377 if_assert_failed return;
379 if (rerender) {
380 rerender--; /* Mind this when analyzing @rerender. */
381 if (!(rerender & 2) && session_is_loading(ses))
382 rerender |= 2;
383 render_document_frames(ses, rerender);
385 /* Rerendering kills the document refreshing so restart it. */
386 start_document_refreshes(ses);
389 if (ses->tab != get_current_tab(ses->tab->term))
390 return;
392 if (!ses->doc_view || !ses->doc_view->document) {
393 /*INTERNAL("document not formatted");*/
394 struct box box;
396 set_box(&box, 0, 1,
397 ses->tab->term->width,
398 ses->tab->term->height - 2);
399 draw_box(ses->tab->term, &box, ' ', 0, NULL);
400 return;
403 if (!ses->doc_view->vs && have_location(ses))
404 ses->doc_view->vs = &cur_loc(ses)->vs;
405 ses->doc_view->last_x = ses->doc_view->last_y = -1;
407 refresh_view(ses, ses->doc_view, 1);
410 void
411 refresh_view(struct session *ses, struct document_view *doc_view, int frames)
413 /* If refresh_view() is being called because the value of a
414 * form field has changed, @ses might not be in the current
415 * tab: consider SELECT pop-ups behind which -remote loads
416 * another tab, or setTimeout in ECMAScript. */
417 if (ses->tab == get_current_tab(ses->tab->term)) {
418 draw_doc(ses, doc_view, 1);
419 if (frames) draw_frames(ses);
421 print_screen_status(ses);