/*
* e-mail-display.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)
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define LIBSOUP_USE_UNSTABLE_REQUEST_API
#include "e-mail-display.h"
#include <glib/gi18n.h>
#include <gdk/gdk.h>
#include "e-util/e-marshal.h"
#include "e-util/e-util.h"
#include "e-util/e-plugin-ui.h"
#include "mail/em-composer-utils.h"
#include "mail/em-utils.h"
#include "mail/e-mail-request.h"
#include "mail/em-format-html-display.h"
#include "mail/e-mail-attachment-bar.h"
#include "widgets/misc/e-attachment-button.h"
#include <camel/camel.h>
#include <libsoup/soup.h>
#include <libsoup/soup-requester.h>
#include <JavaScriptCore/JavaScript.h>
#define d(x)
G_DEFINE_TYPE (EMailDisplay, e_mail_display, E_TYPE_WEB_VIEW)
#define E_MAIL_DISPLAY_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_MAIL_DISPLAY, EMailDisplayPrivate))
struct _EMailDisplayPrivate {
EMFormatHTML *formatter;
EMFormatWriteMode mode;
gboolean headers_collapsable;
gboolean headers_collapsed;
GtkActionGroup *mailto_actions;
GtkActionGroup *images_actions;
gint force_image_load: 1;
};
enum {
PROP_0,
PROP_FORMATTER,
PROP_MODE,
PROP_HEADERS_COLLAPSABLE,
PROP_HEADERS_COLLAPSED,
};
static gpointer parent_class;
static CamelDataCache *emd_global_http_cache = 0;
static const gchar *ui =
"<ui>"
" <popup name='context'>"
" <placeholder name='custom-actions-1'>"
" <menuitem action='add-to-address-book'/>"
" <menuitem action='send-reply'/>"
" </placeholder>"
" <placeholder name='custom-actions-3'>"
" <menu action='search-folder-menu'>"
" <menuitem action='search-folder-recipient'/>"
" <menuitem action='search-folder-sender'/>"
" </menu>"
" </placeholder>"
" </popup>"
"</ui>";
static const gchar *image_ui =
"<ui>"
" <popup name='context'>"
" <placeholder name='custom-actions-2'>"
" <menuitem action='image-save'/>"
" </placeholder>"
" </popup>"
"</ui>";
static GtkActionEntry mailto_entries[] = {
{ "add-to-address-book",
"contact-new",
N_("_Add to Address Book..."),
NULL,
NULL, /* XXX Add a tooltip! */
NULL /* Handled by EMailReader */ },
{ "search-folder-recipient",
NULL,
N_("_To This Address"),
NULL,
NULL, /* XXX Add a tooltip! */
NULL /* Handled by EMailReader */ },
{ "search-folder-sender",
NULL,
N_("_From This Address"),
NULL,
NULL, /* XXX Add a tooltip! */
NULL /* Handled by EMailReader */ },
{ "send-reply",
NULL,
N_("Send _Reply To..."),
NULL,
N_("Send a reply message to this address"),
NULL /* Handled by EMailReader */ },
/*** Menus ***/
{ "search-folder-menu",
"folder-saved-search",
N_("Create Search _Folder"),
NULL,
NULL,
NULL }
};
static GtkActionEntry image_entries[] = {
{ "image-save",
GTK_STOCK_SAVE,
N_("Save _Image..."),
NULL,
N_("Save the image to a file"),
NULL /* Handled by EMailReader */ },
};
static void
mail_display_webview_update_actions (EWebView *web_view,
gpointer user_data)
{
const gchar *image_src;
gboolean visible;
GtkAction *action;
g_return_if_fail (web_view != NULL);
image_src = e_web_view_get_cursor_image_src (web_view);
visible = image_src && g_str_has_prefix (image_src, "cid:");
if (!visible && image_src) {
CamelStream *image_stream;
image_stream = camel_data_cache_get (emd_global_http_cache, "http", image_src, NULL);
visible = image_stream != NULL;
if (image_stream)
g_object_unref (image_stream);
}
action = e_web_view_get_action (web_view, "image-save");
if (action)
gtk_action_set_visible (action, visible);
}
static void
formatter_image_loading_policy_changed_cb (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
EMailDisplay *display = user_data;
e_mail_display_load_images (display);
}
static void
mail_display_update_formatter_colors (EMailDisplay *display)
{
EMFormatHTMLColorType type;
EMFormatHTML *formatter;
GdkColor *color;
GtkStateType state;
GtkStyle *style;
state = gtk_widget_get_state (GTK_WIDGET (display));
formatter = display->priv->formatter;
if (!display->priv->formatter)
return;
style = gtk_widget_get_style (GTK_WIDGET (display));
if (style == NULL)
return;
g_object_freeze_notify (G_OBJECT (formatter));
color = &style->bg[state];
type = EM_FORMAT_HTML_COLOR_BODY;
em_format_html_set_color (formatter, type, color);
color = &style->base[GTK_STATE_NORMAL];
type = EM_FORMAT_HTML_COLOR_CONTENT;
em_format_html_set_color (formatter, type, color);
color = &style->dark[state];
type = EM_FORMAT_HTML_COLOR_FRAME;
em_format_html_set_color (formatter, type, color);
color = &style->fg[state];
type = EM_FORMAT_HTML_COLOR_HEADER;
em_format_html_set_color (formatter, type, color);
color = &style->text[state];
type = EM_FORMAT_HTML_COLOR_TEXT;
em_format_html_set_color (formatter, type, color);
g_object_thaw_notify (G_OBJECT (formatter));
}
static void
mail_display_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FORMATTER:
e_mail_display_set_formatter (
E_MAIL_DISPLAY (object),
g_value_get_object (value));
return;
case PROP_MODE:
e_mail_display_set_mode (
E_MAIL_DISPLAY (object),
g_value_get_int (value));
return;
case PROP_HEADERS_COLLAPSABLE:
e_mail_display_set_headers_collapsable (
E_MAIL_DISPLAY (object),
g_value_get_boolean (value));
return;
case PROP_HEADERS_COLLAPSED:
e_mail_display_set_headers_collapsed (
E_MAIL_DISPLAY (object),
g_value_get_boolean (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
mail_display_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FORMATTER:
g_value_set_object (
value, e_mail_display_get_formatter (
E_MAIL_DISPLAY (object)));
return;
case PROP_MODE:
g_value_set_int (
value, e_mail_display_get_mode (
E_MAIL_DISPLAY (object)));
return;
case PROP_HEADERS_COLLAPSABLE:
g_value_set_boolean (
value, e_mail_display_get_headers_collapsable (
E_MAIL_DISPLAY (object)));
return;
case PROP_HEADERS_COLLAPSED:
g_value_set_boolean (
value, e_mail_display_get_headers_collapsed (
E_MAIL_DISPLAY (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
mail_display_dispose (GObject *object)
{
EMailDisplayPrivate *priv;
priv = E_MAIL_DISPLAY_GET_PRIVATE (object);
if (priv->formatter) {
g_object_unref (priv->formatter);
priv->formatter = NULL;
}
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
mail_display_realize (GtkWidget *widget)
{
/* Chain up to parent's realize() method. */
GTK_WIDGET_CLASS (parent_class)->realize (widget);
mail_display_update_formatter_colors (E_MAIL_DISPLAY (widget));
}
static void
mail_display_style_set (GtkWidget *widget,
GtkStyle *previous_style)
{
EMailDisplay *display = E_MAIL_DISPLAY (widget);
mail_display_update_formatter_colors (display);
/* Chain up to parent's style_set() method. */
GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style);
}
static gboolean
mail_display_process_mailto (EWebView *web_view,
const gchar *mailto_uri,
gpointer user_data)
{
g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
g_return_val_if_fail (mailto_uri != NULL, FALSE);
if (g_ascii_strncasecmp (mailto_uri, "mailto:", 7) == 0) {
EMFormat *format;
CamelFolder *folder = NULL;
EShell *shell;
format = (EMFormat *) E_MAIL_DISPLAY (web_view)->priv->formatter;
if (format != NULL && format->folder != NULL)
folder = format->folder;
shell = e_shell_get_default ();
em_utils_compose_new_message_with_mailto (
shell, mailto_uri, folder);
return TRUE;
}
return FALSE;
}
static gboolean
mail_display_link_clicked (WebKitWebView *web_view,
WebKitWebFrame *frame,
WebKitNetworkRequest *request,
WebKitWebNavigationAction *navigation_action,
WebKitWebPolicyDecision *policy_decision,
gpointer user_data)
{
EMailDisplay *display;
const gchar *uri = webkit_network_request_get_uri (request);
display = E_MAIL_DISPLAY (web_view);
if (display->priv->formatter == NULL)
return FALSE;
if (mail_display_process_mailto (E_WEB_VIEW (web_view), uri, NULL)) {
/* do nothing, function handled the "mailto:" uri already */
webkit_web_policy_decision_ignore (policy_decision);
return TRUE;
} else if (g_ascii_strncasecmp (uri, "thismessage:", 12) == 0) {
/* ignore */ ;
webkit_web_policy_decision_ignore (policy_decision);
return TRUE;
} else if (g_ascii_strncasecmp (uri, "cid:", 4) == 0) {
/* ignore */ ;
webkit_web_policy_decision_ignore (policy_decision);
return TRUE;
}
/* Let webkit handle it */
return FALSE;
}
static void
webkit_request_load_from_file (WebKitNetworkRequest *request,
const gchar *path)
{
gchar *data = NULL;
gsize length = 0;
gboolean status;
gchar *b64, *new_uri;
gchar *ct;
status = g_file_get_contents (path, &data, &length, NULL);
if (!status)
return;
b64 = g_base64_encode ((guchar *) data, length);
ct = g_content_type_guess (path, NULL, 0, NULL);
new_uri = g_strdup_printf ("data:%s;base64,%s", ct, b64);
webkit_network_request_set_uri (request, new_uri);
g_free (b64);
g_free (new_uri);
g_free (ct);
g_free (data);
}
static void
mail_display_resource_requested (WebKitWebView *web_view,
WebKitWebFrame *frame,
WebKitWebResource *resource,
WebKitNetworkRequest *request,
WebKitNetworkResponse *response,
gpointer user_data)
{
EMailDisplay *display = E_MAIL_DISPLAY (web_view);
EMFormat *formatter = EM_FORMAT (display->priv->formatter);
const gchar *uri = webkit_network_request_get_uri (request);
if (!formatter) {
webkit_network_request_set_uri (request, "invalid://uri");
return;
}
/* Redirect cid:part_id to mail://mail_id/cid:part_id */
if (g_str_has_prefix (uri, "cid:")) {
/* Always write raw content of CID object */
gchar *new_uri = em_format_build_mail_uri (formatter->folder,
formatter->message_uid,
"part_id", G_TYPE_STRING, uri,
"mode", G_TYPE_INT, EM_FORMAT_WRITE_MODE_RAW, NULL);
webkit_network_request_set_uri (request, new_uri);
g_free (new_uri);
/* WebKit won't allow to load a local file when displaing "remote" mail://,
protocol, so we need to handle this manually */
} else if (g_str_has_prefix (uri, "file:")) {
gchar *path;
path = g_filename_from_uri (uri, NULL, NULL);
if (!path)
return;
webkit_request_load_from_file (request, path);
g_free (path);
/* Redirect http(s) request to evo-http(s) protocol. See EMailRequest for
* further details about this. */
} else if (g_str_has_prefix (uri, "http:") || g_str_has_prefix (uri, "https")) {
gchar *new_uri, *mail_uri, *enc;
SoupURI *soup_uri;
GHashTable *query;
gchar *uri_md5;
CamelStream *stream;
/* Open Evolution's cache */
uri_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, uri, -1);
stream = camel_data_cache_get (
emd_global_http_cache, "http", uri_md5, NULL);
g_free (uri_md5);
/* If the URI is not cached and we are not allowed to load it
* then redirect to invalid URI, so that webkit would display
* a native placeholder for it. */
if (!stream && !display->priv->force_image_load &&
!em_format_html_can_load_images (display->priv->formatter)) {
webkit_network_request_set_uri (request, "invalid://protocol");
return;
}
new_uri = g_strconcat ("evo-", uri, NULL);
mail_uri = em_format_build_mail_uri (formatter->folder,
formatter->message_uid, NULL, NULL);
soup_uri = soup_uri_new (new_uri);
if (soup_uri->query) {
query = soup_form_decode (soup_uri->query);
} else {
query = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
}
enc = soup_uri_encode (mail_uri, NULL);
g_hash_table_insert (query, g_strdup ("__evo-mail"), enc);
if (display->priv->force_image_load) {
g_hash_table_insert (query,
g_strdup ("__evo-load-images"),
g_strdup ("true"));
}
g_free (mail_uri);
soup_uri_set_query_from_form (soup_uri, query);
g_free (new_uri);
new_uri = soup_uri_to_string (soup_uri, FALSE);
webkit_network_request_set_uri (request, new_uri);
g_free (new_uri);
soup_uri_free (soup_uri);
g_hash_table_unref (query);
}
}
static WebKitDOMElement *
find_element_by_id (WebKitDOMDocument *document,
const gchar *id)
{
WebKitDOMNodeList *frames;
WebKitDOMElement *element;
gulong i, length;
/* Try to look up the element in this DOM document */
element = webkit_dom_document_get_element_by_id (document, id);
if (element)
return element;
/* If the element is not here then recursively scan all frames */
frames = webkit_dom_document_get_elements_by_tag_name(document, "iframe");
length = webkit_dom_node_list_get_length (frames);
for (i = 0; i < length; i++) {
WebKitDOMHTMLIFrameElement *iframe =
WEBKIT_DOM_HTML_IFRAME_ELEMENT (
webkit_dom_node_list_item (frames, i));
WebKitDOMDocument *frame_doc =
webkit_dom_html_iframe_element_get_content_document (iframe);
WebKitDOMElement *el =
find_element_by_id (frame_doc, id);
if (el)
return el;
}
return NULL;
}
static void
mail_display_plugin_widget_resize (GObject *object,
gpointer dummy,
EMailDisplay *display)
{
GtkWidget *widget;
WebKitDOMElement *parent_element;
gchar *dim;
gint height;
widget = GTK_WIDGET (object);
gtk_widget_get_preferred_height (widget, &height, NULL);
parent_element = g_object_get_data (object, "parent_element");
if (!parent_element || !WEBKIT_DOM_IS_ELEMENT (parent_element)) {
d(printf("%s: %s does not have (valid) parent element!\n",
G_STRFUNC, (gchar *) g_object_get_data (object, "uri")));
return;
}
/* Int -> Str */
dim = g_strdup_printf ("%d", height);
/* Set height of the containment <object> to match height of the
* GtkWidget it contains */
webkit_dom_html_object_element_set_height (
WEBKIT_DOM_HTML_OBJECT_ELEMENT (parent_element), dim);
g_free (dim);
}
static void
mail_display_plugin_widget_realize_cb (GtkWidget *widget,
gpointer user_data)
{
/* Initial resize of the <object> element when the widget
* is displayed for the first time. */
mail_display_plugin_widget_resize (G_OBJECT (widget), NULL, user_data);
}
static void
plugin_widget_set_parent_element (GtkWidget *widget,
EMailDisplay *display)
{
const gchar *uri;
WebKitDOMDocument *document;
WebKitDOMElement *element;
uri = g_object_get_data (G_OBJECT (widget), "uri");
if (!uri || !*uri)
return;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (display));
element = find_element_by_id (document, uri);
if (!element || !WEBKIT_DOM_IS_ELEMENT (element)) {
g_warning ("Failed to find parent <object> for '%s' - no ID set?", uri);
return;
}
/* Assign the WebKitDOMElement to "parent_element" data of the GtkWidget
* and the GtkWidget to "widget" data of the DOM Element */
g_object_set_data (G_OBJECT (widget), "parent_element", element);
g_object_set_data (G_OBJECT (element), "widget", widget);
}
static void
attachment_button_expanded (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
EAttachmentButton *button = E_ATTACHMENT_BUTTON (object);
WebKitDOMElement *attachment = user_data;
WebKitDOMCSSStyleDeclaration *css;
gboolean expanded;
d(printf("Attachment button %s (%p) expansion state toggled!\n",
(gchar *) g_object_get_data (object, "uri"), object));
expanded = e_attachment_button_get_expanded (button) &&
gtk_widget_get_visible (GTK_WIDGET (button));
if (!WEBKIT_DOM_IS_ELEMENT (attachment)) {
d(printf("%s: Parent element for button %s does not exist!\n",
G_STRFUNC, (gchar *) g_object_get_data (object, "uri")));
return;
}
/* Show or hide the DIV which contains the attachment (iframe, image...) */
css = webkit_dom_element_get_style (attachment);
webkit_dom_css_style_declaration_set_property (
css, "display", expanded ? "block" : "none", "", NULL);
}
static void
constraint_widget_visibility (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GtkWidget *widget = GTK_WIDGET (object);
EAttachmentButton *button = user_data;
gboolean can_show = e_attachment_button_get_expanded (button);
gboolean is_visible = gtk_widget_get_visible (widget);
if (is_visible && !can_show)
gtk_widget_hide (widget);
else if (!is_visible && can_show)
gtk_widget_show (widget);
/* Otherwise it's OK */
}
static void
bind_iframe_content_visibility (EAttachmentButton *button,
WebKitDOMElement *iframe)
{
WebKitDOMDocument *document;
WebKitDOMNodeList *nodes;
gulong i, length;
if (!WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (iframe))
return;
document = webkit_dom_html_iframe_element_get_content_document (
WEBKIT_DOM_HTML_IFRAME_ELEMENT (iframe));
nodes = webkit_dom_document_get_elements_by_tag_name (document, "object");
length = webkit_dom_node_list_get_length (nodes);
d(printf("Found %ld objects within iframe %s\n", length,
webkit_dom_html_iframe_element_get_name (
WEBKIT_DOM_HTML_IFRAME_ELEMENT (iframe))));
/* Iterate through all <object>s and bind visibility of their widget
* with expanded-state of related attachment button */
for (i = 0; i < length; i++) {
WebKitDOMNode *node = webkit_dom_node_list_item (nodes, i);
GtkWidget *widget;
widget = g_object_get_data (G_OBJECT (node), "widget");
if (!widget)
continue;
d(printf("Binding visibility of widget %s (%p) with button %s (%p)\n",
(gchar *) g_object_get_data (G_OBJECT (widget), "uri"), widget,
(gchar *) g_object_get_data (G_OBJECT (button), "uri"), button));
g_object_bind_property (
button, "expanded",
widget, "visible",
G_BINDING_SYNC_CREATE);
/* Ensure that someone won't attempt to _show() the widget when
* it is supposed to be hidden and vice versa. */
g_signal_connect (widget, "notify::visible",
G_CALLBACK (constraint_widget_visibility), button);
}
}
static void
bind_attachment_iframe_visibility (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
WebKitWebFrame *webframe;
const gchar *frame_name;
gchar *button_uri;
WebKitDOMDocument *document;
WebKitDOMElement *attachment;
WebKitDOMElement *button_element;
WebKitDOMNodeList *nodes;
gulong i, length;
GtkWidget *button;
/* Whenever an <iframe> is loaded, bind visibility of all GtkWidgets
* the document within the <iframe> contains with "expanded" property
* of the EAttachmentButton */
webframe = WEBKIT_WEB_FRAME (object);
if (webkit_web_frame_get_load_status (webframe) != WEBKIT_LOAD_FINISHED)
return;
frame_name = webkit_web_frame_get_name (webframe);
d(printf("Rebinding visibility of frame %s because it's URL changed\n",
frame_name));
/* Get DOMDocument of the main document */
document = webkit_web_view_get_dom_document (
webkit_web_frame_get_web_view (webframe));
if (!document)
return;
/* Find the <DIV> containing the <iframe> and related EAttachmentButton
* within the DOM */
attachment = find_element_by_id (document, frame_name);
if (!attachment)
return;
button_uri = g_strconcat (frame_name, ".attachment_button", NULL);
button_element = find_element_by_id (document, button_uri);
g_free (button_uri);
if (!button_element)
return;
button = g_object_get_data (G_OBJECT (button_element), "widget");
/* Get <iframe> representing the attachment content */
nodes = webkit_dom_element_get_elements_by_tag_name (attachment, "iframe");
length = webkit_dom_node_list_get_length (nodes);
for (i = 0; i < length; i++) {
WebKitDOMNode *node =
webkit_dom_node_list_item (nodes, i);
if (!WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (node))
continue;
/* Bind visibility of all GtkWidget within the
* iframe with "expanded" property of the button */
bind_iframe_content_visibility (
E_ATTACHMENT_BUTTON (button),
WEBKIT_DOM_ELEMENT (node));
}
}
static GtkWidget *
mail_display_plugin_widget_requested (WebKitWebView *web_view,
gchar *mime_type,
gchar *uri,
GHashTable *param,
gpointer user_data)
{
EMFormat *emf;
EMailDisplay *display;
EMFormatPURI *puri;
GtkWidget *widget;
gchar *puri_uri;
puri_uri = g_hash_table_lookup (param, "data");
if (!puri_uri || !g_str_has_prefix (uri, "mail://"))
return NULL;
display = E_MAIL_DISPLAY (web_view);
emf = (EMFormat *) display->priv->formatter;
puri = em_format_find_puri (emf, puri_uri);
if (!puri) {
return NULL;
}
if (puri->widget_func)
widget = puri->widget_func (emf, puri, NULL);
else
widget = NULL;
if (!widget)
return NULL;
if (E_IS_ATTACHMENT_BUTTON (widget)) {
/* Attachment button has URI different then the actual PURI because
* that URI identifies the attachment itself */
gchar *button_uri = g_strconcat (puri_uri, ".attachment_button", NULL);
g_object_set_data_full (G_OBJECT (widget), "uri",
button_uri, (GDestroyNotify) g_free);
} else {
g_object_set_data_full (G_OBJECT (widget), "uri",
g_strdup (puri_uri), (GDestroyNotify) g_free);
}
/* Set widget's <object> container as GObject data "parent_element" */
plugin_widget_set_parent_element (widget, display);
/* Resizing a GtkWidget requires changing size of parent
* <object> HTML element in DOM. */
g_signal_connect (widget, "realize",
G_CALLBACK (mail_display_plugin_widget_realize_cb), display);
g_signal_connect (widget, "size-allocate",
G_CALLBACK (mail_display_plugin_widget_resize), display);
/* Embed the attachment bar into the GtkBox before we do anything
* further with the widget. */
if (E_IS_MAIL_ATTACHMENT_BAR (widget)) {
/* When EMailAttachmentBar is expanded/collapsed it does not
* emit size-allocate signal despite it changes it's height. */
GtkWidget *box = NULL;
/* Only when packed in box, EMailAttachmentBar reports correct
* height */
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
g_signal_connect (widget, "notify::expanded",
G_CALLBACK (mail_display_plugin_widget_resize), display);
g_signal_connect (widget, "notify::active-view",
G_CALLBACK (mail_display_plugin_widget_resize), display);
/* Show the EAttachmentBar but not the containing layout */
gtk_widget_show (widget);
widget = box;
} else if (E_IS_ATTACHMENT_BUTTON (widget)) {
/* Bind visibility of DOM element containing related
* attachment with 'expanded' property of this
* attachment button. */
WebKitDOMElement *attachment;
WebKitDOMDocument *document;
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (display));
attachment = find_element_by_id (document, puri_uri);
if (!attachment) {
e_attachment_button_set_expandable (
E_ATTACHMENT_BUTTON (widget), FALSE);
} else {
const CamelContentDisposition *disposition;
WebKitDOMNodeList *nodes;
gulong i, length;
/* Show/hide the attachment when the EAttachmentButton
* is expanded/collapsed or shown/hidden */
g_signal_connect_data (widget, "notify::expanded",
G_CALLBACK (attachment_button_expanded),
g_object_ref (attachment), (GClosureNotify) g_object_unref, 0);
g_signal_connect_data (widget, "notify::visible",
G_CALLBACK (attachment_button_expanded),
g_object_ref (attachment), (GClosureNotify) g_object_unref, 0);
/* Initial synchronization */
attachment_button_expanded (G_OBJECT (widget),
NULL, attachment);
/* Find all <iframes> within the attachment and bind
* it's visiblity to expanded state of the attachment btn */
nodes = webkit_dom_element_get_elements_by_tag_name (
attachment, "iframe");
length = webkit_dom_node_list_get_length (nodes);
for (i = 0; i < length; i++) {
WebKitDOMNode *node =
webkit_dom_node_list_item (nodes, i);
if (!WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (node))
continue;
bind_iframe_content_visibility (
E_ATTACHMENT_BUTTON (widget),
WEBKIT_DOM_ELEMENT (node));
}
/* Expand inlined attachments */
disposition =
camel_mime_part_get_content_disposition (puri->part);
if (disposition &&
g_ascii_strncasecmp (
disposition->disposition, "inline", 6) == 0) {
e_attachment_button_set_expanded (
E_ATTACHMENT_BUTTON (widget), TRUE);
}
}
}
d(printf("Created widget %s (%p)\n", puri_uri, widget));
return widget;
}
static void
toggle_headers_visibility (WebKitDOMElement *button,
WebKitDOMEvent *event,
WebKitWebView *web_view)
{
WebKitDOMDocument *document;
WebKitDOMElement *short_headers, *full_headers;
WebKitDOMCSSStyleDeclaration *css_short, *css_full;
gboolean expanded;
const gchar *path;
document = webkit_web_view_get_dom_document (web_view);
short_headers = webkit_dom_document_get_element_by_id (
document, "__evo-short-headers");
if (!short_headers)
return;
css_short = webkit_dom_element_get_style (short_headers);
full_headers = webkit_dom_document_get_element_by_id (
document, "__evo-full-headers");
if (!full_headers)
return;
css_full = webkit_dom_element_get_style (full_headers);
expanded = (g_strcmp0 (webkit_dom_css_style_declaration_get_property_value (
css_full, "display"), "block") == 0);
webkit_dom_css_style_declaration_set_property (css_full, "display",
expanded ? "none" : "block", "", NULL);
webkit_dom_css_style_declaration_set_property (css_short, "display",
expanded ? "block" : "none", "", NULL);
if (expanded)
path = "evo-file://" EVOLUTION_IMAGESDIR "/plus.png";
else
path = "evo-file://" EVOLUTION_IMAGESDIR "/minus.png";
webkit_dom_html_image_element_set_src (
WEBKIT_DOM_HTML_IMAGE_ELEMENT (button), path);
e_mail_display_set_headers_collapsed (E_MAIL_DISPLAY (web_view), expanded);
d(printf("Headers %s!\n", expanded ? "collapsed" : "expanded"));
}
static const gchar* addresses[] = { "to", "cc", "bcc" };
static void
toggle_address_visibility (WebKitDOMElement *button,
WebKitDOMEvent *event,
const gchar *address)
{
WebKitDOMElement *full_addr, *ellipsis;
WebKitDOMCSSStyleDeclaration *css_full, *css_ellipsis;
WebKitDOMDocument *document;
gchar *id;
const gchar *path;
gboolean expanded;
document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (button));
id = g_strconcat ("__evo-moreaddr-", address, NULL);
full_addr = webkit_dom_document_get_element_by_id (document, id);
g_free (id);
if (!full_addr)
return;
css_full = webkit_dom_element_get_style (full_addr);
id = g_strconcat ("__evo-moreaddr-ellipsis-", address, NULL);
ellipsis = webkit_dom_document_get_element_by_id (document, id);
g_free (id);
if (!ellipsis)
return;
css_ellipsis = webkit_dom_element_get_style (ellipsis);
expanded = (g_strcmp0 (
webkit_dom_css_style_declaration_get_property_value (
css_full, "display"), "inline") == 0);
webkit_dom_css_style_declaration_set_property (
css_full, "display", (expanded ? "none" : "inline"), "", NULL);
webkit_dom_css_style_declaration_set_property (
css_ellipsis, "display", (expanded ? "inline" : "none"), "", NULL);
if (expanded) {
path = "evo-file://" EVOLUTION_IMAGESDIR "/plus.png";
} else {
path = "evo-file://" EVOLUTION_IMAGESDIR "/minus.png";
}
if (!WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (button)) {
id = g_strconcat ("__evo-moreaddr-img-", address, NULL);
button = webkit_dom_document_get_element_by_id (document, id);
g_free (id);
if (!button)
return;
}
webkit_dom_html_image_element_set_src (
WEBKIT_DOM_HTML_IMAGE_ELEMENT (button), path);
}
static void
setup_DOM_bindings (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
WebKitWebView *web_view;
WebKitWebFrame *frame;
WebKitLoadStatus load_status;
WebKitDOMDocument *document;
WebKitDOMElement *button;
gint i = 0;
frame = WEBKIT_WEB_FRAME (object);
load_status = webkit_web_frame_get_load_status (frame);
if (load_status != WEBKIT_LOAD_FINISHED)
return;
web_view = webkit_web_frame_get_web_view (frame);
document = webkit_web_view_get_dom_document (web_view);
button = webkit_dom_document_get_element_by_id (
document, "__evo-collapse-headers-img");
if (!button)
return;
d(printf("Conntecting to __evo-collapsable-headers-img::click event\n"));
webkit_dom_event_target_add_event_listener (
WEBKIT_DOM_EVENT_TARGET (button), "click",
G_CALLBACK (toggle_headers_visibility), FALSE, web_view);
for (i = 0; i < 3; i++) {
gchar *id;
id = g_strconcat ("__evo-moreaddr-img-", addresses[i], NULL);
button = webkit_dom_document_get_element_by_id (document, id);
g_free (id);
if (!button)
continue;
webkit_dom_event_target_add_event_listener (
WEBKIT_DOM_EVENT_TARGET (button), "click",
G_CALLBACK (toggle_address_visibility), FALSE,
(gpointer) addresses[i]);
id = g_strconcat ("__evo-moreaddr-ellipsis-", addresses[i], NULL);
button = webkit_dom_document_get_element_by_id (document, id);
g_free (id);
if (!button)
continue;
webkit_dom_event_target_add_event_listener (
WEBKIT_DOM_EVENT_TARGET (button), "click",
G_CALLBACK (toggle_address_visibility), FALSE,
(gpointer) addresses[i]);
}
}
static void
puri_bind_dom (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
WebKitWebFrame *frame;
WebKitLoadStatus load_status;
WebKitWebView *web_view;
WebKitDOMDocument *document;
EMailDisplay *display;
GList *iter;
EMFormat *emf;
const gchar *frame_puri;
frame = WEBKIT_WEB_FRAME (object);
load_status = webkit_web_frame_get_load_status (frame);
if (load_status != WEBKIT_LOAD_FINISHED)
return;
frame_puri = webkit_web_frame_get_name (frame);
web_view = webkit_web_frame_get_web_view (frame);
display = E_MAIL_DISPLAY (web_view);
emf = EM_FORMAT (display->priv->formatter);
if (!emf)
return;
iter = g_hash_table_lookup (
emf->mail_part_table,
webkit_web_frame_get_name (frame));
document = webkit_web_view_get_dom_document (web_view);
while (iter) {
EMFormatPURI *puri = iter->data;
if (!puri)
continue;
/* Iterate only the PURI rendered in the frame and all it's "subPURIs" */
if (!g_str_has_prefix (puri->uri, frame_puri))
break;
if (puri->bind_func) {
WebKitDOMElement *el = find_element_by_id (document, puri->uri);
if (el) {
d(printf("bind_func for %s\n", puri->uri));
puri->bind_func (el, puri);
}
}
iter = iter->next;
}
}
static void
mail_display_frame_created (WebKitWebView *web_view,
WebKitWebFrame *frame,
gpointer user_data)
{
d(printf("Frame %s created!\n", webkit_web_frame_get_name (frame)));
/* Re-bind visibility of this newly created <iframe> with
* related EAttachmentButton whenever content of this <iframe> is
* (re)loaded */
g_signal_connect (frame, "notify::load-status",
G_CALLBACK (bind_attachment_iframe_visibility), NULL);
/* Call bind_func of all PURIs written in this frame */
g_signal_connect (frame, "notify::load-status",
G_CALLBACK (puri_bind_dom), NULL);
}
static void
e_mail_display_class_init (EMailDisplayClass *class)
{
GObjectClass *object_class;
GtkWidgetClass *widget_class;
parent_class = g_type_class_peek_parent (class);
g_type_class_add_private (class, sizeof (EMailDisplayPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = mail_display_set_property;
object_class->get_property = mail_display_get_property;
object_class->dispose = mail_display_dispose;
widget_class = GTK_WIDGET_CLASS (class);
widget_class->realize = mail_display_realize;
widget_class->style_set = mail_display_style_set;
g_object_class_install_property (
object_class,
PROP_FORMATTER,
g_param_spec_object (
"formatter",
"HTML Formatter",
NULL,
EM_TYPE_FORMAT_HTML,
G_PARAM_READWRITE));
g_object_class_install_property (
object_class,
PROP_MODE,
g_param_spec_int (
"mode",
"Display Mode",
NULL,
0,
G_MAXINT,
EM_FORMAT_WRITE_MODE_NORMAL,
G_PARAM_READWRITE));
g_object_class_install_property (
object_class,
PROP_HEADERS_COLLAPSABLE,
g_param_spec_boolean (
"headers-collapsable",
"Headers Collapsable",
NULL,
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (
object_class,
PROP_HEADERS_COLLAPSED,
g_param_spec_boolean (
"headers-collapsed",
"Headers Collapsed",
NULL,
FALSE,
G_PARAM_READWRITE));
}
static void
e_mail_display_init (EMailDisplay *display)
{
GtkUIManager *ui_manager;
GError *error = NULL;
SoupSession *session;
SoupSessionFeature *feature;
const gchar *user_cache_dir;
WebKitWebSettings *settings;
WebKitWebFrame *main_frame;
display->priv = E_MAIL_DISPLAY_GET_PRIVATE (display);
display->priv->force_image_load = FALSE;
display->priv->mailto_actions = gtk_action_group_new ("mailto");
gtk_action_group_add_actions (display->priv->mailto_actions, mailto_entries,
G_N_ELEMENTS (mailto_entries), NULL);
display->priv->images_actions = gtk_action_group_new ("image");
gtk_action_group_add_actions (display->priv->images_actions, image_entries,
G_N_ELEMENTS (image_entries), NULL);
webkit_web_view_set_full_content_zoom (WEBKIT_WEB_VIEW (display), TRUE);
settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (display));
g_object_set (settings, "enable-frame-flattening", TRUE, NULL);
g_signal_connect (display, "navigation-policy-decision-requested",
G_CALLBACK (mail_display_link_clicked), NULL);
g_signal_connect (display, "resource-request-starting",
G_CALLBACK (mail_display_resource_requested), NULL);
g_signal_connect (display, "process-mailto",
G_CALLBACK (mail_display_process_mailto), NULL);
g_signal_connect (display, "update-actions",
G_CALLBACK (mail_display_webview_update_actions), NULL);
g_signal_connect (display, "create-plugin-widget",
G_CALLBACK (mail_display_plugin_widget_requested), NULL);
g_signal_connect (display, "frame-created",
G_CALLBACK (mail_display_frame_created), NULL);
main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (display));
g_signal_connect (main_frame, "notify::load-status",
G_CALLBACK (setup_DOM_bindings), NULL);
main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (display));
g_signal_connect (main_frame, "notify::load-status",
G_CALLBACK (puri_bind_dom), NULL);
/* 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. */
ui_manager = e_web_view_get_ui_manager (E_WEB_VIEW (display));
gtk_ui_manager_insert_action_group (ui_manager, display->priv->mailto_actions, 0);
gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
if (error != NULL) {
g_error ("%s", error->message);
g_error_free (error);
}
error = NULL;
gtk_ui_manager_insert_action_group (ui_manager, display->priv->images_actions, 0);
gtk_ui_manager_add_ui_from_string (ui_manager, image_ui, -1, &error);
if (error != NULL) {
g_error ("%s", error->message);
g_error_free (error);
}
/* Register our own handler for our own mail:// protocol */
session = webkit_get_default_session ();
feature = SOUP_SESSION_FEATURE (soup_requester_new ());
soup_session_feature_add_feature (feature, E_TYPE_MAIL_REQUEST);
soup_session_add_feature (session, feature);
g_object_unref (feature);
/* cache expiry - 2 hour access, 1 day max */
user_cache_dir = e_get_user_cache_dir ();
emd_global_http_cache = camel_data_cache_new (user_cache_dir, NULL);
if (emd_global_http_cache) {
camel_data_cache_set_expire_age (emd_global_http_cache, 24 * 60 * 60);
camel_data_cache_set_expire_access (emd_global_http_cache, 2 * 60 * 60);
}
}
EMFormatHTML *
e_mail_display_get_formatter (EMailDisplay *display)
{
g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), NULL);
return display->priv->formatter;
}
void
e_mail_display_set_formatter (EMailDisplay *display,
EMFormatHTML *formatter)
{
g_return_if_fail (E_IS_MAIL_DISPLAY (display));
g_return_if_fail (EM_IS_FORMAT_HTML (formatter));
g_object_ref (formatter);
if (display->priv->formatter != NULL) {
/* The formatter might still exist after unrefing it, so
* we need to stop listening to it's request for redrawing */
g_signal_handlers_disconnect_by_func (
display->priv->formatter, e_mail_display_reload, display);
g_object_unref (display->priv->formatter);
}
display->priv->formatter = formatter;
mail_display_update_formatter_colors (display);
g_signal_connect (formatter, "notify::image-loading-policy",
G_CALLBACK (formatter_image_loading_policy_changed_cb), display);
g_signal_connect_swapped (formatter, "redraw-requested",
G_CALLBACK (e_mail_display_reload), display);
g_signal_connect_swapped (formatter, "notify::charset",
G_CALLBACK (e_mail_display_reload), display);
g_object_notify (G_OBJECT (display), "formatter");
}
EMFormatWriteMode
e_mail_display_get_mode (EMailDisplay *display)
{
g_return_val_if_fail (E_IS_MAIL_DISPLAY (display),
EM_FORMAT_WRITE_MODE_NORMAL);
return display->priv->mode;
}
void
e_mail_display_set_mode (EMailDisplay *display,
EMFormatWriteMode mode)
{
g_return_if_fail (E_IS_MAIL_DISPLAY (display));
if (display->priv->mode == mode)
return;
display->priv->mode = mode;
e_mail_display_reload (display);
g_object_notify (G_OBJECT (display), "mode");
}
gboolean
e_mail_display_get_headers_collapsable (EMailDisplay *display)
{
g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), FALSE);
return display->priv->headers_collapsable;
}
void
e_mail_display_set_headers_collapsable (EMailDisplay *display,
gboolean collapsable)
{
g_return_if_fail (E_IS_MAIL_DISPLAY (display));
if (display->priv->headers_collapsable == collapsable)
return;
display->priv->headers_collapsable = collapsable;
e_mail_display_reload (display);
g_object_notify (G_OBJECT (display), "headers-collapsable");
}
gboolean
e_mail_display_get_headers_collapsed (EMailDisplay *display)
{
g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), FALSE);
if (display->priv->headers_collapsable)
return display->priv->headers_collapsed;
return FALSE;
}
void
e_mail_display_set_headers_collapsed (EMailDisplay *display,
gboolean collapsed)
{
g_return_if_fail (E_IS_MAIL_DISPLAY (display));
if (display->priv->headers_collapsed == collapsed)
return;
display->priv->headers_collapsed = collapsed;
g_object_notify (G_OBJECT (display), "headers-collapsed");
}
void
e_mail_display_load (EMailDisplay *display,
const gchar *msg_uri)
{
EMFormat *emf;
gchar *uri;
g_return_if_fail (E_IS_MAIL_DISPLAY (display));
display->priv->force_image_load = FALSE;
emf = EM_FORMAT (display->priv->formatter);
uri = em_format_build_mail_uri (emf->folder, emf->message_uid,
"mode", G_TYPE_INT, display->priv->mode,
"headers_collapsable", G_TYPE_BOOLEAN, display->priv->headers_collapsable,
"headers_collapsed", G_TYPE_BOOLEAN, display->priv->headers_collapsed,
NULL);
e_web_view_load_uri (E_WEB_VIEW (display), uri);
g_free (uri);
}
void
e_mail_display_reload (EMailDisplay *display)
{
EWebView *web_view;
const gchar *uri;
gchar *base;
GString *new_uri;
GHashTable *table;
GHashTableIter table_iter;
gpointer key, val;
gchar separator;
g_return_if_fail (E_IS_MAIL_DISPLAY (display));
web_view = E_WEB_VIEW (display);
uri = e_web_view_get_uri (web_view);
if (!uri || !*uri)
return;
if (strstr(uri, "?") == NULL) {
e_web_view_reload (web_view);
return;
}
base = g_strndup (uri, strstr (uri, "?") - uri);
new_uri = g_string_new (base);
g_free (base);
table = soup_form_decode (strstr (uri, "?") + 1);
g_hash_table_insert (table, g_strdup ("mode"), g_strdup_printf ("%d", display->priv->mode));
g_hash_table_insert (table, g_strdup ("headers_collapsable"), g_strdup_printf ("%d", display->priv->headers_collapsable));
g_hash_table_insert (table, g_strdup ("headers_collapsed"), g_strdup_printf ("%d", display->priv->headers_collapsed));
g_hash_table_iter_init (&table_iter, table);
separator = '?';
while (g_hash_table_iter_next (&table_iter, &key, &val)) {
g_string_append_printf (new_uri, "%c%s=%s", separator,
(gchar *) key, (gchar *) val);
if (separator == '?')
separator = '&';
}
e_web_view_load_uri (web_view, new_uri->str);
g_string_free (new_uri, TRUE);
g_hash_table_destroy (table);
}
GtkAction *
e_mail_display_get_action (EMailDisplay *display,
const gchar *action_name)
{
GtkAction *action;
g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), NULL);
g_return_val_if_fail (action_name != NULL, NULL);
action = gtk_action_group_get_action (display->priv->mailto_actions, action_name);
if (!action)
action = gtk_action_group_get_action (display->priv->images_actions, action_name);
return action;
}
void
e_mail_display_set_status (EMailDisplay *display,
const gchar *status)
{
gchar *str;
g_return_if_fail (E_IS_MAIL_DISPLAY (display));
str = g_strdup_printf (
"<!DOCTYPE>"
"<html>"
"<head><title>Evolution Mail Display</title></head>"
"<body>"
"<table border=\"0\" width=\"100%%\" height=\"100%%\">"
"<tr height=\"100%%\" valign=\"middle\">"
"<td width=\"100%%\" align=\"center\">"
"<strong>%s</strong>"
"</td>"
"</tr>"
"</table>"
"</body>"
"</html>", status);
e_web_view_load_string (E_WEB_VIEW (display), str);
g_free (str);
gtk_widget_show_all (GTK_WIDGET (display));
}
gchar *
e_mail_display_get_selection_plain_text (EMailDisplay *display,
gint *len)
{
EWebView *web_view;
WebKitWebFrame *frame;
const gchar *frame_name;
GValue value = {0};
GType type;
const gchar *str;
g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), NULL);
web_view = E_WEB_VIEW (display);
frame = webkit_web_view_get_focused_frame (WEBKIT_WEB_VIEW (web_view));
frame_name = webkit_web_frame_get_name (frame);
type = e_web_view_frame_exec_script (web_view, frame_name, "window.getSelection().toString()", &value);
g_return_val_if_fail (type == G_TYPE_STRING, NULL);
str = g_value_get_string (&value);
if (len)
*len = strlen (str);
return g_strdup (str);
}
void
e_mail_display_load_images (EMailDisplay *display)
{
g_return_if_fail (E_IS_MAIL_DISPLAY (display));
display->priv->force_image_load = TRUE;
e_web_view_reload (E_WEB_VIEW (display));
}
void
e_mail_display_set_force_load_images (EMailDisplay *display,
gboolean force_load_images)
{
g_return_if_fail (E_IS_MAIL_DISPLAY (display));
display->priv->force_image_load = force_load_images;
}