1 /*
2  * Copyright 2017 Vincent Sanders <vince@netsurf-browser.org>
3  *
4  * This file is part of NetSurf, http://www.netsurf-browser.org/
5  *
6  * NetSurf is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * NetSurf is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /**
20  * \file
21  * Local history viewer implementation
22  */
23 
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include "utils/nsurl.h"
28 #include "utils/errors.h"
29 
30 #include "netsurf/types.h"
31 #include "netsurf/layout.h"
32 #include "netsurf/browser_window.h"
33 #include "netsurf/core_window.h"
34 #include "netsurf/plotters.h"
35 #include "netsurf/keypress.h"
36 
37 #include "utils/nscolour.h"
38 
39 #include "desktop/cw_helper.h"
40 #include "desktop/gui_internal.h"
41 #include "desktop/system_colour.h"
42 #include "desktop/browser_private.h"
43 #include "desktop/browser_history.h"
44 #include "desktop/local_history.h"
45 
46 /**
47  * local history viewer context
48  */
49 struct local_history_session {
50 	struct browser_window *bw;
51 	struct core_window_callback_table *cw_t;
52 	void *core_window_handle;
53 	struct history_entry *cursor;
54 };
55 
56 
57 /**
58  * plot style for drawing lines between nodes
59  */
60 static plot_style_t pstyle_line = {
61 	.stroke_type = PLOT_OP_TYPE_SOLID,
62 	.stroke_width = plot_style_int_to_fixed(2),
63 };
64 
65 
66 /**
67  * plot style for drawing background
68  */
69 static plot_style_t pstyle_bg = {
70 	.fill_type = PLOT_OP_TYPE_SOLID,
71 };
72 
73 
74 /**
75  * plot style for drawing rectangle round unselected nodes
76  */
77 static plot_style_t pstyle_rect = {
78 	.stroke_type = PLOT_OP_TYPE_SOLID,
79 	.stroke_width = plot_style_int_to_fixed(1),
80 };
81 
82 
83 /**
84  * plot style for drawing rectangle round selected nodes
85  */
86 static plot_style_t pstyle_rect_sel = {
87 	.stroke_type = PLOT_OP_TYPE_SOLID,
88 	.stroke_width = plot_style_int_to_fixed(3),
89 };
90 
91 
92 /**
93  * plot style for drawing rectangle round the cursor node
94  */
95 static plot_style_t pstyle_rect_cursor = {
96 	.stroke_type = PLOT_OP_TYPE_DASH,
97 	.stroke_width = plot_style_int_to_fixed(3),
98 };
99 
100 
101 /**
102  * plot style for font on unselected nodes
103  */
104 static plot_font_style_t pfstyle_node = {
105 	.family = PLOT_FONT_FAMILY_SANS_SERIF,
106 	.size = 8 * PLOT_STYLE_SCALE,
107 	.weight = 400,
108 	.flags = FONTF_NONE,
109 };
110 
111 
112 /**
113  * plot style for font on unselected nodes
114  */
115 static plot_font_style_t pfstyle_node_sel = {
116 	.family = PLOT_FONT_FAMILY_SANS_SERIF,
117 	.size = 8 * PLOT_STYLE_SCALE,
118 	.weight = 900,
119 	.flags = FONTF_NONE,
120 };
121 
122 
123 /**
124  * Recursively redraw a history entry.
125  *
126  * \param history history containing the entry
127  * \param entry   entry to render
128  * \param clip    redraw area
129  * \param x       window x offset
130  * \param y       window y offset
131  * \param ctx     current redraw context
132  */
133 static nserror
redraw_entry(struct history * history,struct history_entry * entry,struct history_entry * cursor,struct rect * clip,int x,int y,const struct redraw_context * ctx)134 redraw_entry(struct history *history,
135 	     struct history_entry *entry,
136 	     struct history_entry *cursor,
137 	     struct rect *clip,
138 	     int x, int y,
139 	     const struct redraw_context *ctx)
140 {
141 	size_t char_offset;
142 	int actual_x;
143 	struct history_entry *child;
144 	int tailsize = 5;
145 
146 	plot_style_t *pstyle;
147 	plot_font_style_t *pfstyle;
148 	struct rect rect;
149 	nserror res;
150 
151 	/* setup plot styles */
152 	if (entry == history->current) {
153 		pstyle = &pstyle_rect_sel;
154 		pfstyle = &pfstyle_node_sel;
155 	} else {
156 		pstyle = &pstyle_rect;
157 		pfstyle = &pfstyle_node;
158 	}
159 
160 	/* Only attempt to plot bitmap if it is present */
161 	if (entry->page.bitmap != NULL) {
162 		res = ctx->plot->bitmap(ctx,
163 					entry->page.bitmap,
164 					entry->x + x,
165 					entry->y + y,
166 					LOCAL_HISTORY_WIDTH,
167 					LOCAL_HISTORY_HEIGHT,
168 					0xffffff,
169 					0);
170 		if (res != NSERROR_OK) {
171 			return res;
172 		}
173 	}
174 
175 	rect.x0 = entry->x - 1 + x;
176 	rect.y0 = entry->y - 1 + y;
177 	rect.x1 = entry->x + x + LOCAL_HISTORY_WIDTH;
178 	rect.y1 = entry->y + y + LOCAL_HISTORY_HEIGHT;
179 
180 	/* Border */
181 	if (entry != cursor) {
182 		/* Not cursor position */
183 		res = ctx->plot->rectangle(ctx, pstyle, &rect);
184 		if (res != NSERROR_OK) {
185 			return res;
186 		}
187 	} else {
188 		/* Cursor position */
189 		rect.x0 -= 1;
190 		rect.y0 -= 1;
191 		rect.x1 += 1;
192 		rect.y1 += 1;
193 		ctx->plot->rectangle(ctx, &pstyle_rect_cursor, &rect);
194 	}
195 
196 	res = guit->layout->position(plot_style_font, entry->page.title,
197 				     strlen(entry->page.title), LOCAL_HISTORY_WIDTH,
198 				     &char_offset, &actual_x);
199 	if (res != NSERROR_OK) {
200 		return res;
201 	}
202 
203 	res = ctx->plot->text(ctx,
204 			      pfstyle,
205 			      entry->x + x,
206 			      entry->y + LOCAL_HISTORY_HEIGHT + 12 + y,
207 			      entry->page.title,
208 			      char_offset);
209 	if (res != NSERROR_OK) {
210 		return res;
211 	}
212 
213 	/* for each child node draw a line and recurse redraw into it */
214 	for (child = entry->forward; child; child = child->next) {
215 		rect.x0 = entry->x + LOCAL_HISTORY_WIDTH + x;
216 		rect.y0 = entry->y + LOCAL_HISTORY_HEIGHT / 2 + y;
217 		rect.x1 = entry->x + LOCAL_HISTORY_WIDTH + tailsize + x;
218 		rect.y1 = entry->y + LOCAL_HISTORY_HEIGHT / 2 + y;
219 		res = ctx->plot->line(ctx, &pstyle_line, &rect);
220 		if (res != NSERROR_OK) {
221 			return res;
222 		}
223 
224 		rect.x0 = entry->x + LOCAL_HISTORY_WIDTH + tailsize + x;
225 		rect.y0 = entry->y + LOCAL_HISTORY_HEIGHT / 2 + y;
226 		rect.x1 = child->x - tailsize + x;
227 		rect.y1 = child->y + LOCAL_HISTORY_HEIGHT / 2 + y;
228 		res = ctx->plot->line(ctx, &pstyle_line, &rect);
229 		if (res != NSERROR_OK) {
230 			return res;
231 		}
232 
233 		rect.x0 = child->x - tailsize + x;
234 		rect.y0 = child->y + LOCAL_HISTORY_HEIGHT / 2 + y;
235 		rect.x1 = child->x + x;
236 		rect.y1 = child->y + LOCAL_HISTORY_HEIGHT / 2 + y;
237 		res = ctx->plot->line(ctx, &pstyle_line, &rect);
238 		if (res != NSERROR_OK) {
239 			return res;
240 		}
241 
242 		res = redraw_entry(history, child, cursor, clip, x, y, ctx);
243 		if (res != NSERROR_OK) {
244 			return res;
245 		}
246 	}
247 
248 	return NSERROR_OK;
249 }
250 
251 
252 /**
253  * Find the history entry at a position.
254  *
255  * \param entry entry to search from
256  * \param x coordinate
257  * \param y coordinate
258  * \return an entry if found, 0 if none
259  */
260 static struct history_entry *
find_entry_position(struct history_entry * entry,int x,int y)261 find_entry_position(struct history_entry *entry, int x, int y)
262 {
263 	struct history_entry *child;
264 	struct history_entry *found;
265 
266 	if (!entry) {
267 		return NULL;
268 	}
269 
270 	if ((entry->x <= x) &&
271 	    (x <= entry->x + LOCAL_HISTORY_WIDTH) &&
272 	    (entry->y <= y) &&
273 	    (y <= entry->y + LOCAL_HISTORY_HEIGHT)) {
274 		return entry;
275 	}
276 
277 	for (child = entry->forward; child; child = child->next) {
278 		found = find_entry_position(child, x, y);
279 		if (found) {
280 			return found;
281 		}
282 	}
283 
284 	return NULL;
285 }
286 
287 /* exported interface documented in desktop/local_history.h */
288 nserror
local_history_scroll_to_cursor(struct local_history_session * session)289 local_history_scroll_to_cursor(struct local_history_session *session)
290 {
291 	rect cursor;
292 
293 	if (session->cursor == NULL) {
294 		return NSERROR_OK;
295 	}
296 
297 	cursor.x0 = session->cursor->x - LOCAL_HISTORY_RIGHT_MARGIN / 2;
298 	cursor.y0 = session->cursor->y - LOCAL_HISTORY_BOTTOM_MARGIN / 2;
299 	cursor.x1 = cursor.x0 + LOCAL_HISTORY_WIDTH +
300 			LOCAL_HISTORY_RIGHT_MARGIN / 2;
301 	cursor.y1 = cursor.y0 + LOCAL_HISTORY_HEIGHT +
302 			LOCAL_HISTORY_BOTTOM_MARGIN / 2;
303 
304 	return cw_helper_scroll_visible(session->cw_t,
305 			session->core_window_handle,
306 			&cursor);
307 }
308 
309 /* exported interface documented in desktop/local_history.h */
310 nserror
local_history_init(struct core_window_callback_table * cw_t,void * core_window_handle,struct browser_window * bw,struct local_history_session ** session)311 local_history_init(struct core_window_callback_table *cw_t,
312 		   void *core_window_handle,
313 		   struct browser_window *bw,
314 		   struct local_history_session **session)
315 {
316 	struct local_history_session *nses;
317 
318 	pstyle_bg.fill_colour = nscolours[NSCOLOUR_WIN_EVEN_BG];
319 	pstyle_line.stroke_colour = nscolours[NSCOLOUR_WIN_EVEN_BORDER];
320 
321 	pstyle_rect.stroke_colour = pstyle_line.stroke_colour;
322 	pstyle_rect_sel.stroke_colour = nscolours[NSCOLOUR_WIN_EVEN_BORDER];
323 	pstyle_rect_cursor.stroke_colour = nscolours[NSCOLOUR_SEL_BG];
324 
325 	pfstyle_node.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG];
326 	pfstyle_node.background = nscolours[NSCOLOUR_WIN_EVEN_BG];
327 	pfstyle_node_sel.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG];
328 	pfstyle_node_sel.background = nscolours[NSCOLOUR_WIN_EVEN_BG];
329 
330 	nses = calloc(1, sizeof(struct local_history_session));
331 	if (nses == NULL) {
332 		return NSERROR_NOMEM;
333 	}
334 
335 	nses->cw_t = cw_t;
336 	nses->core_window_handle = core_window_handle;
337 
338 	local_history_set(nses, bw);
339 
340 	*session = nses;
341 
342 	return NSERROR_OK;
343 }
344 
345 /* exported interface documented in desktop/local_history.h */
local_history_fini(struct local_history_session * session)346 nserror local_history_fini(struct local_history_session *session)
347 {
348 	free(session);
349 
350 	return NSERROR_OK;
351 }
352 
353 
354 /* exported interface documented in desktop/local_history.h */
355 nserror
local_history_redraw(struct local_history_session * session,int x,int y,struct rect * clip,const struct redraw_context * ctx)356 local_history_redraw(struct local_history_session *session,
357 		     int x,
358 		     int y,
359 		     struct rect *clip,
360 		     const struct redraw_context *ctx)
361 {
362 	struct rect r = {
363 		.x0 = clip->x0 + x,
364 		.y0 = clip->y0 + y,
365 		.x1 = clip->x1 + x,
366 		.y1 = clip->y1 + y,
367 	};
368 
369 	if (session->bw == NULL) {
370 		return NSERROR_OK;
371 	}
372 
373 	if (session->bw->history->start == NULL) {
374 		return NSERROR_OK;
375 	}
376 
377 	ctx->plot->clip(ctx, &r);
378 	ctx->plot->rectangle(ctx, &pstyle_bg, &r);
379 
380 	return redraw_entry(
381 		session->bw->history,
382 		session->bw->history->start,
383 		session->cursor,
384 		clip,
385 		x, y,
386 		ctx);
387 }
388 
389 /* exported interface documented in desktop/local_history.h */
390 nserror
local_history_mouse_action(struct local_history_session * session,enum browser_mouse_state mouse,int x,int y)391 local_history_mouse_action(struct local_history_session *session,
392 			   enum browser_mouse_state mouse,
393 			   int x,
394 			   int y)
395 {
396 	struct history_entry *entry;
397 	bool new_window;
398 
399 	if (session->bw == NULL) {
400 		return NSERROR_BAD_PARAMETER;
401 	}
402 
403 	if ((mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2)) == 0) {
404 		return NSERROR_NOT_IMPLEMENTED;
405 	}
406 
407 	entry = find_entry_position(session->bw->history->start, x, y);
408 	if (entry == NULL) {
409 		return NSERROR_NOT_FOUND;
410 	}
411 
412 	if (entry == session->bw->history->current) {
413 		return NSERROR_PERMISSION;
414 	}
415 
416 	if (mouse & BROWSER_MOUSE_PRESS_1) {
417 		new_window = false;
418 	} else if (mouse & BROWSER_MOUSE_PRESS_2) {
419 		new_window = true;
420 	} else {
421 		new_window = false;
422 	}
423 
424 	browser_window_history_go(session->bw, entry, new_window);
425 
426 	return NSERROR_OK;
427 }
428 
429 /**
430  * Determine the point on the parent line where this history line branches.
431  *
432  * If `branch_point` gets set then there is a guarantee that (a) `ent` is
433  * a transitive child (forward) of that point. and (b) `branch_point` has a
434  * parent.
435  *
436  * \param[in] ent The entry to work backward from
437  * \param[out] branch_point The entry to set to the branch point if one is found
438  */
439 static void
_local_history_find_branch_point(struct history_entry * ent,struct history_entry ** branch_point)440 _local_history_find_branch_point(struct history_entry *ent,
441 				 struct history_entry **branch_point)
442 {
443 	if (ent->back == NULL) {
444 		/* We're at the root, nothing to do */
445 		return;
446 	}
447 	/* Start from our immediate parent */
448 	ent = ent->back;
449 	while (ent->back != NULL) {
450 		if (ent->back->forward != ent->back->forward_last) {
451 			/* This point is a branch */
452 			*branch_point = ent;
453 			break;
454 		}
455 		ent = ent->back;
456 	}
457 }
458 
459 /* exported interface documented in desktop/local_history.h */
460 bool
local_history_keypress(struct local_history_session * session,uint32_t key)461 local_history_keypress(struct local_history_session *session, uint32_t key)
462 {
463 	switch (key) {
464 	case NS_KEY_NL:
465 	case NS_KEY_CR:
466 		/* pressed enter */
467 		if (session->cursor != session->bw->history->current) {
468 			browser_window_history_go(session->bw, session->cursor,
469 						  false);
470 			local_history_scroll_to_cursor(session);
471 			session->cw_t->invalidate(session->core_window_handle, NULL);
472 		}
473 		/* We have handled this keypress */
474 		return true;
475 	case NS_KEY_LEFT:
476 		/* Go to parent */
477 		if (session->cursor->back != NULL) {
478 			session->cursor = session->cursor->back;
479 			local_history_scroll_to_cursor(session);
480 			session->cw_t->invalidate(session->core_window_handle, NULL);
481 		}
482 		/* We have handled this keypress */
483 		return true;
484 	case NS_KEY_RIGHT:
485 		/* Go to preferred child if there is one */
486 		if (session->cursor->forward_pref != NULL) {
487 			session->cursor = session->cursor->forward_pref;
488 			local_history_scroll_to_cursor(session);
489 			session->cw_t->invalidate(session->core_window_handle, NULL);
490 		}
491 		/* We have handled this keypress */
492 		return true;
493 	case NS_KEY_DOWN:
494 		/* Go to next sibling down, if there is one */
495 		if (session->cursor->next != NULL) {
496 			session->cursor = session->cursor->next;
497 		} else {
498 			struct history_entry *branch_point = NULL;
499 			_local_history_find_branch_point(
500 				session->cursor,
501 				&branch_point);
502 			if (branch_point != NULL) {
503 				if (branch_point->next != NULL) {
504 					branch_point = branch_point->next;
505 				}
506 				session->cursor = branch_point;
507 			}
508 		}
509 		/* We have handled this keypress */
510 		local_history_scroll_to_cursor(session);
511 		session->cw_t->invalidate(session->core_window_handle, NULL);
512 		return true;
513 	case NS_KEY_UP:
514 		/* Go to next sibling up, if there is one */
515 		if (session->cursor->back != NULL) {
516 			struct history_entry *ent = session->cursor->back->forward;
517 			while (ent != session->cursor &&
518 			       ent->next != NULL &&
519 			       ent->next != session->cursor) {
520 				ent = ent->next;
521 			}
522 			if (session->cursor != ent) {
523 				session->cursor = ent;
524 			} else {
525 				struct history_entry *branch_point = NULL;
526 				_local_history_find_branch_point(
527 					session->cursor,
528 					&branch_point);
529 				if (branch_point != NULL) {
530 					struct history_entry *ent = branch_point->back->forward;
531 					while (ent->next != NULL && ent->next != branch_point) {
532 						ent = ent->next;
533 					}
534 					session->cursor = ent;
535 				}
536 			}
537 		}
538 		/* We have handled this keypress */
539 		local_history_scroll_to_cursor(session);
540 		session->cw_t->invalidate(session->core_window_handle, NULL);
541 		return true;
542 	}
543 	return false;
544 }
545 
546 /* exported interface documented in desktop/local_history.h */
547 nserror
local_history_set(struct local_history_session * session,struct browser_window * bw)548 local_history_set(struct local_history_session *session,
549 		  struct browser_window *bw)
550 {
551 	session->bw = bw;
552 	session->cursor = NULL;
553 
554 	if (bw != NULL) {
555 		assert(session->bw->history != NULL);
556 		session->cursor = bw->history->current;
557 
558 		session->cw_t->update_size(session->core_window_handle,
559 					   session->bw->history->width,
560 					   session->bw->history->height);
561 		local_history_scroll_to_cursor(session);
562 	}
563 
564 	return NSERROR_OK;
565 }
566 
567 
568 /* exported interface documented in desktop/local_history.h */
569 nserror
local_history_get_size(struct local_history_session * session,int * width,int * height)570 local_history_get_size(struct local_history_session *session,
571 		       int *width,
572 		       int *height)
573 {
574 	*width = session->bw->history->width + 20;
575 	*height = session->bw->history->height + 20;
576 
577 	return NSERROR_OK;
578 }
579 
580 
581 /* exported interface documented in desktop/local_history.h */
582 nserror
local_history_get_url(struct local_history_session * session,int x,int y,nsurl ** url_out)583 local_history_get_url(struct local_history_session *session,
584 		      int x, int y,
585 		      nsurl **url_out)
586 {
587 	struct history_entry *entry;
588 
589 	if (session->bw == NULL) {
590 		return NSERROR_BAD_PARAMETER;
591 	}
592 
593 	entry = find_entry_position(session->bw->history->start, x, y);
594 	if (entry == NULL) {
595 		return NSERROR_NOT_FOUND;
596 	}
597 
598 	*url_out = nsurl_ref(entry->page.url);
599 
600 	return NSERROR_OK;
601 }
602