/* * 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 * * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #include "e-mail-display.h" #include #include #include #include #include #include #include #include #include #include #include "e-http-request.h" #include "e-mail-display-popup-extension.h" #include "e-mail-request.h" #include "em-composer-utils.h" #include "em-utils.h" #define d(x) #define E_MAIL_DISPLAY_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_MAIL_DISPLAY, EMailDisplayPrivate)) struct _EMailDisplayPrivate { EMailPartList *part_list; EMailFormatterMode mode; EMailFormatter *formatter; gboolean headers_collapsable; gboolean headers_collapsed; gboolean force_image_load; GSettings *settings; GHashTable *widgets; guint scheduled_reload; }; enum { PROP_0, PROP_FORMATTER, PROP_HEADERS_COLLAPSABLE, PROP_HEADERS_COLLAPSED, PROP_MODE, PROP_PART_LIST }; static CamelDataCache *emd_global_http_cache = NULL; static const gchar *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 } }; G_DEFINE_TYPE ( EMailDisplay, e_mail_display, E_TYPE_WEB_VIEW); static void formatter_image_loading_policy_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data) { EMailDisplay *display = user_data; EMailFormatter *formatter = E_MAIL_FORMATTER (object); EMailImageLoadingPolicy policy; policy = e_mail_formatter_get_image_loading_policy (formatter); if (policy == E_MAIL_IMAGE_LOADING_POLICY_ALWAYS) e_mail_display_load_images (display); else e_mail_display_reload (display); } static gboolean mail_display_image_exists_in_cache (const gchar *image_uri) { gchar *filename; gchar *hash; gboolean exists = FALSE; g_return_val_if_fail (emd_global_http_cache != NULL, FALSE); hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, image_uri, -1); filename = camel_data_cache_get_filename ( emd_global_http_cache, "http", hash); if (filename != NULL) { exists = g_file_test (filename, G_FILE_TEST_EXISTS); g_free (filename); } g_free (hash); return exists; } static void mail_display_update_formatter_colors (EMailDisplay *display) { EMailFormatter *formatter; GtkStateFlags state_flags; formatter = display->priv->formatter; state_flags = gtk_widget_get_state_flags (GTK_WIDGET (display)); if (formatter != NULL) e_mail_formatter_update_style (formatter, state_flags); } static void mail_display_plugin_widget_disconnect_children (GtkWidget *widget, gpointer mail_display) { g_signal_handlers_disconnect_by_data (widget, mail_display); } static void mail_display_plugin_widget_disconnect (gpointer widget_uri, gpointer widget, gpointer mail_display) { if (E_IS_ATTACHMENT_BAR (widget)) g_signal_handlers_disconnect_by_data (widget, mail_display); else if (E_IS_ATTACHMENT_BUTTON (widget)) g_signal_handlers_disconnect_by_data (widget, mail_display); else if (GTK_IS_CONTAINER (widget)) gtk_container_foreach ( widget, mail_display_plugin_widget_disconnect_children, mail_display); } static gboolean mail_display_process_mailto (EWebView *web_view, const gchar *mailto_uri, gpointer user_data) { gboolean handled = FALSE; 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) { EShell *shell; EMailPartList *part_list; CamelFolder *folder; part_list = E_MAIL_DISPLAY (web_view)->priv->part_list; folder = e_mail_part_list_get_folder (part_list); shell = e_shell_get_default (); em_utils_compose_new_message_with_mailto ( shell, mailto_uri, folder); handled = TRUE; } return handled; } static gboolean mail_display_link_clicked (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) { const gchar *uri = webkit_network_request_get_uri (request); if (g_str_has_prefix (uri, "file://")) { gchar *filename; filename = g_filename_from_uri (uri, NULL, NULL); if (g_file_test (filename, G_FILE_TEST_IS_DIR)) { webkit_web_policy_decision_ignore (policy_decision); webkit_network_request_set_uri (request, "about:blank"); g_free (filename); return TRUE; } g_free (filename); } 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 mail_display_resource_requested (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitWebResource *resource, WebKitNetworkRequest *request, WebKitNetworkResponse *response, gpointer user_data) { const gchar *original_uri; original_uri = webkit_network_request_get_uri (request); if (original_uri != NULL) { gchar *redirected_uri; redirected_uri = e_web_view_redirect_uri ( E_WEB_VIEW (web_view), original_uri); webkit_network_request_set_uri (request, redirected_uri); g_free (redirected_uri); } } static WebKitDOMElement * find_element_by_id (WebKitDOMDocument *document, const gchar *id) { WebKitDOMNodeList *frames; WebKitDOMElement *element; gulong ii, length; if (!WEBKIT_DOM_IS_DOCUMENT (document)) return NULL; /* Try to look up the element in this DOM document */ element = webkit_dom_document_get_element_by_id (document, id); if (element != NULL) 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 (ii = 0; ii < length; ii++) { WebKitDOMHTMLIFrameElement *iframe; WebKitDOMDocument *frame_doc; WebKitDOMElement *element; iframe = WEBKIT_DOM_HTML_IFRAME_ELEMENT ( webkit_dom_node_list_item (frames, ii)); frame_doc = webkit_dom_html_iframe_element_get_content_document (iframe); element = find_element_by_id (frame_doc, id); if (element != NULL) return element; } return NULL; } static void mail_display_plugin_widget_resize (GtkWidget *widget, gpointer dummy, EMailDisplay *display) { WebKitDOMElement *parent_element; gchar *dim; gint height, width; gfloat scale; parent_element = g_object_get_data ( G_OBJECT (widget), "parent_element"); if (!WEBKIT_DOM_IS_ELEMENT (parent_element)) { d ( printf ("%s: %s does not have (valid) parent element!\n", G_STRFUNC, (gchar *) g_object_get_data (G_OBJECT (widget), "uri"))); return; } scale = webkit_web_view_get_zoom_level (WEBKIT_WEB_VIEW (display)); width = gtk_widget_get_allocated_width (widget); gtk_widget_get_preferred_height_for_width (widget, width, &height, NULL); /* When zooming WebKit does not change dimensions of the elements, * but only scales them on the canvas. GtkWidget can't be scaled * though so we need to cope with the dimension changes to keep the * the widgets the correct size. Due to inaccuracy in rounding * (float -> int) it still acts a bit funny, but at least it does * not cause widgets in WebKit to go crazy when zooming. */ height = height * (1 / scale); /* Int -> Str */ dim = g_strdup_printf ("%d", height); /* Set height of the containment 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) { WebKitDOMHTMLElement *element; if (GTK_IS_BOX (widget)) { GList *children; children = gtk_container_get_children (GTK_CONTAINER (widget)); if (children != NULL && E_IS_ATTACHMENT_BAR (children->data)) widget = children->data; g_list_free (children); } /* First check if we are actually supposed to be visible */ element = g_object_get_data (G_OBJECT (widget), "parent_element"); if (element == NULL || !WEBKIT_DOM_IS_HTML_ELEMENT (element)) { g_warn_if_reached (); } else if (webkit_dom_html_element_get_hidden (element)) { gtk_widget_hide (widget); return; } /* Initial resize of the element when the widget * is displayed for the first time. */ mail_display_plugin_widget_resize (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 == NULL || *uri == '\0') return; document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (display)); element = find_element_by_id (document, uri); if (!WEBKIT_DOM_IS_ELEMENT (element)) { g_warning ("Failed to find parent 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); g_object_bind_property ( element, "hidden", widget, "visible", G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); } static void toggle_widget_visibility (EAttachmentButton *button, EMailDisplay *display, WebKitDOMElement *element) { GtkWidget *widget = NULL; gchar *id; id = webkit_dom_html_element_get_id (WEBKIT_DOM_HTML_ELEMENT (element)); if (id == NULL || *id == '\0') return; if (display->priv->widgets != NULL) widget = g_hash_table_lookup (display->priv->widgets, id); g_free (id); if (widget == NULL) return; /* If the widget encapsulates EAttachmentBar then check, whether * the attachment bar is not empty. We want to display it only * when there's at least one attachment */ if (GTK_IS_BOX (widget)) { GList *children; children = gtk_container_get_children (GTK_CONTAINER (widget)); if (children != NULL && E_IS_ATTACHMENT_BAR (children->data)) { EAttachmentStore *store; store = e_attachment_bar_get_store ( E_ATTACHMENT_BAR (children->data)); g_list_free (children); /* Don't allow to display such attachment bar, * but always allow to hide it */ if (e_attachment_button_get_expanded (button) && (e_attachment_store_get_num_attachments (store) == 0)) return; else children = NULL; } g_list_free (children); } webkit_dom_html_element_set_hidden ( WEBKIT_DOM_HTML_ELEMENT (element), !e_attachment_button_get_expanded (button)); if (e_attachment_button_get_expanded (button)) gtk_widget_show (widget); else gtk_widget_hide (widget); } /** * @button: An #EAttachmentButton * @iframe: An iframe element containing document with an attachment * represented by the @button */ static void bind_iframe_content_visibility (WebKitDOMElement *iframe, EMailDisplay *display, EAttachmentButton *button) { WebKitDOMDocument *document; WebKitDOMNodeList *nodes; gulong ii, 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)); if (!WEBKIT_DOM_IS_DOCUMENT (document)) return; nodes = webkit_dom_document_get_elements_by_tag_name (document, "object"); length = webkit_dom_node_list_get_length (nodes); d ({ gchar *name = webkit_dom_html_iframe_element_get_name ( WEBKIT_DOM_HTML_IFRAME_ELEMENT (iframe)); printf ("Found %ld objects within iframe %s\n", length, name); g_free (name); }); /* Iterate through all s and bind visibility of their widget * with expanded-state of related attachment button */ for (ii = 0; ii < length; ii++) { WebKitDOMNode *node = webkit_dom_node_list_item (nodes, ii); /* Initial sync */ toggle_widget_visibility ( button, display, WEBKIT_DOM_ELEMENT (node)); } } static void attachment_button_expanded (GObject *object, GParamSpec *pspec, gpointer user_data) { EAttachmentButton *button = E_ATTACHMENT_BUTTON (object); EMailDisplay *display = user_data; WebKitDOMDocument *document; WebKitDOMElement *element; WebKitDOMCSSStyleDeclaration *css; const gchar *attachment_part_id; gchar *element_id; gboolean expanded; d ( printf ("Attachment button %s has been %s!\n", (gchar *) g_object_get_data (object, "uri"), (e_attachment_button_get_expanded (button) ? "expanded" : "collapsed"))); expanded = e_attachment_button_get_expanded (button) && gtk_widget_get_visible (GTK_WIDGET (button)); document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (display)); attachment_part_id = g_object_get_data (object, "attachment_id"); element_id = g_strconcat (attachment_part_id, ".wrapper", NULL); element = find_element_by_id (document, element_id); g_free (element_id); if (!WEBKIT_DOM_IS_ELEMENT (element)) { d ( printf ("%s: Content
of attachment %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 (element); webkit_dom_css_style_declaration_set_property ( css, "display", expanded ? "block" : "none", "", NULL); element_id = g_strconcat (attachment_part_id, ".iframe", NULL); element = find_element_by_id (document, element_id); g_free (element_id); if (!WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (element)) { d (printf ("%s: No