GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-html-webkit1.cpp
1/********************************************************************
2 * gnc-html-webkit.c -- gnucash report renderer using webkit *
3 * *
4 * Copyright (C) 2000 Bill Gribble <grib@billgribble.com> *
5 * Copyright (C) 2001 Linas Vepstas <linas@linas.org> *
6 * Copyright (C) 2009 Phil Longstaff <plongstaff@rogers.com> *
7 * *
8 * This program is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU General Public License as *
10 * published by the Free Software Foundation; either version 2 of *
11 * the License, or (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License*
19 * along with this program; if not, contact: *
20 * *
21 * Free Software Foundation Voice: +1-617-542-5942 *
22 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
23 * Boston, MA 02110-1301, USA gnu@gnu.org *
24 ********************************************************************/
25
26#include <config.h>
27#include "platform.h"
28
29#include <gtk/gtk.h>
30#include <glib/gi18n.h>
31#include <glib/gstdio.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <cstdlib>
35#include <string>
36#include <cerrno>
37#include <fcntl.h>
38#include <unistd.h>
39#include <regex.h>
40
41#include <webkit/webkit.h>
42
43#include "Account.h"
44#include "gnc-prefs.h"
45#include "gnc-gui-query.h"
46#include "gnc-engine.h"
47#include "gnc-html.h"
48#include "gnc-html-webkit.hpp"
49#include "gnc-html-history.h"
50#include "print-session.h"
51
52G_DEFINE_TYPE(GncHtmlWebkit, gnc_html_webkit, GNC_TYPE_HTML )
53
54static void gnc_html_webkit_dispose( GObject* obj );
55static void gnc_html_webkit_finalize( GObject* obj );
56
57#define GNC_HTML_WEBKIT_GET_PRIVATE(o) (GNC_HTML_WEBKIT(o)->priv)
58
59#include "gnc-html-webkit-p.hpp"
60
61/* indicates the debugging module that this .o belongs to. */
62static QofLogModule log_module = GNC_MOD_HTML;
63
64/* hashes an HTML <object classid="ID"> classid to a handler function */
65extern GHashTable* gnc_html_object_handlers;
66
67/* hashes handlers for loading different URLType data */
68extern GHashTable* gnc_html_stream_handlers;
69
70/* hashes handlers for handling different URLType data */
71extern GHashTable* gnc_html_url_handlers;
72
73static char error_404_format[] = "<html><body><h3>%s</h3><p>%s</body></html>";
74static char error_404_title[] = N_("Not found");
75static char error_404_body[] = N_("The specified URL could not be loaded.");
76
77#define BASE_URI_NAME "base-uri"
78#define GNC_PREF_RPT_DFLT_ZOOM "default-zoom"
79
80static WebKitNavigationResponse webkit_navigation_requested_cb(
81 WebKitWebView* web_view,
82 WebKitWebFrame* frame,
83 WebKitNetworkRequest* request,
84 gpointer user_data );
85static gboolean webkit_on_load_error (WebKitWebView *web_view,
86 WebKitWebFrame *web_frame, gchar *uri,
87 GError *error, gpointer data);
88static void webkit_resource_load_error (WebKitWebView *web_view,
89 WebKitWebFrame *web_frame,
90 WebKitWebResource *resource,
91 GError *error, gpointer data);
92static void webkit_on_url_cb( WebKitWebView* web_view, gchar* title, gchar* url,
93 gpointer data );
94static gchar* handle_embedded_object( GncHtmlWebkit* self, gchar* html_str );
95#if 0
96static void gnc_html_set_base_cb( GtkHTML* gtkhtml, const gchar* base, gpointer data );
97static void gnc_html_link_clicked_cb( GtkHTML* html, const gchar* url, gpointer data );
98static gboolean gnc_html_object_requested_cb( GtkHTML* html, GtkHTMLEmbedded* eb,
99 gpointer data );
100#endif
101#if 0 /* Not Used */
102static int gnc_html_button_press_cb( GtkWidget* widg, GdkEventButton* event,
103 gpointer user_data );
104#endif /* Not Used */
105static void impl_webkit_show_url( GncHtml* self, URLType type,
106 const gchar* location, const gchar* label,
107 gboolean new_window_hint );
108static void impl_webkit_show_data( GncHtml* self, const gchar* data, int datalen );
109static void impl_webkit_reload( GncHtml* self, gboolean force_rebuild );
110static void impl_webkit_copy_to_clipboard( GncHtml* self );
111static gboolean impl_webkit_export_to_file( GncHtml* self, const gchar* filepath );
112static void impl_webkit_print( GncHtml* self, const gchar* jobname, gboolean export_pdf );
113static void impl_webkit_cancel( GncHtml* self );
114static void impl_webkit_set_parent( GncHtml* self, GtkWindow* parent );
115static void impl_webkit_default_zoom_changed(gpointer prefs, gchar *pref, gpointer user_data);
116
117static void
118gnc_html_webkit_init( GncHtmlWebkit* self )
119{
120 PangoFontDescription *font_desc = nullptr;
121
122 auto new_priv = static_cast<GncHtmlWebkitPrivate*>(
123 g_realloc( GNC_HTML(self)->priv, sizeof(GncHtmlWebkitPrivate) )
124 );
125 auto priv = self->priv = new_priv;
126 GNC_HTML(self)->priv = reinterpret_cast<GncHtmlPrivate*>(priv);
127
128 priv->html_string = nullptr;
129 priv->web_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
130
131 /* Get the default font family from GtkStyleContext of a GtkWidget(priv->web_view). */
132 auto stylecontext = gtk_widget_get_style_context (GTK_WIDGET(priv->web_view));
133 gtk_style_context_get (stylecontext, gtk_widget_get_state_flags (GTK_WIDGET(priv->web_view)),
134 "font", &font_desc, nullptr);
135
136 const char *default_font_family = pango_font_description_get_family (font_desc);
137 pango_font_description_free (font_desc);
138
139 /* Set default webkit settings */
140 auto webkit_settings = webkit_web_view_get_settings (priv->web_view);
141 g_object_set (G_OBJECT(webkit_settings), "default-encoding", "utf-8", nullptr);
142 if (default_font_family == nullptr)
143 {
144 PWARN("webkit_settings: Cannot get default font family.");
145 }
146 else
147 {
148 g_object_set (G_OBJECT(webkit_settings),
149 "default-font-family", default_font_family,
150 nullptr);
151 PINFO("webkit_settings: Set default font to [%s]", default_font_family);
152 }
153 /* Scale everything up */
154 gdouble zoom = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_RPT_DFLT_ZOOM);
155 webkit_web_view_set_full_content_zoom (priv->web_view, TRUE);
156 webkit_web_view_set_zoom_level (priv->web_view, zoom);
157
158
159 gtk_container_add( GTK_CONTAINER(priv->base.container),
160 GTK_WIDGET(priv->web_view) );
161
162 g_object_ref_sink( priv->base.container );
163
164 /* signals */
165 g_signal_connect( priv->web_view, "navigation-requested",
166 G_CALLBACK(webkit_navigation_requested_cb),
167 self);
168
169 g_signal_connect( priv->web_view, "hovering-over-link",
170 G_CALLBACK(webkit_on_url_cb),
171 self );
172
173#if 0
174 g_signal_connect( priv->html, "set_base",
175 G_CALLBACK(gnc_html_set_base_cb),
176 self);
177
178 g_signal_connect(priv->html, "link_clicked",
179 G_CALLBACK(gnc_html_link_clicked_cb),
180 self);
181
182 g_signal_connect (priv->html, "object_requested",
183 G_CALLBACK (gnc_html_object_requested_cb),
184 self);
185
186 g_signal_connect (priv->html, "button_press_event",
187 G_CALLBACK (gnc_html_button_press_cb),
188 self);
189
190 g_signal_connect (priv->html, "submit",
191 G_CALLBACK(gnc_html_submit_cb),
192 self);
193#endif
194 g_signal_connect (priv->web_view, "load-error",
195 G_CALLBACK (webkit_on_load_error),
196 self);
197
198 g_signal_connect (priv->web_view, "resource-load-failed",
199 G_CALLBACK (webkit_resource_load_error),
200 self);
201
202 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL_REPORT,
203 GNC_PREF_RPT_DFLT_ZOOM,
204 reinterpret_cast<gpointer>(impl_webkit_default_zoom_changed),
205 self);
206
207 LEAVE("retval %p", self);
208}
209
210static void
211gnc_html_webkit_class_init( GncHtmlWebkitClass* klass )
212{
213 GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
214 GncHtmlClass* html_class = GNC_HTML_CLASS(klass);
215
216 gobject_class->dispose = gnc_html_webkit_dispose;
217 gobject_class->finalize = gnc_html_webkit_finalize;
218
219 html_class->show_url = impl_webkit_show_url;
220 html_class->show_data = impl_webkit_show_data;
221 html_class->reload = impl_webkit_reload;
222 html_class->copy_to_clipboard = impl_webkit_copy_to_clipboard;
223 html_class->export_to_file = impl_webkit_export_to_file;
224 html_class->print = impl_webkit_print;
225 html_class->cancel = impl_webkit_cancel;
226 html_class->set_parent = impl_webkit_set_parent;
227}
228
229static void
230gnc_html_webkit_dispose( GObject* obj )
231{
232 GncHtmlWebkit* self = GNC_HTML_WEBKIT(obj);
233 GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
234
235 if ( priv->web_view != nullptr )
236 {
237 gtk_container_remove( GTK_CONTAINER(priv->base.container),
238 GTK_WIDGET(priv->web_view) );
239 priv->web_view = nullptr;
240 }
241
242 if ( priv->html_string != nullptr )
243 {
244 g_free( priv->html_string );
245 priv->html_string = nullptr;
246 }
247
248 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
249 GNC_PREF_RPT_DFLT_ZOOM,
250 reinterpret_cast<gpointer>(
251 impl_webkit_default_zoom_changed
252 ),
253 obj);
254
255 G_OBJECT_CLASS(gnc_html_webkit_parent_class)->dispose( obj );
256}
257
258static void
259gnc_html_webkit_finalize( GObject* obj )
260{
261 GncHtmlWebkit* self = GNC_HTML_WEBKIT(obj);
262
263 self->priv = nullptr;
264
265 G_OBJECT_CLASS(gnc_html_webkit_parent_class)->finalize( obj );
266}
267
268/*****************************************************************************/
269
270static char*
271extract_base_name(URLType type, const gchar* path)
272{
273 constexpr gchar machine_rexp[] = "^(//[^/]*)/*(/.*)?$";
274 constexpr gchar path_rexp[] = "^/*(.*)/+([^/]*)$";
275 regex_t compiled_m, compiled_p;
276 constexpr size_t MATCH_LEN = 4;
277 regmatch_t match[MATCH_LEN];
278 gchar * machine = nullptr, * location = nullptr, * base = nullptr;
279 gchar * basename = nullptr;
280
281 DEBUG(" ");
282 if (!path) return nullptr;
283
284 regcomp(&compiled_m, machine_rexp, REG_EXTENDED);
285 regcomp(&compiled_p, path_rexp, REG_EXTENDED);
286
287 if (!g_strcmp0 (type, URL_TYPE_HTTP) ||
288 !g_strcmp0 (type, URL_TYPE_SECURE) ||
289 !g_strcmp0 (type, URL_TYPE_FTP))
290 {
291
292 /* step 1: split the machine name away from the path
293 * components */
294 if (!regexec(&compiled_m, path, MATCH_LEN, match, 0))
295 {
296 /* $1 is the machine name */
297 if (match[1].rm_so != -1)
298 {
299 machine = g_strndup(path + match[1].rm_so,
300 match[1].rm_eo - match[1].rm_so);
301 }
302 /* $2 is the path */
303 if (match[2].rm_so != -1)
304 {
305 location = g_strndup(path + match[2].rm_so,
306 match[2].rm_eo - match[2].rm_so);
307 }
308 }
309
310 }
311 else
312 {
313 location = g_strdup(path);
314 }
315 /* step 2: split up the path into prefix and file components */
316 if (location)
317 {
318 if (!regexec(&compiled_p, location, 4, match, 0))
319 {
320 if (match[1].rm_so != -1)
321 {
322 base = g_strndup(location + match[1].rm_so,
323 match[1].rm_eo - match[1].rm_so);
324 }
325 else
326 {
327 base = nullptr;
328 }
329 }
330 }
331
332 regfree(&compiled_m);
333 regfree(&compiled_p);
334
335 if (machine)
336 {
337 if (base && (strlen(base) > 0))
338 {
339 basename = g_strconcat(machine, "/", base, "/", nullptr);
340 }
341 else
342 {
343 basename = g_strconcat(machine, "/", nullptr);
344 }
345 }
346 else
347 {
348 if (base && (strlen(base) > 0))
349 {
350 basename = g_strdup(base);
351 }
352 else
353 {
354 basename = nullptr;
355 }
356 }
357
358 g_free(machine);
359 g_free(base);
360 g_free(location);
361 return basename;
362}
363
364static gboolean
365http_allowed()
366{
367 return TRUE;
368}
369
370static gboolean
371https_allowed()
372{
373 return TRUE;
374}
375
376static gchar*
377handle_embedded_object( GncHtmlWebkit* self, gchar* html_str )
378{
379 // Find the <object> tag and get the classid from it. This will provide the correct
380 // object callback handler. Pass the <object> entity text to the handler. What should
381 // come back is embedded image information.
382 gchar* remainder_str = html_str;
383 gchar* object_tag;
384 gchar* end_object_tag;
385 gchar* object_contents;
386 gchar* html_str_start = nullptr;
387 gchar* html_str_middle;
388 gchar* html_str_result = nullptr;
389 gchar* classid_start;
390 gchar* classid_end;
391 gchar* classid_str;
392 gchar* new_chunk;
393 GncHTMLObjectCB h;
394
395 object_tag = g_strstr_len( remainder_str, -1, "<object classid=" );
396 while (object_tag)
397 {
398
399 classid_start = object_tag + strlen( "<object classid=" ) + 1;
400 classid_end = g_strstr_len( classid_start, -1, "\"" );
401 classid_str = g_strndup( classid_start, (classid_end - classid_start) );
402
403 end_object_tag = g_strstr_len( object_tag, -1, "</object>" );
404 if ( end_object_tag == nullptr )
405 {
406 /* Hmmm... no object end tag
407 Return the original html string because we can't properly parse it */
408 g_free (classid_str);
409 g_free (html_str_result);
410 return g_strdup (html_str);
411 }
412 end_object_tag += strlen( "</object>" );
413 object_contents = g_strndup( object_tag, (end_object_tag - object_tag) );
414
415 const gpointer p = g_hash_table_lookup( gnc_html_object_handlers, classid_str );
416 h = reinterpret_cast<GncHTMLObjectCB>(p);
417 if ( h != nullptr )
418 {
419 (void)h( GNC_HTML(self), object_contents, &html_str_middle );
420 }
421 else
422 {
423 html_str_middle = g_strdup_printf( "No handler found for classid \"%s\"", classid_str );
424 }
425
426 html_str_start = html_str_result;
427 new_chunk = g_strndup (remainder_str, (object_tag - remainder_str));
428 if (!html_str_start)
429 html_str_result = g_strconcat (new_chunk, html_str_middle, nullptr);
430 else
431 html_str_result = g_strconcat (html_str_start, new_chunk, html_str_middle, nullptr);
432
433 g_free( html_str_start );
434 g_free( new_chunk );
435 g_free( html_str_middle );
436
437 remainder_str = end_object_tag;
438 object_tag = g_strstr_len( remainder_str, -1, "<object classid=" );
439 }
440
441 if (html_str_result)
442 {
443 html_str_start = html_str_result;
444 html_str_result = g_strconcat (html_str_start, remainder_str, nullptr);
445 g_free (html_str_start);
446 }
447 else
448 html_str_result = g_strdup (remainder_str);
449
450 return html_str_result;
451}
452
453/********************************************************************
454 * load_to_stream : actually do the work of loading the HTML
455 * or binary data referenced by a URL and feeding it into the webkit
456 * widget.
457 ********************************************************************/
458
459static gboolean
460load_to_stream( GncHtmlWebkit* self, URLType type,
461 const gchar* location, const gchar* label )
462{
463 gchar* fdata = nullptr;
464 int fdata_len = 0;
465 GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
466
467 DEBUG( "type %s, location %s, label %s", type ? type : "(null)",
468 location ? location : "(null)", label ? label : "(null)");
469
470 g_return_val_if_fail( self != nullptr, FALSE );
471
472 if ( gnc_html_stream_handlers != nullptr )
473 {
474 const gpointer p = g_hash_table_lookup( gnc_html_stream_handlers, type );
475 GncHTMLStreamCB stream_handler = reinterpret_cast<GncHTMLStreamCB>(p);
476 if ( stream_handler )
477 {
478 GncHtml *weak_html = GNC_HTML(self);
479
480 g_object_add_weak_pointer(G_OBJECT(self), (gpointer *)(&weak_html));
481
482 bool ok = stream_handler(location, &fdata, &fdata_len);
483
484 if (!weak_html) // will be nullptr if self has been destroyed
485 {
486 g_free (fdata);
487 return FALSE;
488 }
489 else
490 {
491 g_object_remove_weak_pointer(G_OBJECT(self),
492 (gpointer*)(&weak_html));
493 }
494
495 if (ok)
496 {
497 fdata = fdata ? fdata : g_strdup("");
498
499 // Until webkitgtk supports download requests, look for "<object
500 // classid=" indicating the beginning of an embedded graph. If
501 // found, handle it
502 if (g_strstr_len(fdata, -1, "<object classid=") != nullptr)
503 {
504 gchar *new_fdata;
505 new_fdata = handle_embedded_object(self, fdata);
506 g_free(fdata);
507 fdata = new_fdata;
508 }
509
510 // Save a copy for export purposes
511 if (priv->html_string != nullptr)
512 {
513 g_free(priv->html_string);
514 }
515 priv->html_string = g_strdup(fdata);
516 impl_webkit_show_data(GNC_HTML(self), fdata, strlen(fdata));
517 }
518 else
519 {
520 fdata = fdata ? fdata :
521 g_strdup_printf( error_404_format,
522 _(error_404_title), _(error_404_body) );
523 webkit_web_view_load_html_string( priv->web_view, fdata, BASE_URI_NAME );
524 }
525
526 g_free( fdata );
527
528 if ( label )
529 {
530 while ( gtk_events_pending() )
531 {
532 gtk_main_iteration();
533 }
534 /* No action required: Webkit jumps to the anchor on its own. */
535 }
536
537 return TRUE;
538 }
539 }
540
541 do
542 {
543 if ( !g_strcmp0( type, URL_TYPE_SECURE ) ||
544 !g_strcmp0( type, URL_TYPE_HTTP ) )
545 {
546
547 if ( !g_strcmp0( type, URL_TYPE_SECURE ) )
548 {
549 if ( !https_allowed() )
550 {
551 gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
552 _("Secure HTTP access is disabled. "
553 "You can enable it in the Network section of "
554 "the Preferences dialog."));
555 break;
556 }
557 }
558
559 if ( !http_allowed() )
560 {
561 gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
562 _("Network HTTP access is disabled. "
563 "You can enable it in the Network section of "
564 "the Preferences dialog."));
565 }
566 else
567 {
568 gnc_build_url( type, location, label );
569 }
570
571 }
572 else
573 {
574 PWARN( "load_to_stream for inappropriate type\n"
575 "\turl = '%s#%s'\n",
576 location ? location : "(null)",
577 label ? label : "(null)" );
578 fdata = g_strdup_printf( error_404_format,
579 _(error_404_title), _(error_404_body) );
580 webkit_web_view_load_html_string( priv->web_view, fdata, BASE_URI_NAME );
581 g_free( fdata );
582 }
583
584 }
585 while ( false );
586 return TRUE;
587}
588
589#if 0
590/********************************************************************
591 * gnc_html_link_clicked_cb - called when user left-clicks on html
592 * anchor.
593 ********************************************************************/
594
595static void
596gnc_html_link_clicked_cb( GtkHTML* html, const gchar* url, gpointer data )
597{
598 URLType type;
599 gchar* location = nullptr;
600 gchar* label = nullptr;
601 GncHtmlWebkit* self = GNC_HTML_WEBKIT(data);
602
603 DEBUG("Clicked %s", url);
604 type = gnc_html_parse_url( GNC_HTML(self), url, &location, &label );
605 gnc_html_show_url( GNC_HTML(self), type, location, label, 0 );
606 g_free( location );
607 g_free( label );
608}
609#endif
610
611/********************************************************************
612 * webkit_navigation_requested_cb - called when a URL needs to be
613 * loaded within the loading of a page (embedded image).
614 ********************************************************************/
615
616static WebKitNavigationResponse
617webkit_navigation_requested_cb( WebKitWebView* web_view, WebKitWebFrame* frame,
618 WebKitNetworkRequest* request,
619 gpointer data )
620{
621 gchar* location = nullptr;
622 gchar* label = nullptr;
623 GncHtmlWebkit* self = GNC_HTML_WEBKIT(data);
624 const gchar* url = webkit_network_request_get_uri( request );
625
626 ENTER( "requesting %s", url );
627 if ( strcmp( url, BASE_URI_NAME ) == 0 )
628 {
629 LEAVE("URI is %s", BASE_URI_NAME);
630 return WEBKIT_NAVIGATION_RESPONSE_ACCEPT;
631 }
632
633 URLType type = gnc_html_parse_url( GNC_HTML(self), url, &location, &label );
634 if ( strcmp( type, "file" ) == 0 )
635 {
636 LEAVE("URI type is 'file'");
637 return WEBKIT_NAVIGATION_RESPONSE_ACCEPT;
638 }
639 gnc_html_show_url( GNC_HTML(self), type, location, label, 0 );
640// load_to_stream( self, type, location, label );
641 g_free( location );
642 g_free( label );
643
644 LEAVE("");
645 return WEBKIT_NAVIGATION_RESPONSE_IGNORE;
646}
647
648static gboolean
649webkit_on_load_error (WebKitWebView *web_view, WebKitWebFrame *web_frame,
650 gchar *uri, GError *error, gpointer data)
651{
652 PERR ("WebKit load of %s failed due to %s\n", uri, error->message);
653 return FALSE;
654}
655
656static void
657webkit_resource_load_error (WebKitWebView *web_view, WebKitWebFrame *web_frame,
658 WebKitWebResource *resource, GError *error,
659 gpointer data)
660{
661 const gchar *uri = webkit_web_resource_get_uri (resource);
662 const gchar *type = webkit_web_resource_get_mime_type (resource);
663 PERR ("WebKit load of resource %s, type %s, failed due to %s\n",
664 uri, type, error->message);
665}
666
667#if 0
668/********************************************************************
669 * gnc_html_object_requested_cb - called when an applet needs to be
670 * loaded.
671 ********************************************************************/
672
673static gboolean
674gnc_html_object_requested_cb( GtkHTML* html, GtkHTMLEmbedded* eb,
675 gpointer data )
676{
677 GncHtmlWebkit* self = GNC_HTML_WEBKIT(data);
678
679 DEBUG( " " );
680 if ( !eb || !(eb->classid) || !gnc_html_object_handlers ) return FALSE;
681
682 auto h = g_hash_table_lookup( gnc_html_object_handlers, eb->classid );
683 if ( h )
684 {
685 return h( GNC_HTML(self), eb, data );
686 }
687 else
688 {
689 return FALSE;
690 }
691}
692#endif
693
694/********************************************************************
695 * webkit_on_url_cb - called when user rolls over html anchor
696 ********************************************************************/
697
698static void
699webkit_on_url_cb( WebKitWebView* web_view, gchar* title, gchar* url, gpointer data )
700{
701 GncHtmlWebkit* self = GNC_HTML_WEBKIT(data);
702 GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
703
704 DEBUG( "Rollover %s", url ? url : "(null)" );
705 g_free( priv->base.current_link );
706 priv->base.current_link = g_strdup( url );
707 if ( priv->base.flyover_cb )
708 {
709 (priv->base.flyover_cb)( GNC_HTML(self), url, priv->base.flyover_cb_data );
710 }
711}
712
713#if 0
714/********************************************************************
715 * gnc_html_set_base_cb
716 ********************************************************************/
717
718static void
719gnc_html_set_base_cb( GtkHTML* gtkhtml, const gchar* base,
720 gpointer data )
721{
722 GncHtmlWebkit* self = GNC_HTML_WEBKIT(data);
723 GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
724 gchar* location = nullptr;
725 gchar* label = nullptr;
726
727 DEBUG( "Setting base location to %s", base );
728 URLType type = gnc_html_parse_url( GNC_HTML(self), base, &location, &label );
729
730 g_free( priv->base.base_location );
731 g_free( label );
732
733 priv->base.base_type = type;
734 priv->base.base_location = location;
735}
736#endif
737
738/********************************************************************
739 * gnc_html_button_press_cb
740 * mouse button callback (if any)
741 ********************************************************************/
742
743#if 0 /* Not Used */
744static int
745gnc_html_button_press_cb( GtkWidget* widg, GdkEventButton* event,
746 gpointer user_data )
747{
748 GncHtmlWebkit* self = GNC_HTML_WEBKIT(user_data);
749 GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
750
751 DEBUG( "Button Press" );
752 if ( priv->base.button_cb != nullptr )
753 {
754 (priv->base.button_cb)( GNC_HTML(self), event, priv->base.button_cb_data );
755 return TRUE;
756 }
757 else
758 {
759 return FALSE;
760 }
761}
762#endif /* Not Used */
763
764/********************************************************************
765 * gnc_html_open_scm
766 * insert some scheme-generated HTML
767 ********************************************************************/
768
769static void
770gnc_html_open_scm( GncHtmlWebkit* self, const gchar * location,
771 const gchar * label, int newwin )
772{
773 PINFO("location='%s'", location ? location : "(null)");
774}
775
776
777/********************************************************************
778 * gnc_html_show_data
779 * display some HTML that the creator of the gnc-html got from
780 * somewhere.
781 ********************************************************************/
782
783static void
784impl_webkit_show_data( GncHtml* self, const gchar* data, int datalen )
785{
786#define TEMPLATE_REPORT_FILE_NAME "gnc-report-XXXXXX.html"
787 g_return_if_fail( self != nullptr );
788 g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
789
790 ENTER( "datalen %d, data %20.20s", datalen, data );
791
792 auto priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
793
794 /* Export the HTML to a file and load the file URI. On Linux, this seems to get around some
795 security problems (otherwise, it can complain that embedded images aren't permitted to be
796 viewed because they are local resources). On Windows, this allows the embedded images to
797 be viewed (maybe for the same reason as on Linux, but I haven't found where it puts those
798 messages. */
799 gchar *filename = g_build_filename(g_get_tmp_dir(), TEMPLATE_REPORT_FILE_NAME, (gchar *)nullptr);
800 int fd = g_mkstemp( filename );
801 impl_webkit_export_to_file( self, filename );
802 close( fd );
803#ifdef G_OS_WIN32
804 gchar *uri = g_strdup_printf( "file:///%s", filename );
805#else
806 gchar *uri = g_strdup_printf( "file://%s", filename );
807#endif
808 g_free(filename);
809 DEBUG("Loading uri '%s'", uri);
810 webkit_web_view_load_uri( priv->web_view, uri );
811 g_free( uri );
812
813 LEAVE("");
814}
815
816/********************************************************************
817 * gnc_html_show_url
818 *
819 * open a URL. This is called when the user clicks a link or
820 * for the creator of the gnc_html window to explicitly request
821 * a URL.
822 ********************************************************************/
823
824static void
825impl_webkit_show_url( GncHtml* self, URLType type,
826 const gchar* location, const gchar* label,
827 gboolean new_window_hint )
828{
829 GncHTMLUrlCB url_handler = nullptr;
830 bool new_window = false;
831 bool stream_loaded = false;
832
833 g_return_if_fail( self != nullptr );
834 g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
835 g_return_if_fail( location != nullptr );
836
837 auto priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
838
839 /* make sure it's OK to show this URL type in this window */
840 if ( new_window_hint == 0 )
841 {
842 if ( priv->base.urltype_cb )
843 {
844 new_window = !((priv->base.urltype_cb)( type ));
845 }
846 }
847 else
848 {
849 new_window = true;
850 }
851
852 if ( !new_window )
853 {
854 gnc_html_cancel( GNC_HTML(self) );
855 }
856
857 if ( gnc_html_url_handlers )
858 {
859 url_handler = reinterpret_cast<GncHTMLUrlCB>(
860 g_hash_table_lookup( gnc_html_url_handlers, type )
861 );
862 }
863
864 if ( url_handler )
865 {
866 GNCURLResult result;
867
868 result.load_to_stream = FALSE;
869 result.url_type = type;
870 result.location = nullptr;
871 result.label = nullptr;
872 result.base_type = URL_TYPE_FILE;
873 result.base_location = nullptr;
874 result.error_message = nullptr;
875 result.parent = GTK_WINDOW (priv->base.parent);
876
877 bool ok = url_handler( location, label, new_window, &result );
878 if ( !ok )
879 {
880 if ( result.error_message )
881 {
882 gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s", result.error_message );
883 }
884 else
885 {
886 /* %s is a URL (some location somewhere). */
887 gnc_error_dialog (GTK_WINDOW (priv->base.parent), _("There was an error accessing %s."), location );
888 }
889
890 if ( priv->base.load_cb )
891 {
892 priv->base.load_cb( GNC_HTML(self), result.url_type,
893 location, label, priv->base.load_cb_data );
894 }
895 }
896 else if ( result.load_to_stream )
897 {
898 gnc_html_history_node *hnode;
899 const char *new_location;
900 const char *new_label;
901
902 new_location = result.location ? result.location : location;
903 new_label = result.label ? result.label : label;
904 hnode = gnc_html_history_node_new( result.url_type, new_location, new_label );
905
906 gnc_html_history_append( priv->base.history, hnode );
907
908 g_free( priv->base.base_location );
909 priv->base.base_type = result.base_type;
910 priv->base.base_location =
911 g_strdup( extract_base_name( result.base_type, new_location ) );
912 DEBUG( "resetting base location to %s",
913 priv->base.base_location ? priv->base.base_location : "(null)" );
914
915 stream_loaded = load_to_stream( GNC_HTML_WEBKIT(self),
916 result.url_type,
917 new_location, new_label );
918
919 if ( stream_loaded && priv->base.load_cb != nullptr )
920 {
921 priv->base.load_cb( GNC_HTML(self), result.url_type,
922 new_location, new_label, priv->base.load_cb_data );
923 }
924 }
925
926 g_free( result.location );
927 g_free( result.label );
928 g_free( result.base_location );
929 g_free( result.error_message );
930
931 return;
932 }
933
934 if ( g_strcmp0( type, URL_TYPE_SCHEME ) == 0 )
935 {
936 gnc_html_open_scm( GNC_HTML_WEBKIT(self), location, label, new_window );
937
938 }
939 else if ( g_strcmp0( type, URL_TYPE_JUMP ) == 0 )
940 {
941 /* Webkit jumps to the anchor on its own */
942 }
943 else if ( g_strcmp0( type, URL_TYPE_SECURE ) == 0 ||
944 g_strcmp0( type, URL_TYPE_HTTP ) == 0 ||
945 g_strcmp0( type, URL_TYPE_FILE ) == 0 )
946 {
947
948 do
949 {
950 if ( g_strcmp0( type, URL_TYPE_SECURE ) == 0 )
951 {
952 if ( !https_allowed() )
953 {
954 gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
955 _("Secure HTTP access is disabled. "
956 "You can enable it in the Network section of "
957 "the Preferences dialog.") );
958 break;
959 }
960 }
961
962 if ( g_strcmp0( type, URL_TYPE_HTTP ) == 0 )
963 {
964 if ( !http_allowed() )
965 {
966 gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
967 _("Network HTTP access is disabled. "
968 "You can enable it in the Network section of "
969 "the Preferences dialog.") );
970 break;
971 }
972 }
973
974 priv->base.base_type = type;
975
976 if ( priv->base.base_location != nullptr ) g_free( priv->base.base_location );
977 priv->base.base_location = extract_base_name( type, location );
978
979 /* FIXME : handle new_window = 1 */
980 gnc_html_history_append( priv->base.history,
981 gnc_html_history_node_new( type, location, label ) );
982 stream_loaded = load_to_stream( GNC_HTML_WEBKIT(self), type,
983 location, label );
984
985 }
986 while ( FALSE );
987
988 }
989 else
990 {
991 PERR( "URLType %s not supported.", type );
992 }
993
994 if ( stream_loaded && priv->base.load_cb != nullptr )
995 {
996 (priv->base.load_cb)( GNC_HTML(self), type, location, label, priv->base.load_cb_data );
997 }
998}
999
1000
1001/********************************************************************
1002 * gnc_html_reload
1003 * reload the current page
1004 * if force_rebuild is TRUE, the report is recreated, if FALSE, report
1005 * is reloaded by webkit
1006 ********************************************************************/
1007
1008static void
1009impl_webkit_reload( GncHtml* self, gboolean force_rebuild )
1010{
1012
1013 g_return_if_fail( self != nullptr );
1014 g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
1015
1016 priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1017
1018 if ( force_rebuild )
1019 {
1020 gnc_html_history_node *n = gnc_html_history_get_current( priv->base.history );
1021 if ( n != nullptr )
1022 gnc_html_show_url( self, n->type, n->location, n->label, 0 );
1023 }
1024 else
1025 webkit_web_view_reload( priv->web_view );
1026}
1027
1028
1029/********************************************************************
1030 * gnc_html_new
1031 * create and set up a new webkit widget.
1032 ********************************************************************/
1033
1034GncHtml*
1035gnc_html_webkit_new( void )
1036{
1037 GncHtmlWebkit* self = static_cast<GncHtmlWebkit *>(
1038 g_object_new( GNC_TYPE_HTML_WEBKIT, nullptr )
1039 );
1040 return GNC_HTML(self);
1041}
1042
1043/********************************************************************
1044 * gnc_html_cancel
1045 * cancel any outstanding HTML fetch requests.
1046 ********************************************************************/
1047
1048static gboolean
1049webkit_cancel_helper(gpointer key, gpointer value, gpointer user_data)
1050{
1051 g_free(key);
1052 g_list_free((GList *)value);
1053 return TRUE;
1054}
1055
1056static void
1057impl_webkit_cancel( GncHtml* self )
1058{
1060
1061 g_return_if_fail( self != nullptr );
1062 g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
1063
1064 priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1065
1066 g_hash_table_foreach_remove( priv->base.request_info, webkit_cancel_helper, nullptr );
1067}
1068
1069static void
1070impl_webkit_copy_to_clipboard( GncHtml* self )
1071{
1073
1074 g_return_if_fail( self != nullptr );
1075 g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
1076
1077 priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1078 if ( webkit_web_view_can_copy_clipboard( priv->web_view ) )
1079 {
1080 webkit_web_view_copy_clipboard( priv->web_view );
1081 }
1082}
1083
1084/**************************************************************
1085 * gnc_html_export_to_file
1086 *
1087 * @param self GncHtmlWebkit object
1088 * @param filepath Where to write the HTML
1089 * @return TRUE if successful, FALSE if unsuccessful
1090 **************************************************************/
1091static gboolean
1092impl_webkit_export_to_file( GncHtml* self, const char *filepath )
1093{
1094 FILE *fh;
1096
1097 g_return_val_if_fail( self != nullptr, FALSE );
1098 g_return_val_if_fail( GNC_IS_HTML_WEBKIT(self), FALSE );
1099 g_return_val_if_fail( filepath != nullptr, FALSE );
1100
1101 priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1102 if ( priv->html_string == nullptr )
1103 {
1104 return FALSE;
1105 }
1106 fh = g_fopen( filepath, "w" );
1107 if ( fh != nullptr )
1108 {
1109 gint written;
1110 gint len = strlen( priv->html_string );
1111
1112 written = fwrite( priv->html_string, 1, len, fh );
1113 fclose (fh);
1114
1115 if ( written != len )
1116 {
1117 return FALSE;
1118 }
1119
1120 return TRUE;
1121 }
1122 else
1123 {
1124 return FALSE;
1125 }
1126}
1127
1140static void
1141impl_webkit_print( GncHtml* self, const gchar* jobname, gboolean export_pdf )
1142{
1143 gchar *export_filename = nullptr;
1145 WebKitWebFrame* frame;
1146 GtkPrintOperation* op = gtk_print_operation_new();
1147 GError* error = nullptr;
1148 GtkPrintSettings *print_settings;
1149
1150 priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1151 frame = webkit_web_view_get_main_frame( priv->web_view );
1152
1153 gnc_print_operation_init( op, jobname );
1154 print_settings = gtk_print_operation_get_print_settings (op);
1155 if (!print_settings)
1156 {
1157 print_settings = gtk_print_settings_new();
1158 gtk_print_operation_set_print_settings(op, print_settings);
1159 }
1160#ifdef G_OS_WIN32
1161 gtk_print_operation_set_unit( op, GTK_UNIT_POINTS );
1162#endif
1163
1164 // Make sure to generate a full export filename
1165 if (g_str_has_suffix(jobname, ".pdf"))
1166 {
1167 export_filename = g_strdup(jobname);
1168 }
1169 else
1170 {
1171 export_filename = g_strconcat(jobname, ".pdf", nullptr);
1172 }
1173
1174 // Two different modes of operation. Either export to PDF, or run the
1175 // normal print dialog
1176 if (export_pdf)
1177 {
1178 GtkWidget *dialog;
1179 gint result;
1180 gchar *export_dirname = nullptr;
1181 gchar* basename;
1182
1183 // Before we save the PDF file, we always ask the user for the export
1184 // file name. We will store the chosen directory in the gtk print settings
1185 // as well.
1186 dialog = gtk_file_chooser_dialog_new (_("Export to PDF File"),
1187 nullptr,
1188 GTK_FILE_CHOOSER_ACTION_SAVE,
1189 _("_Cancel"), GTK_RESPONSE_CANCEL,
1190 _("_Save"), GTK_RESPONSE_ACCEPT,
1191 nullptr);
1192 gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
1193
1194 // Does the jobname look like a valid full file path?
1195 basename = g_path_get_basename(jobname);
1196 if (strcmp(basename, jobname) != 0)
1197 {
1198 gchar *tmp_basename;
1199 gchar *tmp_dirname = g_path_get_dirname(jobname);
1200
1201 if (g_file_test(tmp_dirname,
1202 static_cast<GFileTest>(
1203 G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR
1204 )))
1205 {
1206 // Yes, the jobname starts with a directory name that actually
1207 // exists. Hence we use this as output directory.
1208 export_dirname = tmp_dirname;
1209 tmp_dirname = nullptr;
1210
1211 // As the prefix part of the "jobname" is the directory path, we
1212 // need to extract the suffix part for the filename.
1213 tmp_basename = g_path_get_basename(export_filename);
1214 g_free(export_filename);
1215 export_filename = tmp_basename;
1216 }
1217 g_free(tmp_dirname);
1218 }
1219 g_free(basename);
1220
1221 // Set the output file name from the given jobname
1222 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER(dialog), export_filename);
1223
1224 // Do we have a stored output directory?
1225 if (!export_dirname && gtk_print_settings_has_key(print_settings, GNC_GTK_PRINT_SETTINGS_EXPORT_DIR))
1226 {
1227 const char* tmp_dirname = gtk_print_settings_get(print_settings,
1229 // Only use the directory subsequently if it exists.
1230 if (g_file_test(tmp_dirname,
1231 static_cast<GFileTest>(
1232 G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR
1233 )))
1234 {
1235 export_dirname = g_strdup(tmp_dirname);
1236 }
1237 }
1238
1239 // If we have an already existing directory, propose it now.
1240 if (export_dirname)
1241 {
1242 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), export_dirname);
1243 }
1244 g_free(export_dirname);
1245
1246 result = gtk_dialog_run (GTK_DIALOG (dialog));
1247 // Weird. In gtk_dialog_run, the gtk code will run a fstat() on the
1248 // proposed new output filename, which of course fails with "file not
1249 // found" as this file doesn't exist. It will still show a warning output
1250 // in the trace file, though.
1251
1252 if (result == GTK_RESPONSE_ACCEPT)
1253 {
1254 // The user pressed "Ok", so use the file name for the actual file output.
1255 gchar *dirname;
1256 char *tmp = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
1257 g_free(export_filename);
1258 export_filename = tmp;
1259
1260 // Store the directory part of the file for later
1261 dirname = g_path_get_dirname(export_filename);
1262 if (g_file_test(dirname,
1263 static_cast<GFileTest>(
1264 G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR
1265 )))
1266 {
1267 gtk_print_settings_set(print_settings, GNC_GTK_PRINT_SETTINGS_EXPORT_DIR, dirname);
1268 }
1269 g_free(dirname);
1270 }
1271 gtk_widget_destroy (dialog);
1272
1273 if (result != GTK_RESPONSE_ACCEPT)
1274 {
1275 // User pressed cancel - no saving of the PDF file here.
1276 g_free(export_filename);
1277 g_object_unref( op );
1278 return;
1279 }
1280
1281 // This function expects the full filename including (absolute?) path
1282 gtk_print_operation_set_export_filename(op, export_filename);
1283
1284 // Run the "Export to PDF" print operation
1285 webkit_web_frame_print_full( frame, op, GTK_PRINT_OPERATION_ACTION_EXPORT, &error );
1286 }
1287 else
1288 {
1289
1290 // Also store this export file name as output URI in the settings
1291 if (gtk_print_settings_has_key(print_settings, GTK_PRINT_SETTINGS_OUTPUT_URI))
1292 {
1293 // Get the previous output URI, extract the directory part, and
1294 // append the current filename.
1295 const gchar *olduri = gtk_print_settings_get(print_settings, GTK_PRINT_SETTINGS_OUTPUT_URI);
1296 gchar *dirname = g_path_get_dirname(olduri);
1297 gchar *newuri = (g_strcmp0(dirname, ".") == 0)
1298 ? g_strdup(export_filename)
1299 : g_build_filename(dirname, export_filename, nullptr);
1300 //g_warning("olduri=%s newuri=%s", olduri, newuri);
1301
1302 // This function expects the full filename including protocol, path, and name
1303 gtk_print_settings_set(print_settings, GTK_PRINT_SETTINGS_OUTPUT_URI, newuri);
1304
1305 g_free(newuri);
1306 g_free(dirname);
1307 }
1308 else
1309 {
1310 // No stored output URI from the print settings, so just set our export filename
1311 gtk_print_settings_set(print_settings, GTK_PRINT_SETTINGS_OUTPUT_URI, export_filename);
1312 }
1313
1314 // Run the normal printing dialog
1315 webkit_web_frame_print_full( frame, op, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, &error );
1316 }
1317
1318 if ( error != nullptr )
1319 {
1320 GtkWidget* window = gtk_widget_get_toplevel( GTK_WIDGET(priv->web_view) );
1321 GtkWidget* dialog = gtk_message_dialog_new( gtk_widget_is_toplevel(window) ? GTK_WINDOW(window) : nullptr,
1322 GTK_DIALOG_DESTROY_WITH_PARENT,
1323 GTK_MESSAGE_ERROR,
1324 GTK_BUTTONS_CLOSE,
1325 "%s", error->message );
1326 g_error_free( error );
1327
1328 g_signal_connect( dialog, "response", G_CALLBACK(gtk_widget_destroy), nullptr);
1329 gtk_widget_show( dialog );
1330 }
1331
1332 // Remember to save the printing settings after this print job
1334 g_object_unref( op );
1335 g_free(export_filename);
1336}
1337
1338static void
1339impl_webkit_set_parent( GncHtml* self, GtkWindow* parent )
1340{
1342
1343 g_return_if_fail( self != nullptr );
1344 g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
1345
1346 priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1347 priv->base.parent = GTK_WIDGET(parent);
1348}
1349
1350static void
1351impl_webkit_default_zoom_changed(gpointer prefs, gchar *pref, gpointer user_data)
1352{
1353 gdouble zoom = 1.0;
1354 GncHtmlWebkit* self = GNC_HTML_WEBKIT(user_data);
1355 GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1356
1357 g_return_if_fail(user_data != nullptr);
1358
1359 zoom = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_RPT_DFLT_ZOOM);
1360 webkit_web_view_set_zoom_level (priv->web_view, zoom);
1361
1362}
Account handling public routines.
All type declarations for the whole Gnucash engine.
Generic api to store and retrieve preferences.
#define GNC_GTK_PRINT_SETTINGS_EXPORT_DIR
Key for saving the PDF-export directory in the print settings.
void gnc_print_operation_init(GtkPrintOperation *op, const gchar *jobname)
If print settings have been saved by gnc_print_operation_save_print_settings(), then set them on the ...
void gnc_print_operation_save_print_settings(GtkPrintOperation *op)
Retrieve the print settings from the GtkPrintOperation op and save them in a static variable.
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272
void gnc_prefs_remove_cb_by_func(const gchar *group, const gchar *pref_name, gpointer func, gpointer user_data)
Remove a function that was registered for a callback when the given preference changed.
gdouble gnc_prefs_get_float(const gchar *group, const gchar *pref_name)
Get an float value from the preferences backend.
gulong gnc_prefs_register_cb(const char *group, const gchar *pref_name, gpointer func, gpointer user_data)
Register a callback that gets triggered when the given preference changes.
Functions for printing.