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