1 /*
2 * marker-preview.c
3 *
4 * Copyright (C) 2017-2020 - 2018 Fabio Colacio
5 *
6 * Marker is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public License as
8 * published by the Free Software Foundation; either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * Marker is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with Marker; see the file LICENSE.md. If not,
18 * see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22 #include <string.h>
23 #include <stdlib.h>
24
25 #include <glib.h>
26 #include <time.h>
27
28 #include "marker-markdown.h"
29 #include "marker-prefs.h"
30
31 #include "marker-string.h"
32
33 #include "marker-preview.h"
34 #include "marker.h"
35
36 #define MAX_ZOOM 4.0
37 #define MIN_ZOOM 0.1
38
39 #define SCROLL_STEP 25
40 #define SCROLL_STEP_SCRIPT "window.scrollBy(%d,%d);"
41 #define SCROLL_SCRIPT "window.scrollTo(%d,%d);"
42
43 #define min(a, b) ((a < b) ? a : b)
44 #define max(a, b) ((a < b) ? b : a)
45
46 struct _MarkerPreview
47 {
48 WebKitWebView parent_instance;
49 };
50
G_DEFINE_TYPE(MarkerPreview,marker_preview,WEBKIT_TYPE_WEB_VIEW)51 G_DEFINE_TYPE(MarkerPreview, marker_preview, WEBKIT_TYPE_WEB_VIEW)
52
53 static gboolean
54 open_uri (WebKitPolicyDecision *decision) {
55 WebKitNavigationPolicyDecision *nav_dec = WEBKIT_NAVIGATION_POLICY_DECISION(decision);
56 WebKitNavigationAction *action = webkit_navigation_policy_decision_get_navigation_action (nav_dec);
57 WebKitURIRequest *request = webkit_navigation_action_get_request (action);
58 /* Open only http requests in default browser */
59 /* FIXME: Open also other request like ftp in default browser */
60 if (webkit_uri_request_get_http_method(request) != NULL) {
61 const gchar * uri = webkit_uri_request_get_uri(request);
62 GtkApplication * app = marker_get_app();
63 GList* windows = gtk_application_get_windows(app);
64 time_t now = time(0);
65 gtk_show_uri_on_window (windows->data, uri, now, NULL);
66 webkit_policy_decision_ignore(decision);
67 return TRUE;
68 }
69 return FALSE;
70 }
71
72 static gboolean
navigate(WebKitPolicyDecision * decision)73 navigate(WebKitPolicyDecision *decision)
74 {
75 /** TODO FIX internal navigation
76 WebKitNavigationPolicyDecision * nav_dec = WEBKIT_NAVIGATION_POLICY_DECISION(decision);
77
78 const gchar * uri =webkit_uri_request_get_uri(webkit_navigation_action_get_request (webkit_navigation_policy_decision_get_navigation_action (nav_dec)));
79 g_print(">> %s\n", uri);
80 **/
81
82 /* if request is http ignore default policy and open uri in default browser*/
83 return open_uri (decision);
84 }
85
86 static gboolean
decide_policy_cb(WebKitWebView * web_view,WebKitPolicyDecision * decision,WebKitPolicyDecisionType type)87 decide_policy_cb (WebKitWebView *web_view,
88 WebKitPolicyDecision *decision,
89 WebKitPolicyDecisionType type)
90 {
91 switch (type) {
92 case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
93 return navigate(decision);
94 case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
95 webkit_policy_decision_use (decision);
96 break;
97 case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
98 return navigate(decision);
99 default:
100 /* Making no decision results in webkit_policy_decision_use(). */
101 return FALSE;
102 }
103 return TRUE;
104 }
105
106
107 static gboolean
context_menu_cb(WebKitWebView * web_view,WebKitContextMenu * context_menu,GdkEvent * event,WebKitHitTestResult * hit_test_result,gpointer user_data)108 context_menu_cb (WebKitWebView *web_view,
109 WebKitContextMenu *context_menu,
110 GdkEvent *event,
111 WebKitHitTestResult *hit_test_result,
112 gpointer user_data)
113 {
114 return TRUE;
115 }
116
117 static void
initialize_web_extensions_cb(WebKitWebContext * context,gpointer user_data)118 initialize_web_extensions_cb (WebKitWebContext *context,
119 gpointer user_data)
120 {
121 /* Web Extensions get a different ID for each Web Process */
122 static guint32 unique_id = 0;
123
124 webkit_web_context_set_web_extensions_directory (
125 context, WEB_EXTENSIONS_DIRECTORY);
126 webkit_web_context_set_web_extensions_initialization_user_data (
127 context, g_variant_new_uint32 (unique_id++));
128 }
129
130 gboolean
key_press_event_cb(GtkWidget * widget,GdkEvent * event,gpointer user_data)131 key_press_event_cb (GtkWidget *widget,
132 GdkEvent *event,
133 gpointer user_data)
134 {
135 g_return_val_if_fail (MARKER_IS_PREVIEW (widget), FALSE);
136 MarkerPreview *preview = MARKER_PREVIEW (widget);
137
138 GdkEventKey *key_event = (GdkEventKey *) event;
139
140 if ((key_event->state & GDK_CONTROL_MASK) != 0)
141 {
142 switch (key_event->keyval)
143 {
144 case GDK_KEY_plus:
145 marker_preview_zoom_in (preview);
146 break;
147
148 case GDK_KEY_minus:
149 marker_preview_zoom_out (preview);
150 break;
151
152 case GDK_KEY_0:
153 marker_preview_zoom_original (preview);
154 break;
155 }
156 }
157 else
158 {
159 switch (key_event->keyval)
160 {
161 case GDK_KEY_j:
162 marker_preview_scroll_down (preview);
163 break;
164
165 case GDK_KEY_k:
166 marker_preview_scroll_up (preview);
167 break;
168
169 case GDK_KEY_h:
170 marker_preview_scroll_left (preview);
171 break;
172
173 case GDK_KEY_l:
174 marker_preview_scroll_right (preview);
175 break;
176
177 case GDK_KEY_g:
178 marker_preview_scroll_to_top (preview);
179 break;
180
181 case GDK_KEY_G:
182 marker_preview_scroll_to_bottom (preview);
183 break;
184 }
185 }
186
187 return FALSE;
188 }
189
190 gboolean
scroll_event_cb(GtkWidget * widget,GdkEvent * event,gpointer user_data)191 scroll_event_cb (GtkWidget *widget,
192 GdkEvent *event,
193 gpointer user_data)
194 {
195 g_return_val_if_fail (MARKER_IS_PREVIEW (widget), FALSE);
196 MarkerPreview *preview = MARKER_PREVIEW (widget);
197
198 GdkEventScroll *scroll_event = (GdkEventScroll *) event;
199 guint state = scroll_event->state;
200 if ((state & GDK_CONTROL_MASK) != 0)
201 {
202 gdouble delta_y = scroll_event->delta_y;
203
204 if (delta_y > 0)
205 {
206 marker_preview_zoom_out (preview);
207 }
208 else if (delta_y < 0)
209 {
210 marker_preview_zoom_in (preview);
211 }
212 }
213
214 return FALSE;
215 }
216
217 static void
load_changed_cb(WebKitWebView * preview,WebKitLoadEvent event)218 load_changed_cb (WebKitWebView *preview,
219 WebKitLoadEvent event)
220 {
221 switch (event)
222 {
223 case WEBKIT_LOAD_STARTED:
224 break;
225
226 case WEBKIT_LOAD_REDIRECTED:
227 break;
228
229 case WEBKIT_LOAD_COMMITTED:
230 break;
231
232 case WEBKIT_LOAD_FINISHED:
233 break;
234 }
235 }
236
237 static void
pdf_print_failed_cb(WebKitPrintOperation * print_op,GError * err,gpointer user_data)238 pdf_print_failed_cb (WebKitPrintOperation* print_op,
239 GError* err,
240 gpointer user_data)
241 {
242 g_printerr("print failed with error: %s\n", err->message);
243 }
244
245 static void
scroll_js_finished_cb(GObject * object,GAsyncResult * result,gpointer user_data)246 scroll_js_finished_cb (GObject *object,
247 GAsyncResult *result,
248 gpointer user_data)
249 {
250 WebKitJavascriptResult *js_result;
251 GError *error = NULL;
252
253 js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (object), result, &error);
254 if (error != NULL) {
255 g_print ("Error running scroll script: %s", error->message);
256 g_error_free (error);
257 return;
258 }
259
260 webkit_javascript_result_unref (js_result);
261 }
262
263 void
marker_preview_set_zoom_level(MarkerPreview * preview,gdouble zoom_level)264 marker_preview_set_zoom_level (MarkerPreview *preview,
265 gdouble zoom_level)
266 {
267 g_return_if_fail (MARKER_IS_PREVIEW (preview));
268 webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (preview), zoom_level);
269 g_signal_emit_by_name (preview, "zoom-changed");
270 }
271
272 static void
marker_preview_init(MarkerPreview * preview)273 marker_preview_init (MarkerPreview *preview)
274 {
275 g_signal_connect (webkit_web_context_get_default (),
276 "initialize-web-extensions",
277 G_CALLBACK (initialize_web_extensions_cb),
278 NULL);
279
280 g_signal_connect (preview, "scroll-event", G_CALLBACK (scroll_event_cb), NULL);
281 g_signal_connect (preview, "key-press-event", G_CALLBACK (key_press_event_cb), NULL);
282 }
283
284 static void
marker_preview_class_init(MarkerPreviewClass * class)285 marker_preview_class_init (MarkerPreviewClass *class)
286 {
287 g_signal_newv ("zoom-changed",
288 G_TYPE_FROM_CLASS (class),
289 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
290 NULL, NULL, NULL, NULL,
291 G_TYPE_NONE, 0, NULL);
292
293
294 WEBKIT_WEB_VIEW_CLASS(class)->load_changed = load_changed_cb;
295 }
296
297 MarkerPreview*
marker_preview_new(void)298 marker_preview_new(void)
299 {
300 MarkerPreview * obj = g_object_new(MARKER_TYPE_PREVIEW, NULL);
301 webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (obj), makrer_prefs_get_zoom_level ());
302
303
304 /***
305 WebKitSettings * settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(obj));
306 webkit_settings_set_enable_write_console_messages_to_stdout(settings, TRUE);
307 webkit_web_view_set_settings(WEBKIT_WEB_VIEW(obj), settings);
308 ***/
309
310 return obj;
311 }
312
313 void
marker_preview_zoom_out(MarkerPreview * preview)314 marker_preview_zoom_out (MarkerPreview *preview)
315 {
316 g_return_if_fail (WEBKIT_IS_WEB_VIEW (preview));
317 WebKitWebView *view = WEBKIT_WEB_VIEW (preview);
318
319 gdouble val = webkit_web_view_get_zoom_level (view) - 0.1;
320 val = max (val, MIN_ZOOM);
321
322 marker_prefs_set_zoom_level(val);
323 webkit_web_view_set_zoom_level(view, val);
324
325 g_signal_emit_by_name (preview, "zoom-changed");
326 }
327
328 void
marker_preview_zoom_original(MarkerPreview * preview)329 marker_preview_zoom_original (MarkerPreview *preview)
330 {
331 g_return_if_fail (WEBKIT_IS_WEB_VIEW (preview));
332 WebKitWebView *view = WEBKIT_WEB_VIEW (preview);
333
334 gdouble zoom = 1.0;
335
336 marker_prefs_set_zoom_level (zoom);
337 webkit_web_view_set_zoom_level (view, zoom);
338
339 g_signal_emit_by_name (preview, "zoom-changed");
340 }
341
342 void
marker_preview_zoom_in(MarkerPreview * preview)343 marker_preview_zoom_in (MarkerPreview *preview)
344 {
345 g_return_if_fail (WEBKIT_IS_WEB_VIEW (preview));
346 WebKitWebView *view = WEBKIT_WEB_VIEW (preview);
347
348 gdouble val = webkit_web_view_get_zoom_level (view) + 0.1;
349 val = min (val, MAX_ZOOM);
350
351 marker_prefs_set_zoom_level(val);
352 webkit_web_view_set_zoom_level(view, val);
353
354 g_signal_emit_by_name (preview, "zoom-changed");
355 }
356
357 void
marker_preview_render_markdown(MarkerPreview * preview,const char * markdown,const char * css_theme,const char * base_uri,int cursor)358 marker_preview_render_markdown(MarkerPreview* preview,
359 const char* markdown,
360 const char* css_theme,
361 const char* base_uri,
362 int cursor)
363 {
364 MarkerMathJSMode katex_mode = MATHJS_OFF;
365 if (marker_prefs_get_use_mathjs()) {
366 katex_mode = MATHJS_LOCAL;
367 }
368 MarkerHighlightMode highlight_mode = HIGHLIGHT_OFF;
369 if (marker_prefs_get_use_highlight()){
370 highlight_mode = HIGHLIGHT_LOCAL;
371 }
372 MarkerMermaidMode mermaid_mode = MERMAID_OFF;
373 if (marker_prefs_get_use_mermaid())
374 {
375 mermaid_mode = MERMAID_LOCAL;
376 }
377
378 char * base_folder = NULL;
379 if (base_uri)
380 base_folder = marker_string_filename_get_path(base_uri);
381 char* html = marker_markdown_to_html(markdown,
382 strlen(markdown),
383 base_folder,
384 katex_mode,
385 highlight_mode,
386 mermaid_mode,
387 css_theme,
388 cursor);
389
390 WebKitWebView* web_view = WEBKIT_WEB_VIEW(preview);
391
392 g_signal_connect(web_view,
393 "decide-policy",
394 G_CALLBACK(decide_policy_cb),
395 NULL);
396 g_signal_connect(web_view,
397 "context-menu",
398 G_CALLBACK(context_menu_cb),
399 NULL);
400
401 gchar * uri;
402 if (base_uri) {
403 uri = g_filename_to_uri (g_locale_from_utf8(base_uri, strlen(base_uri), NULL, NULL, NULL), NULL, NULL);
404 }else {
405 uri = g_strdup_printf ("file://%s", getenv("HOME"));
406 }
407 webkit_web_view_load_html(web_view
408 ,html
409 ,uri);
410
411 g_free(uri);
412 free(html);
413 }
414
415 WebKitPrintOperationResponse
marker_preview_run_print_dialog(MarkerPreview * preview,GtkWindow * parent)416 marker_preview_run_print_dialog(MarkerPreview* preview,
417 GtkWindow* parent)
418 {
419 WebKitPrintOperation* print_op =
420 webkit_print_operation_new(WEBKIT_WEB_VIEW(preview));
421
422 g_signal_connect(print_op, "failed", G_CALLBACK(pdf_print_failed_cb), NULL);
423
424 return webkit_print_operation_run_dialog(print_op, parent);
425 }
426
427 void
marker_preview_print_pdf(MarkerPreview * preview,const char * outfile,enum scidown_paper_size paper_size,GtkPageOrientation orientation)428 marker_preview_print_pdf(MarkerPreview* preview,
429 const char* outfile,
430 enum scidown_paper_size paper_size,
431 GtkPageOrientation orientation)
432
433 {
434 WebKitPrintOperation* print_op = NULL;
435 GtkPrintSettings* print_s = NULL;
436 char* uri = g_strdup_printf("file://%s", outfile);
437
438 print_op = webkit_print_operation_new(WEBKIT_WEB_VIEW(preview));
439 g_signal_connect(print_op, "failed", G_CALLBACK(pdf_print_failed_cb), NULL);
440
441 print_s = gtk_print_settings_new();
442 GtkPaperSize * gtk_paper_size = NULL;
443 if (paper_size != B43 && paper_size != B169)
444 gtk_paper_size = gtk_paper_size_new(paper_to_gtkstr(paper_size));
445 else if (paper_size == B43)
446 gtk_paper_size = gtk_paper_size_new_custom("B43", "B43", 166, 221, GTK_UNIT_MM);
447 else
448 gtk_paper_size = gtk_paper_size_new_custom("B43", "B43", 166, 294, GTK_UNIT_MM);
449
450 GtkPageSetup * gtk_page_setup = gtk_page_setup_new();
451
452 gtk_print_settings_set(print_s, GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT, "pdf");
453 gtk_print_settings_set(print_s, GTK_PRINT_SETTINGS_OUTPUT_URI, uri);
454 gtk_print_settings_set(print_s, GTK_PRINT_SETTINGS_PRINTER, "Print to File");
455
456 if (orientation == GTK_PAGE_ORIENTATION_PORTRAIT) {
457 gtk_page_setup_set_paper_size(gtk_page_setup, gtk_paper_size);
458 gtk_print_settings_set_paper_width(print_s, gtk_paper_size_get_width(gtk_paper_size, GTK_UNIT_MM), GTK_UNIT_MM);
459 gtk_print_settings_set_paper_height(print_s, gtk_paper_size_get_height(gtk_paper_size, GTK_UNIT_MM), GTK_UNIT_MM);
460
461 } else {
462 gdouble width = gtk_paper_size_get_width(gtk_paper_size, GTK_UNIT_MM);
463 gdouble height = gtk_paper_size_get_height(gtk_paper_size, GTK_UNIT_MM);
464 GtkPaperSize * custom_size = gtk_paper_size_new_custom(g_strdup_printf("%s_landscape", paper_to_string(paper_size)),
465 "pdf", height, width, GTK_UNIT_MM);
466 gtk_page_setup_set_paper_size(gtk_page_setup, custom_size);
467
468 gtk_print_settings_set_paper_width(print_s, height, GTK_UNIT_MM);
469 gtk_print_settings_set_paper_height(print_s, width, GTK_UNIT_MM);
470 }
471 if (paper_size == B43 || paper_size == B169) {
472 gtk_page_setup_set_left_margin(gtk_page_setup, 0, GTK_UNIT_POINTS);
473 gtk_page_setup_set_right_margin(gtk_page_setup, 0, GTK_UNIT_POINTS);
474 gtk_page_setup_set_top_margin(gtk_page_setup, 0, GTK_UNIT_POINTS);
475 gtk_page_setup_set_bottom_margin(gtk_page_setup, 0, GTK_UNIT_POINTS);
476 }
477 gtk_print_settings_set_orientation(print_s, orientation);
478
479 webkit_print_operation_set_print_settings(print_op, print_s);
480 webkit_print_operation_set_page_setup(print_op, gtk_page_setup);
481
482 webkit_print_operation_print(print_op);
483
484 g_free(uri);
485 }
486
487 void
marker_preview_scroll_left(MarkerPreview * preview)488 marker_preview_scroll_left (MarkerPreview *preview)
489 {
490 g_autofree gchar *script = g_strdup_printf (SCROLL_STEP_SCRIPT, -SCROLL_STEP, 0);
491 webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (preview), script, NULL, scroll_js_finished_cb, NULL);
492 }
493
494 void
marker_preview_scroll_right(MarkerPreview * preview)495 marker_preview_scroll_right (MarkerPreview *preview)
496 {
497 g_autofree gchar *script = g_strdup_printf (SCROLL_STEP_SCRIPT, SCROLL_STEP, 0);
498 webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (preview), script, NULL, scroll_js_finished_cb, NULL);
499 }
500
501 void
marker_preview_scroll_up(MarkerPreview * preview)502 marker_preview_scroll_up (MarkerPreview *preview)
503 {
504 g_autofree gchar *script = g_strdup_printf (SCROLL_STEP_SCRIPT, 0, -SCROLL_STEP);
505 webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (preview), script, NULL, scroll_js_finished_cb, NULL);
506 }
507
508 void
marker_preview_scroll_down(MarkerPreview * preview)509 marker_preview_scroll_down (MarkerPreview *preview)
510 {
511 g_autofree gchar *script = g_strdup_printf (SCROLL_STEP_SCRIPT, 0, SCROLL_STEP);
512 webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (preview), script, NULL, scroll_js_finished_cb, NULL);
513 }
514
515 void
marker_preview_scroll_to_top(MarkerPreview * preview)516 marker_preview_scroll_to_top (MarkerPreview *preview)
517 {
518 g_autofree gchar *script = g_strdup_printf (SCROLL_SCRIPT, 0, 0);
519 webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (preview), script, NULL, scroll_js_finished_cb, NULL);
520 }
521
522 void
marker_preview_scroll_to_bottom(MarkerPreview * preview)523 marker_preview_scroll_to_bottom (MarkerPreview *preview)
524 {
525 const gchar *script = "window.scrollTo(0,document.body.scrollHeight);";
526 webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (preview), script, NULL, scroll_js_finished_cb, NULL);
527 }
528