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