diff options
author | Dan Vrátil <dvratil@redhat.com> | 2012-03-29 00:37:35 +0800 |
---|---|---|
committer | Dan Vrátil <dvratil@redhat.com> | 2012-03-29 00:37:35 +0800 |
commit | 6bd1c6833a2c51898ac45865767dd01ba66a95c5 (patch) | |
tree | 51f9cc360c49e71c455f74f72f1605965e73a932 /widgets | |
parent | 038e0eccec595ce1cc39fe95262272e29d5a6fbf (diff) | |
download | gsoc2013-evolution-6bd1c6833a2c51898ac45865767dd01ba66a95c5.tar.gz gsoc2013-evolution-6bd1c6833a2c51898ac45865767dd01ba66a95c5.tar.zst gsoc2013-evolution-6bd1c6833a2c51898ac45865767dd01ba66a95c5.zip |
WebKit port - port widgets
Diffstat (limited to 'widgets')
-rw-r--r-- | widgets/misc/Makefile.am | 9 | ||||
-rw-r--r-- | widgets/misc/e-attachment-button.c | 58 | ||||
-rw-r--r-- | widgets/misc/e-attachment-button.h | 4 | ||||
-rw-r--r-- | widgets/misc/e-attachment-store.h | 1 | ||||
-rw-r--r-- | widgets/misc/e-contact-map-window.c | 4 | ||||
-rw-r--r-- | widgets/misc/e-contact-map.c | 4 | ||||
-rw-r--r-- | widgets/misc/e-contact-marker.c | 4 | ||||
-rw-r--r-- | widgets/misc/e-port-entry.c | 2 | ||||
-rw-r--r-- | widgets/misc/e-preview-pane.c | 4 | ||||
-rw-r--r-- | widgets/misc/e-search-bar.c | 129 | ||||
-rw-r--r-- | widgets/misc/e-search-bar.h | 3 | ||||
-rw-r--r-- | widgets/misc/e-searching-tokenizer.c | 1210 | ||||
-rw-r--r-- | widgets/misc/e-searching-tokenizer.h | 91 | ||||
-rw-r--r-- | widgets/misc/e-web-view-gtkhtml.c | 2301 | ||||
-rw-r--r-- | widgets/misc/e-web-view-gtkhtml.h | 173 | ||||
-rw-r--r-- | widgets/misc/e-web-view.c | 1402 | ||||
-rw-r--r-- | widgets/misc/e-web-view.h | 79 |
17 files changed, 3560 insertions, 1918 deletions
diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am index 1772e7af4b..ab7efa0667 100644 --- a/widgets/misc/Makefile.am +++ b/widgets/misc/Makefile.am @@ -49,7 +49,6 @@ widgetsinclude_HEADERS = \ e-preview-pane.h \ e-printable.h \ e-search-bar.h \ - e-searching-tokenizer.h \ e-selectable.h \ e-selection-model.h \ e-selection-model-array.h \ @@ -63,6 +62,7 @@ widgetsinclude_HEADERS = \ e-signature-tree-view.h \ e-url-entry.h \ e-web-view.h \ + e-web-view-gtkhtml.h \ e-web-view-preview.h \ ea-calendar-cell.h \ ea-calendar-item.h \ @@ -76,13 +76,14 @@ libemiscwidgets_la_CPPFLAGS = \ -I$(top_srcdir)/widgets \ -DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\" \ -DEVOLUTION_UIDIR=\""$(uidir)"\" \ + -DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \ -DG_LOG_DOMAIN=__FILE__ \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ + $(GTKHTML_CFLAGS) \ $(CHAMPLAIN_CFLAGS) \ $(GEOCLUE_CFLAGS) \ - $(CLUTTER_CFLAGS) \ - $(GTKHTML_CFLAGS) + $(CLUTTER_CFLAGS) libemiscwidgets_la_SOURCES = \ $(widgetsinclude_HEADERS) \ @@ -130,7 +131,6 @@ libemiscwidgets_la_SOURCES = \ e-preview-pane.c \ e-printable.c \ e-search-bar.c \ - e-searching-tokenizer.c \ e-selectable.c \ e-selection-model.c \ e-selection-model-array.c \ @@ -144,6 +144,7 @@ libemiscwidgets_la_SOURCES = \ e-signature-tree-view.c \ e-url-entry.c \ e-web-view.c \ + e-web-view-gtkhtml.c \ e-web-view-preview.c \ ea-calendar-cell.c \ ea-calendar-item.c \ diff --git a/widgets/misc/e-attachment-button.c b/widgets/misc/e-attachment-button.c index 44bd372f13..0d46807307 100644 --- a/widgets/misc/e-attachment-button.c +++ b/widgets/misc/e-attachment-button.c @@ -355,27 +355,6 @@ attachment_button_toggle_button_press_event_cb (EAttachmentButton *button, } static void -attachment_button_set_view (EAttachmentButton *button, - EAttachmentView *view) -{ - GtkWidget *popup_menu; - - g_return_if_fail (button->priv->view == NULL); - - button->priv->view = g_object_ref (view); - - popup_menu = e_attachment_view_get_popup_menu (view); - - g_signal_connect_swapped ( - popup_menu, "deactivate", - G_CALLBACK (attachment_button_menu_deactivate_cb), button); - - /* Keep a reference to the popup menu so we can - * disconnect the signal handler in dispose(). */ - button->priv->popup_menu = g_object_ref (popup_menu); -} - -static void attachment_button_set_property (GObject *object, guint property_id, const GValue *value, @@ -401,7 +380,7 @@ attachment_button_set_property (GObject *object, return; case PROP_VIEW: - attachment_button_set_view ( + e_attachment_button_set_view ( E_ATTACHMENT_BUTTON (object), g_value_get_object (value)); return; @@ -566,8 +545,7 @@ e_attachment_button_class_init (EAttachmentButtonClass *class) "View", NULL, E_TYPE_ATTACHMENT_VIEW, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY)); + G_PARAM_READWRITE)); } static void @@ -701,13 +679,10 @@ e_attachment_button_init (EAttachmentButton *button) } GtkWidget * -e_attachment_button_new (EAttachmentView *view) +e_attachment_button_new () { - g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL); - return g_object_new ( - E_TYPE_ATTACHMENT_BUTTON, - "view", view, NULL); + E_TYPE_ATTACHMENT_BUTTON, NULL); } EAttachmentView * @@ -718,6 +693,31 @@ e_attachment_button_get_view (EAttachmentButton *button) return button->priv->view; } +void +e_attachment_button_set_view (EAttachmentButton *button, + EAttachmentView *view) +{ + GtkWidget *popup_menu; + + g_return_if_fail (button->priv->view == NULL); + + if (button->priv->view) + g_object_unref (button->priv->view); + button->priv->view = g_object_ref (view); + + popup_menu = e_attachment_view_get_popup_menu (view); + + g_signal_connect_swapped ( + popup_menu, "deactivate", + G_CALLBACK (attachment_button_menu_deactivate_cb), button); + + /* Keep a reference to the popup menu so we can + * disconnect the signal handler in dispose(). */ + if (button->priv->popup_menu) + g_object_unref (button->priv->popup_menu); + button->priv->popup_menu = g_object_ref (popup_menu); +} + EAttachment * e_attachment_button_get_attachment (EAttachmentButton *button) { diff --git a/widgets/misc/e-attachment-button.h b/widgets/misc/e-attachment-button.h index a27d8ae146..b8a0cbb16d 100644 --- a/widgets/misc/e-attachment-button.h +++ b/widgets/misc/e-attachment-button.h @@ -61,9 +61,11 @@ struct _EAttachmentButtonClass { }; GType e_attachment_button_get_type (void); -GtkWidget * e_attachment_button_new (EAttachmentView *view); +GtkWidget * e_attachment_button_new (void); EAttachmentView * e_attachment_button_get_view (EAttachmentButton *button); +void e_attachment_button_set_view (EAttachmentButton *button, + EAttachmentView *view); EAttachment * e_attachment_button_get_attachment (EAttachmentButton *button); void e_attachment_button_set_attachment diff --git a/widgets/misc/e-attachment-store.h b/widgets/misc/e-attachment-store.h index 7309dd9415..a963f0558f 100644 --- a/widgets/misc/e-attachment-store.h +++ b/widgets/misc/e-attachment-store.h @@ -130,3 +130,4 @@ gchar ** e_attachment_store_save_finish (EAttachmentStore *store, G_END_DECLS #endif /* E_ATTACHMENT_STORE_H */ + diff --git a/widgets/misc/e-contact-map-window.c b/widgets/misc/e-contact-map-window.c index d80aa17359..38fe56ccdf 100644 --- a/widgets/misc/e-contact-map-window.c +++ b/widgets/misc/e-contact-map-window.c @@ -40,8 +40,8 @@ #include <glib-object.h> #define E_CONTACT_MAP_WINDOW_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE \ - ((obj), E_TYPE_CONTACT_MAP, EContactMapWindowPrivate)) + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CONTACT_MAP_WINDOW, EContactMapWindowPrivate)) G_DEFINE_TYPE (EContactMapWindow, e_contact_map_window, GTK_TYPE_WINDOW) diff --git a/widgets/misc/e-contact-map.c b/widgets/misc/e-contact-map.c index b9c2a99905..6e20ac10c9 100644 --- a/widgets/misc/e-contact-map.c +++ b/widgets/misc/e-contact-map.c @@ -43,8 +43,8 @@ #include <math.h> #define E_CONTACT_MAP_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE \ - ((obj), E_TYPE_CONTACT_MAP, EContactMapPrivate)) + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CONTACT_MAP, EContactMapPrivate)) G_DEFINE_TYPE (EContactMap, e_contact_map, GTK_CHAMPLAIN_TYPE_EMBED) diff --git a/widgets/misc/e-contact-marker.c b/widgets/misc/e-contact-marker.c index d7e5ad679c..98f1ea1cd4 100644 --- a/widgets/misc/e-contact-marker.c +++ b/widgets/misc/e-contact-marker.c @@ -39,8 +39,8 @@ #include <string.h> #define E_CONTACT_MARKER_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE \ - ((obj), E_TYPE_CONTACT_MARKER, EContactMarkerPrivate)) + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CONTACT_MARKER, EContactMarkerPrivate)) G_DEFINE_TYPE (EContactMarker, e_contact_marker, CHAMPLAIN_TYPE_LABEL); diff --git a/widgets/misc/e-port-entry.c b/widgets/misc/e-port-entry.c index 470249a772..cf857b5d55 100644 --- a/widgets/misc/e-port-entry.c +++ b/widgets/misc/e-port-entry.c @@ -128,7 +128,7 @@ port_entry_method_changed (EPortEntry *port_entry) if (valid && port_entry->priv->entries != NULL) { for (ii = 0; port_entry->priv->entries[ii].port > 0 && (!have_ssl || !have_nossl); ii++) { /* Use only the first SSL/no-SSL port as a default in the list - and skip the others */ + * and skip the others */ if (port_entry->priv->entries[ii].is_ssl) { if (have_ssl) continue; diff --git a/widgets/misc/e-preview-pane.c b/widgets/misc/e-preview-pane.c index a94b3139b9..92644ec883 100644 --- a/widgets/misc/e-preview-pane.c +++ b/widgets/misc/e-preview-pane.c @@ -157,14 +157,12 @@ preview_pane_constructed (GObject *object) /* EAlertBar controls its own visibility. */ widget = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_policy ( - GTK_SCROLLED_WINDOW (widget), - GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (object), widget, TRUE, TRUE, 0); gtk_container_add (GTK_CONTAINER (widget), priv->web_view); gtk_widget_show (widget); + gtk_widget_show (priv->web_view); widget = e_search_bar_new (E_WEB_VIEW (priv->web_view)); gtk_box_pack_start (GTK_BOX (object), widget, FALSE, FALSE, 0); diff --git a/widgets/misc/e-search-bar.c b/widgets/misc/e-search-bar.c index eafb1b6ad4..9ed0c2d1c9 100644 --- a/widgets/misc/e-search-bar.c +++ b/widgets/misc/e-search-bar.c @@ -27,7 +27,6 @@ #include <glib/gi18n.h> #include <gdk/gdkkeysyms.h> -#include <gtkhtml/gtkhtml-search.h> #define E_SEARCH_BAR_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ @@ -41,7 +40,6 @@ struct _ESearchBarPrivate { GtkWidget *wrapped_prev_box; GtkWidget *matches_label; - ESearchingTokenizer *tokenizer; gchar *active_search; guint rerun_search : 1; @@ -69,46 +67,33 @@ G_DEFINE_TYPE ( GTK_TYPE_HBOX) static void -search_bar_update_matches (ESearchBar *search_bar) +search_bar_update_matches (ESearchBar *search_bar, + guint matches) { - ESearchingTokenizer *tokenizer; GtkWidget *matches_label; - gint matches; gchar *text; search_bar->priv->rerun_search = FALSE; - - tokenizer = e_search_bar_get_tokenizer (search_bar); matches_label = search_bar->priv->matches_label; - matches = e_searching_tokenizer_match_count (tokenizer); - text = g_strdup_printf (_("Matches: %d"), matches); - + text = g_strdup_printf (_("Matches: %u"), matches); gtk_label_set_text (GTK_LABEL (matches_label), text); gtk_widget_show (matches_label); - g_free (text); } static void -search_bar_update_tokenizer (ESearchBar *search_bar) +search_bar_update_highlights (ESearchBar *search_bar) { - ESearchingTokenizer *tokenizer; - gboolean case_sensitive; - gchar *active_search; + EWebView *web_view; + gboolean visible; - tokenizer = e_search_bar_get_tokenizer (search_bar); - case_sensitive = e_search_bar_get_case_sensitive (search_bar); + web_view = e_search_bar_get_web_view (search_bar); - if (gtk_widget_get_visible (GTK_WIDGET (search_bar))) - active_search = search_bar->priv->active_search; - else - active_search = NULL; + visible = gtk_widget_get_visible (GTK_WIDGET (search_bar)); - e_searching_tokenizer_set_primary_case_sensitivity ( - tokenizer, case_sensitive); - e_searching_tokenizer_set_primary_search_string ( - tokenizer, active_search); + webkit_web_view_set_highlight_text_matches ( + WEBKIT_WEB_VIEW (web_view), visible); e_search_bar_changed (search_bar); } @@ -122,6 +107,7 @@ search_bar_find (ESearchBar *search_bar, gboolean case_sensitive; gboolean new_search; gboolean wrapped = FALSE; + gboolean success; gchar *text; web_view = e_search_bar_get_web_view (search_bar); @@ -138,44 +124,30 @@ search_bar_find (ESearchBar *search_bar, (search_bar->priv->active_search == NULL) || (g_strcmp0 (text, search_bar->priv->active_search) != 0); - /* XXX On a new search, the HTMLEngine's search state gets - * destroyed when we redraw the message with highlighted - * matches (EMHTMLStream's write() method triggers this, - * but it's really GtkHtml's fault). That's why the first - * match isn't selected automatically. It also causes - * gtk_html_engine_search_next() to return FALSE, which we - * assume to mean the search wrapped. - * - * So to avoid mistakenly thinking the search wrapped when - * it hasn't, we have to trap the first button click after a - * search and re-run the search to recreate the HTMLEngine's - * search state, so that gtk_html_engine_search_next() will - * succeed. */ if (new_search) { - g_free (search_bar->priv->active_search); - search_bar->priv->active_search = text; - search_bar->priv->rerun_search = TRUE; - search_bar_update_tokenizer (search_bar); - } else if (search_bar->priv->rerun_search) { - gtk_html_engine_search ( - GTK_HTML (web_view), - search_bar->priv->active_search, - case_sensitive, search_forward, FALSE); - search_bar->priv->rerun_search = FALSE; - g_free (text); - } else { - gtk_html_engine_search_set_forward ( - GTK_HTML (web_view), search_forward); - if (!gtk_html_engine_search_next (GTK_HTML (web_view))) - wrapped = TRUE; - g_free (text); + guint matches; + + webkit_web_view_unmark_text_matches ( + WEBKIT_WEB_VIEW (web_view)); + matches = webkit_web_view_mark_text_matches ( + WEBKIT_WEB_VIEW (web_view), + text, case_sensitive, 0); + webkit_web_view_set_highlight_text_matches ( + WEBKIT_WEB_VIEW (web_view), TRUE); + search_bar_update_matches (search_bar, matches); } - if (new_search || wrapped) - gtk_html_engine_search ( - GTK_HTML (web_view), - search_bar->priv->active_search, - case_sensitive, search_forward, FALSE); + success = webkit_web_view_search_text ( + WEBKIT_WEB_VIEW (web_view), + text, case_sensitive, search_forward, FALSE); + + if (!success) + wrapped = webkit_web_view_search_text ( + WEBKIT_WEB_VIEW (web_view), + text, case_sensitive, search_forward, TRUE); + + g_free (search_bar->priv->active_search); + search_bar->priv->active_search = text; g_object_notify (G_OBJECT (search_bar), "active-search"); @@ -239,16 +211,10 @@ static void search_bar_set_web_view (ESearchBar *search_bar, EWebView *web_view) { - GtkHTML *html; - ESearchingTokenizer *tokenizer; - + g_return_if_fail (E_IS_WEB_VIEW (web_view)); g_return_if_fail (search_bar->priv->web_view == NULL); search_bar->priv->web_view = g_object_ref (web_view); - - html = GTK_HTML (web_view); - tokenizer = e_search_bar_get_tokenizer (search_bar); - gtk_html_set_tokenizer (html, HTML_TOKENIZER (tokenizer)); } static void @@ -352,11 +318,6 @@ search_bar_dispose (GObject *object) priv->matches_label = NULL; } - if (priv->tokenizer != NULL) { - g_object_unref (priv->tokenizer); - priv->tokenizer = NULL; - } - /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_search_bar_parent_class)->dispose (object); } @@ -403,7 +364,7 @@ search_bar_show (GtkWidget *widget) gtk_widget_grab_focus (search_bar->priv->entry); - search_bar_update_tokenizer (search_bar); + search_bar_update_highlights (search_bar); } static void @@ -416,7 +377,7 @@ search_bar_hide (GtkWidget *widget) /* Chain up to parent's hide() method. */ GTK_WIDGET_CLASS (e_search_bar_parent_class)->hide (widget); - search_bar_update_tokenizer (search_bar); + search_bar_update_highlights (search_bar); } static gboolean @@ -438,6 +399,8 @@ search_bar_key_press_event (GtkWidget *widget, static void search_bar_clear (ESearchBar *search_bar) { + WebKitWebView *web_view; + g_free (search_bar->priv->active_search); search_bar->priv->active_search = NULL; @@ -447,7 +410,10 @@ search_bar_clear (ESearchBar *search_bar) gtk_widget_hide (search_bar->priv->wrapped_prev_box); gtk_widget_hide (search_bar->priv->matches_label); - search_bar_update_tokenizer (search_bar); + search_bar_update_highlights (search_bar); + + web_view = WEBKIT_WEB_VIEW (search_bar->priv->web_view); + webkit_web_view_unmark_text_matches (web_view); g_object_notify (G_OBJECT (search_bar), "active-search"); } @@ -542,11 +508,6 @@ e_search_bar_init (ESearchBar *search_bar) GtkWidget *container; search_bar->priv = E_SEARCH_BAR_GET_PRIVATE (search_bar); - search_bar->priv->tokenizer = e_searching_tokenizer_new (); - - g_signal_connect_swapped ( - search_bar->priv->tokenizer, "match", - G_CALLBACK (search_bar_update_matches), search_bar); gtk_box_set_spacing (GTK_BOX (search_bar), 12); @@ -743,14 +704,6 @@ e_search_bar_get_web_view (ESearchBar *search_bar) return search_bar->priv->web_view; } -ESearchingTokenizer * -e_search_bar_get_tokenizer (ESearchBar *search_bar) -{ - g_return_val_if_fail (E_IS_SEARCH_BAR (search_bar), NULL); - - return search_bar->priv->tokenizer; -} - gboolean e_search_bar_get_active_search (ESearchBar *search_bar) { diff --git a/widgets/misc/e-search-bar.h b/widgets/misc/e-search-bar.h index 87e1023baf..4df8c97e6d 100644 --- a/widgets/misc/e-search-bar.h +++ b/widgets/misc/e-search-bar.h @@ -23,7 +23,6 @@ #define E_SEARCH_BAR_H #include <gtk/gtk.h> -#include <misc/e-searching-tokenizer.h> #include <misc/e-web-view.h> /* Standard GObject macros */ @@ -69,8 +68,6 @@ GtkWidget * e_search_bar_new (EWebView *web_view); void e_search_bar_clear (ESearchBar *search_bar); void e_search_bar_changed (ESearchBar *search_bar); EWebView * e_search_bar_get_web_view (ESearchBar *search_bar); -ESearchingTokenizer * - e_search_bar_get_tokenizer (ESearchBar *search_bar); gboolean e_search_bar_get_active_search (ESearchBar *search_bar); gboolean e_search_bar_get_case_sensitive diff --git a/widgets/misc/e-searching-tokenizer.c b/widgets/misc/e-searching-tokenizer.c deleted file mode 100644 index 878efa1654..0000000000 --- a/widgets/misc/e-searching-tokenizer.c +++ /dev/null @@ -1,1210 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see <http://www.gnu.org/licenses/> - * - * - * Authors: - * Developed by Jon Trowbridge <trow@ximian.com> - * Rewritten significantly to handle multiple strings and improve performance - * by Michael Zucchi <notzed@ximian.com> - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <ctype.h> - -#include "e-searching-tokenizer.h" - -#include "libedataserver/e-memory.h" - -#define d(x) - -#define E_SEARCHING_TOKENIZER_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE \ - ((obj), E_TYPE_SEARCHING_TOKENIZER, ESearchingTokenizerPrivate)) - -enum { - MATCH_SIGNAL, - LAST_SIGNAL -}; - -static guint signals[LAST_SIGNAL]; - -G_DEFINE_TYPE ( - ESearchingTokenizer, - e_searching_tokenizer, - HTML_TYPE_TOKENIZER) - -/* Utility functions */ - -/* This is faster and safer than glib2's utf8 abomination, - * but isn't exported from camel as yet */ -static inline guint32 -camel_utf8_getc (const guchar **ptr) -{ - register guchar *p = (guchar *) * ptr; - register guchar c, r; - register guint32 v, m; - -again: - r = *p++; -loop: - if (r < 0x80) { - *ptr = p; - v = r; - } else if (r < 0xfe) { /* valid start char? */ - v = r; - m = 0x7f80; /* used to mask out the length bits */ - do { - c = *p++; - if ((c & 0xc0) != 0x80) { - r = c; - goto loop; - } - v = (v << 6) | (c & 0x3f); - r<<=1; - m<<=5; - } while (r & 0x40); - - *ptr = p; - - v &= ~m; - } else { - goto again; - } - - return v; -} - -/* note: our tags of interest are 7 bit ascii - * only no need to do any fancy utf8 stuff */ -/* tags should be upper case - * if this list gets longer than 10 entries, consider binary search */ -static const gchar *ignored_tags[] = { - "B", "I", "FONT", "TT", "EM", /* and more? */}; - -static gint -ignore_tag (const gchar *tag) -{ - gchar *t = g_alloca (strlen (tag) + 1), c, *out; - const gchar *in; - gint i; - - /* we could use a aho-corasick matcher here too ... but we wont */ - - /* normalise tag into 't'. - * Note we use the property that the only tags we're interested in - * are 7 bit ascii to shortcut and simplify case insensitivity */ - in = tag+2; /* skip: TAG_ESCAPE '<' */ - if (*in == '/') - in++; - out = t; - while ((c = *in++)) { - if (c >= 'A' && c <= 'Z') - *out++ = c; - else if (c >= 'a' && c <= 'z') - *out++ = c & 0xdf; /* convert ASCII to upper case */ - else - /* maybe should check for > or ' ' etc? */ - break; - } - *out = 0; - - for (i = 0; i < G_N_ELEMENTS (ignored_tags); i++) { - if (strcmp (t, ignored_tags[i]) == 0) - return 1; - } - - return 0; -} - -/* ********************************************************************** */ - -/* Aho-Corasick search tree implmeentation */ - -/* next state if we match a character */ -struct _match { - struct _match *next; - guint32 ch; - struct _state *match; -}; - -/* tree state node */ -struct _state { - struct _match *matches; - guint final; /* max no of chars we just matched */ - struct _state *fail; /* where to try next if we fail */ - struct _state *next; /* next on this level? */ -}; - -/* base tree structure */ -struct _trie { - struct _state root; - gint max_depth; - - EMemChunk *state_chunks; - EMemChunk *match_chunks; -}; - -/* will be enabled only if debug is enabled */ -#if d(1) -1 != -1 -static void -dump_trie (struct _state *s, - gint d) -{ - gchar *p = g_alloca (d *2 + 1); - struct _match *m; - - memset (p, ' ', d *2); - p[d *2]=0; - - printf("%s[state] %p: %d fail->%p\n", p, s, s->final, s->fail); - m = s->matches; - while (m) { - printf(" %s'%c' -> %p\n", p, m->ch, m->match); - if (m->match) - dump_trie (m->match, d + 1); - m = m->next; - } -} -#endif - -/* This builds an Aho-Corasick search trie for a set of utf8 words */ -/* See - * http://www-sr.informatik.uni-tuebingen.de/~buehler/AC/AC.html - * for a neat demo */ - -static inline struct _match * -g (struct _state *q, - guint32 c) -{ - struct _match *m = q->matches; - - while (m && m->ch != c) - m = m->next; - - return m; -} - -static struct _trie * -build_trie (gint nocase, - gint len, - guchar **words) -{ - struct _state *q, *qt, *r; - const guchar *word; - struct _match *m, *n = NULL; - gint i, depth; - guint32 c; - struct _trie *trie; - gint state_depth_max, state_depth_size; - struct _state **state_depth; - - trie = g_malloc (sizeof (*trie)); - trie->root.matches = NULL; - trie->root.final = 0; - trie->root.fail = NULL; - trie->root.next = NULL; - - trie->state_chunks = e_memchunk_new (8, sizeof (struct _state)); - trie->match_chunks = e_memchunk_new (8, sizeof (struct _match)); - - /* This will correspond to the length of the longest pattern */ - state_depth_size = 0; - state_depth_max = 64; - state_depth = g_malloc (sizeof (*state_depth[0]) * 64); - state_depth[0] = NULL; - - /* Step 1: Build trie */ - - /* This just builds a tree that merges all common prefixes into the same branch */ - - for (i = 0; i < len; i++) { - word = words[i]; - q = &trie->root; - depth = 0; - while ((c = camel_utf8_getc (&word))) { - if (nocase) - c = g_unichar_tolower (c); - m = g (q, c); - if (m == NULL) { - m = e_memchunk_alloc (trie->match_chunks); - m->ch = c; - m->next = q->matches; - q->matches = m; - q = m->match = e_memchunk_alloc (trie->state_chunks); - q->matches = NULL; - q->fail = &trie->root; - q->final = 0; - if (state_depth_max < depth) { - state_depth_max += 64; - state_depth = g_realloc ( - state_depth, - sizeof (*state_depth[0]) * - state_depth_max); - } - if (state_depth_size < depth) { - state_depth[depth] = NULL; - state_depth_size = depth; - } - q->next = state_depth[depth]; - state_depth[depth] = q; - } else { - q = m->match; - } - depth++; - } - q->final = depth; - } - - d(printf("Dumping trie:\n")); - d (dump_trie (&trie->root, 0)); - - /* Step 2: Build failure graph */ - - /* This searches for the longest substring which is a prefix of - * another string and builds a graph of failure links so you can - * find multiple substrings concurrently, using aho-corasick's - * algorithm. */ - - for (i = 0; i < state_depth_size; i++) { - q = state_depth[i]; - while (q) { - m = q->matches; - while (m) { - c = m->ch; - qt = m->match; - r = q->fail; - while (r && (n = g (r, c)) == NULL) - r = r->fail; - if (r != NULL) { - qt->fail = n->match; - if (qt->fail->final > qt->final) - qt->final = qt->fail->final; - } else { - if ((n = g (&trie->root, c))) - qt->fail = n->match; - else - qt->fail = &trie->root; - } - m = m->next; - } - q = q->next; - } - } - - d (printf("After failure analysis\n")); - d (dump_trie (&trie->root, 0)); - - g_free (state_depth); - - trie->max_depth = state_depth_size; - - return trie; -} - -static void -free_trie (struct _trie *t) -{ - e_memchunk_destroy (t->match_chunks); - e_memchunk_destroy (t->state_chunks); - - g_free (t); -} - -/* ********************************************************************** */ - -/* html token searcher */ - -struct _token { - struct _token *next; - struct _token *prev; - guint offset; - /* we need to copy the token for memory management, so why not copy it whole */ - gchar tok[1]; -}; - -/* stack of submatches currently being scanned, used for merging */ -struct _submatch { - guint offstart, offend; /* in bytes */ -}; - -/* flags for new func */ -#define SEARCH_CASE (1) -#define SEARCH_BOLD (2) - -struct _searcher { - struct _trie *t; - - gchar *(*next_token)(); /* callbacks for more tokens */ - gpointer next_data; - - gint words; /* how many words */ - gchar *tags, *tage; /* the tag we used to highlight */ - - gint flags; /* case sensitive or not */ - - struct _state *state; /* state is the current trie state */ - - gint matchcount; - - GQueue input; /* pending 'input' tokens, processed but might match */ - GQueue output; /* output tokens ready for source */ - - struct _token *current; /* for token output memory management */ - - guint32 offset; /* current offset through searchable stream? */ - guint32 offout; /* last output position */ - - guint lastp; /* current position in rotating last buffer */ - guint32 *last; /* buffer that goes back last 'n' positions */ - guint32 last_mask; /* bitmask for efficient rotation calculation */ - - guint submatchp; /* submatch stack */ - struct _submatch *submatches; -}; - -static void -searcher_set_tokenfunc (struct _searcher *s, - gchar *(*next)(), - gpointer data) -{ - s->next_token = next; - s->next_data = data; -} - -static struct _searcher * -searcher_new (gint flags, - gint argc, - guchar **argv, - const gchar *tags, - const gchar *tage) -{ - gint i, m; - struct _searcher *s; - - s = g_malloc (sizeof (*s)); - - s->t = build_trie ((flags&SEARCH_CASE) == 0, argc, argv); - s->words = argc; - s->tags = g_strdup (tags); - s->tage = g_strdup (tage); - s->flags = flags; - s->state = &s->t->root; - s->matchcount = -1; - - g_queue_init (&s->input); - g_queue_init (&s->output); - s->current = NULL; - - s->offset = 0; - s->offout = 0; - - /* rotating queue of previous character positions */ - m = s->t->max_depth + 1; - i = 2; - while (i < m) - i<<=2; - s->last = g_malloc (sizeof (s->last[0]) * i); - s->last_mask = i - 1; - s->lastp = 0; - - /* a stack of possible submatches */ - s->submatchp = 0; - s->submatches = g_malloc (sizeof (s->submatches[0]) * argc + 1); - - return s; -} - -static void -searcher_free (struct _searcher *s) -{ - struct _token *t; - - while ((t = g_queue_pop_head (&s->input)) != NULL) - g_free (t); - while ((t = g_queue_pop_head (&s->output)) != NULL) - g_free (t); - g_free (s->tags); - g_free (s->tage); - g_free (s->last); - g_free (s->submatches); - free_trie (s->t); - g_free (s); -} - -static struct _token * -append_token (GQueue *queue, - const gchar *tok, - gint len) -{ - struct _token *token; - - if (len == -1) - len = strlen (tok); - token = g_malloc (sizeof (*token) + len + 1); - token->offset = 0; /* set by caller when required */ - memcpy (token->tok, tok, len); - token->tok[len] = 0; - g_queue_push_tail (queue, token); - - return token; -} - -#define free_token(x) (g_free (x)) - -static void -output_token (struct _searcher *s, - struct _token *token) -{ - gint offend; - gint left, pre; - - if (token->tok[0] == TAG_ESCAPE) { - if (token->offset >= s->offout) { - g_queue_push_tail (&s->output, token); - } else { - free_token (token); - } - } else { - offend = token->offset + strlen (token->tok); - left = offend - s->offout; - if (left > 0) { - pre = s->offout - token->offset; - if (pre > 0) - memmove (token->tok, token->tok + pre, left + 1); - s->offout = offend; - g_queue_push_tail (&s->output, token); - } else { - free_token (token); - } - } -} - -static struct _token * -find_token (struct _searcher *s, - gint start) -{ - GList *link; - - /* find token which is start token, from end of list back */ - link = g_queue_peek_tail_link (&s->input); - while (link != NULL) { - struct _token *token = link->data; - - if (token->offset <= start) - return token; - - link = g_list_previous (link); - } - - return NULL; -} - -static void -output_match (struct _searcher *s, - guint start, - guint end) -{ - register struct _token *token; - struct _token *starttoken, *endtoken; - gchar b[8]; - - d (printf("output match: %d-%d at %d\n", start, end, s->offout)); - - starttoken = find_token (s, start); - endtoken = find_token (s, end); - - if (starttoken == NULL || endtoken == NULL) { - d (printf("Cannot find match history for match %d-%d\n", start, end)); - return; - } - - /* output pending stuff that didn't match afterall */ - while ((struct _token *) g_queue_peek_head (&s->input) != starttoken) { - token = g_queue_pop_head (&s->input); - output_token (s, token); - } - - /* output any pre-match text */ - if (s->offout < start) { - token = append_token ( - &s->output, starttoken->tok + - (s->offout - starttoken->offset), - start - s->offout); - s->offout = start; - } - - /* output highlight/bold */ - if (s->flags & SEARCH_BOLD) { - sprintf(b, "%c<b>", (gchar)TAG_ESCAPE); - append_token (&s->output, b, -1); - } - if (s->tags) - append_token (&s->output, s->tags, -1); - - /* output match node (s) */ - if (starttoken != endtoken) { - while ((struct _token *) g_queue_peek_head (&s->input) != endtoken) { - token = g_queue_pop_head (&s->input); - output_token (s, token); - } - } - - /* any remaining partial content */ - if (s->offout < end) { - token = append_token ( - &s->output, endtoken->tok + - (s->offout - endtoken->offset), - end - s->offout); - s->offout = end; - } - - /* end highlight */ - if (s->tage) - append_token (&s->output, s->tage, -1); - - /* and close bold if we need to */ - if (s->flags & SEARCH_BOLD) { - sprintf(b, "%c</b>", (gchar)TAG_ESCAPE); - append_token (&s->output, b, -1); - } -} - -/* output any sub-pending blocks */ -static void -output_subpending (struct _searcher *s) -{ - gint i; - - for (i = s->submatchp - 1; i >= 0; i--) - output_match (s, s->submatches[i].offstart, s->submatches[i].offend); - s->submatchp = 0; -} - -/* returns true if a merge took place */ -static gint -merge_subpending (struct _searcher *s, - gint offstart, - gint offend) -{ - gint i; - - /* merges overlapping or abutting match strings */ - if (s->submatchp && - s->submatches[s->submatchp - 1].offend >= offstart) { - - /* go from end, any that match 'invalidate' follow-on ones too */ - for (i = s->submatchp - 1; i >= 0; i--) { - if (s->submatches[i].offend >= offstart) { - if (offstart < s->submatches[i].offstart) - s->submatches[i].offstart = offstart; - s->submatches[i].offend = offend; - if (s->submatchp > i) - s->submatchp = i + 1; - } - } - return 1; - } - - return 0; -} - -static void -push_subpending (struct _searcher *s, - gint offstart, - gint offend) -{ - /* This is really an assertion, we just ignore the - * last pending match instead of crashing though. */ - if (s->submatchp >= s->words) { - d (printf("ERROR: submatch pending stack overflow\n")); - s->submatchp = s->words - 1; - } - - s->submatches[s->submatchp].offstart = offstart; - s->submatches[s->submatchp].offend = offend; - s->submatchp++; -} - -/* move any (partial) tokens from input to output - * if they are beyond the current output position */ -static void -output_pending (struct _searcher *s) -{ - struct _token *token; - - while ((token = g_queue_pop_head (&s->input)) != NULL) - output_token (s, token); -} - -/* flushes any nodes we cannot possibly match anymore */ -static void -flush_extra (struct _searcher *s) -{ - guint start; - gint i; - struct _token *starttoken, *token; - - /* find earliest gchar that can be in contention */ - start = s->offset - s->t->max_depth; - for (i = 0; i < s->submatchp; i++) - if (s->submatches[i].offstart < start) - start = s->submatches[i].offstart; - - /* now, flush out any tokens which are before this point */ - starttoken = find_token (s, start); - if (starttoken == NULL) - return; - - while ((struct _token *) g_queue_peek_head (&s->input) != starttoken) { - token = g_queue_pop_head (&s->input); - output_token (s, token); - } -} - -static gchar * -searcher_next_token (struct _searcher *s) -{ - struct _token *token; - const guchar *tok, *stok, *pre_tok; - struct _trie *t = s->t; - struct _state *q = s->state; - struct _match *m = NULL; - gint offstart, offend; - guint32 c; - - while (g_queue_is_empty (&s->output)) { - /* get next token */ - tok = (guchar *) s->next_token (s->next_data); - if (tok == NULL) { - output_subpending (s); - output_pending (s); - break; - } - - /* we dont always have to copy each token, e.g. if we dont match anything */ - token = append_token (&s->input, (gchar *) tok, -1); - token->offset = s->offset; - tok = (guchar *) token->tok; - - /* tag test, reset state on unknown tags */ - if (tok[0] == TAG_ESCAPE) { - if (!ignore_tag ((gchar *) tok)) { - /* force reset */ - output_subpending (s); - output_pending (s); - q = &t->root; - } - - continue; - } - - /* process whole token */ - pre_tok = stok = tok; - while ((c = camel_utf8_getc (&tok))) { - if ((s->flags & SEARCH_CASE) == 0) - c = g_unichar_tolower (c); - while (q && (m = g (q, c)) == NULL) - q = q->fail; - if (q == NULL) { - /* mismatch ... reset state */ - output_subpending (s); - q = &t->root; - } else if (m != NULL) { - /* keep track of previous offsets of utf8 chars, rotating buffer */ - s->last[s->lastp] = s->offset + (pre_tok - stok); - s->lastp = (s->lastp + 1) &s->last_mask; - - q = m->match; - /* we have a match of q->final characters for a matching word */ - if (q->final) { - s->matchcount++; - - /* use the last buffer to find the real offset of this gchar */ - offstart = s->last[(s->lastp - q->final) &s->last_mask]; - offend = s->offset + (tok - stok); - - if (q->matches == NULL) { - if (s->submatchp == 0) { - /* nothing pending, always put - * something in so we can try merge */ - push_subpending (s, offstart, offend); - } else if (!merge_subpending (s, offstart, offend)) { - /* can't merge, output what we have, and start againt */ - output_subpending (s); - push_subpending (s, offstart, offend); - /*output_match(s, offstart, offend);*/ - } else if (g_queue_get_length (&s->input) > 8) { - /* we're continuing to match and merge, - * but we have a lot of stuff waiting, - * so flush it out now since this is a - * safe point to do it */ - output_subpending (s); - } - } else { - /* merge/add subpending */ - if (!merge_subpending (s, offstart, offend)) - push_subpending (s, offstart, offend); - } - } - } - pre_tok = tok; - } - - s->offset += (pre_tok - stok); - - flush_extra (s); - } - - s->state = q; - - if (s->current) - free_token (s->current); - - s->current = token = g_queue_pop_head (&s->output); - - return token ? g_strdup (token->tok) : NULL; -} - -static gchar * -searcher_peek_token (struct _searcher *s) -{ - gchar *tok; - - /* we just get it and then put it back, it's fast enuf */ - tok = searcher_next_token (s); - if (tok) { - /* need to clear this so we dont free it while its still active */ - g_queue_push_head (&s->output, s->current); - s->current = NULL; - } - - return tok; -} - -static gint -searcher_pending (struct _searcher *s) -{ - return !(g_queue_is_empty (&s->input) && g_queue_is_empty (&s->output)); -} - -/* ********************************************************************** */ - -struct _search_info { - GPtrArray *strv; - gchar *color; - guint size : 8; - guint flags : 8; -}; - -/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/ - -static struct _search_info * -search_info_new (void) -{ - struct _search_info *s; - - s = g_malloc0 (sizeof (struct _search_info)); - s->strv = g_ptr_array_new (); - - return s; -} - -static void -search_info_set_flags (struct _search_info *si, - guint flags, - guint mask) -{ - si->flags = (si->flags & ~mask) | (flags & mask); -} - -static void -search_info_set_color (struct _search_info *si, - const gchar *color) -{ - g_free (si->color); - si->color = g_strdup (color); -} - -static void -search_info_add_string (struct _search_info *si, - const gchar *s) -{ - const guchar *start; - guint32 c; - - if (s && s[0]) { - const guchar *us = (guchar *) s; - /* strip leading whitespace */ - start = us; - while ((c = camel_utf8_getc (&us))) { - if (!g_unichar_isspace (c)) { - break; - } - start = us; - } - /* should probably also strip trailing, but i'm lazy today */ - if (start[0]) - g_ptr_array_add (si->strv, g_strdup ((gchar *) start)); - } -} - -static void -search_info_clear (struct _search_info *si) -{ - gint i; - - for (i = 0; i < si->strv->len; i++) - g_free (si->strv->pdata[i]); - - g_ptr_array_set_size (si->strv, 0); -} - -static void -search_info_free (struct _search_info *si) -{ - gint i; - - for (i = 0; i < si->strv->len; i++) - g_free (si->strv->pdata[i]); - - g_ptr_array_free (si->strv, TRUE); - g_free (si->color); - g_free (si); -} - -static struct _search_info * -search_info_clone (struct _search_info *si) -{ - struct _search_info *out; - gint i; - - out = search_info_new (); - for (i = 0; i < si->strv->len; i++) - g_ptr_array_add (out->strv, g_strdup (si->strv->pdata[i])); - out->color = g_strdup (si->color); - out->flags = si->flags; - out->size = si->size; - - return out; -} - -static struct _searcher * -search_info_to_searcher (struct _search_info *si) -{ - gchar *tags, *tage; - const gchar *col; - - if (si->strv->len == 0) - return NULL; - - if (si->color == NULL) - col = "red"; - else - col = si->color; - - tags = g_alloca (20 + strlen (col)); - sprintf(tags, "%c<font color=\"%s\">", TAG_ESCAPE, col); - tage = g_alloca (20); - sprintf(tage, "%c</font>", TAG_ESCAPE); - - return searcher_new ( - si->flags, si->strv->len, - (guchar **) si->strv->pdata, tags, tage); -} - -/* ********************************************************************** */ - -struct _ESearchingTokenizerPrivate { - struct _search_info *primary, *secondary; - struct _searcher *engine; -}; - -/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/ - -/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/ - -/* blah blah the htmltokeniser doesn't like being asked - * for a token if it doens't hvae any! */ -static gchar * -get_token (HTMLTokenizer *tokenizer) -{ - HTMLTokenizerClass *class; - - class = HTML_TOKENIZER_CLASS (e_searching_tokenizer_parent_class); - - if (class->has_more (tokenizer)) - return class->next_token (tokenizer); - - return NULL; -} - -/* proxy matched event, not sure what its for otherwise */ -static void -matched (ESearchingTokenizer *tokenizer) -{ - /*++tokenizer->priv->match_count;*/ - g_signal_emit (tokenizer, signals[MATCH_SIGNAL], 0); -} - -/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */ - -static void -searching_tokenizer_finalize (GObject *object) -{ - ESearchingTokenizerPrivate *priv; - - priv = E_SEARCHING_TOKENIZER_GET_PRIVATE (object); - - search_info_free (priv->primary); - search_info_free (priv->secondary); - - if (priv->engine != NULL) - searcher_free (priv->engine); - - /* Chain up to parent's finalize () method. */ - G_OBJECT_CLASS (e_searching_tokenizer_parent_class)->finalize (object); -} - -static void -searching_tokenizer_begin (HTMLTokenizer *tokenizer, - const gchar *content_type) -{ - ESearchingTokenizerPrivate *priv; - - priv = E_SEARCHING_TOKENIZER_GET_PRIVATE (tokenizer); - - /* reset search */ - if (priv->engine != NULL) { - searcher_free (priv->engine); - priv->engine = NULL; - } - - if ((priv->engine = search_info_to_searcher (priv->primary)) - || (priv->engine = search_info_to_searcher (priv->secondary))) { - searcher_set_tokenfunc (priv->engine, get_token, tokenizer); - } - /* else - no engine, no search active */ - - /* Chain up to parent's begin() method. */ - HTML_TOKENIZER_CLASS (e_searching_tokenizer_parent_class)-> - begin (tokenizer, content_type); -} - -static gchar * -searching_tokenizer_peek_token (HTMLTokenizer *tokenizer) -{ - ESearchingTokenizerPrivate *priv; - - priv = E_SEARCHING_TOKENIZER_GET_PRIVATE (tokenizer); - - if (priv->engine != NULL) - return searcher_peek_token (priv->engine); - - /* Chain up to parent's peek_token() method. */ - return HTML_TOKENIZER_CLASS (e_searching_tokenizer_parent_class)-> - peek_token (tokenizer); -} - -static gchar * -searching_tokenizer_next_token (HTMLTokenizer *tokenizer) -{ - ESearchingTokenizerPrivate *priv; - gint oldmatched; - gchar *token; - - priv = E_SEARCHING_TOKENIZER_GET_PRIVATE (tokenizer); - - /* If no search is active, just use the default method. */ - if (priv->engine == NULL) - return HTML_TOKENIZER_CLASS ( - e_searching_tokenizer_parent_class)-> - next_token (tokenizer); - - oldmatched = priv->engine->matchcount; - if (priv->engine->matchcount == -1) - priv->engine->matchcount = 0; - - token = searcher_next_token (priv->engine); - - /* not sure if this has to be accurate or just say we had some matches */ - if (oldmatched != priv->engine->matchcount) - g_signal_emit (tokenizer, signals[MATCH_SIGNAL], 0); - - return token; -} - -static gboolean -searching_tokenizer_has_more (HTMLTokenizer *tokenizer) -{ - ESearchingTokenizerPrivate *priv; - - priv = E_SEARCHING_TOKENIZER_GET_PRIVATE (tokenizer); - - return (priv->engine != NULL && searcher_pending (priv->engine)) || - HTML_TOKENIZER_CLASS (e_searching_tokenizer_parent_class)-> - has_more (tokenizer); -} - -static HTMLTokenizer * -searching_tokenizer_clone (HTMLTokenizer *tokenizer) -{ - ESearchingTokenizer *orig_st; - ESearchingTokenizer *new_st; - - orig_st = E_SEARCHING_TOKENIZER (tokenizer); - new_st = e_searching_tokenizer_new (); - - search_info_free (new_st->priv->primary); - search_info_free (new_st->priv->secondary); - - new_st->priv->primary = search_info_clone (orig_st->priv->primary); - new_st->priv->secondary = search_info_clone (orig_st->priv->secondary); - - g_signal_connect_swapped ( - new_st, "match", G_CALLBACK (matched), orig_st); - - return HTML_TOKENIZER (new_st); -} -static void -e_searching_tokenizer_class_init (ESearchingTokenizerClass *class) -{ - GObjectClass *object_class; - HTMLTokenizerClass *tokenizer_class; - - g_type_class_add_private (class, sizeof (ESearchingTokenizerPrivate)); - - object_class = G_OBJECT_CLASS (class); - object_class->finalize = searching_tokenizer_finalize; - - tokenizer_class = HTML_TOKENIZER_CLASS (class); - tokenizer_class->begin = searching_tokenizer_begin; - tokenizer_class->peek_token = searching_tokenizer_peek_token; - tokenizer_class->next_token = searching_tokenizer_next_token; - tokenizer_class->has_more = searching_tokenizer_has_more; - tokenizer_class->clone = searching_tokenizer_clone; - - signals[MATCH_SIGNAL] = g_signal_new ( - "match", - G_TYPE_FROM_CLASS (class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (ESearchingTokenizerClass, match), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); -} - -static void -e_searching_tokenizer_init (ESearchingTokenizer *tokenizer) -{ - tokenizer->priv = E_SEARCHING_TOKENIZER_GET_PRIVATE (tokenizer); - - tokenizer->priv->primary = search_info_new (); - search_info_set_flags ( - tokenizer->priv->primary, - SEARCH_BOLD, SEARCH_CASE | SEARCH_BOLD); - search_info_set_color (tokenizer->priv->primary, "red"); - - tokenizer->priv->secondary = search_info_new (); - search_info_set_flags ( - tokenizer->priv->secondary, - SEARCH_BOLD, SEARCH_CASE | SEARCH_BOLD); - search_info_set_color (tokenizer->priv->secondary, "purple"); -} - -ESearchingTokenizer * -e_searching_tokenizer_new (void) -{ - return g_object_new (E_TYPE_SEARCHING_TOKENIZER, NULL); -} - -void -e_searching_tokenizer_set_primary_search_string (ESearchingTokenizer *tokenizer, - const gchar *primary_string) -{ - g_return_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer)); - - search_info_clear (tokenizer->priv->primary); - search_info_add_string (tokenizer->priv->primary, primary_string); -} - -void -e_searching_tokenizer_add_primary_search_string (ESearchingTokenizer *tokenizer, - const gchar *primary_string) -{ - g_return_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer)); - - search_info_add_string (tokenizer->priv->primary, primary_string); -} - -void -e_searching_tokenizer_set_primary_case_sensitivity (ESearchingTokenizer *tokenizer, - gboolean case_sensitive) -{ - g_return_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer)); - - search_info_set_flags ( - tokenizer->priv->primary, - case_sensitive ? SEARCH_CASE : 0, SEARCH_CASE); -} - -void -e_searching_tokenizer_set_secondary_search_string (ESearchingTokenizer *tokenizer, - const gchar *secondary_string) -{ - g_return_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer)); - - search_info_clear (tokenizer->priv->secondary); - search_info_add_string (tokenizer->priv->secondary, secondary_string); -} - -void -e_searching_tokenizer_add_secondary_search_string (ESearchingTokenizer *tokenizer, - const gchar *secondary_string) -{ - g_return_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer)); - - search_info_add_string (tokenizer->priv->secondary, secondary_string); -} - -void -e_searching_tokenizer_set_secondary_case_sensitivity (ESearchingTokenizer *tokenizer, - gboolean case_sensitive) -{ - g_return_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer)); - - search_info_set_flags ( - tokenizer->priv->secondary, - case_sensitive ? SEARCH_CASE : 0, SEARCH_CASE); -} - -/* Note: only returns the primary search string count */ -gint -e_searching_tokenizer_match_count (ESearchingTokenizer *tokenizer) -{ - g_return_val_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer), -1); - - if (tokenizer->priv->engine && tokenizer->priv->primary->strv->len) - return tokenizer->priv->engine->matchcount; - - return 0; -} diff --git a/widgets/misc/e-searching-tokenizer.h b/widgets/misc/e-searching-tokenizer.h deleted file mode 100644 index ab13203c36..0000000000 --- a/widgets/misc/e-searching-tokenizer.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see <http://www.gnu.org/licenses/> - * - * - * Authors: - * Jon Trowbridge <trow@ximian.com> - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef E_SEARCHING_TOKENIZER_H -#define E_SEARCHING_TOKENIZER_H - -#include <gtkhtml/htmltokenizer.h> - -/* Standard GObject macros */ -#define E_TYPE_SEARCHING_TOKENIZER \ - (e_searching_tokenizer_get_type ()) -#define E_SEARCHING_TOKENIZER(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST \ - ((obj), E_TYPE_SEARCHING_TOKENIZER, ESearchingTokenizer)) -#define E_SEARCHING_TOKENIZER_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_CAST \ - ((cls), E_TYPE_SEARCHING_TOKENIZER, ESearchingTokenizerClass)) -#define E_IS_SEARCHING_TOKENIZER(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE \ - ((obj), E_TYPE_SEARCHING_TOKENIZER)) -#define E_IS_SEARCHING_TOKENIZER_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_TYPE \ - ((cls), E_TYPE_SEARCHING_TOKENIZER)) -#define E_SEARCH_TOKENIZER_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS \ - ((obj), E_TYPE_SEARCHING_TOKENIZER, ESearchingTokenizerClass)) - -G_BEGIN_DECLS - -typedef struct _ESearchingTokenizer ESearchingTokenizer; -typedef struct _ESearchingTokenizerClass ESearchingTokenizerClass; -typedef struct _ESearchingTokenizerPrivate ESearchingTokenizerPrivate; - -struct _ESearchingTokenizer { - HTMLTokenizer parent; - ESearchingTokenizerPrivate *priv; -}; - -struct _ESearchingTokenizerClass { - HTMLTokenizerClass parent_class; - - void (*match) (ESearchingTokenizer *tokenizer); -}; - -GType e_searching_tokenizer_get_type (void); -ESearchingTokenizer * - e_searching_tokenizer_new (void); -void e_searching_tokenizer_set_primary_search_string - (ESearchingTokenizer *tokenizer, - const gchar *primary_string); -void e_searching_tokenizer_add_primary_search_string - (ESearchingTokenizer *tokenizer, - const gchar *primary_string); -void e_searching_tokenizer_set_primary_case_sensitivity - (ESearchingTokenizer *tokenizer, - gboolean case_sensitive); -void e_searching_tokenizer_set_secondary_search_string - (ESearchingTokenizer *tokenizer, - const gchar *secondary_string); -void e_searching_tokenizer_add_secondary_search_string - (ESearchingTokenizer *tokenizer, - const gchar *secondary_string); -void e_searching_tokenizer_set_secondary_case_sensitivity - (ESearchingTokenizer *tokenizer, - gboolean case_sensitive); -gint e_searching_tokenizer_match_count - (ESearchingTokenizer *tokenizer); - -G_END_DECLS - -#endif /* E_SEARCHING_TOKENIZER_H */ diff --git a/widgets/misc/e-web-view-gtkhtml.c b/widgets/misc/e-web-view-gtkhtml.c new file mode 100644 index 0000000000..be8b1c596f --- /dev/null +++ b/widgets/misc/e-web-view-gtkhtml.c @@ -0,0 +1,2301 @@ +/* + * e-web-view-gtkhtml.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-web-view-gtkhtml.h" + +#include <string.h> +#include <glib/gi18n-lib.h> + +#include <camel/camel.h> + +#include <e-util/e-util.h> +#include <e-util/e-plugin-ui.h> +#include <libevolution-utils/e-alert-dialog.h> +#include <libevolution-utils/e-alert-sink.h> + +#include <libebackend/e-extensible.h> + +#include "e-popup-action.h" +#include "e-selectable.h" + +#define E_WEB_VIEW_GTKHTML_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_WEB_VIEW_GTKHTML, EWebViewGtkHTMLPrivate)) + +typedef struct _EWebViewGtkHTMLRequest EWebViewGtkHTMLRequest; + +struct _EWebViewGtkHTMLPrivate { + GList *requests; + GtkUIManager *ui_manager; + gchar *selected_uri; + GdkPixbufAnimation *cursor_image; + + GtkAction *open_proxy; + GtkAction *print_proxy; + GtkAction *save_as_proxy; + + GtkTargetList *copy_target_list; + GtkTargetList *paste_target_list; + + /* Lockdown Options */ + guint disable_printing : 1; + guint disable_save_to_disk : 1; +}; + +struct _EWebViewGtkHTMLRequest { + GFile *file; + EWebViewGtkHTML *web_view; + GCancellable *cancellable; + GInputStream *input_stream; + GtkHTMLStream *output_stream; + gchar buffer[4096]; +}; + +enum { + PROP_0, + PROP_ANIMATE, + PROP_CARET_MODE, + PROP_COPY_TARGET_LIST, + PROP_DISABLE_PRINTING, + PROP_DISABLE_SAVE_TO_DISK, + PROP_EDITABLE, + PROP_INLINE_SPELLING, + PROP_MAGIC_LINKS, + PROP_MAGIC_SMILEYS, + PROP_OPEN_PROXY, + PROP_PASTE_TARGET_LIST, + PROP_PRINT_PROXY, + PROP_SAVE_AS_PROXY, + PROP_SELECTED_URI, + PROP_CURSOR_IMAGE +}; + +enum { + COPY_CLIPBOARD, + CUT_CLIPBOARD, + PASTE_CLIPBOARD, + POPUP_EVENT, + STATUS_MESSAGE, + STOP_LOADING, + UPDATE_ACTIONS, + PROCESS_MAILTO, + LAST_SIGNAL +}; + +static gpointer parent_class; +static guint signals[LAST_SIGNAL]; + +static const gchar *ui = +"<ui>" +" <popup name='context'>" +" <menuitem action='copy-clipboard'/>" +" <separator/>" +" <placeholder name='custom-actions-1'>" +" <menuitem action='open'/>" +" <menuitem action='save-as'/>" +" <menuitem action='http-open'/>" +" <menuitem action='send-message'/>" +" <menuitem action='print'/>" +" </placeholder>" +" <placeholder name='custom-actions-2'>" +" <menuitem action='uri-copy'/>" +" <menuitem action='mailto-copy'/>" +" <menuitem action='image-copy'/>" +" </placeholder>" +" <placeholder name='custom-actions-3'/>" +" <separator/>" +" <menuitem action='select-all'/>" +" </popup>" +"</ui>"; + +/* Forward Declarations */ +static void e_web_view_gtkhtml_alert_sink_init (EAlertSinkInterface *interface); +static void e_web_view_gtkhtml_selectable_init (ESelectableInterface *interface); + +G_DEFINE_TYPE_WITH_CODE ( + EWebViewGtkHTML, + e_web_view_gtkhtml, + GTK_TYPE_HTML, + G_IMPLEMENT_INTERFACE ( + E_TYPE_EXTENSIBLE, NULL) + G_IMPLEMENT_INTERFACE ( + E_TYPE_ALERT_SINK, + e_web_view_gtkhtml_alert_sink_init) + G_IMPLEMENT_INTERFACE ( + E_TYPE_SELECTABLE, + e_web_view_gtkhtml_selectable_init)) + +static EWebViewGtkHTMLRequest * +web_view_gtkhtml_request_new (EWebViewGtkHTML *web_view, + const gchar *uri, + GtkHTMLStream *stream) +{ + EWebViewGtkHTMLRequest *request; + GList *list; + + request = g_slice_new (EWebViewGtkHTMLRequest); + + /* Try to detect file paths posing as URIs. */ + if (*uri == '/') + request->file = g_file_new_for_path (uri); + else + request->file = g_file_new_for_uri (uri); + + request->web_view = g_object_ref (web_view); + request->cancellable = g_cancellable_new (); + request->input_stream = NULL; + request->output_stream = stream; + + list = request->web_view->priv->requests; + list = g_list_prepend (list, request); + request->web_view->priv->requests = list; + + return request; +} + +static void +web_view_gtkhtml_request_free (EWebViewGtkHTMLRequest *request) +{ + GList *list; + + list = request->web_view->priv->requests; + list = g_list_remove (list, request); + request->web_view->priv->requests = list; + + g_object_unref (request->file); + g_object_unref (request->web_view); + g_object_unref (request->cancellable); + + if (request->input_stream != NULL) + g_object_unref (request->input_stream); + + g_slice_free (EWebViewGtkHTMLRequest, request); +} + +static void +web_view_gtkhtml_request_cancel (EWebViewGtkHTMLRequest *request) +{ + g_cancellable_cancel (request->cancellable); +} + +static gboolean +web_view_gtkhtml_request_check_for_error (EWebViewGtkHTMLRequest *request, + GError *error) +{ + GtkHTML *html; + GtkHTMLStream *stream; + + if (error == NULL) + return FALSE; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { + /* use this error, but do not close the stream */ + g_error_free (error); + return TRUE; + } + + /* XXX Should we log errors that are not cancellations? */ + + html = GTK_HTML (request->web_view); + stream = request->output_stream; + + gtk_html_end (html, stream, GTK_HTML_STREAM_ERROR); + web_view_gtkhtml_request_free (request); + g_error_free (error); + + return TRUE; +} + +static void +web_view_gtkhtml_request_stream_read_cb (GInputStream *input_stream, + GAsyncResult *result, + EWebViewGtkHTMLRequest *request) +{ + gssize bytes_read; + GError *error = NULL; + + bytes_read = g_input_stream_read_finish (input_stream, result, &error); + + if (web_view_gtkhtml_request_check_for_error (request, error)) + return; + + if (bytes_read == 0) { + gtk_html_end ( + GTK_HTML (request->web_view), + request->output_stream, GTK_HTML_STREAM_OK); + web_view_gtkhtml_request_free (request); + return; + } + + gtk_html_write ( + GTK_HTML (request->web_view), + request->output_stream, request->buffer, bytes_read); + + g_input_stream_read_async ( + request->input_stream, request->buffer, + sizeof (request->buffer), G_PRIORITY_DEFAULT, + request->cancellable, (GAsyncReadyCallback) + web_view_gtkhtml_request_stream_read_cb, request); +} + +static void +web_view_gtkhtml_request_read_cb (GFile *file, + GAsyncResult *result, + EWebViewGtkHTMLRequest *request) +{ + GFileInputStream *input_stream; + GError *error = NULL; + + /* Input stream might be NULL, so don't use cast macro. */ + input_stream = g_file_read_finish (file, result, &error); + request->input_stream = (GInputStream *) input_stream; + + if (web_view_gtkhtml_request_check_for_error (request, error)) + return; + + g_input_stream_read_async ( + request->input_stream, request->buffer, + sizeof (request->buffer), G_PRIORITY_DEFAULT, + request->cancellable, (GAsyncReadyCallback) + web_view_gtkhtml_request_stream_read_cb, request); +} + +static void +action_copy_clipboard_cb (GtkAction *action, + EWebViewGtkHTML *web_view) +{ + e_web_view_gtkhtml_copy_clipboard (web_view); +} + +static void +action_http_open_cb (GtkAction *action, + EWebViewGtkHTML *web_view) +{ + const gchar *uri; + gpointer parent; + + parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view)); + parent = gtk_widget_is_toplevel (parent) ? parent : NULL; + + uri = e_web_view_gtkhtml_get_selected_uri (web_view); + g_return_if_fail (uri != NULL); + + e_show_uri (parent, uri); +} + +static void +action_mailto_copy_cb (GtkAction *action, + EWebViewGtkHTML *web_view) +{ + CamelURL *curl; + CamelInternetAddress *inet_addr; + GtkClipboard *clipboard; + const gchar *uri; + gchar *text; + + uri = e_web_view_gtkhtml_get_selected_uri (web_view); + g_return_if_fail (uri != NULL); + + /* This should work because we checked it in update_actions(). */ + curl = camel_url_new (uri, NULL); + g_return_if_fail (curl != NULL); + + inet_addr = camel_internet_address_new (); + camel_address_decode (CAMEL_ADDRESS (inet_addr), curl->path); + text = camel_address_format (CAMEL_ADDRESS (inet_addr)); + if (text == NULL || *text == '\0') + text = g_strdup (uri + strlen ("mailto:")); + + g_object_unref (inet_addr); + camel_url_free (curl); + + clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY); + gtk_clipboard_set_text (clipboard, text, -1); + gtk_clipboard_store (clipboard); + + clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text (clipboard, text, -1); + gtk_clipboard_store (clipboard); + + g_free (text); +} + +static void +action_select_all_cb (GtkAction *action, + EWebViewGtkHTML *web_view) +{ + e_web_view_gtkhtml_select_all (web_view); +} + +static void +action_send_message_cb (GtkAction *action, + EWebViewGtkHTML *web_view) +{ + const gchar *uri; + gpointer parent; + gboolean handled; + + parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view)); + parent = gtk_widget_is_toplevel (parent) ? parent : NULL; + + uri = e_web_view_gtkhtml_get_selected_uri (web_view); + g_return_if_fail (uri != NULL); + + handled = FALSE; + g_signal_emit (web_view, signals[PROCESS_MAILTO], 0, uri, &handled); + + if (!handled) + e_show_uri (parent, uri); +} + +static void +action_uri_copy_cb (GtkAction *action, + EWebViewGtkHTML *web_view) +{ + GtkClipboard *clipboard; + const gchar *uri; + + clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + uri = e_web_view_gtkhtml_get_selected_uri (web_view); + g_return_if_fail (uri != NULL); + + gtk_clipboard_set_text (clipboard, uri, -1); + gtk_clipboard_store (clipboard); +} + +static void +action_image_copy_cb (GtkAction *action, + EWebViewGtkHTML *web_view) +{ + GtkClipboard *clipboard; + GdkPixbufAnimation *animation; + GdkPixbuf *pixbuf; + + clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + animation = e_web_view_gtkhtml_get_cursor_image (web_view); + g_return_if_fail (animation != NULL); + + pixbuf = gdk_pixbuf_animation_get_static_image (animation); + if (!pixbuf) + return; + + gtk_clipboard_set_image (clipboard, pixbuf); + gtk_clipboard_store (clipboard); +} + +static GtkActionEntry uri_entries[] = { + + { "uri-copy", + GTK_STOCK_COPY, + N_("_Copy Link Location"), + NULL, + N_("Copy the link to the clipboard"), + G_CALLBACK (action_uri_copy_cb) } +}; + +static GtkActionEntry http_entries[] = { + + { "http-open", + "emblem-web", + N_("_Open Link in Browser"), + NULL, + N_("Open the link in a web browser"), + G_CALLBACK (action_http_open_cb) } +}; + +static GtkActionEntry mailto_entries[] = { + + { "mailto-copy", + GTK_STOCK_COPY, + N_("_Copy Email Address"), + NULL, + N_("Copy the email address to the clipboard"), + G_CALLBACK (action_mailto_copy_cb) }, + + { "send-message", + "mail-message-new", + N_("_Send New Message To..."), + NULL, + N_("Send a mail message to this address"), + G_CALLBACK (action_send_message_cb) } +}; + +static GtkActionEntry image_entries[] = { + + { "image-copy", + GTK_STOCK_COPY, + N_("_Copy Image"), + NULL, + N_("Copy the image to the clipboard"), + G_CALLBACK (action_image_copy_cb) } +}; + +static GtkActionEntry selection_entries[] = { + + { "copy-clipboard", + GTK_STOCK_COPY, + NULL, + NULL, + N_("Copy the selection"), + G_CALLBACK (action_copy_clipboard_cb) }, +}; + +static GtkActionEntry standard_entries[] = { + + { "select-all", + GTK_STOCK_SELECT_ALL, + NULL, + NULL, + N_("Select all text and images"), + G_CALLBACK (action_select_all_cb) } +}; + +static gboolean +web_view_gtkhtml_button_press_event_cb (EWebViewGtkHTML *web_view, + GdkEventButton *event, + GtkHTML *frame) +{ + gboolean event_handled = FALSE; + gchar *uri = NULL; + + if (event) { + GdkPixbufAnimation *anim; + + if (frame == NULL) + frame = GTK_HTML (web_view); + + anim = gtk_html_get_image_at (frame, event->x, event->y); + e_web_view_gtkhtml_set_cursor_image (web_view, anim); + if (anim != NULL) + g_object_unref (anim); + } + + if (event != NULL && event->button != 3) + return FALSE; + + /* Only extract a URI if no selection is active. Selected text + * implies the user is more likely to want to copy the selection + * to the clipboard than open a link within the selection. */ + if (!e_web_view_gtkhtml_is_selection_active (web_view)) + uri = e_web_view_gtkhtml_extract_uri (web_view, event, frame); + + if (uri != NULL && g_str_has_prefix (uri, "##")) { + g_free (uri); + return FALSE; + } + + g_signal_emit ( + web_view, signals[POPUP_EVENT], 0, + event, uri, &event_handled); + + g_free (uri); + + return event_handled; +} + +static void +web_view_gtkhtml_menu_item_select_cb (EWebViewGtkHTML *web_view, + GtkWidget *widget) +{ + GtkAction *action; + GtkActivatable *activatable; + const gchar *tooltip; + + activatable = GTK_ACTIVATABLE (widget); + action = gtk_activatable_get_related_action (activatable); + tooltip = gtk_action_get_tooltip (action); + + if (tooltip == NULL) + return; + + e_web_view_gtkhtml_status_message (web_view, tooltip); +} + +static void +web_view_gtkhtml_menu_item_deselect_cb (EWebViewGtkHTML *web_view) +{ + e_web_view_gtkhtml_status_message (web_view, NULL); +} + +static void +web_view_gtkhtml_connect_proxy_cb (EWebViewGtkHTML *web_view, + GtkAction *action, + GtkWidget *proxy) +{ + if (!GTK_IS_MENU_ITEM (proxy)) + return; + + g_signal_connect_swapped ( + proxy, "select", + G_CALLBACK (web_view_gtkhtml_menu_item_select_cb), web_view); + + g_signal_connect_swapped ( + proxy, "deselect", + G_CALLBACK (web_view_gtkhtml_menu_item_deselect_cb), web_view); +} + +static void +web_view_gtkhtml_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_ANIMATE: + e_web_view_gtkhtml_set_animate ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_boolean (value)); + return; + + case PROP_CARET_MODE: + e_web_view_gtkhtml_set_caret_mode ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_boolean (value)); + return; + + case PROP_DISABLE_PRINTING: + e_web_view_gtkhtml_set_disable_printing ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_boolean (value)); + return; + + case PROP_DISABLE_SAVE_TO_DISK: + e_web_view_gtkhtml_set_disable_save_to_disk ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_boolean (value)); + return; + + case PROP_EDITABLE: + e_web_view_gtkhtml_set_editable ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_boolean (value)); + return; + + case PROP_INLINE_SPELLING: + e_web_view_gtkhtml_set_inline_spelling ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_boolean (value)); + return; + + case PROP_MAGIC_LINKS: + e_web_view_gtkhtml_set_magic_links ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_boolean (value)); + return; + + case PROP_MAGIC_SMILEYS: + e_web_view_gtkhtml_set_magic_smileys ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_boolean (value)); + return; + + case PROP_OPEN_PROXY: + e_web_view_gtkhtml_set_open_proxy ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_object (value)); + return; + + case PROP_PRINT_PROXY: + e_web_view_gtkhtml_set_print_proxy ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_object (value)); + return; + + case PROP_SAVE_AS_PROXY: + e_web_view_gtkhtml_set_save_as_proxy ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_object (value)); + return; + + case PROP_SELECTED_URI: + e_web_view_gtkhtml_set_selected_uri ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_string (value)); + return; + case PROP_CURSOR_IMAGE: + e_web_view_gtkhtml_set_cursor_image ( + E_WEB_VIEW_GTKHTML (object), + g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +web_view_gtkhtml_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_ANIMATE: + g_value_set_boolean ( + value, e_web_view_gtkhtml_get_animate ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_CARET_MODE: + g_value_set_boolean ( + value, e_web_view_gtkhtml_get_caret_mode ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_COPY_TARGET_LIST: + g_value_set_boxed ( + value, e_web_view_gtkhtml_get_copy_target_list ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_DISABLE_PRINTING: + g_value_set_boolean ( + value, e_web_view_gtkhtml_get_disable_printing ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_DISABLE_SAVE_TO_DISK: + g_value_set_boolean ( + value, e_web_view_gtkhtml_get_disable_save_to_disk ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_EDITABLE: + g_value_set_boolean ( + value, e_web_view_gtkhtml_get_editable ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_INLINE_SPELLING: + g_value_set_boolean ( + value, e_web_view_gtkhtml_get_inline_spelling ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_MAGIC_LINKS: + g_value_set_boolean ( + value, e_web_view_gtkhtml_get_magic_links ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_MAGIC_SMILEYS: + g_value_set_boolean ( + value, e_web_view_gtkhtml_get_magic_smileys ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_OPEN_PROXY: + g_value_set_object ( + value, e_web_view_gtkhtml_get_open_proxy ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_PASTE_TARGET_LIST: + g_value_set_boxed ( + value, e_web_view_gtkhtml_get_paste_target_list ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_PRINT_PROXY: + g_value_set_object ( + value, e_web_view_gtkhtml_get_print_proxy ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_SAVE_AS_PROXY: + g_value_set_object ( + value, e_web_view_gtkhtml_get_save_as_proxy ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_SELECTED_URI: + g_value_set_string ( + value, e_web_view_gtkhtml_get_selected_uri ( + E_WEB_VIEW_GTKHTML (object))); + return; + + case PROP_CURSOR_IMAGE: + g_value_set_object ( + value, e_web_view_gtkhtml_get_cursor_image ( + E_WEB_VIEW_GTKHTML (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +web_view_gtkhtml_dispose (GObject *object) +{ + EWebViewGtkHTMLPrivate *priv; + + priv = E_WEB_VIEW_GTKHTML_GET_PRIVATE (object); + + if (priv->ui_manager != NULL) { + g_object_unref (priv->ui_manager); + priv->ui_manager = NULL; + } + + if (priv->open_proxy != NULL) { + g_object_unref (priv->open_proxy); + priv->open_proxy = NULL; + } + + if (priv->print_proxy != NULL) { + g_object_unref (priv->print_proxy); + priv->print_proxy = NULL; + } + + if (priv->save_as_proxy != NULL) { + g_object_unref (priv->save_as_proxy); + priv->save_as_proxy = NULL; + } + + if (priv->copy_target_list != NULL) { + gtk_target_list_unref (priv->copy_target_list); + priv->copy_target_list = NULL; + } + + if (priv->paste_target_list != NULL) { + gtk_target_list_unref (priv->paste_target_list); + priv->paste_target_list = NULL; + } + + if (priv->cursor_image != NULL) { + g_object_unref (priv->cursor_image); + priv->cursor_image = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +web_view_gtkhtml_finalize (GObject *object) +{ + EWebViewGtkHTMLPrivate *priv; + + priv = E_WEB_VIEW_GTKHTML_GET_PRIVATE (object); + + /* All URI requests should be complete or cancelled by now. */ + if (priv->requests != NULL) + g_warning ("Finalizing EWebViewGtkHTML with active URI requests"); + + g_free (priv->selected_uri); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +web_view_gtkhtml_constructed (GObject *object) +{ +#ifndef G_OS_WIN32 + GSettings *settings; + + settings = g_settings_new ("org.gnome.desktop.lockdown"); + + g_settings_bind ( + settings, "disable-printing", + object, "disable-printing", + G_SETTINGS_BIND_GET); + + g_settings_bind ( + settings, "disable-save-to-disk", + object, "disable-save-to-disk", + G_SETTINGS_BIND_GET); + + g_object_unref (settings); +#endif + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static gboolean +web_view_gtkhtml_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + GtkWidgetClass *widget_class; + EWebViewGtkHTML *web_view; + + web_view = E_WEB_VIEW_GTKHTML (widget); + + if (web_view_gtkhtml_button_press_event_cb (web_view, event, NULL)) + return TRUE; + + /* Chain up to parent's button_press_event() method. */ + widget_class = GTK_WIDGET_CLASS (parent_class); + return widget_class->button_press_event (widget, event); +} + +static gboolean +web_view_gtkhtml_scroll_event (GtkWidget *widget, + GdkEventScroll *event) +{ + if (event->state & GDK_CONTROL_MASK) { + switch (event->direction) { + case GDK_SCROLL_UP: + gtk_html_zoom_in (GTK_HTML (widget)); + return TRUE; + case GDK_SCROLL_DOWN: + gtk_html_zoom_out (GTK_HTML (widget)); + return TRUE; + default: + break; + } + } + + return FALSE; +} + +static void +web_view_gtkhtml_url_requested (GtkHTML *html, + const gchar *uri, + GtkHTMLStream *stream) +{ + EWebViewGtkHTMLRequest *request; + + request = web_view_gtkhtml_request_new (E_WEB_VIEW_GTKHTML (html), uri, stream); + + g_file_read_async ( + request->file, G_PRIORITY_DEFAULT, + request->cancellable, (GAsyncReadyCallback) + web_view_gtkhtml_request_read_cb, request); +} + +static void +web_view_gtkhtml_gtkhtml_link_clicked (GtkHTML *html, + const gchar *uri) +{ + EWebViewGtkHTMLClass *class; + EWebViewGtkHTML *web_view; + + web_view = E_WEB_VIEW_GTKHTML (html); + + class = E_WEB_VIEW_GTKHTML_GET_CLASS (web_view); + g_return_if_fail (class->link_clicked != NULL); + + class->link_clicked (web_view, uri); +} + +static void +web_view_gtkhtml_on_url (GtkHTML *html, + const gchar *uri) +{ + EWebViewGtkHTMLClass *class; + EWebViewGtkHTML *web_view; + + web_view = E_WEB_VIEW_GTKHTML (html); + + class = E_WEB_VIEW_GTKHTML_GET_CLASS (web_view); + g_return_if_fail (class->hovering_over_link != NULL); + + /* XXX WebKit would supply a title here. */ + class->hovering_over_link (web_view, NULL, uri); +} + +static void +web_view_gtkhtml_iframe_created (GtkHTML *html, + GtkHTML *iframe) +{ + g_signal_connect_swapped ( + iframe, "button-press-event", + G_CALLBACK (web_view_gtkhtml_button_press_event_cb), html); +} + +static gchar * +web_view_gtkhtml_extract_uri (EWebViewGtkHTML *web_view, + GdkEventButton *event, + GtkHTML *html) +{ + gchar *uri; + + if (event != NULL) + uri = gtk_html_get_url_at (html, event->x, event->y); + else + uri = gtk_html_get_cursor_url (html); + + return uri; +} + +static void +web_view_gtkhtml_hovering_over_link (EWebViewGtkHTML *web_view, + const gchar *title, + const gchar *uri) +{ + CamelInternetAddress *address; + CamelURL *curl; + const gchar *format = NULL; + gchar *message = NULL; + gchar *who; + + if (uri == NULL || *uri == '\0') + goto exit; + + if (g_str_has_prefix (uri, "mailto:")) + format = _("Click to mail %s"); + else if (g_str_has_prefix (uri, "callto:")) + format = _("Click to call %s"); + else if (g_str_has_prefix (uri, "h323:")) + format = _("Click to call %s"); + else if (g_str_has_prefix (uri, "sip:")) + format = _("Click to call %s"); + else if (g_str_has_prefix (uri, "##")) + message = g_strdup (_("Click to hide/unhide addresses")); + else + message = g_strdup_printf (_("Click to open %s"), uri); + + if (format == NULL) + goto exit; + + /* XXX Use something other than Camel here. Surely + * there's other APIs around that can do this. */ + curl = camel_url_new (uri, NULL); + address = camel_internet_address_new (); + camel_address_decode (CAMEL_ADDRESS (address), curl->path); + who = camel_address_format (CAMEL_ADDRESS (address)); + g_object_unref (address); + camel_url_free (curl); + + if (who == NULL) + who = g_strdup (strchr (uri, ':') + 1); + + message = g_strdup_printf (format, who); + + g_free (who); + +exit: + e_web_view_gtkhtml_status_message (web_view, message); + + g_free (message); +} + +static void +web_view_gtkhtml_link_clicked (EWebViewGtkHTML *web_view, + const gchar *uri) +{ + gpointer parent; + + parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view)); + parent = gtk_widget_is_toplevel (parent) ? parent : NULL; + + e_show_uri (parent, uri); +} + +static void +web_view_gtkhtml_load_string (EWebViewGtkHTML *web_view, + const gchar *string) +{ + if (string != NULL && *string != '\0') + gtk_html_load_from_string (GTK_HTML (web_view), string, -1); + else + e_web_view_gtkhtml_clear (web_view); +} + +static void +web_view_gtkhtml_copy_clipboard (EWebViewGtkHTML *web_view) +{ + gtk_html_command (GTK_HTML (web_view), "copy"); +} + +static void +web_view_gtkhtml_cut_clipboard (EWebViewGtkHTML *web_view) +{ + if (e_web_view_gtkhtml_get_editable (web_view)) + gtk_html_command (GTK_HTML (web_view), "cut"); +} + +static void +web_view_gtkhtml_paste_clipboard (EWebViewGtkHTML *web_view) +{ + if (e_web_view_gtkhtml_get_editable (web_view)) + gtk_html_command (GTK_HTML (web_view), "paste"); +} + +static gboolean +web_view_gtkhtml_popup_event (EWebViewGtkHTML *web_view, + GdkEventButton *event, + const gchar *uri) +{ + e_web_view_gtkhtml_set_selected_uri (web_view, uri); + e_web_view_gtkhtml_show_popup_menu (web_view, event, NULL, NULL); + + return TRUE; +} + +static void +web_view_gtkhtml_stop_loading (EWebViewGtkHTML *web_view) +{ + g_list_foreach ( + web_view->priv->requests, (GFunc) + web_view_gtkhtml_request_cancel, NULL); + + gtk_html_stop (GTK_HTML (web_view)); +} + +static void +web_view_gtkhtml_update_actions (EWebViewGtkHTML *web_view) +{ + GtkActionGroup *action_group; + gboolean have_selection; + gboolean scheme_is_http = FALSE; + gboolean scheme_is_mailto = FALSE; + gboolean uri_is_valid = FALSE; + gboolean has_cursor_image; + gboolean visible; + const gchar *group_name; + const gchar *uri; + + uri = e_web_view_gtkhtml_get_selected_uri (web_view); + have_selection = e_web_view_gtkhtml_is_selection_active (web_view); + has_cursor_image = e_web_view_gtkhtml_get_cursor_image (web_view) != NULL; + + /* Parse the URI early so we know if the actions will work. */ + if (uri != NULL) { + CamelURL *curl; + + curl = camel_url_new (uri, NULL); + uri_is_valid = (curl != NULL); + camel_url_free (curl); + + scheme_is_http = + (g_ascii_strncasecmp (uri, "http:", 5) == 0) || + (g_ascii_strncasecmp (uri, "https:", 6) == 0); + + scheme_is_mailto = + (g_ascii_strncasecmp (uri, "mailto:", 7) == 0); + } + + /* Allow copying the URI even if it's malformed. */ + group_name = "uri"; + visible = (uri != NULL) && !scheme_is_mailto; + action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name); + gtk_action_group_set_visible (action_group, visible); + + group_name = "http"; + visible = uri_is_valid && scheme_is_http; + action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name); + gtk_action_group_set_visible (action_group, visible); + + group_name = "mailto"; + visible = uri_is_valid && scheme_is_mailto; + action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name); + gtk_action_group_set_visible (action_group, visible); + + group_name = "image"; + visible = has_cursor_image; + action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name); + gtk_action_group_set_visible (action_group, visible); + + group_name = "selection"; + visible = have_selection; + action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name); + gtk_action_group_set_visible (action_group, visible); + + group_name = "standard"; + visible = (uri == NULL); + action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name); + gtk_action_group_set_visible (action_group, visible); + + group_name = "lockdown-printing"; + visible = (uri == NULL) && !web_view->priv->disable_printing; + action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name); + gtk_action_group_set_visible (action_group, visible); + + group_name = "lockdown-save-to-disk"; + visible = (uri == NULL) && !web_view->priv->disable_save_to_disk; + action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name); + gtk_action_group_set_visible (action_group, visible); +} + +static void +web_view_gtkhtml_submit_alert (EAlertSink *alert_sink, + EAlert *alert) +{ + GtkIconInfo *icon_info; + EWebViewGtkHTML *web_view; + GtkWidget *dialog; + GString *buffer; + const gchar *icon_name = NULL; + const gchar *filename; + gpointer parent; + gchar *icon_uri; + gint size = 0; + GError *error = NULL; + + web_view = E_WEB_VIEW_GTKHTML (alert_sink); + + parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view)); + parent = gtk_widget_is_toplevel (parent) ? parent : NULL; + + /* We use equivalent named icons instead of stock IDs, + * since it's easier to get the filename of the icon. */ + switch (e_alert_get_message_type (alert)) { + case GTK_MESSAGE_INFO: + icon_name = "dialog-information"; + break; + + case GTK_MESSAGE_WARNING: + icon_name = "dialog-warning"; + break; + + case GTK_MESSAGE_ERROR: + icon_name = "dialog-error"; + break; + + default: + dialog = e_alert_dialog_new (parent, alert); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + return; + } + + gtk_icon_size_lookup (GTK_ICON_SIZE_DIALOG, &size, NULL); + + icon_info = gtk_icon_theme_lookup_icon ( + gtk_icon_theme_get_default (), + icon_name, size, GTK_ICON_LOOKUP_NO_SVG); + g_return_if_fail (icon_info != NULL); + + filename = gtk_icon_info_get_filename (icon_info); + icon_uri = g_filename_to_uri (filename, NULL, &error); + + if (error != NULL) { + g_warning ("%s", error->message); + g_clear_error (&error); + } + + buffer = g_string_sized_new (512); + + g_string_append ( + buffer, + "<html>" + "<head>" + "<meta http-equiv=\"content-type\"" + " content=\"text/html; charset=utf-8\">" + "</head>" + "<body>"); + + g_string_append ( + buffer, + "<table bgcolor='#000000' width='100%'" + " cellpadding='1' cellspacing='0'>" + "<tr>" + "<td>" + "<table bgcolor='#dddddd' width='100%' cellpadding='6'>" + "<tr>"); + + g_string_append_printf ( + buffer, + "<tr>" + "<td valign='top'>" + "<img src='%s'/>" + "</td>" + "<td align='left' width='100%%'>" + "<h3>%s</h3>" + "%s" + "</td>" + "</tr>", + icon_uri, + e_alert_get_primary_text (alert), + e_alert_get_secondary_text (alert)); + + g_string_append ( + buffer, + "</table>" + "</td>" + "</tr>" + "</table>" + "</body>" + "</html>"); + + e_web_view_gtkhtml_load_string (web_view, buffer->str); + + g_string_free (buffer, TRUE); + + gtk_icon_info_free (icon_info); + g_free (icon_uri); +} + +static void +web_view_gtkhtml_selectable_update_actions (ESelectable *selectable, + EFocusTracker *focus_tracker, + GdkAtom *clipboard_targets, + gint n_clipboard_targets) +{ + EWebViewGtkHTML *web_view; + GtkAction *action; + /*GtkTargetList *target_list;*/ + gboolean can_paste = FALSE; + gboolean editable; + gboolean have_selection; + gboolean sensitive; + const gchar *tooltip; + /*gint ii;*/ + + web_view = E_WEB_VIEW_GTKHTML (selectable); + editable = e_web_view_gtkhtml_get_editable (web_view); + have_selection = e_web_view_gtkhtml_is_selection_active (web_view); + + /* XXX GtkHtml implements its own clipboard instead of using + * GDK_SELECTION_CLIPBOARD, so we don't get notifications + * when the clipboard contents change. The logic below + * is what we would do if GtkHtml worked properly. + * Instead, we need to keep the Paste action sensitive so + * its accelerator overrides GtkHtml's key binding. */ +#if 0 + target_list = e_selectable_get_paste_target_list (selectable); + for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++) + can_paste = gtk_target_list_find ( + target_list, clipboard_targets[ii], NULL); +#endif + can_paste = TRUE; + + action = e_focus_tracker_get_cut_clipboard_action (focus_tracker); + sensitive = editable && have_selection; + tooltip = _("Cut the selection"); + gtk_action_set_sensitive (action, sensitive); + gtk_action_set_tooltip (action, tooltip); + + action = e_focus_tracker_get_copy_clipboard_action (focus_tracker); + sensitive = have_selection; + tooltip = _("Copy the selection"); + gtk_action_set_sensitive (action, sensitive); + gtk_action_set_tooltip (action, tooltip); + + action = e_focus_tracker_get_paste_clipboard_action (focus_tracker); + sensitive = editable && can_paste; + tooltip = _("Paste the clipboard"); + gtk_action_set_sensitive (action, sensitive); + gtk_action_set_tooltip (action, tooltip); + + action = e_focus_tracker_get_select_all_action (focus_tracker); + sensitive = TRUE; + tooltip = _("Select all text and images"); + gtk_action_set_sensitive (action, sensitive); + gtk_action_set_tooltip (action, tooltip); +} + +static void +web_view_gtkhtml_selectable_cut_clipboard (ESelectable *selectable) +{ + e_web_view_gtkhtml_cut_clipboard (E_WEB_VIEW_GTKHTML (selectable)); +} + +static void +web_view_gtkhtml_selectable_copy_clipboard (ESelectable *selectable) +{ + e_web_view_gtkhtml_copy_clipboard (E_WEB_VIEW_GTKHTML (selectable)); +} + +static void +web_view_gtkhtml_selectable_paste_clipboard (ESelectable *selectable) +{ + e_web_view_gtkhtml_paste_clipboard (E_WEB_VIEW_GTKHTML (selectable)); +} + +static void +web_view_gtkhtml_selectable_select_all (ESelectable *selectable) +{ + e_web_view_gtkhtml_select_all (E_WEB_VIEW_GTKHTML (selectable)); +} + +static void +e_web_view_gtkhtml_class_init (EWebViewGtkHTMLClass *class) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkHTMLClass *html_class; + + parent_class = g_type_class_peek_parent (class); + g_type_class_add_private (class, sizeof (EWebViewGtkHTMLPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = web_view_gtkhtml_set_property; + object_class->get_property = web_view_gtkhtml_get_property; + object_class->dispose = web_view_gtkhtml_dispose; + object_class->finalize = web_view_gtkhtml_finalize; + object_class->constructed = web_view_gtkhtml_constructed; + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->button_press_event = web_view_gtkhtml_button_press_event; + widget_class->scroll_event = web_view_gtkhtml_scroll_event; + + html_class = GTK_HTML_CLASS (class); + html_class->url_requested = web_view_gtkhtml_url_requested; + html_class->link_clicked = web_view_gtkhtml_gtkhtml_link_clicked; + html_class->on_url = web_view_gtkhtml_on_url; + html_class->iframe_created = web_view_gtkhtml_iframe_created; + + class->extract_uri = web_view_gtkhtml_extract_uri; + class->hovering_over_link = web_view_gtkhtml_hovering_over_link; + class->link_clicked = web_view_gtkhtml_link_clicked; + class->load_string = web_view_gtkhtml_load_string; + class->copy_clipboard = web_view_gtkhtml_copy_clipboard; + class->cut_clipboard = web_view_gtkhtml_cut_clipboard; + class->paste_clipboard = web_view_gtkhtml_paste_clipboard; + class->popup_event = web_view_gtkhtml_popup_event; + class->stop_loading = web_view_gtkhtml_stop_loading; + class->update_actions = web_view_gtkhtml_update_actions; + + g_object_class_install_property ( + object_class, + PROP_ANIMATE, + g_param_spec_boolean ( + "animate", + "Animate Images", + NULL, + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_CARET_MODE, + g_param_spec_boolean ( + "caret-mode", + "Caret Mode", + NULL, + FALSE, + G_PARAM_READWRITE)); + + /* Inherited from ESelectableInterface */ + g_object_class_override_property ( + object_class, + PROP_COPY_TARGET_LIST, + "copy-target-list"); + + g_object_class_install_property ( + object_class, + PROP_DISABLE_PRINTING, + g_param_spec_boolean ( + "disable-printing", + "Disable Printing", + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property ( + object_class, + PROP_DISABLE_SAVE_TO_DISK, + g_param_spec_boolean ( + "disable-save-to-disk", + "Disable Save-to-Disk", + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property ( + object_class, + PROP_EDITABLE, + g_param_spec_boolean ( + "editable", + "Editable", + NULL, + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_INLINE_SPELLING, + g_param_spec_boolean ( + "inline-spelling", + "Inline Spelling", + NULL, + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_MAGIC_LINKS, + g_param_spec_boolean ( + "magic-links", + "Magic Links", + NULL, + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_MAGIC_SMILEYS, + g_param_spec_boolean ( + "magic-smileys", + "Magic Smileys", + NULL, + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_OPEN_PROXY, + g_param_spec_object ( + "open-proxy", + "Open Proxy", + NULL, + GTK_TYPE_ACTION, + G_PARAM_READWRITE)); + + /* Inherited from ESelectableInterface */ + g_object_class_override_property ( + object_class, + PROP_PASTE_TARGET_LIST, + "paste-target-list"); + + g_object_class_install_property ( + object_class, + PROP_PRINT_PROXY, + g_param_spec_object ( + "print-proxy", + "Print Proxy", + NULL, + GTK_TYPE_ACTION, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_SAVE_AS_PROXY, + g_param_spec_object ( + "save-as-proxy", + "Save As Proxy", + NULL, + GTK_TYPE_ACTION, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_SELECTED_URI, + g_param_spec_string ( + "selected-uri", + "Selected URI", + NULL, + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_CURSOR_IMAGE, + g_param_spec_object ( + "cursor-image", + "Image animation at the mouse cursor", + NULL, + GDK_TYPE_PIXBUF_ANIMATION, + G_PARAM_READWRITE)); + + signals[COPY_CLIPBOARD] = g_signal_new ( + "copy-clipboard", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EWebViewGtkHTMLClass, copy_clipboard), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[CUT_CLIPBOARD] = g_signal_new ( + "cut-clipboard", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EWebViewGtkHTMLClass, cut_clipboard), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[PASTE_CLIPBOARD] = g_signal_new ( + "paste-clipboard", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EWebViewGtkHTMLClass, paste_clipboard), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[POPUP_EVENT] = g_signal_new ( + "popup-event", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EWebViewGtkHTMLClass, popup_event), + g_signal_accumulator_true_handled, NULL, + e_marshal_BOOLEAN__BOXED_STRING, + G_TYPE_BOOLEAN, 2, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE, + G_TYPE_STRING); + + signals[STATUS_MESSAGE] = g_signal_new ( + "status-message", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EWebViewGtkHTMLClass, status_message), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, + G_TYPE_STRING); + + signals[STOP_LOADING] = g_signal_new ( + "stop-loading", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EWebViewGtkHTMLClass, stop_loading), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[UPDATE_ACTIONS] = g_signal_new ( + "update-actions", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EWebViewGtkHTMLClass, update_actions), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /* return TRUE when a signal handler processed the mailto URI */ + signals[PROCESS_MAILTO] = g_signal_new ( + "process-mailto", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EWebViewGtkHTMLClass, process_mailto), + NULL, NULL, + e_marshal_BOOLEAN__STRING, + G_TYPE_BOOLEAN, 1, G_TYPE_STRING); +} + +static void +e_web_view_gtkhtml_alert_sink_init (EAlertSinkInterface *interface) +{ + interface->submit_alert = web_view_gtkhtml_submit_alert; +} + +static void +e_web_view_gtkhtml_selectable_init (ESelectableInterface *interface) +{ + interface->update_actions = web_view_gtkhtml_selectable_update_actions; + interface->cut_clipboard = web_view_gtkhtml_selectable_cut_clipboard; + interface->copy_clipboard = web_view_gtkhtml_selectable_copy_clipboard; + interface->paste_clipboard = web_view_gtkhtml_selectable_paste_clipboard; + interface->select_all = web_view_gtkhtml_selectable_select_all; +} + +static void +e_web_view_gtkhtml_init (EWebViewGtkHTML *web_view) +{ + GtkUIManager *ui_manager; + GtkActionGroup *action_group; + GtkTargetList *target_list; + EPopupAction *popup_action; + const gchar *domain = GETTEXT_PACKAGE; + const gchar *id; + GError *error = NULL; + + web_view->priv = E_WEB_VIEW_GTKHTML_GET_PRIVATE (web_view); + + ui_manager = gtk_ui_manager_new (); + web_view->priv->ui_manager = ui_manager; + + g_signal_connect_swapped ( + ui_manager, "connect-proxy", + G_CALLBACK (web_view_gtkhtml_connect_proxy_cb), web_view); + + target_list = gtk_target_list_new (NULL, 0); + web_view->priv->copy_target_list = target_list; + + target_list = gtk_target_list_new (NULL, 0); + web_view->priv->paste_target_list = target_list; + + action_group = gtk_action_group_new ("uri"); + gtk_action_group_set_translation_domain (action_group, domain); + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); + + gtk_action_group_add_actions ( + action_group, uri_entries, + G_N_ELEMENTS (uri_entries), web_view); + + action_group = gtk_action_group_new ("http"); + gtk_action_group_set_translation_domain (action_group, domain); + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); + + gtk_action_group_add_actions ( + action_group, http_entries, + G_N_ELEMENTS (http_entries), web_view); + + action_group = gtk_action_group_new ("mailto"); + gtk_action_group_set_translation_domain (action_group, domain); + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); + + gtk_action_group_add_actions ( + action_group, mailto_entries, + G_N_ELEMENTS (mailto_entries), web_view); + + action_group = gtk_action_group_new ("image"); + gtk_action_group_set_translation_domain (action_group, domain); + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); + + gtk_action_group_add_actions ( + action_group, image_entries, + G_N_ELEMENTS (image_entries), web_view); + + action_group = gtk_action_group_new ("selection"); + gtk_action_group_set_translation_domain (action_group, domain); + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); + + gtk_action_group_add_actions ( + action_group, selection_entries, + G_N_ELEMENTS (selection_entries), web_view); + + action_group = gtk_action_group_new ("standard"); + gtk_action_group_set_translation_domain (action_group, domain); + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); + + gtk_action_group_add_actions ( + action_group, standard_entries, + G_N_ELEMENTS (standard_entries), web_view); + + popup_action = e_popup_action_new ("open"); + gtk_action_group_add_action (action_group, GTK_ACTION (popup_action)); + g_object_unref (popup_action); + + g_object_bind_property ( + web_view, "open-proxy", + popup_action, "related-action", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + /* Support lockdown. */ + + action_group = gtk_action_group_new ("lockdown-printing"); + gtk_action_group_set_translation_domain (action_group, domain); + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); + + popup_action = e_popup_action_new ("print"); + gtk_action_group_add_action (action_group, GTK_ACTION (popup_action)); + g_object_unref (popup_action); + + g_object_bind_property ( + web_view, "print-proxy", + popup_action, "related-action", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + action_group = gtk_action_group_new ("lockdown-save-to-disk"); + gtk_action_group_set_translation_domain (action_group, domain); + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + g_object_unref (action_group); + + popup_action = e_popup_action_new ("save-as"); + gtk_action_group_add_action (action_group, GTK_ACTION (popup_action)); + g_object_unref (popup_action); + + g_object_bind_property ( + web_view, "save-as-proxy", + popup_action, "related-action", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + /* Because we are loading from a hard-coded string, there is + * no chance of I/O errors. Failure here implies a malformed + * UI definition. Full stop. */ + gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error); + if (error != NULL) + g_error ("%s", error->message); + + id = "org.gnome.evolution.webview"; + e_plugin_ui_register_manager (ui_manager, id, web_view); + e_plugin_ui_enable_manager (ui_manager, id); + + e_extensible_load_extensions (E_EXTENSIBLE (web_view)); +} + +GtkWidget * +e_web_view_gtkhtml_new (void) +{ + return g_object_new (E_TYPE_WEB_VIEW_GTKHTML, NULL); +} + +void +e_web_view_gtkhtml_clear (EWebViewGtkHTML *web_view) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + gtk_html_load_empty (GTK_HTML (web_view)); +} + +void +e_web_view_gtkhtml_load_string (EWebViewGtkHTML *web_view, + const gchar *string) +{ + EWebViewGtkHTMLClass *class; + + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + class = E_WEB_VIEW_GTKHTML_GET_CLASS (web_view); + g_return_if_fail (class->load_string != NULL); + + class->load_string (web_view, string); +} + +gboolean +e_web_view_gtkhtml_get_animate (EWebViewGtkHTML *web_view) +{ + /* XXX This is just here to maintain symmetry + * with e_web_view_set_animate(). */ + + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return gtk_html_get_animate (GTK_HTML (web_view)); +} + +void +e_web_view_gtkhtml_set_animate (EWebViewGtkHTML *web_view, + gboolean animate) +{ + /* XXX GtkHTML does not utilize GObject properties as well + * as it could. This just wraps gtk_html_set_animate() + * so we can get a "notify::animate" signal. */ + + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + gtk_html_set_animate (GTK_HTML (web_view), animate); + + g_object_notify (G_OBJECT (web_view), "animate"); +} + +gboolean +e_web_view_gtkhtml_get_caret_mode (EWebViewGtkHTML *web_view) +{ + /* XXX This is just here to maintain symmetry + * with e_web_view_set_caret_mode(). */ + + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return gtk_html_get_caret_mode (GTK_HTML (web_view)); +} + +void +e_web_view_gtkhtml_set_caret_mode (EWebViewGtkHTML *web_view, + gboolean caret_mode) +{ + /* XXX GtkHTML does not utilize GObject properties as well + * as it could. This just wraps gtk_html_set_caret_mode() + * so we can get a "notify::caret-mode" signal. */ + + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + gtk_html_set_caret_mode (GTK_HTML (web_view), caret_mode); + + g_object_notify (G_OBJECT (web_view), "caret-mode"); +} + +GtkTargetList * +e_web_view_gtkhtml_get_copy_target_list (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL); + + return web_view->priv->copy_target_list; +} + +gboolean +e_web_view_gtkhtml_get_disable_printing (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return web_view->priv->disable_printing; +} + +void +e_web_view_gtkhtml_set_disable_printing (EWebViewGtkHTML *web_view, + gboolean disable_printing) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + web_view->priv->disable_printing = disable_printing; + + g_object_notify (G_OBJECT (web_view), "disable-printing"); +} + +gboolean +e_web_view_gtkhtml_get_disable_save_to_disk (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return web_view->priv->disable_save_to_disk; +} + +void +e_web_view_gtkhtml_set_disable_save_to_disk (EWebViewGtkHTML *web_view, + gboolean disable_save_to_disk) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + web_view->priv->disable_save_to_disk = disable_save_to_disk; + + g_object_notify (G_OBJECT (web_view), "disable-save-to-disk"); +} + +gboolean +e_web_view_gtkhtml_get_editable (EWebViewGtkHTML *web_view) +{ + /* XXX This is just here to maintain symmetry + * with e_web_view_set_editable(). */ + + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return gtk_html_get_editable (GTK_HTML (web_view)); +} + +void +e_web_view_gtkhtml_set_editable (EWebViewGtkHTML *web_view, + gboolean editable) +{ + /* XXX GtkHTML does not utilize GObject properties as well + * as it could. This just wraps gtk_html_set_editable() + * so we can get a "notify::editable" signal. */ + + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + gtk_html_set_editable (GTK_HTML (web_view), editable); + + g_object_notify (G_OBJECT (web_view), "editable"); +} + +gboolean +e_web_view_gtkhtml_get_inline_spelling (EWebViewGtkHTML *web_view) +{ + /* XXX This is just here to maintain symmetry + * with e_web_view_set_inline_spelling(). */ + + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return gtk_html_get_inline_spelling (GTK_HTML (web_view)); +} + +void +e_web_view_gtkhtml_set_inline_spelling (EWebViewGtkHTML *web_view, + gboolean inline_spelling) +{ + /* XXX GtkHTML does not utilize GObject properties as well + * as it could. This just wraps gtk_html_set_inline_spelling() + * so we get a "notify::inline-spelling" signal. */ + + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + gtk_html_set_inline_spelling (GTK_HTML (web_view), inline_spelling); + + g_object_notify (G_OBJECT (web_view), "inline-spelling"); +} + +gboolean +e_web_view_gtkhtml_get_magic_links (EWebViewGtkHTML *web_view) +{ + /* XXX This is just here to maintain symmetry + * with e_web_view_set_magic_links(). */ + + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return gtk_html_get_magic_links (GTK_HTML (web_view)); +} + +void +e_web_view_gtkhtml_set_magic_links (EWebViewGtkHTML *web_view, + gboolean magic_links) +{ + /* XXX GtkHTML does not utilize GObject properties as well + * as it could. This just wraps gtk_html_set_magic_links() + * so we can get a "notify::magic-links" signal. */ + + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + gtk_html_set_magic_links (GTK_HTML (web_view), magic_links); + + g_object_notify (G_OBJECT (web_view), "magic-links"); +} + +gboolean +e_web_view_gtkhtml_get_magic_smileys (EWebViewGtkHTML *web_view) +{ + /* XXX This is just here to maintain symmetry + * with e_web_view_set_magic_smileys(). */ + + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return gtk_html_get_magic_smileys (GTK_HTML (web_view)); +} + +void +e_web_view_gtkhtml_set_magic_smileys (EWebViewGtkHTML *web_view, + gboolean magic_smileys) +{ + /* XXX GtkHTML does not utilize GObject properties as well + * as it could. This just wraps gtk_html_set_magic_smileys() + * so we can get a "notify::magic-smileys" signal. */ + + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + gtk_html_set_magic_smileys (GTK_HTML (web_view), magic_smileys); + + g_object_notify (G_OBJECT (web_view), "magic-smileys"); +} + +const gchar * +e_web_view_gtkhtml_get_selected_uri (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL); + + return web_view->priv->selected_uri; +} + +void +e_web_view_gtkhtml_set_selected_uri (EWebViewGtkHTML *web_view, + const gchar *selected_uri) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + g_free (web_view->priv->selected_uri); + web_view->priv->selected_uri = g_strdup (selected_uri); + + g_object_notify (G_OBJECT (web_view), "selected-uri"); +} + +GdkPixbufAnimation * +e_web_view_gtkhtml_get_cursor_image (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL); + + return web_view->priv->cursor_image; +} + +void +e_web_view_gtkhtml_set_cursor_image (EWebViewGtkHTML *web_view, + GdkPixbufAnimation *image) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + if (image != NULL) + g_object_ref (image); + + if (web_view->priv->cursor_image != NULL) + g_object_unref (web_view->priv->cursor_image); + + web_view->priv->cursor_image = image; + + g_object_notify (G_OBJECT (web_view), "cursor-image"); +} + +GtkAction * +e_web_view_gtkhtml_get_open_proxy (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return web_view->priv->open_proxy; +} + +void +e_web_view_gtkhtml_set_open_proxy (EWebViewGtkHTML *web_view, + GtkAction *open_proxy) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + if (open_proxy != NULL) { + g_return_if_fail (GTK_IS_ACTION (open_proxy)); + g_object_ref (open_proxy); + } + + if (web_view->priv->open_proxy != NULL) + g_object_unref (web_view->priv->open_proxy); + + web_view->priv->open_proxy = open_proxy; + + g_object_notify (G_OBJECT (web_view), "open-proxy"); +} + +GtkTargetList * +e_web_view_gtkhtml_get_paste_target_list (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL); + + return web_view->priv->paste_target_list; +} + +GtkAction * +e_web_view_gtkhtml_get_print_proxy (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return web_view->priv->print_proxy; +} + +void +e_web_view_gtkhtml_set_print_proxy (EWebViewGtkHTML *web_view, + GtkAction *print_proxy) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + if (print_proxy != NULL) { + g_return_if_fail (GTK_IS_ACTION (print_proxy)); + g_object_ref (print_proxy); + } + + if (web_view->priv->print_proxy != NULL) + g_object_unref (web_view->priv->print_proxy); + + web_view->priv->print_proxy = print_proxy; + + g_object_notify (G_OBJECT (web_view), "print-proxy"); +} + +GtkAction * +e_web_view_gtkhtml_get_save_as_proxy (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return web_view->priv->save_as_proxy; +} + +void +e_web_view_gtkhtml_set_save_as_proxy (EWebViewGtkHTML *web_view, + GtkAction *save_as_proxy) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + if (save_as_proxy != NULL) { + g_return_if_fail (GTK_IS_ACTION (save_as_proxy)); + g_object_ref (save_as_proxy); + } + + if (web_view->priv->save_as_proxy != NULL) + g_object_unref (web_view->priv->save_as_proxy); + + web_view->priv->save_as_proxy = save_as_proxy; + + g_object_notify (G_OBJECT (web_view), "save-as-proxy"); +} + +GtkAction * +e_web_view_gtkhtml_get_action (EWebViewGtkHTML *web_view, + const gchar *action_name) +{ + GtkUIManager *ui_manager; + + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL); + g_return_val_if_fail (action_name != NULL, NULL); + + ui_manager = e_web_view_gtkhtml_get_ui_manager (web_view); + + return e_lookup_action (ui_manager, action_name); +} + +GtkActionGroup * +e_web_view_gtkhtml_get_action_group (EWebViewGtkHTML *web_view, + const gchar *group_name) +{ + GtkUIManager *ui_manager; + + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL); + g_return_val_if_fail (group_name != NULL, NULL); + + ui_manager = e_web_view_gtkhtml_get_ui_manager (web_view); + + return e_lookup_action_group (ui_manager, group_name); +} + +gchar * +e_web_view_gtkhtml_extract_uri (EWebViewGtkHTML *web_view, + GdkEventButton *event, + GtkHTML *frame) +{ + EWebViewGtkHTMLClass *class; + + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL); + + if (frame == NULL) + frame = GTK_HTML (web_view); + + class = E_WEB_VIEW_GTKHTML_GET_CLASS (web_view); + g_return_val_if_fail (class->extract_uri != NULL, NULL); + + return class->extract_uri (web_view, event, frame); +} + +void +e_web_view_gtkhtml_copy_clipboard (EWebViewGtkHTML *web_view) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + g_signal_emit (web_view, signals[COPY_CLIPBOARD], 0); +} + +void +e_web_view_gtkhtml_cut_clipboard (EWebViewGtkHTML *web_view) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + g_signal_emit (web_view, signals[CUT_CLIPBOARD], 0); +} + +gboolean +e_web_view_gtkhtml_is_selection_active (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return gtk_html_command (GTK_HTML (web_view), "is-selection-active"); +} + +void +e_web_view_gtkhtml_paste_clipboard (EWebViewGtkHTML *web_view) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + g_signal_emit (web_view, signals[PASTE_CLIPBOARD], 0); +} + +gboolean +e_web_view_gtkhtml_scroll_forward (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return gtk_html_command (GTK_HTML (web_view), "scroll-forward"); +} + +gboolean +e_web_view_gtkhtml_scroll_backward (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE); + + return gtk_html_command (GTK_HTML (web_view), "scroll-backward"); +} + +void +e_web_view_gtkhtml_select_all (EWebViewGtkHTML *web_view) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + gtk_html_command (GTK_HTML (web_view), "select-all"); +} + +void +e_web_view_gtkhtml_unselect_all (EWebViewGtkHTML *web_view) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + gtk_html_command (GTK_HTML (web_view), "unselect-all"); +} + +void +e_web_view_gtkhtml_zoom_100 (EWebViewGtkHTML *web_view) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + gtk_html_command (GTK_HTML (web_view), "zoom-reset"); +} + +void +e_web_view_gtkhtml_zoom_in (EWebViewGtkHTML *web_view) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + gtk_html_command (GTK_HTML (web_view), "zoom-in"); +} + +void +e_web_view_gtkhtml_zoom_out (EWebViewGtkHTML *web_view) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + gtk_html_command (GTK_HTML (web_view), "zoom-out"); +} + +GtkUIManager * +e_web_view_gtkhtml_get_ui_manager (EWebViewGtkHTML *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL); + + return web_view->priv->ui_manager; +} + +GtkWidget * +e_web_view_gtkhtml_get_popup_menu (EWebViewGtkHTML *web_view) +{ + GtkUIManager *ui_manager; + GtkWidget *menu; + + g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL); + + ui_manager = e_web_view_gtkhtml_get_ui_manager (web_view); + menu = gtk_ui_manager_get_widget (ui_manager, "/context"); + g_return_val_if_fail (GTK_IS_MENU (menu), NULL); + + return menu; +} + +void +e_web_view_gtkhtml_show_popup_menu (EWebViewGtkHTML *web_view, + GdkEventButton *event, + GtkMenuPositionFunc func, + gpointer user_data) +{ + GtkWidget *menu; + + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + e_web_view_gtkhtml_update_actions (web_view); + + menu = e_web_view_gtkhtml_get_popup_menu (web_view); + + if (event != NULL) + gtk_menu_popup ( + GTK_MENU (menu), NULL, NULL, func, + user_data, event->button, event->time); + else + gtk_menu_popup ( + GTK_MENU (menu), NULL, NULL, func, + user_data, 0, gtk_get_current_event_time ()); +} + +void +e_web_view_gtkhtml_status_message (EWebViewGtkHTML *web_view, + const gchar *status_message) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + g_signal_emit (web_view, signals[STATUS_MESSAGE], 0, status_message); +} + +void +e_web_view_gtkhtml_stop_loading (EWebViewGtkHTML *web_view) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + g_signal_emit (web_view, signals[STOP_LOADING], 0); +} + +void +e_web_view_gtkhtml_update_actions (EWebViewGtkHTML *web_view) +{ + g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view)); + + g_signal_emit (web_view, signals[UPDATE_ACTIONS], 0); +} diff --git a/widgets/misc/e-web-view-gtkhtml.h b/widgets/misc/e-web-view-gtkhtml.h new file mode 100644 index 0000000000..aab06e8b54 --- /dev/null +++ b/widgets/misc/e-web-view-gtkhtml.h @@ -0,0 +1,173 @@ +/* + * e-web-view-gtkhtml.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + */ + +/* This is intended to serve as a common base class for all HTML viewing + * needs in Evolution. Currently based on GtkHTML, the idea is to wrap + * the GtkHTML API enough that we no longer have to make direct calls to + * it. This should help smooth the transition to WebKit/GTK+. + * + * This class handles basic tasks like mouse hovers over links, clicked + * links, and servicing URI requests asynchronously via GIO. */ + +#ifndef E_WEB_VIEW_GTKHTML_H +#define E_WEB_VIEW_GTKHTML_H + +#include <gtkhtml/gtkhtml.h> + +/* Standard GObject macros */ +#define E_TYPE_WEB_VIEW_GTKHTML \ + (e_web_view_gtkhtml_get_type ()) +#define E_WEB_VIEW_GTKHTML(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_WEB_VIEW_GTKHTML, EWebViewGtkHTML)) +#define E_WEB_VIEW_GTKHTML_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_WEB_VIEW_GTKHTML, EWebViewGtkHTMLClass)) +#define E_IS_WEB_VIEW_GTKHTML(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_WEB_VIEW_GTKHTML)) +#define E_IS_WEB_VIEW_GTKHTML_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_WEB_VIEW_GTKHTML)) +#define E_WEB_VIEW_GTKHTML_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_WEB_VIEW_GTKHTML, EWebViewGtkHTMLClass)) + +G_BEGIN_DECLS + +typedef struct _EWebViewGtkHTML EWebViewGtkHTML; +typedef struct _EWebViewGtkHTMLClass EWebViewGtkHTMLClass; +typedef struct _EWebViewGtkHTMLPrivate EWebViewGtkHTMLPrivate; + +struct _EWebViewGtkHTML { + GtkHTML parent; + EWebViewGtkHTMLPrivate *priv; +}; + +struct _EWebViewGtkHTMLClass { + GtkHTMLClass parent_class; + + /* Methods */ + gchar * (*extract_uri) (EWebViewGtkHTML *web_view, + GdkEventButton *event, + GtkHTML *frame); + void (*hovering_over_link) (EWebViewGtkHTML *web_view, + const gchar *title, + const gchar *uri); + void (*link_clicked) (EWebViewGtkHTML *web_view, + const gchar *uri); + void (*load_string) (EWebViewGtkHTML *web_view, + const gchar *load_string); + + /* Signals */ + void (*copy_clipboard) (EWebViewGtkHTML *web_view); + void (*cut_clipboard) (EWebViewGtkHTML *web_view); + void (*paste_clipboard) (EWebViewGtkHTML *web_view); + gboolean (*popup_event) (EWebViewGtkHTML *web_view, + GdkEventButton *event, + const gchar *uri); + void (*status_message) (EWebViewGtkHTML *web_view, + const gchar *status_message); + void (*stop_loading) (EWebViewGtkHTML *web_view); + void (*update_actions) (EWebViewGtkHTML *web_view); + gboolean (*process_mailto) (EWebViewGtkHTML *web_view, + const gchar *mailto_uri); +}; + +GType e_web_view_gtkhtml_get_type (void); +GtkWidget * e_web_view_gtkhtml_new (void); +void e_web_view_gtkhtml_clear (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_load_string (EWebViewGtkHTML *web_view, + const gchar *string); +gboolean e_web_view_gtkhtml_get_animate (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_animate (EWebViewGtkHTML *web_view, + gboolean animate); +gboolean e_web_view_gtkhtml_get_caret_mode (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_caret_mode (EWebViewGtkHTML *web_view, + gboolean caret_mode); +GtkTargetList * e_web_view_gtkhtml_get_copy_target_list (EWebViewGtkHTML *web_view); +gboolean e_web_view_gtkhtml_get_disable_printing (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_disable_printing (EWebViewGtkHTML *web_view, + gboolean disable_printing); +gboolean e_web_view_gtkhtml_get_disable_save_to_disk + (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_disable_save_to_disk + (EWebViewGtkHTML *web_view, + gboolean disable_save_to_disk); +gboolean e_web_view_gtkhtml_get_editable (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_editable (EWebViewGtkHTML *web_view, + gboolean editable); +gboolean e_web_view_gtkhtml_get_inline_spelling (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_inline_spelling (EWebViewGtkHTML *web_view, + gboolean inline_spelling); +gboolean e_web_view_gtkhtml_get_magic_links (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_magic_links (EWebViewGtkHTML *web_view, + gboolean magic_links); +gboolean e_web_view_gtkhtml_get_magic_smileys (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_magic_smileys (EWebViewGtkHTML *web_view, + gboolean magic_smileys); +const gchar * e_web_view_gtkhtml_get_selected_uri (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_selected_uri (EWebViewGtkHTML *web_view, + const gchar *selected_uri); +GdkPixbufAnimation * + e_web_view_gtkhtml_get_cursor_image (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_cursor_image (EWebViewGtkHTML *web_view, + GdkPixbufAnimation *animation); +GtkAction * e_web_view_gtkhtml_get_open_proxy (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_open_proxy (EWebViewGtkHTML *web_view, + GtkAction *open_proxy); +GtkTargetList * e_web_view_gtkhtml_get_paste_target_list + (EWebViewGtkHTML *web_view); +GtkAction * e_web_view_gtkhtml_get_print_proxy (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_print_proxy (EWebViewGtkHTML *web_view, + GtkAction *print_proxy); +GtkAction * e_web_view_gtkhtml_get_save_as_proxy (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_set_save_as_proxy (EWebViewGtkHTML *web_view, + GtkAction *save_as_proxy); +GtkAction * e_web_view_gtkhtml_get_action (EWebViewGtkHTML *web_view, + const gchar *action_name); +GtkActionGroup *e_web_view_gtkhtml_get_action_group (EWebViewGtkHTML *web_view, + const gchar *group_name); +gchar * e_web_view_gtkhtml_extract_uri (EWebViewGtkHTML *web_view, + GdkEventButton *event, + GtkHTML *frame); +void e_web_view_gtkhtml_copy_clipboard (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_cut_clipboard (EWebViewGtkHTML *web_view); +gboolean e_web_view_gtkhtml_is_selection_active (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_paste_clipboard (EWebViewGtkHTML *web_view); +gboolean e_web_view_gtkhtml_scroll_forward (EWebViewGtkHTML *web_view); +gboolean e_web_view_gtkhtml_scroll_backward (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_select_all (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_unselect_all (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_zoom_100 (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_zoom_in (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_zoom_out (EWebViewGtkHTML *web_view); +GtkUIManager * e_web_view_gtkhtml_get_ui_manager (EWebViewGtkHTML *web_view); +GtkWidget * e_web_view_gtkhtml_get_popup_menu (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_show_popup_menu (EWebViewGtkHTML *web_view, + GdkEventButton *event, + GtkMenuPositionFunc func, + gpointer user_data); +void e_web_view_gtkhtml_status_message (EWebViewGtkHTML *web_view, + const gchar *status_message); +void e_web_view_gtkhtml_stop_loading (EWebViewGtkHTML *web_view); +void e_web_view_gtkhtml_update_actions (EWebViewGtkHTML *web_view); + +G_END_DECLS + +#endif /* E_WEB_VIEW_GTKHTML_H */ diff --git a/widgets/misc/e-web-view.c b/widgets/misc/e-web-view.c index 26c922470b..1fdc5559d9 100644 --- a/widgets/misc/e-web-view.c +++ b/widgets/misc/e-web-view.c @@ -22,6 +22,10 @@ #include "e-web-view.h" +#include <math.h> + +#include <JavaScriptCore/JavaScript.h> + #include <string.h> #include <glib/gi18n-lib.h> @@ -33,8 +37,11 @@ #include <libevolution-utils/e-alert-sink.h> #include <e-util/e-plugin-ui.h> +#include <mail/e-mail-request.h> + #include "e-popup-action.h" #include "e-selectable.h" +#include <stdlib.h> #define E_WEB_VIEW_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ @@ -49,51 +56,39 @@ struct _EWebViewPrivate { GdkPixbufAnimation *cursor_image; gchar *cursor_image_src; + GHashTable *js_callbacks; + + GSList *highlights; + GtkAction *open_proxy; GtkAction *print_proxy; GtkAction *save_as_proxy; - GtkTargetList *copy_target_list; - GtkTargetList *paste_target_list; - /* Lockdown Options */ guint disable_printing : 1; guint disable_save_to_disk : 1; -}; -struct _EWebViewRequest { - GFile *file; - EWebView *web_view; - GCancellable *cancellable; - GInputStream *input_stream; - GtkHTMLStream *output_stream; - gchar buffer[4096]; + guint caret_mode : 1; }; enum { PROP_0, - PROP_ANIMATE, PROP_CARET_MODE, PROP_COPY_TARGET_LIST, PROP_CURSOR_IMAGE, PROP_CURSOR_IMAGE_SRC, PROP_DISABLE_PRINTING, PROP_DISABLE_SAVE_TO_DISK, - PROP_EDITABLE, PROP_INLINE_SPELLING, PROP_MAGIC_LINKS, PROP_MAGIC_SMILEYS, PROP_OPEN_PROXY, - PROP_PASTE_TARGET_LIST, PROP_PRINT_PROXY, PROP_SAVE_AS_PROXY, PROP_SELECTED_URI }; enum { - COPY_CLIPBOARD, - CUT_CLIPBOARD, - PASTE_CLIPBOARD, POPUP_EVENT, STATUS_MESSAGE, STOP_LOADING, @@ -125,6 +120,7 @@ static const gchar *ui = " <placeholder name='custom-actions-3'/>" " <separator/>" " <menuitem action='select-all'/>" +" <placeholder name='inspect-menu' />" " </popup>" "</ui>"; @@ -135,7 +131,7 @@ static void e_web_view_selectable_init (ESelectableInterface *interface); G_DEFINE_TYPE_WITH_CODE ( EWebView, e_web_view, - GTK_TYPE_HTML, + WEBKIT_TYPE_WEB_VIEW, G_IMPLEMENT_INTERFACE ( E_TYPE_EXTENSIBLE, NULL) G_IMPLEMENT_INTERFACE ( @@ -145,141 +141,6 @@ G_DEFINE_TYPE_WITH_CODE ( E_TYPE_SELECTABLE, e_web_view_selectable_init)) -static EWebViewRequest * -web_view_request_new (EWebView *web_view, - const gchar *uri, - GtkHTMLStream *stream) -{ - EWebViewRequest *request; - GList *list; - - request = g_slice_new (EWebViewRequest); - - /* Try to detect file paths posing as URIs. */ - if (*uri == '/') - request->file = g_file_new_for_path (uri); - else - request->file = g_file_new_for_uri (uri); - - request->web_view = g_object_ref (web_view); - request->cancellable = g_cancellable_new (); - request->input_stream = NULL; - request->output_stream = stream; - - list = request->web_view->priv->requests; - list = g_list_prepend (list, request); - request->web_view->priv->requests = list; - - return request; -} - -static void -web_view_request_free (EWebViewRequest *request) -{ - GList *list; - - list = request->web_view->priv->requests; - list = g_list_remove (list, request); - request->web_view->priv->requests = list; - - g_object_unref (request->file); - g_object_unref (request->web_view); - g_object_unref (request->cancellable); - - if (request->input_stream != NULL) - g_object_unref (request->input_stream); - - g_slice_free (EWebViewRequest, request); -} - -static void -web_view_request_cancel (EWebViewRequest *request) -{ - g_cancellable_cancel (request->cancellable); -} - -static gboolean -web_view_request_check_for_error (EWebViewRequest *request, - GError *error) -{ - GtkHTML *html; - GtkHTMLStream *stream; - - if (error == NULL) - return FALSE; - - if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { - /* use this error, but do not close the stream */ - g_error_free (error); - return TRUE; - } - - /* XXX Should we log errors that are not cancellations? */ - - html = GTK_HTML (request->web_view); - stream = request->output_stream; - - gtk_html_end (html, stream, GTK_HTML_STREAM_ERROR); - web_view_request_free (request); - g_error_free (error); - - return TRUE; -} - -static void -web_view_request_stream_read_cb (GInputStream *input_stream, - GAsyncResult *result, - EWebViewRequest *request) -{ - gssize bytes_read; - GError *error = NULL; - - bytes_read = g_input_stream_read_finish (input_stream, result, &error); - - if (web_view_request_check_for_error (request, error)) - return; - - if (bytes_read == 0) { - gtk_html_end ( - GTK_HTML (request->web_view), - request->output_stream, GTK_HTML_STREAM_OK); - web_view_request_free (request); - return; - } - - gtk_html_write ( - GTK_HTML (request->web_view), - request->output_stream, request->buffer, bytes_read); - - g_input_stream_read_async ( - request->input_stream, request->buffer, - sizeof (request->buffer), G_PRIORITY_DEFAULT, - request->cancellable, (GAsyncReadyCallback) - web_view_request_stream_read_cb, request); -} - -static void -web_view_request_read_cb (GFile *file, - GAsyncResult *result, - EWebViewRequest *request) -{ - GFileInputStream *input_stream; - GError *error = NULL; - - /* Input stream might be NULL, so don't use cast macro. */ - input_stream = g_file_read_finish (file, result, &error); - request->input_stream = (GInputStream *) input_stream; - - if (web_view_request_check_for_error (request, error)) - return; - - g_input_stream_read_async ( - request->input_stream, request->buffer, - sizeof (request->buffer), G_PRIORITY_DEFAULT, - request->cancellable, (GAsyncReadyCallback) - web_view_request_stream_read_cb, request); -} - static void action_copy_clipboard_cb (GtkAction *action, EWebView *web_view) @@ -470,71 +331,149 @@ static GtkActionEntry standard_entries[] = { G_CALLBACK (action_select_all_cb) } }; -static gboolean -web_view_button_press_event_cb (EWebView *web_view, - GdkEventButton *event, - GtkHTML *frame) +static void +web_view_menu_item_select_cb (EWebView *web_view, + GtkWidget *widget) { - gboolean event_handled = FALSE; - gchar *uri = NULL; + GtkAction *action; + GtkActivatable *activatable; + const gchar *tooltip; - if (event) { - GdkPixbufAnimation *anim; - gchar *image_src; + activatable = GTK_ACTIVATABLE (widget); + action = gtk_activatable_get_related_action (activatable); + tooltip = gtk_action_get_tooltip (action); - if (frame == NULL) - frame = GTK_HTML (web_view); + if (tooltip == NULL) + return; - anim = gtk_html_get_image_at (frame, event->x, event->y); - e_web_view_set_cursor_image (web_view, anim); - if (anim != NULL) - g_object_unref (anim); + e_web_view_status_message (web_view, tooltip); +} - image_src = gtk_html_get_image_src_at ( - frame, event->x, event->y); - e_web_view_set_cursor_image_src (web_view, image_src); - g_free (image_src); - } +static void +replace_text (WebKitDOMNode *node, + const gchar *text, + WebKitDOMNode *replacement) +{ + /* NodeType 3 = TEXT_NODE */ + if (webkit_dom_node_get_node_type (node) == 3) { + + gint text_length = strlen (text); + + while (node) { + + WebKitDOMNode *current_node, *replacement_node; + const gchar *node_data, *offset; + goffset split_offset; + gint data_length; + + current_node = node; + + /* Don't use the WEBKIT_DOM_CHARACTER_DATA macro for + * casting. WebKit lies about type of the object and + * GLib will throw runtime warning about node not being + * WebKitDOMCharacterData, but the function will return + * correct and valid data. + * IMO it's bug in the Gtk bindings and WebKit internally + * handles it by the nodeType so therefor it works + * event for "invalid" objects. But really, who knows..? + */ + node_data = webkit_dom_character_data_get_data ( + (WebKitDOMCharacterData *) node); + + offset = strstr (node_data, text); + if (!offset) { + node = NULL; + continue; + } + + split_offset = offset - node_data + text_length; + replacement_node = + webkit_dom_node_clone_node (replacement, TRUE); + + data_length = webkit_dom_character_data_get_length ( + (WebKitDOMCharacterData *) node); + if (split_offset < data_length) { + + WebKitDOMNode *parent_node; + + node = WEBKIT_DOM_NODE ( + webkit_dom_text_split_text ( + (WebKitDOMText *) node, + offset - node_data + text_length, + NULL)); + parent_node = webkit_dom_node_get_parent_node (node); + webkit_dom_node_insert_before ( + parent_node, replacement_node, + node, NULL); + + } else { + WebKitDOMNode *parent_node; + + parent_node = webkit_dom_node_get_parent_node (node); + webkit_dom_node_append_child ( + parent_node, + replacement_node, NULL); + } + + webkit_dom_character_data_delete_data ( + (WebKitDOMCharacterData *) (current_node), + offset - node_data, text_length, NULL); + } - if (event != NULL && event->button != 3) - return FALSE; + } else { - /* Only extract a URI if no selection is active. Selected text - * implies the user is more likely to want to copy the selection - * to the clipboard than open a link within the selection. */ - if (!e_web_view_is_selection_active (web_view)) - uri = e_web_view_extract_uri (web_view, event, frame); + WebKitDOMNode *child, *next_child; - if (uri != NULL && g_str_has_prefix (uri, "##")) { - g_free (uri); - return FALSE; - } + /* Iframe? Let's traverse inside! */ + if (WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (node)) { - g_signal_emit ( - web_view, signals[POPUP_EVENT], 0, - event, uri, &event_handled); + WebKitDOMDocument *frame_document; - g_free (uri); + frame_document = + webkit_dom_html_iframe_element_get_content_document ( + WEBKIT_DOM_HTML_IFRAME_ELEMENT (node)); + replace_text (WEBKIT_DOM_NODE (frame_document), + text, replacement); + + } else { + + child = webkit_dom_node_get_first_child (node); + while (child) { + next_child = webkit_dom_node_get_next_sibling (child); + replace_text (child, text, replacement); + child = next_child; + } + } + } - return event_handled; } static void -web_view_menu_item_select_cb (EWebView *web_view, - GtkWidget *widget) +web_view_update_document_highlights (EWebView *web_view) { - GtkAction *action; - GtkActivatable *activatable; - const gchar *tooltip; + WebKitDOMDocument *document; + GSList *iter; - activatable = GTK_ACTIVATABLE (widget); - action = gtk_activatable_get_related_action (activatable); - tooltip = gtk_action_get_tooltip (action); + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (web_view)); - if (tooltip == NULL) - return; + for (iter = web_view->priv->highlights; iter; iter = iter->next) { - e_web_view_status_message (web_view, tooltip); + WebKitDOMDocumentFragment *frag; + WebKitDOMElement *span; + + span = webkit_dom_document_create_element (document, "span", NULL); + webkit_dom_html_element_set_class_name ( + WEBKIT_DOM_HTML_ELEMENT (span), "__evo-highlight"); + webkit_dom_html_element_set_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (span), iter->data, NULL); + + frag = webkit_dom_document_create_document_fragment (document); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (frag), WEBKIT_DOM_NODE (span), NULL); + + replace_text (WEBKIT_DOM_NODE (document), + iter->data, WEBKIT_DOM_NODE (frag)); + } } static void @@ -560,6 +499,86 @@ web_view_connect_proxy_cb (EWebView *web_view, G_CALLBACK (web_view_menu_item_deselect_cb), web_view); } +static GtkWidget * +web_view_create_plugin_widget_cb (EWebView *web_view, + const gchar *mime_type, + const gchar *uri, + GHashTable *param) +{ + EWebViewClass *class; + + /* XXX WebKitWebView does not provide a class method for + * this signal, so we do so we can override the default + * behavior from subclasses for special URI types. */ + + class = E_WEB_VIEW_GET_CLASS (web_view); + g_return_val_if_fail (class->create_plugin_widget != NULL, NULL); + + return class->create_plugin_widget (web_view, mime_type, uri, param); +} + +static void +web_view_hovering_over_link_cb (EWebView *web_view, + const gchar *title, + const gchar *uri) +{ + EWebViewClass *class; + + /* XXX WebKitWebView does not provide a class method for + * this signal, so we do so we can override the default + * behavior from subclasses for special URI types. */ + + class = E_WEB_VIEW_GET_CLASS (web_view); + g_return_if_fail (class->hovering_over_link != NULL); + + class->hovering_over_link (web_view, title, uri); +} + +static gboolean +web_view_navigation_policy_decision_requested_cb (EWebView *web_view, + WebKitWebFrame *frame, + WebKitNetworkRequest *request, + WebKitWebNavigationAction *navigation_action, + WebKitWebPolicyDecision *policy_decision) +{ + EWebViewClass *class; + WebKitWebNavigationReason reason; + const gchar *uri; + + reason = webkit_web_navigation_action_get_reason (navigation_action); + if (reason != WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) + return FALSE; + + /* XXX WebKitWebView does not provide a class method for + * this signal, so we do so we can override the default + * behavior from subclasses for special URI types. */ + + class = E_WEB_VIEW_GET_CLASS (web_view); + g_return_val_if_fail (class->link_clicked != NULL, FALSE); + + webkit_web_policy_decision_ignore (policy_decision); + + uri = webkit_network_request_get_uri (request); + + class->link_clicked (web_view, uri); + + return TRUE; +} + +static void +web_view_load_status_changed_cb (WebKitWebView *web_view, + GParamSpec *pspec, + gpointer user_data) +{ + WebKitLoadStatus status; + + status = webkit_web_view_get_load_status (web_view); + if (status != WEBKIT_LOAD_FINISHED) + return; + + web_view_update_document_highlights (E_WEB_VIEW (web_view)); +} + static void web_view_set_property (GObject *object, guint property_id, @@ -567,12 +586,6 @@ web_view_set_property (GObject *object, GParamSpec *pspec) { switch (property_id) { - case PROP_ANIMATE: - e_web_view_set_animate ( - E_WEB_VIEW (object), - g_value_get_boolean (value)); - return; - case PROP_CARET_MODE: e_web_view_set_caret_mode ( E_WEB_VIEW (object), @@ -603,12 +616,6 @@ web_view_set_property (GObject *object, g_value_get_boolean (value)); return; - case PROP_EDITABLE: - e_web_view_set_editable ( - E_WEB_VIEW (object), - g_value_get_boolean (value)); - return; - case PROP_INLINE_SPELLING: e_web_view_set_inline_spelling ( E_WEB_VIEW (object), @@ -662,24 +669,12 @@ web_view_get_property (GObject *object, GParamSpec *pspec) { switch (property_id) { - case PROP_ANIMATE: - g_value_set_boolean ( - value, e_web_view_get_animate ( - E_WEB_VIEW (object))); - return; - case PROP_CARET_MODE: g_value_set_boolean ( value, e_web_view_get_caret_mode ( E_WEB_VIEW (object))); return; - case PROP_COPY_TARGET_LIST: - g_value_set_boxed ( - value, e_web_view_get_copy_target_list ( - E_WEB_VIEW (object))); - return; - case PROP_CURSOR_IMAGE: g_value_set_object ( value, e_web_view_get_cursor_image ( @@ -704,12 +699,6 @@ web_view_get_property (GObject *object, E_WEB_VIEW (object))); return; - case PROP_EDITABLE: - g_value_set_boolean ( - value, e_web_view_get_editable ( - E_WEB_VIEW (object))); - return; - case PROP_INLINE_SPELLING: g_value_set_boolean ( value, e_web_view_get_inline_spelling ( @@ -734,12 +723,6 @@ web_view_get_property (GObject *object, E_WEB_VIEW (object))); return; - case PROP_PASTE_TARGET_LIST: - g_value_set_boxed ( - value, e_web_view_get_paste_target_list ( - E_WEB_VIEW (object))); - return; - case PROP_PRINT_PROXY: g_value_set_object ( value, e_web_view_get_print_proxy ( @@ -789,16 +772,6 @@ web_view_dispose (GObject *object) priv->save_as_proxy = NULL; } - if (priv->copy_target_list != NULL) { - gtk_target_list_unref (priv->copy_target_list); - priv->copy_target_list = NULL; - } - - if (priv->paste_target_list != NULL) { - gtk_target_list_unref (priv->paste_target_list); - priv->paste_target_list = NULL; - } - if (priv->cursor_image != NULL) { g_object_unref (priv->cursor_image); priv->cursor_image = NULL; @@ -809,6 +782,16 @@ web_view_dispose (GObject *object) priv->cursor_image_src = NULL; } + if (priv->js_callbacks != NULL) { + g_hash_table_destroy (priv->js_callbacks); + priv->js_callbacks = NULL; + } + + if (priv->highlights != NULL) { + g_slist_free_full (priv->highlights, g_free); + priv->highlights = NULL; + } + /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (parent_class)->dispose (object); } @@ -861,12 +844,103 @@ web_view_button_press_event (GtkWidget *widget, { GtkWidgetClass *widget_class; EWebView *web_view; + gboolean event_handled = FALSE; + gchar *uri; web_view = E_WEB_VIEW (widget); - if (web_view_button_press_event_cb (web_view, event, NULL)) + if (event) { + WebKitHitTestResult *test; + WebKitHitTestResultContext context; + + if (web_view->priv->cursor_image) { + g_object_unref (web_view->priv->cursor_image); + web_view->priv->cursor_image = NULL; + } + + if (web_view->priv->cursor_image_src) { + g_free (web_view->priv->cursor_image_src); + web_view->priv->cursor_image_src = NULL; + } + + test = webkit_web_view_get_hit_test_result (WEBKIT_WEB_VIEW (web_view), event); + + if (!test) + goto chainup; + + g_object_get (G_OBJECT (test), "context", &context, NULL); + if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) { + WebKitWebDataSource *data_source; + WebKitWebFrame *frame; + GList *subresources, *res; + + g_object_get (G_OBJECT (test), "image-uri", &uri, NULL); + + if (!uri) + goto chainup; + + if (web_view->priv->cursor_image_src) + g_free (web_view->priv->cursor_image_src); + web_view->priv->cursor_image_src = uri; + + /* Iterate through all resources of the loaded webpage and + try to find resource with URI matching cursor_image_src */ + frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view)); + data_source = webkit_web_frame_get_data_source (frame); + subresources = webkit_web_data_source_get_subresources (data_source); + for (res = subresources; res; res = res->next) { + WebKitWebResource *src = res->data; + GdkPixbufLoader *loader; + GString *data; + + if (g_strcmp0 (webkit_web_resource_get_uri (src), + web_view->priv->cursor_image_src) != 0) + continue; + + data = webkit_web_resource_get_data (src); + if (!data) + break; + + loader = gdk_pixbuf_loader_new (); + if (!gdk_pixbuf_loader_write (loader, + (guchar *) data->str, data->len, NULL)) { + g_object_unref (loader); + break; + } + gdk_pixbuf_loader_close (loader, NULL); + + if (web_view->priv->cursor_image) + g_object_unref (web_view->priv->cursor_image); + + web_view->priv->cursor_image = + g_object_ref (gdk_pixbuf_loader_get_animation (loader)); + + g_object_unref (loader); + break; + } + } + } + + if (event != NULL && event->button != 3) + goto chainup; + + uri = e_web_view_extract_uri (web_view, event); + + if (uri != NULL && g_str_has_prefix (uri, "##")) { + g_free (uri); + goto chainup; + } + + g_signal_emit ( + web_view, signals[POPUP_EVENT], 0, + event, uri, &event_handled); + + g_free (uri); + + if (event_handled) return TRUE; +chainup: /* Chain up to parent's button_press_event() method. */ widget_class = GTK_WIDGET_CLASS (parent_class); return widget_class->button_press_event (widget, event); @@ -879,10 +953,10 @@ web_view_scroll_event (GtkWidget *widget, if (event->state & GDK_CONTROL_MASK) { switch (event->direction) { case GDK_SCROLL_UP: - gtk_html_zoom_in (GTK_HTML (widget)); + e_web_view_zoom_in (E_WEB_VIEW (widget)); return TRUE; case GDK_SCROLL_DOWN: - gtk_html_zoom_out (GTK_HTML (widget)); + e_web_view_zoom_out (E_WEB_VIEW (widget)); return TRUE; default: break; @@ -892,74 +966,71 @@ web_view_scroll_event (GtkWidget *widget, return FALSE; } -static void -web_view_url_requested (GtkHTML *html, - const gchar *uri, - GtkHTMLStream *stream) +static GtkWidget * +web_view_create_plugin_widget (EWebView *web_view, + const gchar *mime_type, + const gchar *uri, + GHashTable *param) { - EWebViewRequest *request; + GtkWidget *widget = NULL; - request = web_view_request_new (E_WEB_VIEW (html), uri, stream); + if (g_strcmp0 (mime_type, "image/x-themed-icon") == 0) { + GtkIconTheme *icon_theme; + GdkPixbuf *pixbuf; + gpointer data; + glong size = 0; + GError *error = NULL; - g_file_read_async ( - request->file, G_PRIORITY_DEFAULT, - request->cancellable, (GAsyncReadyCallback) - web_view_request_read_cb, request); -} + icon_theme = gtk_icon_theme_get_default (); -static void -web_view_gtkhtml_link_clicked (GtkHTML *html, - const gchar *uri) -{ - EWebViewClass *class; - EWebView *web_view; + if (size == 0) { + data = g_hash_table_lookup (param, "width"); + if (data != NULL) + size = MAX (size, strtol (data, NULL, 10)); + } - web_view = E_WEB_VIEW (html); + if (size == 0) { + data = g_hash_table_lookup (param, "height"); + if (data != NULL) + size = MAX (size, strtol (data, NULL, 10)); + } - class = E_WEB_VIEW_GET_CLASS (web_view); - g_return_if_fail (class->link_clicked != NULL); + if (size == 0) + size = 32; /* arbitrary default */ + + pixbuf = gtk_icon_theme_load_icon ( + icon_theme, uri, size, 0, &error); + if (pixbuf != NULL) { + widget = gtk_image_new_from_pixbuf (pixbuf); + g_object_unref (pixbuf); + } else if (error != NULL) { + g_warning ("%s", error->message); + g_error_free (error); + } + } - class->link_clicked (web_view, uri); + return widget; } -static void -web_view_on_url (GtkHTML *html, - const gchar *uri) +static gchar * +web_view_extract_uri (EWebView *web_view, + GdkEventButton *event) { - EWebViewClass *class; - EWebView *web_view; - - web_view = E_WEB_VIEW (html); - - class = E_WEB_VIEW_GET_CLASS (web_view); - g_return_if_fail (class->hovering_over_link != NULL); + WebKitHitTestResult *result; + WebKitHitTestResultContext context; + gchar *uri = NULL; - /* XXX WebKit would supply a title here. */ - class->hovering_over_link (web_view, NULL, uri); -} + result = webkit_web_view_get_hit_test_result ( + WEBKIT_WEB_VIEW (web_view), event); -static void -web_view_iframe_created (GtkHTML *html, - GtkHTML *iframe) -{ - g_signal_connect_swapped ( - iframe, "button-press-event", - G_CALLBACK (web_view_button_press_event_cb), html); -} + g_object_get (result, "context", &context, "link-uri", &uri, NULL); -static gchar * -web_view_extract_uri (EWebView *web_view, - GdkEventButton *event, - GtkHTML *html) -{ - gchar *uri; + if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) + return uri; - if (event != NULL) - uri = gtk_html_get_url_at (html, event->x, event->y); - else - uri = gtk_html_get_cursor_url (html); + g_free (uri); - return uri; + return NULL; } static void @@ -1030,30 +1101,62 @@ static void web_view_load_string (EWebView *web_view, const gchar *string) { - if (string != NULL && *string != '\0') - gtk_html_load_from_string (GTK_HTML (web_view), string, -1); - else - e_web_view_clear (web_view); + if (string == NULL) + string = ""; + + webkit_web_view_load_string ( + WEBKIT_WEB_VIEW (web_view), + string, "text/html", "UTF-8", "file://"); } static void -web_view_copy_clipboard (EWebView *web_view) +web_view_load_uri (EWebView *web_view, + const gchar *uri) { - gtk_html_command (GTK_HTML (web_view), "copy"); + if (uri == NULL) + uri = "about:blank"; + + webkit_web_view_load_uri ( + WEBKIT_WEB_VIEW (web_view), uri); } static void -web_view_cut_clipboard (EWebView *web_view) +web_view_frame_load_string (EWebView *web_view, + const gchar *frame_name, + const gchar *string) { - if (e_web_view_get_editable (web_view)) - gtk_html_command (GTK_HTML (web_view), "cut"); + WebKitWebFrame *main_frame, *frame; + + if (string == NULL) + string = ""; + + main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view)); + if (main_frame) { + frame = webkit_web_frame_find_frame (main_frame, frame_name); + + if (frame) + webkit_web_frame_load_string ( + frame, string, "text/html", "UTF-8", "file://"); + } } static void -web_view_paste_clipboard (EWebView *web_view) +web_view_frame_load_uri (EWebView *web_view, + const gchar *frame_name, + const gchar *uri) { - if (e_web_view_get_editable (web_view)) - gtk_html_command (GTK_HTML (web_view), "paste"); + WebKitWebFrame *main_frame, *frame; + + if (uri == NULL) + uri = "about:blank"; + + main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view)); + if (main_frame) { + frame = webkit_web_frame_find_frame (main_frame, frame_name); + + if (frame) + webkit_web_frame_load_uri (frame, uri); + } } static gboolean @@ -1070,11 +1173,7 @@ web_view_popup_event (EWebView *web_view, static void web_view_stop_loading (EWebView *web_view) { - g_list_foreach ( - web_view->priv->requests, (GFunc) - web_view_request_cancel, NULL); - - gtk_html_stop (GTK_HTML (web_view)); + webkit_web_view_stop_loading (WEBKIT_WEB_VIEW (web_view)); } static void @@ -1267,48 +1366,27 @@ web_view_selectable_update_actions (ESelectable *selectable, GdkAtom *clipboard_targets, gint n_clipboard_targets) { - EWebView *web_view; + WebKitWebView *web_view; GtkAction *action; - /*GtkTargetList *target_list;*/ - gboolean can_paste = FALSE; - gboolean editable; - gboolean have_selection; gboolean sensitive; const gchar *tooltip; - /*gint ii;*/ - web_view = E_WEB_VIEW (selectable); - editable = e_web_view_get_editable (web_view); - have_selection = e_web_view_is_selection_active (web_view); - - /* XXX GtkHtml implements its own clipboard instead of using - * GDK_SELECTION_CLIPBOARD, so we don't get notifications - * when the clipboard contents change. The logic below - * is what we would do if GtkHtml worked properly. - * Instead, we need to keep the Paste action sensitive so - * its accelerator overrides GtkHtml's key binding. */ -#if 0 - target_list = e_selectable_get_paste_target_list (selectable); - for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++) - can_paste = gtk_target_list_find ( - target_list, clipboard_targets[ii], NULL); -#endif - can_paste = TRUE; + web_view = WEBKIT_WEB_VIEW (selectable); action = e_focus_tracker_get_cut_clipboard_action (focus_tracker); - sensitive = editable && have_selection; + sensitive = webkit_web_view_can_cut_clipboard (web_view); tooltip = _("Cut the selection"); gtk_action_set_sensitive (action, sensitive); gtk_action_set_tooltip (action, tooltip); action = e_focus_tracker_get_copy_clipboard_action (focus_tracker); - sensitive = have_selection; + sensitive = webkit_web_view_can_copy_clipboard (web_view); tooltip = _("Copy the selection"); gtk_action_set_sensitive (action, sensitive); gtk_action_set_tooltip (action, tooltip); action = e_focus_tracker_get_paste_clipboard_action (focus_tracker); - sensitive = editable && can_paste; + sensitive = webkit_web_view_can_paste_clipboard (web_view); tooltip = _("Paste the clipboard"); gtk_action_set_sensitive (action, sensitive); gtk_action_set_tooltip (action, tooltip); @@ -1349,7 +1427,9 @@ e_web_view_class_init (EWebViewClass *class) { GObjectClass *object_class; GtkWidgetClass *widget_class; +#if 0 /* WEBKIT */ GtkHTMLClass *html_class; +#endif parent_class = g_type_class_peek_parent (class); g_type_class_add_private (class, sizeof (EWebViewPrivate)); @@ -1365,35 +1445,25 @@ e_web_view_class_init (EWebViewClass *class) widget_class->button_press_event = web_view_button_press_event; widget_class->scroll_event = web_view_scroll_event; +#if 0 /* WEBKIT */ html_class = GTK_HTML_CLASS (class); html_class->url_requested = web_view_url_requested; - html_class->link_clicked = web_view_gtkhtml_link_clicked; - html_class->on_url = web_view_on_url; - html_class->iframe_created = web_view_iframe_created; +#endif + class->create_plugin_widget = web_view_create_plugin_widget; class->extract_uri = web_view_extract_uri; class->hovering_over_link = web_view_hovering_over_link; class->link_clicked = web_view_link_clicked; class->load_string = web_view_load_string; - class->copy_clipboard = web_view_copy_clipboard; - class->cut_clipboard = web_view_cut_clipboard; - class->paste_clipboard = web_view_paste_clipboard; + class->load_uri = web_view_load_uri; + class->frame_load_string = web_view_frame_load_string; + class->frame_load_uri = web_view_frame_load_uri; class->popup_event = web_view_popup_event; class->stop_loading = web_view_stop_loading; class->update_actions = web_view_update_actions; g_object_class_install_property ( object_class, - PROP_ANIMATE, - g_param_spec_boolean ( - "animate", - "Animate Images", - NULL, - FALSE, - G_PARAM_READWRITE)); - - g_object_class_install_property ( - object_class, PROP_CARET_MODE, g_param_spec_boolean ( "caret-mode", @@ -1402,12 +1472,6 @@ e_web_view_class_init (EWebViewClass *class) FALSE, G_PARAM_READWRITE)); - /* Inherited from ESelectableInterface */ - g_object_class_override_property ( - object_class, - PROP_COPY_TARGET_LIST, - "copy-target-list"); - g_object_class_install_property ( object_class, PROP_CURSOR_IMAGE, @@ -1452,16 +1516,6 @@ e_web_view_class_init (EWebViewClass *class) g_object_class_install_property ( object_class, - PROP_EDITABLE, - g_param_spec_boolean ( - "editable", - "Editable", - NULL, - FALSE, - G_PARAM_READWRITE)); - - g_object_class_install_property ( - object_class, PROP_INLINE_SPELLING, g_param_spec_boolean ( "inline-spelling", @@ -1500,12 +1554,6 @@ e_web_view_class_init (EWebViewClass *class) GTK_TYPE_ACTION, G_PARAM_READWRITE)); - /* Inherited from ESelectableInterface */ - g_object_class_override_property ( - object_class, - PROP_PASTE_TARGET_LIST, - "paste-target-list"); - g_object_class_install_property ( object_class, PROP_PRINT_PROXY, @@ -1536,33 +1584,6 @@ e_web_view_class_init (EWebViewClass *class) NULL, G_PARAM_READWRITE)); - signals[COPY_CLIPBOARD] = g_signal_new ( - "copy-clipboard", - G_TYPE_FROM_CLASS (class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (EWebViewClass, copy_clipboard), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - signals[CUT_CLIPBOARD] = g_signal_new ( - "cut-clipboard", - G_TYPE_FROM_CLASS (class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (EWebViewClass, cut_clipboard), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - signals[PASTE_CLIPBOARD] = g_signal_new ( - "paste-clipboard", - G_TYPE_FROM_CLASS (class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (EWebViewClass, paste_clipboard), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - signals[POPUP_EVENT] = g_signal_new ( "popup-event", G_TYPE_FROM_CLASS (class), @@ -1634,26 +1655,46 @@ e_web_view_init (EWebView *web_view) { GtkUIManager *ui_manager; GtkActionGroup *action_group; - GtkTargetList *target_list; EPopupAction *popup_action; + WebKitWebSettings *web_settings; const gchar *domain = GETTEXT_PACKAGE; const gchar *id; GError *error = NULL; web_view->priv = E_WEB_VIEW_GET_PRIVATE (web_view); + web_view->priv->highlights = NULL; + + g_signal_connect ( + web_view, "create-plugin-widget", + G_CALLBACK (web_view_create_plugin_widget_cb), NULL); + + g_signal_connect ( + web_view, "hovering-over-link", + G_CALLBACK (web_view_hovering_over_link_cb), NULL); + + g_signal_connect ( + web_view, "navigation-policy-decision-requested", + G_CALLBACK (web_view_navigation_policy_decision_requested_cb), + NULL); + + g_signal_connect ( + web_view, "notify::load-status", + G_CALLBACK (web_view_load_status_changed_cb), NULL); + ui_manager = gtk_ui_manager_new (); web_view->priv->ui_manager = ui_manager; + web_view->priv->js_callbacks = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, NULL); + g_signal_connect_swapped ( ui_manager, "connect-proxy", G_CALLBACK (web_view_connect_proxy_cb), web_view); - target_list = gtk_target_list_new (NULL, 0); - web_view->priv->copy_target_list = target_list; - - target_list = gtk_target_list_new (NULL, 0); - web_view->priv->paste_target_list = target_list; + web_settings = e_web_view_get_default_settings (GTK_WIDGET (web_view)); + e_web_view_set_settings (web_view, web_settings); + g_object_unref (web_settings); action_group = gtk_action_group_new ("uri"); gtk_action_group_set_translation_domain (action_group, domain); @@ -1763,6 +1804,7 @@ e_web_view_init (EWebView *web_view) e_plugin_ui_enable_manager (ui_manager, id); e_extensible_load_extensions (E_EXTENSIBLE (web_view)); + } GtkWidget * @@ -1776,7 +1818,7 @@ e_web_view_clear (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); - gtk_html_load_empty (GTK_HTML (web_view)); + e_web_view_load_string (web_view, NULL); } void @@ -1793,54 +1835,316 @@ e_web_view_load_string (EWebView *web_view, class->load_string (web_view, string); } -gboolean -e_web_view_get_animate (EWebView *web_view) +void +e_web_view_load_uri (EWebView *web_view, + const gchar *uri) { - /* XXX This is just here to maintain symmetry - * with e_web_view_set_animate(). */ + EWebViewClass *class; - g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); + g_return_if_fail (E_IS_WEB_VIEW (web_view)); - return gtk_html_get_animate (GTK_HTML (web_view)); + class = E_WEB_VIEW_GET_CLASS (web_view); + g_return_if_fail (class->load_uri != NULL); + + class->load_uri (web_view, uri); } void -e_web_view_set_animate (EWebView *web_view, - gboolean animate) +e_web_view_reload (EWebView *web_view) { - /* XXX GtkHTML does not utilize GObject properties as well - * as it could. This just wraps gtk_html_set_animate() - * so we can get a "notify::animate" signal. */ + g_return_if_fail (E_IS_WEB_VIEW (web_view)); + + webkit_web_view_reload (WEBKIT_WEB_VIEW (web_view)); +} + +const gchar * +e_web_view_get_uri (EWebView *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); + + return webkit_web_view_get_uri (WEBKIT_WEB_VIEW (web_view)); +} + +void +e_web_view_frame_load_string (EWebView *web_view, + const gchar *frame_name, + const gchar *string) +{ + EWebViewClass *class; + + g_return_if_fail (E_IS_WEB_VIEW (web_view)); + g_return_if_fail (frame_name != NULL); + + class = E_WEB_VIEW_GET_CLASS (web_view); + g_return_if_fail (class->frame_load_string != NULL); + + class->frame_load_string (web_view, frame_name, string); +} + +void +e_web_view_frame_load_uri (EWebView *web_view, + const gchar *frame_name, + const gchar *uri) +{ + EWebViewClass *class; + + g_return_if_fail (E_IS_WEB_VIEW (web_view)); + g_return_if_fail (frame_name != NULL); + + class = E_WEB_VIEW_GET_CLASS (web_view); + g_return_if_fail (class->frame_load_uri != NULL); + + class->frame_load_uri (web_view, frame_name, uri); +} + +const gchar * +e_web_view_frame_get_uri (EWebView *web_view, + const gchar *frame_name) +{ + WebKitWebFrame *main_frame, *frame; + + g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); + g_return_val_if_fail (frame_name != NULL, NULL); + + main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view)); + if (main_frame) { + frame = webkit_web_frame_find_frame (main_frame, frame_name); + + if (frame) + return webkit_web_frame_get_uri (frame); + } + + return NULL; +} + +gchar * +e_web_view_get_html (EWebView *web_view) +{ + GValue html = {0}; + + g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); + + if (e_web_view_exec_script (web_view, "return document.documentElement.innerHTML;", &html) == G_TYPE_STRING) + return g_strdup (g_value_get_string (&html)); + else + return NULL; +} + +JSGlobalContextRef +e_web_view_get_global_context (EWebView *web_view) +{ + WebKitWebFrame *main_frame; + + g_return_val_if_fail (E_IS_WEB_VIEW (web_view), 0); + + main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view)); + return webkit_web_frame_get_global_context (main_frame); +} + +GType +e_web_view_exec_script (EWebView *web_view, + const gchar *script, + GValue *value) +{ + WebKitWebFrame *main_frame; + + g_return_val_if_fail (E_IS_WEB_VIEW (web_view), G_TYPE_INVALID); + g_return_val_if_fail (script != NULL, G_TYPE_INVALID); + + main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view)); + + return e_web_view_frame_exec_script (web_view, + webkit_web_frame_get_name (main_frame), + script, value); +} + +GType +e_web_view_frame_exec_script (EWebView *web_view, + const gchar *frame_name, + const gchar *script, + GValue *value) +{ + WebKitWebFrame *main_frame, *frame; + JSGlobalContextRef context; + JSValueRef js_value, error = NULL; + JSType js_type; + JSStringRef js_script; + JSStringRef js_str; + size_t str_len; + gchar *str; + + g_return_val_if_fail (E_IS_WEB_VIEW (web_view), G_TYPE_INVALID); + g_return_val_if_fail (script != NULL, G_TYPE_INVALID); + + main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view)); + frame = webkit_web_frame_find_frame (main_frame, frame_name); + + context = webkit_web_frame_get_global_context (frame); + + js_script = JSStringCreateWithUTF8CString (script); + js_value = JSEvaluateScript (context, js_script, NULL, NULL, 0, &error); + JSStringRelease (js_script); + + if (error) { + gchar *msg; + js_str = JSValueToStringCopy (context, error, NULL); + str_len = JSStringGetLength (js_str); + + msg = g_malloc (str_len + 1); + JSStringGetUTF8CString (js_str, msg, str_len + 1); + JSStringRelease (js_str); + + g_message ("JavaScript Execution Failed: %s", msg); + g_free (msg); + + return G_TYPE_INVALID; + } + + if (!value) + return G_TYPE_NONE; + + js_type = JSValueGetType (context, js_value); + switch (js_type) { + case kJSTypeBoolean: + g_value_init (value, G_TYPE_BOOLEAN); + g_value_set_boolean (value, JSValueToBoolean (context, js_value)); + break; + case kJSTypeNumber: + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, JSValueToNumber (context, js_value, NULL)); + break; + case kJSTypeString: + js_str = JSValueToStringCopy (context, js_value, NULL); + str_len = JSStringGetLength (js_str); + str = g_malloc (str_len + 1); + JSStringGetUTF8CString (js_str, str, str_len + 1); + JSStringRelease (js_str); + g_value_init (value, G_TYPE_STRING); + g_value_set_string (value, str); + g_free (str); + break; + case kJSTypeObject: + g_value_init (value, G_TYPE_OBJECT); + g_value_set_object (value, JSValueToObject (context, js_value, NULL)); + break; + case kJSTypeNull: + g_value_init (value, G_TYPE_POINTER); + g_value_set_pointer (value, NULL); + break; + case kJSTypeUndefined: + break; + } + + return G_VALUE_TYPE (value); +} + +static JSValueRef +web_view_handle_js_callback (JSContextRef ctx, + JSObjectRef function, + JSObjectRef this_object, + size_t argument_count, + const JSValueRef arguments[], + JSValueRef *exception) +{ + gpointer web_view; + gpointer user_data; + gchar *fnc_name; + size_t fnc_name_len; + + EWebViewJSFunctionCallback callback; + + JSStringRef js_webview_prop = JSStringCreateWithUTF8CString ("webview"); + JSStringRef js_userdata_prop = JSStringCreateWithUTF8CString ("user-data"); + JSStringRef js_fncname_prop = JSStringCreateWithUTF8CString ("fnc-name"); + JSStringRef js_fncname_str; + + JSValueRef js_webview = JSObjectGetProperty (ctx, function, js_webview_prop, NULL); + JSValueRef js_userdata = JSObjectGetProperty (ctx, function, js_userdata_prop, NULL); + JSValueRef js_fncname = JSObjectGetProperty (ctx, function, js_fncname_prop, NULL); + + web_view = GINT_TO_POINTER ((gint) JSValueToNumber (ctx, js_webview, NULL)); + user_data = GINT_TO_POINTER ((gint) JSValueToNumber (ctx, js_userdata, NULL)); + js_fncname_str = JSValueToStringCopy (ctx, js_fncname, NULL); + fnc_name_len = JSStringGetLength (js_fncname_str); + + g_return_val_if_fail (E_IS_WEB_VIEW (web_view), 0); + + /* Convert fncname to gchar* and lookup the callback in hashtable */ + fnc_name = g_malloc (fnc_name_len + 1); + JSStringGetUTF8CString (js_fncname_str, fnc_name, fnc_name_len + 1); + callback = g_hash_table_lookup (E_WEB_VIEW (web_view)->priv->js_callbacks, fnc_name); + + g_return_val_if_fail (callback != NULL, 0); + + /* Call the callback function */ + callback (E_WEB_VIEW (web_view), argument_count, arguments, user_data); + + JSStringRelease (js_fncname_str); + JSStringRelease (js_fncname_prop); + JSStringRelease (js_webview_prop); + JSStringRelease (js_userdata_prop); + + g_free (fnc_name); + + return 0; +} + +void +e_web_view_install_js_callback (EWebView *web_view, + const gchar *fnc_name, + EWebViewJSFunctionCallback callback, + gpointer user_data) +{ + WebKitWebFrame *frame; + JSGlobalContextRef ctx; + JSObjectRef global_obj, js_func; + JSStringRef js_fnc_name, js_webview_prop, js_userdata_prop, js_fncname_prop; g_return_if_fail (E_IS_WEB_VIEW (web_view)); + g_return_if_fail (fnc_name != NULL); + g_return_if_fail (callback != NULL); + + frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view)); + ctx = webkit_web_frame_get_global_context (frame); + global_obj = JSContextGetGlobalObject (ctx); + js_fnc_name = JSStringCreateWithUTF8CString (fnc_name); + js_func = JSObjectMakeFunctionWithCallback (ctx, NULL, + (JSObjectCallAsFunctionCallback) web_view_handle_js_callback); + js_webview_prop = JSStringCreateWithUTF8CString ("webview"); + js_userdata_prop = JSStringCreateWithUTF8CString ("user-data"); + js_fncname_prop = JSStringCreateWithUTF8CString ("fnc-name"); - gtk_html_set_animate (GTK_HTML (web_view), animate); + /* Set some properties to the function */ + JSObjectSetProperty (ctx, js_func, js_webview_prop, JSValueMakeNumber (ctx, GPOINTER_TO_INT (web_view)), 0, NULL); + JSObjectSetProperty (ctx, js_func, js_userdata_prop, JSValueMakeNumber (ctx, GPOINTER_TO_INT (user_data)), 0, NULL); + JSObjectSetProperty (ctx, js_func, js_fncname_prop, JSValueMakeString (ctx, js_fnc_name), 0, NULL); - g_object_notify (G_OBJECT (web_view), "animate"); + /* Set the function as a property of global object */ + JSObjectSetProperty (ctx, global_obj, js_fnc_name, js_func, 0, NULL); + + JSStringRelease (js_fncname_prop); + JSStringRelease (js_userdata_prop); + JSStringRelease (js_webview_prop); + JSStringRelease (js_fnc_name); + + g_hash_table_insert (web_view->priv->js_callbacks, g_strdup (fnc_name), callback); } gboolean e_web_view_get_caret_mode (EWebView *web_view) { - /* XXX This is just here to maintain symmetry - * with e_web_view_set_caret_mode(). */ - g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); - return gtk_html_get_caret_mode (GTK_HTML (web_view)); + return web_view->priv->caret_mode; } void e_web_view_set_caret_mode (EWebView *web_view, gboolean caret_mode) { - /* XXX GtkHTML does not utilize GObject properties as well - * as it could. This just wraps gtk_html_set_caret_mode() - * so we can get a "notify::caret-mode" signal. */ - g_return_if_fail (E_IS_WEB_VIEW (web_view)); - gtk_html_set_caret_mode (GTK_HTML (web_view), caret_mode); + web_view->priv->caret_mode = caret_mode; g_object_notify (G_OBJECT (web_view), "caret-mode"); } @@ -1850,7 +2154,8 @@ e_web_view_get_copy_target_list (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); - return web_view->priv->copy_target_list; + return webkit_web_view_get_copy_target_list ( + WEBKIT_WEB_VIEW (web_view)); } gboolean @@ -1892,46 +2197,74 @@ e_web_view_set_disable_save_to_disk (EWebView *web_view, } gboolean -e_web_view_get_editable (EWebView *web_view) +e_web_view_get_enable_frame_flattening (EWebView * web_view) { - /* XXX This is just here to maintain symmetry - * with e_web_view_set_editable(). */ + WebKitWebSettings *settings; + gboolean flattening; + + /* Return TRUE with fail since it's default value we set in _init(). */ + g_return_val_if_fail (E_IS_WEB_VIEW (web_view), TRUE); + + settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view)); + g_return_val_if_fail (settings != NULL, TRUE); + + g_object_get (G_OBJECT (settings), "enable-frame-flattening", &flattening, NULL); + + return flattening; +} + +void +e_web_view_set_enable_frame_flattening (EWebView * web_view, + gboolean enable_frame_flattening) +{ + WebKitWebSettings *settings; + + g_return_if_fail (E_IS_WEB_VIEW (web_view)); + + settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view)); + g_return_if_fail (settings != NULL); + + g_object_set (G_OBJECT (settings), "enable-frame-flattening", + enable_frame_flattening, NULL); +} +gboolean +e_web_view_get_editable (EWebView *web_view) +{ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); - return gtk_html_get_editable (GTK_HTML (web_view)); + return webkit_web_view_get_editable (WEBKIT_WEB_VIEW (web_view)); } void e_web_view_set_editable (EWebView *web_view, gboolean editable) { - /* XXX GtkHTML does not utilize GObject properties as well - * as it could. This just wraps gtk_html_set_editable() - * so we can get a "notify::editable" signal. */ - g_return_if_fail (E_IS_WEB_VIEW (web_view)); - gtk_html_set_editable (GTK_HTML (web_view), editable); - - g_object_notify (G_OBJECT (web_view), "editable"); + webkit_web_view_set_editable (WEBKIT_WEB_VIEW (web_view), editable); } gboolean e_web_view_get_inline_spelling (EWebView *web_view) { +#if 0 /* WEBKIT - XXX No equivalent property? */ /* XXX This is just here to maintain symmetry * with e_web_view_set_inline_spelling(). */ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return gtk_html_get_inline_spelling (GTK_HTML (web_view)); +#endif + + return FALSE; } void e_web_view_set_inline_spelling (EWebView *web_view, gboolean inline_spelling) { +#if 0 /* WEBKIT - XXX No equivalent property? */ /* XXX GtkHTML does not utilize GObject properties as well * as it could. This just wraps gtk_html_set_inline_spelling() * so we get a "notify::inline-spelling" signal. */ @@ -1941,23 +2274,29 @@ e_web_view_set_inline_spelling (EWebView *web_view, gtk_html_set_inline_spelling (GTK_HTML (web_view), inline_spelling); g_object_notify (G_OBJECT (web_view), "inline-spelling"); +#endif } gboolean e_web_view_get_magic_links (EWebView *web_view) { +#if 0 /* WEBKIT - XXX No equivalent property? */ /* XXX This is just here to maintain symmetry * with e_web_view_set_magic_links(). */ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return gtk_html_get_magic_links (GTK_HTML (web_view)); +#endif + + return FALSE; } void e_web_view_set_magic_links (EWebView *web_view, gboolean magic_links) { +#if 0 /* WEBKIT - XXX No equivalent property? */ /* XXX GtkHTML does not utilize GObject properties as well * as it could. This just wraps gtk_html_set_magic_links() * so we can get a "notify::magic-links" signal. */ @@ -1967,23 +2306,29 @@ e_web_view_set_magic_links (EWebView *web_view, gtk_html_set_magic_links (GTK_HTML (web_view), magic_links); g_object_notify (G_OBJECT (web_view), "magic-links"); +#endif } gboolean e_web_view_get_magic_smileys (EWebView *web_view) { +#if 0 /* WEBKIT - No equivalent property? */ /* XXX This is just here to maintain symmetry * with e_web_view_set_magic_smileys(). */ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return gtk_html_get_magic_smileys (GTK_HTML (web_view)); +#endif + + return FALSE; } void e_web_view_set_magic_smileys (EWebView *web_view, gboolean magic_smileys) { +#if 0 /* WEBKIT - No equivalent property? */ /* XXX GtkHTML does not utilize GObject properties as well * as it could. This just wraps gtk_html_set_magic_smileys() * so we can get a "notify::magic-smileys" signal. */ @@ -1993,6 +2338,7 @@ e_web_view_set_magic_smileys (EWebView *web_view, gtk_html_set_magic_smileys (GTK_HTML (web_view), magic_smileys); g_object_notify (G_OBJECT (web_view), "magic-smileys"); +#endif } const gchar * @@ -2092,7 +2438,8 @@ e_web_view_get_paste_target_list (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); - return web_view->priv->paste_target_list; + return webkit_web_view_get_paste_target_list ( + WEBKIT_WEB_VIEW (web_view)); } GtkAction * @@ -2149,6 +2496,40 @@ e_web_view_set_save_as_proxy (EWebView *web_view, g_object_notify (G_OBJECT (web_view), "save-as-proxy"); } +GSList * +e_web_view_get_highlights (EWebView *web_view) +{ + g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); + + return web_view->priv->highlights; +} + +void +e_web_view_add_highlight (EWebView *web_view, + const gchar *highlight) +{ + g_return_if_fail (E_IS_WEB_VIEW (web_view)); + g_return_if_fail (highlight && *highlight); + + web_view->priv->highlights = + g_slist_append (web_view->priv->highlights, g_strdup (highlight)); + + web_view_update_document_highlights (web_view); +} + +void e_web_view_clear_highlights (EWebView *web_view) +{ + g_return_if_fail (E_IS_WEB_VIEW (web_view)); + + if (!web_view->priv->highlights) + return; + + g_slist_free_full (web_view->priv->highlights, g_free); + web_view->priv->highlights = NULL; + + web_view_update_document_highlights (web_view); +} + GtkAction * e_web_view_get_action (EWebView *web_view, const gchar *action_name) @@ -2179,20 +2560,16 @@ e_web_view_get_action_group (EWebView *web_view, gchar * e_web_view_extract_uri (EWebView *web_view, - GdkEventButton *event, - GtkHTML *frame) + GdkEventButton *event) { EWebViewClass *class; g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); - if (frame == NULL) - frame = GTK_HTML (web_view); - class = E_WEB_VIEW_GET_CLASS (web_view); g_return_val_if_fail (class->extract_uri != NULL, NULL); - return class->extract_uri (web_view, event, frame); + return class->extract_uri (web_view, event); } void @@ -2200,7 +2577,7 @@ e_web_view_copy_clipboard (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); - g_signal_emit (web_view, signals[COPY_CLIPBOARD], 0); + webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (web_view)); } void @@ -2208,7 +2585,7 @@ e_web_view_cut_clipboard (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); - g_signal_emit (web_view, signals[CUT_CLIPBOARD], 0); + webkit_web_view_cut_clipboard (WEBKIT_WEB_VIEW (web_view)); } gboolean @@ -2216,7 +2593,7 @@ e_web_view_is_selection_active (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); - return gtk_html_command (GTK_HTML (web_view), "is-selection-active"); + return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (web_view)); } void @@ -2224,7 +2601,7 @@ e_web_view_paste_clipboard (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); - g_signal_emit (web_view, signals[PASTE_CLIPBOARD], 0); + webkit_web_view_paste_clipboard (WEBKIT_WEB_VIEW (web_view)); } gboolean @@ -2232,7 +2609,10 @@ e_web_view_scroll_forward (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); - return gtk_html_command (GTK_HTML (web_view), "scroll-forward"); + webkit_web_view_move_cursor ( + WEBKIT_WEB_VIEW (web_view), GTK_MOVEMENT_PAGES, 1); + + return TRUE; /* XXX This means nothing. */ } gboolean @@ -2240,7 +2620,10 @@ e_web_view_scroll_backward (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); - return gtk_html_command (GTK_HTML (web_view), "scroll-backward"); + webkit_web_view_move_cursor ( + WEBKIT_WEB_VIEW (web_view), GTK_MOVEMENT_PAGES, -1); + + return TRUE; /* XXX This means nothing. */ } void @@ -2248,15 +2631,17 @@ e_web_view_select_all (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); - gtk_html_command (GTK_HTML (web_view), "select-all"); + webkit_web_view_select_all (WEBKIT_WEB_VIEW (web_view)); } void e_web_view_unselect_all (EWebView *web_view) { +#if 0 /* WEBKIT */ g_return_if_fail (E_IS_WEB_VIEW (web_view)); gtk_html_command (GTK_HTML (web_view), "unselect-all"); +#endif } void @@ -2264,7 +2649,7 @@ e_web_view_zoom_100 (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); - gtk_html_command (GTK_HTML (web_view), "zoom-reset"); + webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (web_view), 1.0); } void @@ -2272,7 +2657,7 @@ e_web_view_zoom_in (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); - gtk_html_command (GTK_HTML (web_view), "zoom-in"); + webkit_web_view_zoom_in (WEBKIT_WEB_VIEW (web_view)); } void @@ -2280,7 +2665,7 @@ e_web_view_zoom_out (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); - gtk_html_command (GTK_HTML (web_view), "zoom-out"); + webkit_web_view_zoom_out (WEBKIT_WEB_VIEW (web_view)); } GtkUIManager * @@ -2354,3 +2739,84 @@ e_web_view_update_actions (EWebView *web_view) g_signal_emit (web_view, signals[UPDATE_ACTIONS], 0); } + +gchar * +e_web_view_get_selection_html (EWebView *web_view) +{ + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *selection; + WebKitDOMRange *range; + WebKitDOMDocumentFragment *fragment; + WebKitDOMHTMLElement *element; + + g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); + + if (!webkit_web_view_has_selection (WEBKIT_WEB_VIEW (web_view))) + return NULL; + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (web_view)); + window = webkit_dom_document_get_default_view (document); + selection = webkit_dom_dom_window_get_selection (window); + if (!selection) + return NULL; + + range = webkit_dom_dom_selection_get_range_at (selection, 0, NULL); + if (!range) + return NULL; + + fragment = webkit_dom_range_clone_contents (range, NULL); + if (!fragment) + return NULL; + + element = WEBKIT_DOM_HTML_ELEMENT (webkit_dom_document_create_element (document, "div", NULL)); + webkit_dom_node_append_child (WEBKIT_DOM_NODE (element), + WEBKIT_DOM_NODE (fragment), NULL); + + return webkit_dom_html_element_get_inner_html (element); +} + +void +e_web_view_set_settings (EWebView *web_view, + WebKitWebSettings *settings) +{ + g_return_if_fail (E_IS_WEB_VIEW (web_view)); + + if (settings == webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view))) + return; + + g_object_bind_property (settings, "enable-caret-browsing", web_view, "caret-mode", + G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + + webkit_web_view_set_settings (WEBKIT_WEB_VIEW (web_view), settings); +} + +WebKitWebSettings * +e_web_view_get_default_settings (GtkWidget *parent_widget) +{ + GtkStyleContext *context; + const PangoFontDescription *font; + WebKitWebSettings *settings; + + g_return_val_if_fail (GTK_IS_WIDGET (parent_widget), NULL); + + settings = webkit_web_settings_new (); + + /* Use same font-size as rest of Evolution */ + context = gtk_widget_get_style_context (parent_widget); + font = gtk_style_context_get_font (context, GTK_STATE_FLAG_NORMAL); + + g_object_set (G_OBJECT (settings), + "default-font-size", (pango_font_description_get_size (font) / PANGO_SCALE), + "default-monospace-font-size", (pango_font_description_get_size (font) / PANGO_SCALE), + "enable-frame-flattening", TRUE, + "enable-java-applet", FALSE, + "enable-html5-database", FALSE, + "enable-html5-local-storage", FALSE, + "enable-offline-web-application-cache", FALSE, + "enable-site-specific-quirks", TRUE, + "enable-scripts", FALSE, + NULL); + + return settings; +} diff --git a/widgets/misc/e-web-view.h b/widgets/misc/e-web-view.h index 1ca9915d5e..d71cf04822 100644 --- a/widgets/misc/e-web-view.h +++ b/widgets/misc/e-web-view.h @@ -27,7 +27,8 @@ #ifndef E_WEB_VIEW_H #define E_WEB_VIEW_H -#include <gtkhtml/gtkhtml.h> +#include <webkit/webkit.h> +#include <JavaScriptCore/JavaScript.h> /* Standard GObject macros */ #define E_TYPE_WEB_VIEW \ @@ -55,17 +56,25 @@ typedef struct _EWebViewClass EWebViewClass; typedef struct _EWebViewPrivate EWebViewPrivate; struct _EWebView { - GtkHTML parent; + WebKitWebView parent; EWebViewPrivate *priv; }; +typedef void (*EWebViewJSFunctionCallback) (EWebView *web_view, + size_t arg_count, + const JSValueRef args[], + gpointer user_data); + struct _EWebViewClass { - GtkHTMLClass parent_class; + WebKitWebViewClass parent_class; /* Methods */ + GtkWidget * (*create_plugin_widget) (EWebView *web_view, + const gchar *mime_type, + const gchar *uri, + GHashTable *param); gchar * (*extract_uri) (EWebView *web_view, - GdkEventButton *event, - GtkHTML *frame); + GdkEventButton *event); void (*hovering_over_link) (EWebView *web_view, const gchar *title, const gchar *uri); @@ -73,11 +82,15 @@ struct _EWebViewClass { const gchar *uri); void (*load_string) (EWebView *web_view, const gchar *load_string); - + void (*load_uri) (EWebView *web_view, + const gchar *load_uri); + void (*frame_load_string) (EWebView *web_view, + const gchar *frame_name, + const gchar *string); + void (*frame_load_uri) (EWebView *web_view, + const gchar *frame_name, + const gchar *uri); /* Signals */ - void (*copy_clipboard) (EWebView *web_view); - void (*cut_clipboard) (EWebView *web_view); - void (*paste_clipboard) (EWebView *web_view); gboolean (*popup_event) (EWebView *web_view, GdkEventButton *event, const gchar *uri); @@ -94,9 +107,32 @@ GtkWidget * e_web_view_new (void); void e_web_view_clear (EWebView *web_view); void e_web_view_load_string (EWebView *web_view, const gchar *string); -gboolean e_web_view_get_animate (EWebView *web_view); -void e_web_view_set_animate (EWebView *web_view, - gboolean animate); +void e_web_view_load_uri (EWebView *web_view, + const gchar *uri); +const gchar * e_web_view_get_uri (EWebView *web_view); +void e_web_view_reload (EWebView *web_view); +void e_web_view_frame_load_string (EWebView *web_view, + const gchar *frame_name, + const gchar *string); +void e_web_view_frame_load_uri (EWebView *web_view, + const gchar *frame_name, + const gchar *uri); +const gchar * e_web_view_frame_get_uri (EWebView *web_view, + const gchar *frame_name); +JSGlobalContextRef + e_web_view_get_global_context (EWebView *web_view); +GType e_web_view_exec_script (EWebView *web_view, + const gchar *script, + GValue *value); +GType e_web_view_frame_exec_script (EWebView *web_view, + const gchar *frame_name, + const gchar *script, + GValue *value); +void e_web_view_install_js_callback (EWebView *web_view, + const gchar *fnc_name, + EWebViewJSFunctionCallback callback, + gpointer user_data); +gchar * e_web_view_get_html (EWebView *web_view); gboolean e_web_view_get_caret_mode (EWebView *web_view); void e_web_view_set_caret_mode (EWebView *web_view, gboolean caret_mode); @@ -109,6 +145,11 @@ gboolean e_web_view_get_disable_save_to_disk void e_web_view_set_disable_save_to_disk (EWebView *web_view, gboolean disable_save_to_disk); +gboolean e_web_view_get_enable_frame_flattening + (EWebView *web_view); +void e_web_view_set_enable_frame_flattening + (EWebView *web_view, + gboolean enable_frame_flattening); gboolean e_web_view_get_editable (EWebView *web_view); void e_web_view_set_editable (EWebView *web_view, gboolean editable); @@ -142,13 +183,16 @@ void e_web_view_set_print_proxy (EWebView *web_view, GtkAction * e_web_view_get_save_as_proxy (EWebView *web_view); void e_web_view_set_save_as_proxy (EWebView *web_view, GtkAction *save_as_proxy); +GSList * e_web_view_get_highlights (EWebView *web_view); +void e_web_view_add_highlight (EWebView *web_view, + const gchar *highlight); +void e_web_view_clear_highlights (EWebView *web_view); GtkAction * e_web_view_get_action (EWebView *web_view, const gchar *action_name); GtkActionGroup *e_web_view_get_action_group (EWebView *web_view, const gchar *group_name); gchar * e_web_view_extract_uri (EWebView *web_view, - GdkEventButton *event, - GtkHTML *frame); + GdkEventButton *event); void e_web_view_copy_clipboard (EWebView *web_view); void e_web_view_cut_clipboard (EWebView *web_view); gboolean e_web_view_is_selection_active (EWebView *web_view); @@ -171,6 +215,13 @@ void e_web_view_status_message (EWebView *web_view, void e_web_view_stop_loading (EWebView *web_view); void e_web_view_update_actions (EWebView *web_view); +gchar * e_web_view_get_selection_html (EWebView *web_view); + +void e_web_view_set_settings (EWebView *web_view, + WebKitWebSettings *settings); +WebKitWebSettings * + e_web_view_get_default_settings (); + G_END_DECLS #endif /* E_WEB_VIEW_H */ |