GnuCash  5.6-150-g038405b370+
gnc-html-webkit2.c
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 
28 #include <platform.h>
29 #ifdef __MINGW32__
30 #define _GL_UNISTD_H //Deflect poisonous define of close in Guile's GnuLib
31 #endif
32 #include <libguile.h>
33 #if PLATFORM(WINDOWS)
34 #include <windows.h>
35 #endif
36 
37 #include <gtk/gtk.h>
38 #include <glib/gi18n.h>
39 #include <glib/gstdio.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <unistd.h>
47 #include <regex.h>
48 
49 #include <webkit2/webkit2.h>
50 
51 #include "Account.h"
52 #include "gnc-prefs.h"
53 #include "gnc-gui-query.h"
54 #include "gnc-engine.h"
55 #include "gnc-html.h"
56 #include "gnc-html-webkit.h"
57 #include "gnc-html-history.h"
58 #include "print-session.h"
59 
60 
61 G_DEFINE_TYPE(GncHtmlWebkit, gnc_html_webkit, GNC_TYPE_HTML )
62 
63 static void gnc_html_webkit_dispose( GObject* obj );
64 static void gnc_html_webkit_finalize( GObject* obj );
65 
66 #define GNC_HTML_WEBKIT_GET_PRIVATE(o) (GNC_HTML_WEBKIT(o)->priv)
67 
68 #include "gnc-html-webkit-p.h"
69 
70 /* indicates the debugging module that this .o belongs to. */
71 static QofLogModule log_module = GNC_MOD_HTML;
72 
73 /* hashes for URLType -> protocol and protocol -> URLType */
74 //extern GHashTable* gnc_html_type_to_proto_hash;
75 //extern GHashTable* gnc_html_proto_to_type_hash;
76 
77 /* hashes an HTML <object classid="ID"> classid to a handler function */
78 extern GHashTable* gnc_html_object_handlers;
79 
80 /* hashes handlers for loading different URLType data */
81 extern GHashTable* gnc_html_stream_handlers;
82 
83 /* hashes handlers for handling different URLType data */
84 extern GHashTable* gnc_html_url_handlers;
85 
86 static char error_404_format[] = "<html><body><h3>%s</h3><p>%s</body></html>";
87 static char error_404_title[] = N_("Not found");
88 static char error_404_body[] = N_("The specified URL could not be loaded.");
89 
90 #define BASE_URI_NAME "base-uri"
91 #define GNC_PREF_RPT_DFLT_ZOOM "default-zoom"
92 
93 static gboolean webkit_decide_policy_cb (WebKitWebView* web_view,
94  WebKitPolicyDecision *decision,
95  WebKitPolicyDecisionType decision_type,
96  gpointer user_data);
97 static void webkit_mouse_target_cb (WebKitWebView* web_view,
98  WebKitHitTestResult *hit,
99  guint modifiers, gpointer data);
100 static gboolean webkit_notification_cb (WebKitWebView *web_view,
101  WebKitNotification *note,
102  gpointer user_data);
103 static gboolean webkit_load_failed_cb (WebKitWebView *web_view,
104  WebKitLoadEvent event,
105  gchar *uri, GError *error,
106  gpointer user_data);
107 static void webkit_resource_load_started_cb (WebKitWebView *web_view,
108  WebKitWebResource *resource,
109  WebKitURIRequest *request,
110  gpointer data);
111 static gchar* handle_embedded_object( GncHtmlWebkit* self, gchar* html_str );
112 static void impl_webkit_show_url( GncHtml* self, URLType type,
113  const gchar* location, const gchar* label,
114  gboolean new_window_hint );
115 static void impl_webkit_show_data( GncHtml* self, const gchar* data, int datalen );
116 static void impl_webkit_reload( GncHtml* self, gboolean force_rebuild );
117 static void impl_webkit_copy_to_clipboard( GncHtml* self );
118 static gboolean impl_webkit_export_to_file( GncHtml* self, const gchar* filepath );
119 static void impl_webkit_print (GncHtml* self,const gchar* jobname);
120 static void impl_webkit_cancel( GncHtml* self );
121 static void impl_webkit_set_parent( GncHtml* self, GtkWindow* parent );
122 static void impl_webkit_default_zoom_changed(gpointer prefs, gchar *pref, gpointer user_data);
123 
124 static GtkWidget*
125 gnc_html_webkit_webview_new (void)
126 {
127  GtkWidget *view = webkit_web_view_new ();
128  WebKitSettings *webkit_settings = NULL;
129  const char *default_font_family = NULL;
130  GtkStyleContext *style = gtk_widget_get_style_context (view);
131  GValue val = G_VALUE_INIT;
132  GtkStateFlags state = gtk_style_context_get_state (style);
133  gtk_style_context_get_property (style, GTK_STYLE_PROPERTY_FONT,
134  state, &val);
135 
136  if (G_VALUE_HOLDS_BOXED (&val))
137  {
138  const PangoFontDescription *font =
139  (const PangoFontDescription*)g_value_get_boxed (&val);
140  default_font_family = pango_font_description_get_family (font);
141  }
142 /* Set default webkit settings */
143  webkit_settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
144  g_object_set (G_OBJECT(webkit_settings),
145  "default-charset", "utf-8",
146  "allow-file-access-from-file-urls", TRUE,
147  "allow-universal-access-from-file-urls", TRUE,
148  "enable-java", FALSE,
149  "enable-page-cache", FALSE,
150  "enable-plugins", FALSE,
151  "enable-site-specific-quirks", FALSE,
152  "enable-xss-auditor", FALSE,
153  "enable-developer-extras", TRUE,
154  NULL);
155  if (default_font_family != NULL)
156  {
157  g_object_set (G_OBJECT (webkit_settings),
158  "default-font-family", default_font_family, NULL);
159  }
160  g_value_unset (&val);
161  return view;
162 }
163 
164 static void
165 gnc_html_webkit_init( GncHtmlWebkit* self )
166 {
167  GncHtmlWebkitPrivate* priv;
168  GncHtmlWebkitPrivate* new_priv;
169  gdouble zoom = 1.0;
170 
171  new_priv = g_realloc (GNC_HTML(self)->priv, sizeof(GncHtmlWebkitPrivate));
172  priv = self->priv = new_priv;
173  GNC_HTML(self)->priv = (GncHtmlPrivate*)priv;
174 
175  priv->html_string = NULL;
176  priv->web_view = WEBKIT_WEB_VIEW (gnc_html_webkit_webview_new ());
177 
178 
179  /* Scale everything up */
180  zoom = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL_REPORT,
181  GNC_PREF_RPT_DFLT_ZOOM);
182  webkit_web_view_set_zoom_level (priv->web_view, zoom);
183 
184 
185  gtk_container_add( GTK_CONTAINER(priv->base.container),
186  GTK_WIDGET(priv->web_view) );
187 
188  g_object_ref_sink( priv->base.container );
189 
190  /* signals */
191  g_signal_connect (priv->web_view, "decide-policy",
192  G_CALLBACK (webkit_decide_policy_cb),
193  self);
194 
195  g_signal_connect (priv->web_view, "mouse-target-changed",
196  G_CALLBACK (webkit_mouse_target_cb),
197  self);
198 
199  g_signal_connect (priv->web_view, "show-notification",
200  G_CALLBACK (webkit_notification_cb),
201  self);
202 
203  g_signal_connect (priv->web_view, "load-failed",
204  G_CALLBACK (webkit_load_failed_cb),
205  self);
206  g_signal_connect (priv->web_view, "resource-load-started",
207  G_CALLBACK (webkit_resource_load_started_cb),
208  self);
209  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL_REPORT,
210  GNC_PREF_RPT_DFLT_ZOOM,
211  impl_webkit_default_zoom_changed,
212  self);
213 
214  LEAVE("retval %p", self);
215 }
216 
217 static void
218 gnc_html_webkit_class_init( GncHtmlWebkitClass* klass )
219 {
220  GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
221  GncHtmlClass* html_class = GNC_HTML_CLASS(klass);
222 
223  gobject_class->dispose = gnc_html_webkit_dispose;
224  gobject_class->finalize = gnc_html_webkit_finalize;
225 
226  html_class->show_url = impl_webkit_show_url;
227  html_class->show_data = impl_webkit_show_data;
228  html_class->reload = impl_webkit_reload;
229  html_class->copy_to_clipboard = impl_webkit_copy_to_clipboard;
230  html_class->export_to_file = impl_webkit_export_to_file;
231  html_class->print = impl_webkit_print;
232  html_class->cancel = impl_webkit_cancel;
233  html_class->set_parent = impl_webkit_set_parent;
234 }
235 
236 static void
237 gnc_html_webkit_dispose( GObject* obj )
238 {
239  GncHtmlWebkit* self = GNC_HTML_WEBKIT(obj);
240  GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
241 
242  if ( priv->web_view != NULL )
243  {
244  gtk_container_remove (GTK_CONTAINER(priv->base.container),
245  GTK_WIDGET(priv->web_view));
246 
247  priv->web_view = NULL;
248  }
249 
250  if ( priv->html_string != NULL )
251  {
252  g_free( priv->html_string );
253  priv->html_string = NULL;
254  }
255 
256  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
257  GNC_PREF_RPT_DFLT_ZOOM,
258  impl_webkit_default_zoom_changed,
259  obj);
260 
261  G_OBJECT_CLASS(gnc_html_webkit_parent_class)->dispose( obj );
262 }
263 
264 static void
265 gnc_html_webkit_finalize( GObject* obj )
266 {
267  GncHtmlWebkit* self = GNC_HTML_WEBKIT(obj);
268 
269 // if( self->priv != NULL ) {
270 // g_free( self->priv );
271  self->priv = NULL;
272 // }
273 
274  G_OBJECT_CLASS(gnc_html_webkit_parent_class)->finalize( obj );
275 }
276 
277 /*****************************************************************************/
278 
279 static char*
280 extract_base_name(URLType type, const gchar* path)
281 {
282  gchar machine_rexp[] = "^(//[^/]*)/*(/.*)?$";
283  gchar path_rexp[] = "^/*(.*)/+([^/]*)$";
284  regex_t compiled_m, compiled_p;
285  regmatch_t match[4];
286  gchar * machine = NULL, * location = NULL, * base = NULL;
287  gchar * basename = NULL;
288 
289  DEBUG(" ");
290  if (!path) return NULL;
291 
292  regcomp(&compiled_m, machine_rexp, REG_EXTENDED);
293  regcomp(&compiled_p, path_rexp, REG_EXTENDED);
294 
295  if (!g_strcmp0 (type, URL_TYPE_HTTP) ||
296  !g_strcmp0 (type, URL_TYPE_SECURE) ||
297  !g_strcmp0 (type, URL_TYPE_FTP))
298  {
299 
300  /* step 1: split the machine name away from the path
301  * components */
302  if (!regexec(&compiled_m, path, 4, match, 0))
303  {
304  /* $1 is the machine name */
305  if (match[1].rm_so != -1)
306  {
307  machine = g_strndup(path + match[1].rm_so,
308  match[1].rm_eo - match[1].rm_so);
309  }
310  /* $2 is the path */
311  if (match[2].rm_so != -1)
312  {
313  location = g_strndup(path + match[2].rm_so,
314  match[2].rm_eo - match[2].rm_so);
315  }
316  }
317  }
318  else
319  {
320  location = g_strdup(path);
321  }
322  /* step 2: split up the path into prefix and file components */
323  if (location)
324  {
325  if (!regexec(&compiled_p, location, 4, match, 0))
326  {
327  if (match[1].rm_so != -1)
328  {
329  base = g_strndup(location + match[1].rm_so,
330  match[1].rm_eo - match[1].rm_so);
331  }
332  else
333  {
334  base = NULL;
335  }
336  }
337  }
338 
339  regfree(&compiled_m);
340  regfree(&compiled_p);
341 
342  if (machine)
343  {
344  if (base && (strlen(base) > 0))
345  {
346  basename = g_strconcat(machine, "/", base, "/", NULL);
347  }
348  else
349  {
350  basename = g_strconcat(machine, "/", NULL);
351  }
352  }
353  else
354  {
355  if (base && (strlen(base) > 0))
356  {
357  basename = g_strdup(base);
358  }
359  else
360  {
361  basename = NULL;
362  }
363  }
364 
365  g_free(machine);
366  g_free(base);
367  g_free(location);
368  return basename;
369 }
370 
371 static gboolean
372 http_allowed()
373 {
374  return TRUE;
375 }
376 
377 static gboolean
378 https_allowed()
379 {
380  return TRUE;
381 }
382 
383 static gchar*
384 handle_embedded_object( GncHtmlWebkit* self, gchar* html_str )
385 {
386  // Find the <object> tag and get the classid from it. This will provide the correct
387  // object callback handler. Pass the <object> entity text to the handler. What should
388  // come back is embedded image information.
389  gchar* remainder_str = html_str;
390  gchar* object_tag;
391  gchar* end_object_tag;
392  gchar* object_contents;
393  gchar* html_str_start = NULL;
394  gchar* html_str_middle;
395  gchar* html_str_result = NULL;
396  gchar* classid_start;
397  gchar* classid_end;
398  gchar* classid_str;
399  gchar* new_chunk;
400  GncHTMLObjectCB h;
401 
402  object_tag = g_strstr_len( remainder_str, -1, "<object classid=" );
403  while (object_tag)
404  {
405 
406  classid_start = object_tag + strlen( "<object classid=" ) + 1;
407  classid_end = g_strstr_len( classid_start, -1, "\"" );
408  classid_str = g_strndup( classid_start, (classid_end - classid_start) );
409 
410  end_object_tag = g_strstr_len( object_tag, -1, "</object>" );
411  if ( end_object_tag == NULL )
412  {
413  /* Hmmm... no object end tag
414  Return the original html string because we can't properly parse it */
415  g_free (classid_str);
416  g_free (html_str_result);
417  return g_strdup (html_str);
418  }
419  end_object_tag += strlen( "</object>" );
420  object_contents = g_strndup( object_tag, (end_object_tag - object_tag) );
421 
422  h = g_hash_table_lookup( gnc_html_object_handlers, classid_str );
423  if ( h != NULL )
424  {
425  (void)h( GNC_HTML(self), object_contents, &html_str_middle );
426  }
427  else
428  {
429  html_str_middle = g_strdup_printf( "No handler found for classid \"%s\"", classid_str );
430  }
431 
432  html_str_start = html_str_result;
433  new_chunk = g_strndup (remainder_str, (object_tag - remainder_str));
434  if (!html_str_start)
435  html_str_result = g_strconcat (new_chunk, html_str_middle, NULL);
436  else
437  html_str_result = g_strconcat (html_str_start, new_chunk, html_str_middle, NULL);
438 
439  g_free( html_str_start );
440  g_free( new_chunk );
441  g_free( html_str_middle );
442 
443  remainder_str = end_object_tag;
444  object_tag = g_strstr_len( remainder_str, -1, "<object classid=" );
445  }
446 
447  if (html_str_result)
448  {
449  html_str_start = html_str_result;
450  html_str_result = g_strconcat (html_str_start, remainder_str, NULL);
451  g_free (html_str_start);
452  }
453  else
454  html_str_result = g_strdup (remainder_str);
455 
456  return html_str_result;
457 }
458 
459 /********************************************************************
460  * load_to_stream : actually do the work of loading the HTML
461  * or binary data referenced by a URL and feeding it into the webkit
462  * widget.
463  ********************************************************************/
464 
465 static gboolean
466 load_to_stream( GncHtmlWebkit* self, URLType type,
467  const gchar* location, const gchar* label )
468 {
469  gchar* fdata = NULL;
470  int fdata_len = 0;
471  GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
472 
473  DEBUG( "type %s, location %s, label %s", type ? type : "(null)",
474  location ? location : "(null)", label ? label : "(null)");
475 
476  g_return_val_if_fail( self != NULL, FALSE );
477 
478  if ( gnc_html_stream_handlers != NULL )
479  {
480  GncHTMLStreamCB stream_handler;
481 
482  stream_handler = g_hash_table_lookup( gnc_html_stream_handlers, type );
483  if ( stream_handler )
484  {
485  GncHtml *weak_html = GNC_HTML(self);
486  gboolean ok;
487 
488  g_object_add_weak_pointer(G_OBJECT(self),
489  (gpointer*)(&weak_html));
490  ok = stream_handler( location, &fdata, &fdata_len );
491 
492  if (!weak_html) // will be NULL if self has been destroyed
493  {
494  g_free (fdata);
495  return FALSE;
496  }
497  else
498  {
499  g_object_remove_weak_pointer(G_OBJECT(self),
500  (gpointer*)(&weak_html));
501  }
502 
503  if ( ok )
504  {
505  fdata = fdata ? fdata : g_strdup( "" );
506 
507  // Until webkitgtk supports download requests,
508  // look for "<object classid=" indicating the
509  // beginning of an embedded graph. If found,
510  // handle it
511  if ( g_strstr_len( fdata, -1, "<object classid=" ) != NULL )
512  {
513  gchar* new_fdata;
514  new_fdata = handle_embedded_object( self, fdata );
515  g_free( fdata );
516  fdata = new_fdata;
517  }
518 
519  // Save a copy for export purposes
520  if ( priv->html_string != NULL )
521  {
522  g_free( priv->html_string );
523  }
524  priv->html_string = g_strdup( fdata );
525  impl_webkit_show_data( GNC_HTML(self), fdata, strlen(fdata) );
526 // webkit_web_view_load_html (priv->web_view, fdata,
527 // BASE_URI_NAME);
528  }
529  else
530  {
531  fdata = fdata ? fdata :
532  g_strdup_printf( error_404_format,
533  _(error_404_title), _(error_404_body) );
534  webkit_web_view_load_html (priv->web_view, fdata,
535  BASE_URI_NAME);
536  }
537 
538  g_free( fdata );
539 
540  if ( label )
541  {
542  while ( gtk_events_pending() )
543  {
544  gtk_main_iteration();
545  }
546  /* No action required: Webkit jumps to the anchor on its own. */
547  }
548  return TRUE;
549  }
550  }
551 
552  do
553  {
554  if ( !g_strcmp0( type, URL_TYPE_SECURE ) ||
555  !g_strcmp0( type, URL_TYPE_HTTP ) )
556  {
557 
558  if ( !g_strcmp0( type, URL_TYPE_SECURE ) )
559  {
560  if ( !https_allowed() )
561  {
562  gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
563  _("Secure HTTP access is disabled. "
564  "You can enable it in the Network section of "
565  "the Preferences dialog."));
566  break;
567  }
568  }
569 
570  if ( !http_allowed() )
571  {
572  gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
573  _("Network HTTP access is disabled. "
574  "You can enable it in the Network section of "
575  "the Preferences dialog."));
576  }
577  else
578  {
579  gnc_build_url( type, location, label );
580  }
581  }
582  else
583  {
584  PWARN( "load_to_stream for inappropriate type\n"
585  "\turl = '%s#%s'\n",
586  location ? location : "(null)",
587  label ? label : "(null)" );
588  fdata = g_strdup_printf( error_404_format,
589  _(error_404_title), _(error_404_body) );
590  webkit_web_view_load_html (priv->web_view, fdata, BASE_URI_NAME);
591  g_free( fdata );
592  }
593  }
594  while ( FALSE );
595  return TRUE;
596 }
597 
598 static gboolean
599 perform_navigation_policy (WebKitWebView *web_view,
600  WebKitNavigationPolicyDecision *decision,
601  GncHtml *self)
602 {
603  WebKitURIRequest *req = NULL;
604  const gchar* uri, *scheme; // Can't init it here.
605  gchar *location = NULL, *label = NULL;
606  gboolean ignore = FALSE;
607  WebKitNavigationAction *action =
608  webkit_navigation_policy_decision_get_navigation_action (decision);
609  if (webkit_navigation_action_get_navigation_type (action) !=
610  WEBKIT_NAVIGATION_TYPE_LINK_CLICKED)
611  {
612  webkit_policy_decision_use ((WebKitPolicyDecision*)decision);
613  return TRUE;
614  }
615  req = webkit_navigation_action_get_request (action);
616  uri = webkit_uri_request_get_uri (req);
617  scheme = gnc_html_parse_url (self, uri, &location, &label);
618  if (strcmp (scheme, URL_TYPE_FILE) != 0)
619  {
620  impl_webkit_show_url (self, scheme, location, label, FALSE);
621  ignore = TRUE;
622  }
623  g_free (location);
624  g_free (label);
625  if (ignore)
626  webkit_policy_decision_ignore ((WebKitPolicyDecision*)decision);
627  else
628  webkit_policy_decision_use ((WebKitPolicyDecision*)decision);
629  return TRUE;
630 }
631 
632 static gboolean
633 webkit_decide_policy_cb (WebKitWebView *web_view,
634  WebKitPolicyDecision *decision,
635  WebKitPolicyDecisionType decision_type,
636  gpointer user_data)
637 {
638 /* This turns out to be the signal to intercept for handling a link-click. */
639  if (decision_type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION)
640  {
641  webkit_policy_decision_use (decision);
642  return TRUE;
643  }
644  return perform_navigation_policy (
645  web_view, (WebKitNavigationPolicyDecision*) decision,
646  GNC_HTML (user_data));
647 }
648 
649 static void
650 webkit_mouse_target_cb (WebKitWebView *web_view, WebKitHitTestResult *hit,
651  guint modifiers, gpointer user_data)
652 {
653  GncHtmlWebkitPrivate* priv;
654  GncHtmlWebkit *self = (GncHtmlWebkit*)user_data;
655  gchar *uri;
656 
657  if (!webkit_hit_test_result_context_is_link (hit))
658  return;
659 
660  priv = GNC_HTML_WEBKIT_GET_PRIVATE (self);
661  uri = g_strdup (webkit_hit_test_result_get_link_uri (hit));
662  g_free (priv->base.current_link);
663  priv->base.current_link = uri;
664  if (priv->base.flyover_cb)
665  {
666  (priv->base.flyover_cb) (GNC_HTML (self), uri,
667  priv->base.flyover_cb_data);
668  }
669 }
670 static gboolean
671 webkit_notification_cb (WebKitWebView* web_view, WebKitNotification *note,
672  gpointer user_data)
673 {
674  GtkWindow *top = NULL;
675  GtkWidget *dialog = NULL;
676  GncHtmlWebkit *self = (GncHtmlWebkit*)user_data;
677  g_return_val_if_fail (self != NULL, FALSE);
678  g_return_val_if_fail (note != NULL, FALSE);
679 
680  top = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (web_view)));
681  dialog = gtk_message_dialog_new (top, GTK_DIALOG_MODAL,
682  GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
683  "%s\n%s",
684  webkit_notification_get_title (note),
685  webkit_notification_get_body (note));
686  gtk_dialog_run (GTK_DIALOG (dialog));
687  gtk_widget_destroy (dialog);
688  return TRUE;
689 }
690 
691 static gboolean
692 webkit_load_failed_cb (WebKitWebView *web_view, WebKitLoadEvent event,
693  gchar *uri, GError *error, gpointer user_data)
694 {
695  PERR ("WebKit load of %s failed due to %s\n", uri, error->message);
696  return FALSE;
697 }
698 static void
699 webkit_resource_load_failed_cb (WebKitWebResource *resource,
700  GError *error,
701  gpointer data)
702 {
703  WebKitURIResponse *response = webkit_web_resource_get_response (resource);
704  const gchar * uri = webkit_web_resource_get_uri (resource);
705  PERR ("Load of resource at %s failed with error %s and status code %d.\n",
706  uri, error->message, webkit_uri_response_get_status_code (response));
707 }
708 
709 static void
710 webkit_resource_load_finished_cb (WebKitWebResource *resource, gpointer data)
711 {
712  DEBUG ("Load of resource %s completed.\n", webkit_web_resource_get_uri(resource));
713 }
714 
715 static void
716 webkit_resource_load_started_cb (WebKitWebView *web_view,
717  WebKitWebResource *resource,
718  WebKitURIRequest *request,
719  gpointer data)
720 {
721  DEBUG ("Load of resource %s begun.\n", webkit_web_resource_get_uri(resource));
722  g_signal_connect (resource, "failed",
723  G_CALLBACK (webkit_resource_load_failed_cb),
724  data);
725  g_signal_connect (resource, "finished",
726  G_CALLBACK (webkit_resource_load_finished_cb),
727  data);
728 }
729 
730 /********************************************************************
731  * gnc_html_open_scm
732  * insert some scheme-generated HTML
733  ********************************************************************/
734 
735 static void
736 gnc_html_open_scm( GncHtmlWebkit* self, const gchar * location,
737  const gchar * label, int newwin )
738 {
739  PINFO("location='%s'", location ? location : "(null)");
740 }
741 
742 
743 /********************************************************************
744  * gnc_html_show_data
745  * display some HTML that the creator of the gnc-html got from
746  * somewhere.
747  ********************************************************************/
748 
749 static void
750 impl_webkit_show_data( GncHtml* self, const gchar* data, int datalen )
751 {
752  GncHtmlWebkitPrivate* priv;
753 #define TEMPLATE_REPORT_FILE_NAME "gnc-report-XXXXXX.html"
754  int fd;
755  gchar* uri;
756  gchar *filename;
757 
758  g_return_if_fail( self != NULL );
759  g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
760 
761  ENTER( "datalen %d, data %20.20s", datalen, data );
762 
763  priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
764 
765  /* Export the HTML to a file and load the file URI. On Linux, this seems to get around some
766  security problems (otherwise, it can complain that embedded images aren't permitted to be
767  viewed because they are local resources). On Windows, this allows the embedded images to
768  be viewed (maybe for the same reason as on Linux, but I haven't found where it puts those
769  messages. */
770  filename = g_build_filename(g_get_tmp_dir(), TEMPLATE_REPORT_FILE_NAME, (gchar *)NULL);
771  fd = g_mkstemp( filename );
772  impl_webkit_export_to_file( self, filename );
773  close( fd );
774  uri = g_strdup_printf( "file://%s", filename );
775  g_free(filename);
776  DEBUG("Loading uri '%s'", uri);
777  webkit_web_view_load_uri( priv->web_view, uri );
778  g_free( uri );
779 
780  LEAVE("");
781 }
782 
783 /********************************************************************
784  * gnc_html_show_url
785  *
786  * open a URL. This is called when the user clicks a link or
787  * for the creator of the gnc_html window to explicitly request
788  * a URL.
789  ********************************************************************/
790 
791 static void
792 impl_webkit_show_url( GncHtml* self, URLType type,
793  const gchar* location, const gchar* label,
794  gboolean new_window_hint )
795 {
796  GncHTMLUrlCB url_handler;
797  gboolean new_window;
798  GncHtmlWebkitPrivate* priv;
799  gboolean stream_loaded = FALSE;
800 
801  g_return_if_fail( self != NULL );
802  g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
803  g_return_if_fail( location != NULL );
804 
805  priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
806 
807  /* make sure it's OK to show this URL type in this window */
808  if ( new_window_hint == 0 )
809  {
810  if ( priv->base.urltype_cb )
811  {
812  new_window = !((priv->base.urltype_cb)( type ));
813  }
814  else
815  {
816  new_window = FALSE;
817  }
818  }
819  else
820  {
821  new_window = TRUE;
822  }
823 
824  if ( !new_window )
825  {
826  gnc_html_cancel( GNC_HTML(self) );
827  }
828 
829  if ( gnc_html_url_handlers )
830  {
831  url_handler = g_hash_table_lookup( gnc_html_url_handlers, type );
832  }
833  else
834  {
835  url_handler = NULL;
836  }
837 
838  if ( url_handler )
839  {
840  GNCURLResult result;
841  gboolean ok;
842 
843  result.load_to_stream = FALSE;
844  result.url_type = type;
845  result.location = NULL;
846  result.label = NULL;
847  result.base_type = URL_TYPE_FILE;
848  result.base_location = NULL;
849  result.error_message = NULL;
850  result.parent = GTK_WINDOW (priv->base.parent);
851 
852  ok = url_handler( location, label, new_window, &result );
853  if ( !ok )
854  {
855  if ( result.error_message )
856  {
857  gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s", result.error_message );
858  }
859  else
860  {
861  /* %s is a URL (some location somewhere). */
862  gnc_error_dialog (GTK_WINDOW (priv->base.parent), _("There was an error accessing %s."), location );
863  }
864 
865  if ( priv->base.load_cb )
866  {
867  priv->base.load_cb( GNC_HTML(self), result.url_type,
868  location, label, priv->base.load_cb_data );
869  }
870  }
871  else if ( result.load_to_stream )
872  {
873  gnc_html_history_node *hnode;
874  const char *new_location;
875  const char *new_label;
876 
877  new_location = result.location ? result.location : location;
878  new_label = result.label ? result.label : label;
879  hnode = gnc_html_history_node_new( result.url_type, new_location, new_label );
880 
881  gnc_html_history_append( priv->base.history, hnode );
882 
883  g_free( priv->base.base_location );
884  priv->base.base_type = result.base_type;
885  priv->base.base_location =
886  g_strdup( extract_base_name( result.base_type, new_location ) );
887  DEBUG( "resetting base location to %s",
888  priv->base.base_location ? priv->base.base_location : "(null)" );
889 
890  stream_loaded = load_to_stream( GNC_HTML_WEBKIT(self),
891  result.url_type,
892  new_location, new_label );
893 
894  if ( stream_loaded && priv->base.load_cb != NULL )
895  {
896  priv->base.load_cb( GNC_HTML(self), result.url_type,
897  new_location, new_label, priv->base.load_cb_data );
898  }
899  }
900 
901  g_free( result.location );
902  g_free( result.label );
903  g_free( result.base_location );
904  g_free( result.error_message );
905 
906  return;
907  }
908 
909  if ( g_strcmp0( type, URL_TYPE_SCHEME ) == 0 )
910  {
911  gnc_html_open_scm( GNC_HTML_WEBKIT(self), location, label, new_window );
912 
913  }
914  else if ( g_strcmp0( type, URL_TYPE_JUMP ) == 0 )
915  {
916  /* Webkit jumps to the anchor on its own */
917  }
918  else if ( g_strcmp0( type, URL_TYPE_SECURE ) == 0 ||
919  g_strcmp0( type, URL_TYPE_HTTP ) == 0 ||
920  g_strcmp0( type, URL_TYPE_FILE ) == 0 )
921  {
922 
923  do
924  {
925  if ( g_strcmp0( type, URL_TYPE_SECURE ) == 0 )
926  {
927  if ( !https_allowed() )
928  {
929  gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
930  _("Secure HTTP access is disabled. "
931  "You can enable it in the Network section of "
932  "the Preferences dialog.") );
933  break;
934  }
935  }
936 
937  if ( g_strcmp0( type, URL_TYPE_HTTP ) == 0 )
938  {
939  if ( !http_allowed() )
940  {
941  gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
942  _("Network HTTP access is disabled. "
943  "You can enable it in the Network section of "
944  "the Preferences dialog.") );
945  break;
946  }
947  }
948 
949  priv->base.base_type = type;
950 
951  if ( priv->base.base_location != NULL ) g_free( priv->base.base_location );
952  priv->base.base_location = extract_base_name( type, location );
953 
954  /* FIXME : handle new_window = 1 */
955  gnc_html_history_append( priv->base.history,
956  gnc_html_history_node_new( type, location, label ) );
957  stream_loaded = load_to_stream( GNC_HTML_WEBKIT(self),
958  type, location, label );
959 
960  }
961  while ( FALSE );
962  }
963  else
964  {
965  PERR( "URLType %s not supported.", type );
966  }
967 
968  if ( stream_loaded && priv->base.load_cb != NULL )
969  {
970  (priv->base.load_cb)( GNC_HTML(self), type, location, label, priv->base.load_cb_data );
971  }
972 }
973 
974 
975 /********************************************************************
976  * gnc_html_reload
977  * reload the current page
978  * if force_rebuild is TRUE, the report is recreated, if FALSE, report
979  * is reloaded by webkit
980  ********************************************************************/
981 
982 static void
983 impl_webkit_reload( GncHtml* self, gboolean force_rebuild )
984 {
985  GncHtmlWebkitPrivate* priv;
986 
987  g_return_if_fail( self != NULL );
988  g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
989 
990  priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
991 
992  if ( force_rebuild )
993  {
994  gnc_html_history_node *n = gnc_html_history_get_current( priv->base.history );
995  if ( n != NULL )
996  gnc_html_show_url( self, n->type, n->location, n->label, 0 );
997  }
998  else
999  webkit_web_view_reload( priv->web_view );
1000 }
1001 
1002 
1003 /********************************************************************
1004  * gnc_html_new
1005  * create and set up a new webkit widget.
1006  ********************************************************************/
1007 
1008 GncHtml*
1009 gnc_html_webkit_new( void )
1010 {
1011  GncHtmlWebkit* self = g_object_new( GNC_TYPE_HTML_WEBKIT, NULL );
1012  return GNC_HTML(self);
1013 }
1014 
1015 /********************************************************************
1016  * gnc_html_cancel
1017  * cancel any outstanding HTML fetch requests.
1018  ********************************************************************/
1019 
1020 static gboolean
1021 webkit_cancel_helper(gpointer key, gpointer value, gpointer user_data)
1022 {
1023  g_free(key);
1024  g_list_free((GList *)value);
1025  return TRUE;
1026 }
1027 
1028 static void
1029 impl_webkit_cancel( GncHtml* self )
1030 {
1031  GncHtmlWebkitPrivate* priv;
1032 
1033  g_return_if_fail( self != NULL );
1034  g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
1035 
1036  priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1037 
1038  /* remove our own references to requests */
1039  //gnc_http_cancel_requests( priv->http );
1040 
1041  g_hash_table_foreach_remove( priv->base.request_info, webkit_cancel_helper, NULL );
1042 }
1043 
1044 static void
1045 impl_webkit_copy_to_clipboard( GncHtml* self )
1046 {
1047  GncHtmlWebkitPrivate* priv;
1048 
1049  g_return_if_fail( self != NULL );
1050  g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
1051 
1052  priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1053  webkit_web_view_execute_editing_command (priv->web_view,
1054  WEBKIT_EDITING_COMMAND_COPY);
1055 }
1056 
1057 /**************************************************************
1058  * gnc_html_export_to_file
1059  *
1060  * @param self GncHtmlWebkit object
1061  * @param filepath Where to write the HTML
1062  * @return TRUE if successful, FALSE if unsuccessful
1063  **************************************************************/
1064 static gboolean
1065 impl_webkit_export_to_file( GncHtml* self, const char *filepath )
1066 {
1067  FILE *fh;
1068  GncHtmlWebkitPrivate* priv;
1069 
1070  g_return_val_if_fail( self != NULL, FALSE );
1071  g_return_val_if_fail( GNC_IS_HTML_WEBKIT(self), FALSE );
1072  g_return_val_if_fail( filepath != NULL, FALSE );
1073 
1074  priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1075  if ( priv->html_string == NULL )
1076  {
1077  return FALSE;
1078  }
1079  fh = g_fopen( filepath, "w" );
1080  if ( fh != NULL )
1081  {
1082  gint written;
1083  gint len = strlen( priv->html_string );
1084 
1085  written = fwrite( priv->html_string, 1, len, fh );
1086  fclose (fh);
1087 
1088  if ( written != len )
1089  {
1090  return FALSE;
1091  }
1092 
1093  return TRUE;
1094  }
1095  else
1096  {
1097  return FALSE;
1098  }
1099 }
1100 
1101 /* The webkit1 comment was
1102  * If printing on WIN32, in order to prevent the font from being tiny, (see bug
1103  * #591177), A GtkPrintOperation object needs to be created so that the unit can
1104  * be set, and then webkit_web_frame_print_full() needs to be called to use that
1105  * GtkPrintOperation. On other platforms (specifically linux - not sure about
1106  * MacOSX), the version of webkit may not contain the function
1107  * webkit_web_frame_print_full(), so webkit_web_frame_print() is called instead
1108  * (the font size problem doesn't show up on linux).
1109  *
1110  * Webkit2 exposes only a very simple WebKitPrintOperation API. In order to
1111  * implement the above if it proves still to be necessary we'll have to use
1112  * GtkPrintOperation instead, passing it the results of
1113  * webkit_web_view_get_snapshot for each page.
1114  */
1115 static void
1116 impl_webkit_print (GncHtml* self,const gchar* jobname)
1117 {
1118  WebKitPrintOperation *op = NULL;
1119  GtkWindow *top = NULL;
1120  GncHtmlWebkitPrivate *priv;
1121  GtkPrintSettings *print_settings = NULL;
1122  WebKitPrintOperationResponse print_response;
1123  gchar *export_dirname = NULL;
1124  gchar *export_filename = NULL;
1125  gchar* basename = NULL;
1126 
1127  g_return_if_fail (self != NULL);
1128  g_return_if_fail (GNC_IS_HTML_WEBKIT (self));
1129  priv = GNC_HTML_WEBKIT_GET_PRIVATE (self);
1130  op = webkit_print_operation_new (priv->web_view);
1131  basename = g_path_get_basename(jobname);
1132  print_settings = gtk_print_settings_new();
1133  webkit_print_operation_set_print_settings(op, print_settings);
1134  export_filename = g_strdup(jobname);
1135  g_free(basename);
1136  gtk_print_settings_set(print_settings,
1137  GTK_PRINT_SETTINGS_OUTPUT_BASENAME,
1138  export_filename);
1139  webkit_print_operation_set_print_settings(op, print_settings);
1140  // Open a print dialog
1141  top = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (priv->web_view)));
1142  print_response = webkit_print_operation_run_dialog (op, top);
1143  if (print_response == WEBKIT_PRINT_OPERATION_RESPONSE_PRINT)
1144  {
1145  // Get the newly updated print settings
1146  g_object_unref(print_settings);
1147  print_settings = g_object_ref(webkit_print_operation_get_print_settings(op));
1148  }
1149  g_free(export_dirname);
1150  g_free(export_filename);
1151  g_object_unref (op);
1152  g_object_unref (print_settings);
1153 }
1154 
1155 static void
1156 impl_webkit_set_parent( GncHtml* self, GtkWindow* parent )
1157 {
1158  GncHtmlWebkitPrivate* priv;
1159 
1160  g_return_if_fail( self != NULL );
1161  g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
1162 
1163  priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1164  priv->base.parent = GTK_WIDGET(parent);
1165 }
1166 
1167 static void
1168 impl_webkit_default_zoom_changed(gpointer prefs, gchar *pref, gpointer user_data)
1169 {
1170  gdouble zoom = 1.0;
1171  GncHtmlWebkit* self = GNC_HTML_WEBKIT(user_data);
1172  GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1173 
1174  g_return_if_fail(user_data != NULL);
1175 
1176  zoom = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_RPT_DFLT_ZOOM);
1177  webkit_web_view_set_zoom_level (priv->web_view, zoom);
1178 
1179 }
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.
Definition: gnc-prefs.c:128
#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 PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
Account handling public routines.
All type declarations for the whole Gnucash engine.
Generic api to store and retrieve preferences.
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
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.
Definition: gnc-prefs.c:143
gdouble gnc_prefs_get_float(const gchar *group, const gchar *pref_name)
Get an float value from the preferences backend.