aboutsummaryrefslogtreecommitdiffstats
path: root/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'widgets')
-rw-r--r--widgets/misc/Makefile.am2
-rw-r--r--widgets/misc/e-attachment-view.c44
-rw-r--r--widgets/misc/e-signature-preview.c71
-rw-r--r--widgets/misc/e-signature-preview.h6
-rw-r--r--widgets/misc/e-web-view.c1110
-rw-r--r--widgets/misc/e-web-view.h119
6 files changed, 1269 insertions, 83 deletions
diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am
index 29607758e5..91906b0448 100644
--- a/widgets/misc/Makefile.am
+++ b/widgets/misc/Makefile.am
@@ -86,6 +86,7 @@ widgetsinclude_HEADERS = \
e-spinner.h \
e-timeout-activity.h \
e-url-entry.h \
+ e-web-view.h \
ea-calendar-cell.h \
ea-calendar-item.h \
ea-cell-table.h \
@@ -148,6 +149,7 @@ libemiscwidgets_la_SOURCES = \
e-signature-tree-view.c \
e-timeout-activity.c \
e-url-entry.c \
+ e-web-view.c \
ea-calendar-cell.c \
ea-calendar-item.c \
ea-cell-table.c \
diff --git a/widgets/misc/e-attachment-view.c b/widgets/misc/e-attachment-view.c
index 17a2b6c9b4..cd5301a987 100644
--- a/widgets/misc/e-attachment-view.c
+++ b/widgets/misc/e-attachment-view.c
@@ -682,12 +682,30 @@ attachment_view_update_actions (EAttachmentView *view)
}
static void
+attachment_view_add_handler (GType type,
+ EAttachmentView *view)
+{
+ EAttachmentViewPrivate *priv;
+ EAttachmentHandler *handler;
+ const GtkTargetEntry *targets;
+ guint n_targets;
+
+ priv = e_attachment_view_get_private (view);
+
+ handler = g_object_new (type, "view", view, NULL);
+
+ targets = e_attachment_handler_get_target_table (handler, &n_targets);
+ gtk_target_list_add_table (priv->target_list, targets, n_targets);
+ priv->drag_actions = e_attachment_handler_get_drag_actions (handler);
+
+ g_ptr_array_add (priv->handlers, handler);
+}
+
+static void
attachment_view_init_handlers (EAttachmentView *view)
{
EAttachmentViewPrivate *priv;
GtkTargetList *target_list;
- GType *children;
- guint ii;
priv = e_attachment_view_get_private (view);
@@ -700,25 +718,9 @@ attachment_view_init_handlers (EAttachmentView *view)
priv->target_list = target_list;
priv->drag_actions = GDK_ACTION_COPY;
- children = g_type_children (E_TYPE_ATTACHMENT_HANDLER, NULL);
-
- for (ii = 0; children[ii] != G_TYPE_INVALID; ii++) {
- EAttachmentHandler *handler;
- const GtkTargetEntry *targets;
- guint n_targets;
-
- handler = g_object_new (children[ii], "view", view, NULL);
-
- targets = e_attachment_handler_get_target_table (
- handler, &n_targets);
- gtk_target_list_add_table (target_list, targets, n_targets);
- priv->drag_actions |=
- e_attachment_handler_get_drag_actions (handler);
-
- g_ptr_array_add (priv->handlers, handler);
- }
-
- g_free (children);
+ e_type_traverse (
+ E_TYPE_ATTACHMENT_HANDLER, (ETypeFunc)
+ attachment_view_add_handler, view);
}
static void
diff --git a/widgets/misc/e-signature-preview.c b/widgets/misc/e-signature-preview.c
index bbef65f39c..a9316e44ec 100644
--- a/widgets/misc/e-signature-preview.c
+++ b/widgets/misc/e-signature-preview.c
@@ -113,56 +113,17 @@ signature_preview_dispose (GObject *object)
}
static void
-signature_preview_url_requested (GtkHTML *html,
- const gchar *url,
- GtkHTMLStream *handle)
-{
- GtkHTMLStreamStatus status;
- gchar buffer[128];
- gchar *filename;
- gssize size;
- gint fd;
-
- /* FIXME Use GInputStream for this. */
-
- if (g_str_has_prefix (url, "file:"))
- filename = g_filename_from_uri (url, NULL, NULL);
- else
- filename = g_strdup (url);
- fd = g_open (filename, O_RDONLY, 0);
- g_free (filename);
-
- status = GTK_HTML_STREAM_OK;
- if (fd != -1) {
- while ((size = read (fd, buffer, sizeof (buffer)))) {
- if (size == -1) {
- status = GTK_HTML_STREAM_ERROR;
- break;
- } else
- gtk_html_write (html, handle, buffer, size);
- }
- } else
- status = GTK_HTML_STREAM_ERROR;
-
- gtk_html_end (html, handle, status);
-
- if (fd > 0)
- close (fd);
-}
-
-static void
signature_preview_refresh (ESignaturePreview *preview)
{
- GtkHTML *html;
+ EWebView *web_view;
ESignature *signature;
const gchar *filename;
gboolean is_script;
gchar *content = NULL;
- gsize length;
/* XXX We should show error messages in the preview. */
- html = GTK_HTML (preview);
+ web_view = E_WEB_VIEW (preview);
signature = e_signature_preview_get_signature (preview);
if (signature == NULL)
@@ -182,27 +143,23 @@ signature_preview_refresh (ESignaturePreview *preview)
if (content == NULL || *content == '\0')
goto clear;
- length = strlen (content);
-
if (e_signature_get_is_html (signature))
- gtk_html_load_from_string (html, content, length);
+ e_web_view_load_string (web_view, content);
else {
- GtkHTMLStream *stream;
-
- stream = gtk_html_begin_content (
- html, "text/html; charset=utf-8");
- gtk_html_write (html, stream, "<PRE>", 5);
- if (length > 0)
- gtk_html_write (html, stream, content, length);
- gtk_html_write (html, stream, "</PRE>", 6);
- gtk_html_end (html, stream, GTK_HTML_STREAM_OK);
+ gchar *string;
+
+ string = g_strdup_printf ("<PRE>%s</PRE>", content);
+ e_web_view_load_string (web_view, string);
+ g_free (string);
}
g_free (content);
+
return;
clear:
- gtk_html_load_from_string (html, " ", 1);
+ e_web_view_clear (web_view);
+
g_free (content);
}
@@ -210,7 +167,6 @@ static void
signature_preview_class_init (ESignaturePreviewClass *class)
{
GObjectClass *object_class;
- GtkHTMLClass *html_class;
parent_class = g_type_class_peek_parent (class);
g_type_class_add_private (class, sizeof (ESignaturePreviewPrivate));
@@ -220,9 +176,6 @@ signature_preview_class_init (ESignaturePreviewClass *class)
object_class->get_property = signature_preview_get_property;
object_class->dispose = signature_preview_dispose;
- html_class = GTK_HTML_CLASS (class);
- html_class->url_requested = signature_preview_url_requested;
-
class->refresh = signature_preview_refresh;
g_object_class_install_property (
@@ -282,7 +235,7 @@ e_signature_preview_get_type (void)
};
type = g_type_register_static (
- GTK_TYPE_HTML, "ESignaturePreview", &type_info, 0);
+ E_TYPE_WEB_VIEW, "ESignaturePreview", &type_info, 0);
}
return type;
diff --git a/widgets/misc/e-signature-preview.h b/widgets/misc/e-signature-preview.h
index a4221832c2..d0d5c22eff 100644
--- a/widgets/misc/e-signature-preview.h
+++ b/widgets/misc/e-signature-preview.h
@@ -22,8 +22,8 @@
#ifndef E_SIGNATURE_PREVIEW_H
#define E_SIGNATURE_PREVIEW_H
-#include <gtkhtml/gtkhtml.h>
#include <e-util/e-signature.h>
+#include <misc/e-web-view.h>
/* Standard GObject macros */
#define E_TYPE_SIGNATURE_PREVIEW \
@@ -51,12 +51,12 @@ typedef struct _ESignaturePreviewClass ESignaturePreviewClass;
typedef struct _ESignaturePreviewPrivate ESignaturePreviewPrivate;
struct _ESignaturePreview {
- GtkHTML parent;
+ EWebView parent;
ESignaturePreviewPrivate *priv;
};
struct _ESignaturePreviewClass {
- GtkHTMLClass parent_class;
+ EWebViewClass parent_class;
/* Signals */
void (*refresh) (ESignaturePreview *preview);
diff --git a/widgets/misc/e-web-view.c b/widgets/misc/e-web-view.c
new file mode 100644
index 0000000000..a49fd22527
--- /dev/null
+++ b/widgets/misc/e-web-view.c
@@ -0,0 +1,1110 @@
+/*
+ * e-web-view.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/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-web-view.h"
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel-internet-address.h>
+#include <camel/camel-url.h>
+
+#include "e-util/e-util.h"
+#include "e-util/e-plugin-ui.h"
+
+#define E_WEB_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_WEB_VIEW, EWebViewPrivate))
+
+typedef struct _EWebViewRequest EWebViewRequest;
+
+struct _EWebViewPrivate {
+ GList *requests;
+ GtkUIManager *ui_manager;
+ gchar *selected_uri;
+};
+
+struct _EWebViewRequest {
+ GFile *file;
+ EWebView *web_view;
+ GCancellable *cancellable;
+ GInputStream *input_stream;
+ GtkHTMLStream *output_stream;
+ gchar buffer[4096];
+};
+
+enum {
+ PROP_0,
+ PROP_ANIMATE,
+ PROP_CARET_MODE,
+ PROP_SELECTED_URI
+};
+
+enum {
+ POPUP_EVENT,
+ STATUS_MESSAGE,
+ STOP_LOADING,
+ UPDATE_ACTIONS,
+ LAST_SIGNAL
+};
+
+static gpointer parent_class;
+static guint signals[LAST_SIGNAL];
+
+static const gchar *ui =
+"<ui>"
+" <popup name='context'>"
+" <placeholder name='custom-actions-1'>"
+" <menuitem action='http-open'/>"
+" <menuitem action='send-message'/>"
+" </placeholder>"
+" <placeholder name='custom-actions-2'>"
+" <menuitem action='uri-copy'/>"
+" <menuitem action='mailto-copy'/>"
+" </placeholder>"
+" <placeholder name='custom-actions-3'/>"
+" </popup>"
+"</ui>";
+
+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;
+
+ /* 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)
+{
+ GtkHTML *html;
+ gssize bytes_read;
+ GError *error = NULL;
+
+ html = GTK_HTML (request->web_view);
+ 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_http_open_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ const gchar *uri;
+ gpointer parent;
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ uri = e_web_view_get_selected_uri (web_view);
+ g_return_if_fail (uri != NULL);
+
+ e_show_uri (parent, uri);
+}
+
+static void
+action_mailto_copy_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ CamelURL *curl;
+ CamelInternetAddress *inet_addr;
+ GtkClipboard *clipboard;
+ const gchar *uri;
+ gchar *text;
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ uri = e_web_view_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_encode (CAMEL_ADDRESS (inet_addr));
+ if (text == NULL || *text == '\0')
+ text = g_strdup (uri + strlen ("mailto:"));
+
+ camel_object_unref (inet_addr);
+ camel_url_free (curl);
+
+ gtk_clipboard_set_text (clipboard, text, -1);
+ gtk_clipboard_store (clipboard);
+
+ g_free (text);
+}
+
+static void
+action_send_message_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ const gchar *uri;
+ gpointer parent;
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ uri = e_web_view_get_selected_uri (web_view);
+ g_return_if_fail (uri != NULL);
+
+ e_show_uri (parent, uri);
+}
+
+static void
+action_uri_copy_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ GtkClipboard *clipboard;
+ const gchar *uri;
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ uri = e_web_view_get_selected_uri (web_view);
+ g_return_if_fail (uri != NULL);
+
+ gtk_clipboard_set_text (clipboard, uri, -1);
+ 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 gboolean
+web_view_button_press_event_cb (EWebView *web_view,
+ GdkEventButton *event,
+ GtkHTML *frame)
+{
+ gboolean event_handled = FALSE;
+ gchar *uri;
+
+ if (event != NULL && event->button != 3)
+ return FALSE;
+
+ uri = e_web_view_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_menu_item_select_cb (EWebView *web_view,
+ GtkWidget *widget)
+{
+ GtkAction *action;
+ const gchar *tooltip;
+
+ action = gtk_widget_get_action (widget);
+ tooltip = gtk_action_get_tooltip (action);
+
+ if (tooltip == NULL)
+ return;
+
+ e_web_view_status_message (web_view, tooltip);
+}
+
+static void
+web_view_menu_item_deselect_cb (EWebView *web_view)
+{
+ e_web_view_status_message (web_view, NULL);
+}
+
+static void
+web_view_connect_proxy_cb (EWebView *web_view,
+ GtkAction *action,
+ GtkWidget *proxy)
+{
+ if (!GTK_IS_MENU_ITEM (proxy))
+ return;
+
+ g_signal_connect_swapped (
+ proxy, "select",
+ G_CALLBACK (web_view_menu_item_select_cb), web_view);
+
+ g_signal_connect_swapped (
+ proxy, "deselect",
+ G_CALLBACK (web_view_menu_item_deselect_cb), web_view);
+}
+
+static void
+web_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ 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),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_SELECTED_URI:
+ e_web_view_set_selected_uri (
+ E_WEB_VIEW (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+web_view_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_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_SELECTED_URI:
+ g_value_set_string (
+ value, e_web_view_get_selected_uri (
+ E_WEB_VIEW (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+web_view_dispose (GObject *object)
+{
+ EWebViewPrivate *priv;
+
+ priv = E_WEB_VIEW_GET_PRIVATE (object);
+
+ if (priv->ui_manager != NULL) {
+ g_object_unref (priv->ui_manager);
+ priv->ui_manager = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+web_view_finalize (GObject *object)
+{
+ EWebViewPrivate *priv;
+
+ priv = E_WEB_VIEW_GET_PRIVATE (object);
+
+ /* All URI requests should be complete or cancelled by now. */
+ if (priv->requests != NULL)
+ g_warning ("Finalizing EWebView with active URI requests");
+
+ g_free (priv->selected_uri);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+web_view_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GtkWidgetClass *widget_class;
+ EWebView *web_view;
+
+ web_view = E_WEB_VIEW (widget);
+
+ if (web_view_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_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_url_requested (GtkHTML *html,
+ const gchar *uri,
+ GtkHTMLStream *stream)
+{
+ EWebViewRequest *request;
+
+ request = web_view_request_new (E_WEB_VIEW (html), uri, stream);
+
+ g_file_read_async (
+ request->file, G_PRIORITY_DEFAULT,
+ request->cancellable, (GAsyncReadyCallback)
+ web_view_request_read_cb, request);
+}
+
+static void
+web_view_link_clicked (GtkHTML *html,
+ const gchar *uri)
+{
+ gpointer parent;
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (html));
+ parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;
+
+ e_show_uri (parent, uri);
+}
+
+static void
+web_view_on_url (GtkHTML *html,
+ const gchar *uri)
+{
+ EWebView *web_view;
+ CamelInternetAddress *address;
+ CamelURL *curl;
+ const gchar *format = NULL;
+ gchar *message = NULL;
+ gchar *who;
+
+ web_view = E_WEB_VIEW (html);
+
+ 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));
+ camel_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_status_message (web_view, message);
+
+ g_free (message);
+}
+
+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);
+}
+
+static gchar *
+web_view_extract_uri (EWebView *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 gboolean
+web_view_popup_event (EWebView *web_view,
+ GdkEventButton *event,
+ const gchar *uri)
+{
+ e_web_view_set_selected_uri (web_view, uri);
+ e_web_view_show_popup_menu (web_view, event, NULL, NULL);
+
+ return TRUE;
+}
+
+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));
+}
+
+static void
+web_view_update_actions (EWebView *web_view)
+{
+ CamelURL *curl;
+ GtkActionGroup *action_group;
+ gboolean scheme_is_http;
+ gboolean scheme_is_mailto;
+ gboolean uri_is_valid;
+ gboolean visible;
+ const gchar *uri;
+
+ uri = e_web_view_get_selected_uri (web_view);
+ g_return_if_fail (uri != NULL);
+
+ /* Parse the URI early so we know if the actions will work. */
+ 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. */
+ visible = !scheme_is_mailto;
+ action_group = e_web_view_get_action_group (web_view, "uri");
+ gtk_action_group_set_visible (action_group, visible);
+
+ visible = uri_is_valid && scheme_is_http;
+ action_group = e_web_view_get_action_group (web_view, "http");
+ gtk_action_group_set_visible (action_group, visible);
+
+ visible = uri_is_valid && scheme_is_mailto;
+ action_group = e_web_view_get_action_group (web_view, "mailto");
+ gtk_action_group_set_visible (action_group, visible);
+}
+
+static void
+web_view_class_init (EWebViewClass *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 (EWebViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = web_view_set_property;
+ object_class->get_property = web_view_get_property;
+ object_class->dispose = web_view_dispose;
+ object_class->finalize = web_view_finalize;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->button_press_event = web_view_button_press_event;
+ widget_class->scroll_event = web_view_scroll_event;
+
+ html_class = GTK_HTML_CLASS (class);
+ html_class->url_requested = web_view_url_requested;
+ html_class->link_clicked = web_view_link_clicked;
+ html_class->on_url = web_view_on_url;
+ html_class->iframe_created = web_view_iframe_created;
+
+ class->extract_uri = web_view_extract_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",
+ "Caret Mode",
+ NULL,
+ FALSE,
+ 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));
+
+ signals[POPUP_EVENT] = g_signal_new (
+ "popup-event",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EWebViewClass, 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 (EWebViewClass, 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 (EWebViewClass, 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 (EWebViewClass, update_actions),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+web_view_init (EWebView *web_view)
+{
+ GtkUIManager *ui_manager;
+ GtkActionGroup *action_group;
+ const gchar *domain = GETTEXT_PACKAGE;
+ const gchar *id;
+ GError *error = NULL;
+
+ web_view->priv = E_WEB_VIEW_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_connect_proxy_cb), web_view);
+
+ 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);
+
+ /* 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);
+}
+
+GType
+e_web_view_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EWebViewClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) web_view_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EWebView),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) web_view_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ GTK_TYPE_HTML, "EWebView", &type_info, 0);
+ }
+
+ return type;
+}
+
+GtkWidget *
+e_web_view_new (void)
+{
+ return g_object_new (E_TYPE_WEB_VIEW, NULL);
+}
+
+void
+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));
+}
+
+void
+e_web_view_load_string (EWebView *web_view,
+ const gchar *string)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (string != NULL && *string != '\0')
+ gtk_html_load_from_string (GTK_HTML (web_view), string, -1);
+ else
+ e_web_view_clear (web_view);
+}
+
+gboolean
+e_web_view_get_animate (EWebView *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 (web_view), FALSE);
+
+ return gtk_html_get_animate (GTK_HTML (web_view));
+}
+
+void
+e_web_view_set_animate (EWebView *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 (web_view));
+
+ gtk_html_set_animate (GTK_HTML (web_view), animate);
+
+ g_object_notify (G_OBJECT (web_view), "animate");
+}
+
+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));
+}
+
+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);
+
+ g_object_notify (G_OBJECT (web_view), "caret-mode");
+}
+
+const gchar *
+e_web_view_get_selected_uri (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return web_view->priv->selected_uri;
+}
+
+void
+e_web_view_set_selected_uri (EWebView *web_view,
+ const gchar *selected_uri)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (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");
+}
+
+GtkAction *
+e_web_view_get_action (EWebView *web_view,
+ const gchar *action_name)
+{
+ GtkUIManager *ui_manager;
+
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+ g_return_val_if_fail (action_name != NULL, NULL);
+
+ ui_manager = e_web_view_get_ui_manager (web_view);
+
+ return e_lookup_action (ui_manager, action_name);
+}
+
+GtkActionGroup *
+e_web_view_get_action_group (EWebView *web_view,
+ const gchar *group_name)
+{
+ GtkUIManager *ui_manager;
+
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+ g_return_val_if_fail (group_name != NULL, NULL);
+
+ ui_manager = e_web_view_get_ui_manager (web_view);
+
+ return e_lookup_action_group (ui_manager, group_name);
+}
+
+gchar *
+e_web_view_extract_uri (EWebView *web_view,
+ GdkEventButton *event,
+ GtkHTML *frame)
+{
+ 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);
+}
+
+gboolean
+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");
+}
+
+gboolean
+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");
+}
+
+GtkUIManager *
+e_web_view_get_ui_manager (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return web_view->priv->ui_manager;
+}
+
+GtkWidget *
+e_web_view_get_popup_menu (EWebView *web_view)
+{
+ GtkUIManager *ui_manager;
+ GtkWidget *menu;
+
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ ui_manager = e_web_view_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_show_popup_menu (EWebView *web_view,
+ GdkEventButton *event,
+ GtkMenuPositionFunc func,
+ gpointer user_data)
+{
+ GtkWidget *menu;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ e_web_view_update_actions (web_view);
+
+ menu = e_web_view_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_status_message (EWebView *web_view,
+ const gchar *status_message)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ g_signal_emit (web_view, signals[STATUS_MESSAGE], 0, status_message);
+}
+
+void
+e_web_view_stop_loading (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ g_signal_emit (web_view, signals[STOP_LOADING], 0);
+}
+
+void
+e_web_view_update_actions (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ g_signal_emit (web_view, signals[UPDATE_ACTIONS], 0);
+}
diff --git a/widgets/misc/e-web-view.h b/widgets/misc/e-web-view.h
new file mode 100644
index 0000000000..3bce2b4887
--- /dev/null
+++ b/widgets/misc/e-web-view.h
@@ -0,0 +1,119 @@
+/*
+ * e-web-view.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/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/* 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_H
+#define E_WEB_VIEW_H
+
+#include <gtkhtml/gtkhtml.h>
+
+/* Standard GObject macros */
+#define E_TYPE_WEB_VIEW \
+ (e_web_view_get_type ())
+#define E_WEB_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_WEB_VIEW, EWebView))
+#define E_WEB_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_WEB_VIEW, EWebViewClass))
+#define E_IS_WEB_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_WEB_VIEW))
+#define E_IS_WEB_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_WEB_VIEW))
+#define E_WEB_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_WEB_VIEW, EWebViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EWebView EWebView;
+typedef struct _EWebViewClass EWebViewClass;
+typedef struct _EWebViewPrivate EWebViewPrivate;
+
+struct _EWebView {
+ GtkHTML parent;
+ EWebViewPrivate *priv;
+};
+
+struct _EWebViewClass {
+ GtkHTMLClass parent_class;
+
+ /* Methods */
+ gchar * (*extract_uri) (EWebView *web_view,
+ GdkEventButton *event,
+ GtkHTML *frame);
+
+ /* Signals */
+ gboolean (*popup_event) (EWebView *web_view,
+ GdkEventButton *event,
+ const gchar *uri);
+ void (*status_message) (EWebView *web_view,
+ const gchar *status_message);
+ void (*stop_loading) (EWebView *web_view);
+ void (*update_actions) (EWebView *web_view);
+};
+
+GType e_web_view_get_type (void);
+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);
+gboolean e_web_view_get_caret_mode (EWebView *web_view);
+void e_web_view_set_caret_mode (EWebView *web_view,
+ gboolean caret_mode);
+const gchar * e_web_view_get_selected_uri (EWebView *web_view);
+void e_web_view_set_selected_uri (EWebView *web_view,
+ const gchar *selected_uri);
+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);
+gboolean e_web_view_scroll_forward (EWebView *web_view);
+gboolean e_web_view_scroll_backward (EWebView *web_view);
+GtkUIManager * e_web_view_get_ui_manager (EWebView *web_view);
+GtkWidget * e_web_view_get_popup_menu (EWebView *web_view);
+void e_web_view_show_popup_menu (EWebView *web_view,
+ GdkEventButton *event,
+ GtkMenuPositionFunc func,
+ gpointer user_data);
+void e_web_view_status_message (EWebView *web_view,
+ const gchar *status_message);
+void e_web_view_stop_loading (EWebView *web_view);
+void e_web_view_update_actions (EWebView *web_view);
+
+G_END_DECLS
+
+#endif /* E_WEB_VIEW_H */