/* * 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 * */ #ifdef HAVE_CONFIG_H #include #endif #include "e-web-view.h" #include #include #include #include #include #include #include #define LIBSOUP_USE_UNSTABLE_REQUEST_API #include #include #include "e-alert-dialog.h" #include "e-alert-sink.h" #include "e-file-request.h" #include "e-misc-utils.h" #include "e-plugin-ui.h" #include "e-popup-action.h" #include "e-selectable.h" #include "e-stock-request.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; GdkPixbufAnimation *cursor_image; gchar *cursor_image_src; GQueue highlights; GtkAction *open_proxy; GtkAction *print_proxy; GtkAction *save_as_proxy; /* Lockdown Options */ guint disable_printing : 1; guint disable_save_to_disk : 1; guint caret_mode : 1; GSettings *font_settings; GSettings *aliasing_settings; }; enum { PROP_0, PROP_CARET_MODE, PROP_COPY_TARGET_LIST, PROP_CURSOR_IMAGE, PROP_CURSOR_IMAGE_SRC, PROP_DISABLE_PRINTING, PROP_DISABLE_SAVE_TO_DISK, PROP_INLINE_SPELLING, PROP_MAGIC_LINKS, PROP_MAGIC_SMILEYS, PROP_OPEN_PROXY, PROP_PRINT_PROXY, PROP_SAVE_AS_PROXY, PROP_SELECTED_URI }; enum { POPUP_EVENT, STATUS_MESSAGE, STOP_LOADING, UPDATE_ACTIONS, PROCESS_MAILTO, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; static GOnce disable_webkit_3rd_party_plugins_once = G_ONCE_INIT; static const gchar *ui = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; /* Forward Declarations */ static void e_web_view_alert_sink_init (EAlertSinkInterface *interface); static void e_web_view_selectable_init (ESelectableInterface *interface); G_DEFINE_TYPE_WITH_CODE ( EWebView, e_web_view, WEBKIT_TYPE_WEB_VIEW, G_IMPLEMENT_INTERFACE ( E_TYPE_EXTENSIBLE, NULL) G_IMPLEMENT_INTERFACE ( E_TYPE_ALERT_SINK, e_web_view_alert_sink_init) G_IMPLEMENT_INTERFACE ( E_TYPE_SELECTABLE, e_web_view_selectable_init)) static void action_copy_clipboard_cb (GtkAction *action, EWebView *web_view) { e_web_view_copy_clipboard (web_view); } 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_is_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; 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_format (CAMEL_ADDRESS (inet_addr)); if (text == NULL || *text == '\0') text = g_strdup (uri + strlen ("mailto:")); g_object_unref (inet_addr); camel_url_free (curl); clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY); gtk_clipboard_set_text (clipboard, text, -1); gtk_clipboard_store (clipboard); clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (clipboard, text, -1); gtk_clipboard_store (clipboard); g_free (text); } static void action_select_all_cb (GtkAction *action, EWebView *web_view) { e_web_view_select_all (web_view); } static void action_send_message_cb (GtkAction *action, EWebView *web_view) { const gchar *uri; gpointer parent; gboolean handled; parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; uri = e_web_view_get_selected_uri (web_view); g_return_if_fail (uri != NULL); handled = FALSE; g_signal_emit (web_view, signals[PROCESS_MAILTO], 0, uri, &handled); if (!handled) e_show_uri (parent, uri); } static void action_uri_copy_cb (GtkAction *action, 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 void action_image_copy_cb (GtkAction *action, EWebView *web_view) { GtkClipboard *clipboard; GdkPixbufAnimation *animation; GdkPixbuf *pixbuf; clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); animation = e_web_view_get_cursor_image (web_view); g_return_if_fail (animation != NULL); pixbuf = gdk_pixbuf_animation_get_static_image (animation); if (pixbuf == NULL) return; gtk_clipboard_set_image (clipboard, pixbuf); gtk_clipboard_store (clipboard); } static GtkActionEntry uri_entries[] = { { "uri-copy", GTK_STOCK_COPY, N_("_Copy Link Location"), NULL, N_("Copy the link to the clipboard"), G_CALLBACK (action_uri_copy_cb) } }; static GtkActionEntry http_entries[] = { { "http-open", "emblem-web", N_("_Open Link in Browser"), NULL, N_("Open the link in a web browser"), G_CALLBACK (action_http_open_cb) } }; static GtkActionEntry mailto_entries[] = { { "mailto-copy", GTK_STOCK_COPY, N_("_Copy Email Address"), NULL, N_("Copy the email address to the clipboard"), G_CALLBACK (action_mailto_copy_cb) }, { "send-message", "mail-message-new", N_("_Send New Message To..."), NULL, N_("Send a mail message to this address"), G_CALLBACK (action_send_message_cb) } }; static GtkActionEntry image_entries[] = { { "image-copy", GTK_STOCK_COPY, N_("_Copy Image"), NULL, N_("Copy the image to the clipboard"), G_CALLBACK (action_image_copy_cb) } }; static GtkActionEntry selection_entries[] = { { "copy-clipboard", GTK_STOCK_COPY, NULL, NULL, N_("Copy the selection"), G_CALLBACK (action_copy_clipboard_cb) }, }; static GtkActionEntry standard_entries[] = { { "select-all", GTK_STOCK_SELECT_ALL, NULL, NULL, N_("Select all text and images"), G_CALLBACK (action_select_all_cb) } }; static void web_view_menu_item_select_cb (EWebView *web_view, GtkWidget *widget) { GtkAction *action; GtkActivatable *activatable; const gchar *tooltip; activatable = GTK_ACTIVATABLE (widget); action = gtk_activatable_get_related_action (activatable); tooltip = gtk_action_get_tooltip (action); if (tooltip == NULL) return; e_web_view_status_message (web_view, tooltip); } static void web_view_update_document_highlights (EWebView *web_view) { GList *head, *link; head = g_queue_peek_head_link (&web_view->priv->highlights); for (link = head; link != NULL; link = g_list_next (link)) webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (web_view), link->data, FALSE, 0); webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (web_view), TRUE); } 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 GtkWidget * web_view_create_plugin_widget_cb (EWebView *web_view, const gchar *mime_type, const gchar *uri, GHashTable *param) { EWebViewClass *class; /* XXX WebKitWebView does not provide a class method for * this signal, so we do so we can override the default * behavior from subclasses for special URI types. */ class = E_WEB_VIEW_GET_CLASS (web_view); g_return_val_if_fail (class->create_plugin_widget != NULL, NULL); return class->create_plugin_widget (web_view, mime_type, uri, param); } static void web_view_hovering_over_link_cb (EWebView *web_view, const gchar *title, const gchar *uri) { EWebViewClass *class; /* XXX WebKitWebView does not provide a class method for * this signal, so we do so we can override the default * behavior from subclasses for special URI types. */ class = E_WEB_VIEW_GET_CLASS (web_view); g_return_if_fail (class->hovering_over_link != NULL); class->hovering_over_link (web_view, title, uri); } static gboolean web_view_navigation_policy_decision_requested_cb (EWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision) { EWebViewClass *class; WebKitWebNavigationReason reason; const gchar *uri; reason = webkit_web_navigation_action_get_reason (navigation_action); if (reason != WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) return FALSE; /* XXX WebKitWebView does not provide a class method for * this signal, so we do so we can override the default * behavior from subclasses for special URI types. */ class = E_WEB_VIEW_GET_CLASS (web_view); g_return_val_if_fail (class->link_clicked != NULL, FALSE); webkit_web_policy_decision_ignore (policy_decision); uri = webkit_network_request_get_uri (request); class->link_clicked (web_view, uri); return TRUE; } static void web_view_load_status_changed_cb (WebKitWebView *webkit_web_view, GParamSpec *pspec, gpointer user_data) { WebKitLoadStatus status; EWebView *web_view; status = webkit_web_view_get_load_status (webkit_web_view); if (status != WEBKIT_LOAD_FINISHED) return; web_view = E_WEB_VIEW (webkit_web_view); web_view_update_document_highlights (web_view); /* Workaround webkit bug https://bugs.webkit.org/show_bug.cgi?id=89553 */ e_web_view_zoom_in (web_view); e_web_view_zoom_out (web_view); } static void web_view_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CARET_MODE: e_web_view_set_caret_mode ( E_WEB_VIEW (object), g_value_get_boolean (value)); return; case PROP_CURSOR_IMAGE: e_web_view_set_cursor_image ( E_WEB_VIEW (object), g_value_get_object (value)); return; case PROP_CURSOR_IMAGE_SRC: e_web_view_set_cursor_image_src ( E_WEB_VIEW (object), g_value_get_string (value)); return; case PROP_DISABLE_PRINTING: e_web_view_set_disable_printing ( E_WEB_VIEW (object), g_value_get_boolean (value)); return; case PROP_DISABLE_SAVE_TO_DISK: e_web_view_set_disable_save_to_disk ( E_WEB_VIEW (object), g_value_get_boolean (value)); return; case PROP_INLINE_SPELLING: e_web_view_set_inline_spelling ( E_WEB_VIEW (object), g_value_get_boolean (value)); return; case PROP_MAGIC_LINKS: e_web_view_set_magic_links ( E_WEB_VIEW (object), g_value_get_boolean (value)); return; case PROP_MAGIC_SMILEYS: e_web_view_set_magic_smileys ( E_WEB_VIEW (object), g_value_get_boolean (value)); return; case PROP_OPEN_PROXY: e_web_view_set_open_proxy ( E_WEB_VIEW (object), g_value_get_object (value)); return; case PROP_PRINT_PROXY: e_web_view_set_print_proxy ( E_WEB_VIEW (object), g_value_get_object (value)); return; case PROP_SAVE_AS_PROXY: e_web_view_set_save_as_proxy ( E_WEB_VIEW (object), g_value_get_object (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_CARET_MODE: g_value_set_boolean ( value, e_web_view_get_caret_mode ( E_WEB_VIEW (object))); return; case PROP_CURSOR_IMAGE: g_value_set_object ( value, e_web_view_get_cursor_image ( E_WEB_VIEW (object))); return; case PROP_CURSOR_IMAGE_SRC: g_value_set_string ( value, e_web_view_get_cursor_image_src ( E_WEB_VIEW (object))); return; case PROP_DISABLE_PRINTING: g_value_set_boolean ( value, e_web_view_get_disable_printing ( E_WEB_VIEW (object))); return; case PROP_DISABLE_SAVE_TO_DISK: g_value_set_boolean ( value, e_web_view_get_disable_save_to_disk ( E_WEB_VIEW (object))); return; case PROP_INLINE_SPELLING: g_value_set_boolean ( value, e_web_view_get_inline_spelling ( E_WEB_VIEW (object))); return; case PROP_MAGIC_LINKS: g_value_set_boolean ( value, e_web_view_get_magic_links ( E_WEB_VIEW (object))); return; case PROP_MAGIC_SMILEYS: g_value_set_boolean ( value, e_web_view_get_magic_smileys ( E_WEB_VIEW (object))); return; case PROP_OPEN_PROXY: g_value_set_object ( value, e_web_view_get_open_proxy ( E_WEB_VIEW (object))); return; case PROP_PRINT_PROXY: g_value_set_object ( value, e_web_view_get_print_proxy ( E_WEB_VIEW (object))); return; case PROP_SAVE_AS_PROXY: g_value_set_object ( value, e_web_view_get_save_as_proxy ( 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; } if (priv->open_proxy != NULL) { g_object_unref (priv->open_proxy); priv->open_proxy = NULL; } if (priv->print_proxy != NULL) { g_object_unref (priv->print_proxy); priv->print_proxy = NULL; } if (priv->save_as_proxy != NULL) { g_object_unref (priv->save_as_proxy); priv->save_as_proxy = NULL; } if (priv->cursor_image != NULL) { g_object_unref (priv->cursor_image); priv->cursor_image = NULL; } if (priv->cursor_image_src != NULL) { g_free (priv->cursor_image_src); priv->cursor_image_src = NULL; } if (priv->aliasing_settings != NULL) { g_signal_handlers_disconnect_matched ( priv->aliasing_settings, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object); g_object_unref (priv->aliasing_settings); priv->aliasing_settings = NULL; } if (priv->font_settings != NULL) { g_signal_handlers_disconnect_matched ( priv->font_settings, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object); g_object_unref (priv->font_settings); priv->font_settings = NULL; } /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_web_view_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); while (!g_queue_is_empty (&priv->highlights)) g_free (g_queue_pop_head (&priv->highlights)); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_web_view_parent_class)->finalize (object); } static void web_view_constructed (GObject *object) { #ifndef G_OS_WIN32 GSettings *settings; settings = g_settings_new ("org.gnome.desktop.lockdown"); g_settings_bind ( settings, "disable-printing", object, "disable-printing", G_SETTINGS_BIND_GET); g_settings_bind ( settings, "disable-save-to-disk", object, "disable-save-to-disk", G_SETTINGS_BIND_GET); g_object_unref (settings); #endif e_extensible_load_extensions (E_EXTENSIBLE (object)); /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_web_view_parent_class)->constructed (object); } static gboolean web_view_context_menu_cb (WebKitWebView *webkit_web_view, GtkWidget *default_menu, WebKitHitTestResult *hit_test_result, gboolean triggered_with_keyboard) { WebKitHitTestResultContext context; EWebView *web_view; gboolean event_handled = FALSE; gchar *uri; web_view = E_WEB_VIEW (webkit_web_view); if (web_view->priv->cursor_image != NULL) { g_object_unref (web_view->priv->cursor_image); web_view->priv->cursor_image = NULL; } if (web_view->priv->cursor_image_src != NULL) { g_free (web_view->priv->cursor_image_src); web_view->priv->cursor_image_src = NULL; } if (hit_test_result == NULL) return FALSE; g_object_get (G_OBJECT (hit_test_result), "context", &context, NULL); if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) { WebKitWebDataSource *data_source; WebKitWebFrame *frame; GList *subresources, *res; g_object_get ( G_OBJECT (hit_test_result), "image-uri", &uri, NULL); if (uri == NULL) return FALSE; g_free (web_view->priv->cursor_image_src); web_view->priv->cursor_image_src = uri; /* Iterate through all resources of the loaded webpage and * try to find resource with URI matching cursor_image_src */ frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view)); data_source = webkit_web_frame_get_data_source (frame); subresources = webkit_web_data_source_get_subresources (data_source); for (res = subresources; res; res = res->next) { WebKitWebResource *src = res->data; GdkPixbufLoader *loader; GString *data; if (g_strcmp0 (webkit_web_resource_get_uri (src), web_view->priv->cursor_image_src) != 0) continue; data = webkit_web_resource_get_data (src); if (data == NULL) break; loader = gdk_pixbuf_loader_new (); if (!gdk_pixbuf_loader_write (loader, (guchar *) data->str, data->len, NULL)) { g_object_unref (loader); break; } gdk_pixbuf_loader_close (loader, NULL); if (web_view->priv->cursor_image != NULL) g_object_unref (web_view->priv->cursor_image); web_view->priv->cursor_image = g_object_ref (gdk_pixbuf_loader_get_animation (loader)); g_object_unref (loader); break; } g_list_free (subresources); } g_object_get (hit_test_result, "link-uri", &uri, NULL); if (!(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)) { g_free (uri); uri = NULL; } g_signal_emit (web_view, signals[POPUP_EVENT], 0, uri, &event_handled); g_free (uri); return event_handled; } static gboolean web_view_scroll_event (GtkWidget *widget, GdkEventScroll *event) { if (event->state & GDK_CONTROL_MASK) { GdkScrollDirection direction = event->direction; if (direction == GDK_SCROLL_SMOOTH) { static gdouble total_delta_y = 0.0; total_delta_y += event->delta_y; if (total_delta_y >= 1.0) { total_delta_y = 0.0; direction = GDK_SCROLL_DOWN; } else if (total_delta_y <= -1.0) { total_delta_y = 0.0; direction = GDK_SCROLL_UP; } else { return FALSE; } } switch (direction) { case GDK_SCROLL_UP: e_web_view_zoom_in (E_WEB_VIEW (widget)); return TRUE; case GDK_SCROLL_DOWN: e_web_view_zoom_out (E_WEB_VIEW (widget)); return TRUE; default: break; } } return FALSE; } static GtkWidget * web_view_create_plugin_widget (EWebView *web_view, const gchar *mime_type, const gchar *uri, GHashTable *param) { GtkWidget *widget = NULL; if (g_strcmp0 (mime_type, "image/x-themed-icon") == 0) { GtkIconTheme *icon_theme; GdkPixbuf *pixbuf; gpointer data; glong size = 0; GError *error = NULL; icon_theme = gtk_icon_theme_get_default (); if (size == 0) { data = g_hash_table_lookup (param, "width"); if (data != NULL) size = MAX (size, strtol (data, NULL, 10)); } if (size == 0) { data = g_hash_table_lookup (param, "height"); if (data != NULL) size = MAX (size, strtol (data, NULL, 10)); } if (size == 0) size = 32; /* arbitrary default */ pixbuf = gtk_icon_theme_load_icon ( icon_theme, uri, size, 0, &error); if (pixbuf != NULL) { widget = gtk_image_new_from_pixbuf (pixbuf); g_object_unref (pixbuf); } else if (error != NULL) { g_warning ("%s", error->message); g_error_free (error); } } return widget; } static gchar * web_view_extract_uri (EWebView *web_view, GdkEventButton *event) { WebKitHitTestResult *result; WebKitHitTestResultContext context; gchar *uri = NULL; result = webkit_web_view_get_hit_test_result ( WEBKIT_WEB_VIEW (web_view), event); g_object_get (result, "context", &context, "link-uri", &uri, NULL); g_object_unref (result); if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) return uri; g_free (uri); return NULL; } static void web_view_hovering_over_link (EWebView *web_view, const gchar *title, const gchar *uri) { CamelInternetAddress *address; CamelURL *curl; const gchar *format = NULL; gchar *message = NULL; gchar *who; if (uri == NULL || *uri == '\0') goto exit; if (g_str_has_prefix (uri, "mailto:")) format = _("Click to mail %s"); else if (g_str_has_prefix (uri, "callto:")) format = _("Click to call %s"); else if (g_str_has_prefix (uri, "h323:")) format = _("Click to call %s"); else if (g_str_has_prefix (uri, "sip:")) format = _("Click to call %s"); else if (g_str_has_prefix (uri, "##")) message = g_strdup (_("Click to hide/unhide addresses")); else message = g_strdup_printf (_("Click to open %s"), uri); if (format == NULL) goto exit; /* XXX Use something other than Camel here. Surely * there's other APIs around that can do this. */ curl = camel_url_new (uri, NULL); address = camel_internet_address_new (); camel_address_decode (CAMEL_ADDRESS (address), curl->path); who = camel_address_format (CAMEL_ADDRESS (address)); g_object_unref (address); camel_url_free (curl); if (who == NULL) who = g_strdup (strchr (uri, ':') + 1); message = g_strdup_printf (format, who); g_free (who); exit: e_web_view_status_message (web_view, message); g_free (message); } static void web_view_link_clicked (EWebView *web_view, const gchar *uri) { gpointer parent; if (uri && g_ascii_strncasecmp (uri, "mailto:", 7) == 0) { gboolean handled = FALSE; g_signal_emit ( web_view, signals[PROCESS_MAILTO], 0, uri, &handled); if (handled) return; } parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; e_show_uri (parent, uri); } static void web_view_load_string (EWebView *web_view, const gchar *string) { if (string == NULL) string = ""; webkit_web_view_load_string ( WEBKIT_WEB_VIEW (web_view), string, "text/html", "UTF-8", "evo-file:///"); } static void web_view_load_uri (EWebView *web_view, const gchar *uri) { if (uri == NULL) uri = "about:blank"; webkit_web_view_load_uri (WEBKIT_WEB_VIEW (web_view), uri); } static void web_view_frame_load_string (EWebView *web_view, const gchar *frame_name, const gchar *string) { WebKitWebFrame *main_frame; if (string == NULL) string = ""; main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view)); if (main_frame != NULL) { WebKitWebFrame *frame; frame = webkit_web_frame_find_frame (main_frame, frame_name); if (frame != NULL) webkit_web_frame_load_string ( frame, string, "text/html", "UTF-8", "evo-file:///"); } } static void web_view_frame_load_uri (EWebView *web_view, const gchar *frame_name, const gchar *uri) { WebKitWebFrame *main_frame; if (uri == NULL) uri = "about:blank"; main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view)); if (main_frame != NULL) { WebKitWebFrame *frame; frame = webkit_web_frame_find_frame (main_frame, frame_name); if (frame != NULL) webkit_web_frame_load_uri (frame, uri); } } static gboolean web_view_popup_event (EWebView *web_view, const gchar *uri) { e_web_view_set_selected_uri (web_view, uri); e_web_view_show_popup_menu (web_view); return TRUE; } static void web_view_stop_loading (EWebView *web_view) { webkit_web_view_stop_loading (WEBKIT_WEB_VIEW (web_view)); } static void web_view_update_actions (EWebView *web_view) { GtkActionGroup *action_group; gboolean can_copy; gboolean scheme_is_http = FALSE; gboolean scheme_is_mailto = FALSE; gboolean uri_is_valid = FALSE; gboolean has_cursor_image; gboolean visible; const gchar *group_name; const gchar *uri; uri = e_web_view_get_selected_uri (web_view); can_copy = webkit_web_view_can_copy_clipboard (WEBKIT_WEB_VIEW (web_view)); has_cursor_image = e_web_view_get_cursor_image_src (web_view) || e_web_view_get_cursor_image (web_view); /* Parse the URI early so we know if the actions will work. */ if (uri != NULL) { CamelURL *curl; curl = camel_url_new (uri, NULL); uri_is_valid = (curl != NULL); camel_url_free (curl); scheme_is_http = (g_ascii_strncasecmp (uri, "http:", 5) == 0) || (g_ascii_strncasecmp (uri, "https:", 6) == 0); scheme_is_mailto = (g_ascii_strncasecmp (uri, "mailto:", 7) == 0); } /* Allow copying the URI even if it's malformed. */ group_name = "uri"; visible = (uri != NULL) && !scheme_is_mailto; action_group = e_web_view_get_action_group (web_view, group_name); gtk_action_group_set_visible (action_group, visible); group_name = "http"; visible = uri_is_valid && scheme_is_http; action_group = e_web_view_get_action_group (web_view, group_name); gtk_action_group_set_visible (action_group, visible); group_name = "mailto"; visible = uri_is_valid && scheme_is_mailto; action_group = e_web_view_get_action_group (web_view, group_name); gtk_action_group_set_visible (action_group, visible); group_name = "image"; visible = has_cursor_image; action_group = e_web_view_get_action_group (web_view, group_name); gtk_action_group_set_visible (action_group, visible); group_name = "selection"; visible = can_copy; action_group = e_web_view_get_action_group (web_view, group_name); gtk_action_group_set_visible (action_group, visible); group_name = "standard"; visible = (uri == NULL); action_group = e_web_view_get_action_group (web_view, group_name); gtk_action_group_set_visible (action_group, visible); group_name = "lockdown-printing"; visible = (uri == NULL) && !web_view->priv->disable_printing; action_group = e_web_view_get_action_group (web_view, group_name); gtk_action_group_set_visible (action_group, visible); group_name = "lockdown-save-to-disk"; visible = (uri == NULL) && !web_view->priv->disable_save_to_disk; action_group = e_web_view_get_action_group (web_view, group_name); gtk_action_group_set_visible (action_group, visible); } static void web_view_submit_alert (EAlertSink *alert_sink, EAlert *alert) { EWebView *web_view; GtkWidget *dialog; GString *buffer; const gchar *icon_name = NULL; gpointer parent; web_view = E_WEB_VIEW (alert_sink); parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view)); parent = gtk_widget_is_toplevel (parent) ? parent : NULL; switch (e_alert_get_message_type (alert)) { case GTK_MESSAGE_INFO: icon_name = GTK_STOCK_DIALOG_INFO; break; case GTK_MESSAGE_WARNING: icon_name = GTK_STOCK_DIALOG_WARNING; break; case GTK_MESSAGE_ERROR: icon_name = GTK_STOCK_DIALOG_ERROR; break; default: dialog = e_alert_dialog_new (parent, alert); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); return; } buffer = g_string_sized_new (512); g_string_append ( buffer, "" "" "" "" ""); g_string_append ( buffer, "" "" "" "" ""); g_string_append_printf ( buffer, "" "" "" "" "" "%s" "%s" "" "", icon_name, GTK_ICON_SIZE_DIALOG, e_alert_get_primary_text (alert), e_alert_get_secondary_text (alert)); g_string_append ( buffer, "" "" "" "" "" ""); e_web_view_load_string (web_view, buffer->str); g_string_free (buffer, TRUE); } static void web_view_selectable_update_actions (ESelectable *selectable, EFocusTracker *focus_tracker, GdkAtom *clipboard_targets, gint n_clipboard_targets) { WebKitWebView *web_view; GtkAction *action; gboolean sensitive; const gchar *tooltip; web_view = WEBKIT_WEB_VIEW (selectable); action = e_focus_tracker_get_cut_clipboard_action (focus_tracker); sensitive = webkit_web_view_can_cut_clipboard (web_view); tooltip = _("Cut the selection"); gtk_action_set_sensitive (action, sensitive); gtk_action_set_tooltip (action, tooltip); action = e_focus_tracker_get_copy_clipboard_action (focus_tracker); sensitive = webkit_web_view_can_copy_clipboard (web_view); tooltip = _("Copy the selection"); gtk_action_set_sensitive (action, sensitive); gtk_action_set_tooltip (action, tooltip); action = e_focus_tracker_get_paste_clipboard_action (focus_tracker); sensitive = webkit_web_view_can_paste_clipboard (web_view); tooltip = _("Paste the clipboard"); gtk_action_set_sensitive (action, sensitive); gtk_action_set_tooltip (action, tooltip); action = e_focus_tracker_get_select_all_action (focus_tracker); sensitive = TRUE; tooltip = _("Select all text and images"); gtk_action_set_sensitive (action, sensitive); gtk_action_set_tooltip (action, tooltip); } static void web_view_selectable_cut_clipboard (ESelectable *selectable) { e_web_view_cut_clipboard (E_WEB_VIEW (selectable)); } static void web_view_selectable_copy_clipboard (ESelectable *selectable) { e_web_view_copy_clipboard (E_WEB_VIEW (selectable)); } static void web_view_selectable_paste_clipboard (ESelectable *selectable) { e_web_view_paste_clipboard (E_WEB_VIEW (selectable)); } static void web_view_selectable_select_all (ESelectable *selectable) { e_web_view_select_all (E_WEB_VIEW (selectable)); } static gboolean web_view_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time_) { return FALSE; } static gpointer web_view_disable_webkit_3rd_party_plugins (gpointer unused) { WebKitWebPluginDatabase *database; GSList *installed_plugins, *iterator; database = webkit_get_web_plugin_database (); if (!database) return NULL; installed_plugins = webkit_web_plugin_database_get_plugins (database); if (!installed_plugins) return NULL; for (iterator = installed_plugins; iterator; iterator = iterator->next) webkit_web_plugin_set_enabled (iterator->data, FALSE); webkit_web_plugin_database_plugins_list_free (installed_plugins); return NULL; } static void e_web_view_class_init (EWebViewClass *class) { GObjectClass *object_class; GtkWidgetClass *widget_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; object_class->constructed = web_view_constructed; widget_class = GTK_WIDGET_CLASS (class); widget_class->scroll_event = web_view_scroll_event; widget_class->drag_motion = web_view_drag_motion; #if 0 /* WEBKIT */ html_class = GTK_HTML_CLASS (class); html_class->url_requested = web_view_url_requested; #endif class->create_plugin_widget = web_view_create_plugin_widget; class->extract_uri = web_view_extract_uri; class->hovering_over_link = web_view_hovering_over_link; class->link_clicked = web_view_link_clicked; class->load_string = web_view_load_string; class->load_uri = web_view_load_uri; class->frame_load_string = web_view_frame_load_string; class->frame_load_uri = web_view_frame_load_uri; class->popup_event = web_view_popup_event; class->stop_loading = web_view_stop_loading; class->update_actions = web_view_update_actions; g_object_class_install_property ( object_class, PROP_CARET_MODE, g_param_spec_boolean ( "caret-mode", "Caret Mode", NULL, FALSE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_CURSOR_IMAGE, g_param_spec_object ( "cursor-image", "Image animation at the mouse cursor", NULL, GDK_TYPE_PIXBUF_ANIMATION, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_CURSOR_IMAGE_SRC, g_param_spec_string ( "cursor-image-src", "Image source uri at the mouse cursor", NULL, NULL, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_DISABLE_PRINTING, g_param_spec_boolean ( "disable-printing", "Disable Printing", NULL, FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property ( object_class, PROP_DISABLE_SAVE_TO_DISK, g_param_spec_boolean ( "disable-save-to-disk", "Disable Save-to-Disk", NULL, FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property ( object_class, PROP_INLINE_SPELLING, g_param_spec_boolean ( "inline-spelling", "Inline Spelling", NULL, FALSE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_MAGIC_LINKS, g_param_spec_boolean ( "magic-links", "Magic Links", NULL, FALSE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_MAGIC_SMILEYS, g_param_spec_boolean ( "magic-smileys", "Magic Smileys", NULL, FALSE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_OPEN_PROXY, g_param_spec_object ( "open-proxy", "Open Proxy", NULL, GTK_TYPE_ACTION, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_PRINT_PROXY, g_param_spec_object ( "print-proxy", "Print Proxy", NULL, GTK_TYPE_ACTION, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_SAVE_AS_PROXY, g_param_spec_object ( "save-as-proxy", "Save As Proxy", NULL, GTK_TYPE_ACTION, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_SELECTED_URI, g_param_spec_string ( "selected-uri", "Selected URI", NULL, NULL, G_PARAM_READWRITE)); 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__STRING, G_TYPE_BOOLEAN, 1, 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); /* return TRUE when a signal handler processed the mailto URI */ signals[PROCESS_MAILTO] = g_signal_new ( "process-mailto", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EWebViewClass, process_mailto), NULL, NULL, e_marshal_BOOLEAN__STRING, G_TYPE_BOOLEAN, 1, G_TYPE_STRING); webkit_set_cache_model (WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER); webkit_set_default_web_database_quota (0); webkit_application_cache_set_maximum_size (0); } static void e_web_view_alert_sink_init (EAlertSinkInterface *interface) { interface->submit_alert = web_view_submit_alert; } static void e_web_view_selectable_init (ESelectableInterface *interface) { interface->update_actions = web_view_selectable_update_actions; interface->cut_clipboard = web_view_selectable_cut_clipboard; interface->copy_clipboard = web_view_selectable_copy_clipboard; interface->paste_clipboard = web_view_selectable_paste_clipboard; interface->select_all = web_view_selectable_select_all; } static void e_web_view_init (EWebView *web_view) { GtkUIManager *ui_manager; GtkActionGroup *action_group; EPopupAction *popup_action; WebKitWebSettings *web_settings; GSettingsSchema *settings_schema; GSettings *settings; const gchar *domain = GETTEXT_PACKAGE; const gchar *id; GError *error = NULL; g_once ( &disable_webkit_3rd_party_plugins_once, web_view_disable_webkit_3rd_party_plugins, NULL); web_view->priv = E_WEB_VIEW_GET_PRIVATE (web_view); g_signal_connect ( web_view, "create-plugin-widget", G_CALLBACK (web_view_create_plugin_widget_cb), NULL); g_signal_connect ( web_view, "hovering-over-link", G_CALLBACK (web_view_hovering_over_link_cb), NULL); g_signal_connect ( web_view, "navigation-policy-decision-requested", G_CALLBACK (web_view_navigation_policy_decision_requested_cb), NULL); g_signal_connect ( web_view, "new-window-policy-decision-requested", G_CALLBACK (web_view_navigation_policy_decision_requested_cb), NULL); g_signal_connect ( web_view, "context-menu", G_CALLBACK (web_view_context_menu_cb), NULL); g_signal_connect ( web_view, "notify::load-status", G_CALLBACK (web_view_load_status_changed_cb), NULL); ui_manager = gtk_ui_manager_new (); web_view->priv->ui_manager = ui_manager; g_signal_connect_swapped ( ui_manager, "connect-proxy", G_CALLBACK (web_view_connect_proxy_cb), web_view); web_settings = e_web_view_get_default_settings (); e_web_view_set_settings (web_view, web_settings); g_object_unref (web_settings); e_web_view_install_request_handler (web_view, E_TYPE_FILE_REQUEST); e_web_view_install_request_handler (web_view, E_TYPE_STOCK_REQUEST); settings = g_settings_new ("org.gnome.desktop.interface"); g_signal_connect_swapped ( settings, "changed::font-name", G_CALLBACK (e_web_view_update_fonts), web_view); g_signal_connect_swapped ( settings, "changed::monospace-font-name", G_CALLBACK (e_web_view_update_fonts), web_view); web_view->priv->font_settings = settings; /* This schema is optional. Use if available. */ id = "org.gnome.settings-daemon.plugins.xsettings"; settings_schema = g_settings_schema_source_lookup ( g_settings_schema_source_get_default (), id, FALSE); if (settings_schema != NULL) { settings = g_settings_new (id); g_signal_connect_swapped ( settings, "changed::antialiasing", G_CALLBACK (e_web_view_update_fonts), web_view); web_view->priv->aliasing_settings = settings; } e_web_view_update_fonts (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); action_group = gtk_action_group_new ("image"); gtk_action_group_set_translation_domain (action_group, domain); gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); g_object_unref (action_group); gtk_action_group_add_actions ( action_group, image_entries, G_N_ELEMENTS (image_entries), web_view); action_group = gtk_action_group_new ("selection"); gtk_action_group_set_translation_domain (action_group, domain); gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); g_object_unref (action_group); gtk_action_group_add_actions ( action_group, selection_entries, G_N_ELEMENTS (selection_entries), web_view); action_group = gtk_action_group_new ("standard"); gtk_action_group_set_translation_domain (action_group, domain); gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); g_object_unref (action_group); gtk_action_group_add_actions ( action_group, standard_entries, G_N_ELEMENTS (standard_entries), web_view); popup_action = e_popup_action_new ("open"); gtk_action_group_add_action (action_group, GTK_ACTION (popup_action)); g_object_unref (popup_action); g_object_bind_property ( web_view, "open-proxy", popup_action, "related-action", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); /* Support lockdown. */ action_group = gtk_action_group_new ("lockdown-printing"); gtk_action_group_set_translation_domain (action_group, domain); gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); g_object_unref (action_group); popup_action = e_popup_action_new ("print"); gtk_action_group_add_action (action_group, GTK_ACTION (popup_action)); g_object_unref (popup_action); g_object_bind_property ( web_view, "print-proxy", popup_action, "related-action", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); action_group = gtk_action_group_new ("lockdown-save-to-disk"); gtk_action_group_set_translation_domain (action_group, domain); gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); g_object_unref (action_group); popup_action = e_popup_action_new ("save-as"); gtk_action_group_add_action (action_group, GTK_ACTION (popup_action)); g_object_unref (popup_action); g_object_bind_property ( web_view, "save-as-proxy", popup_action, "related-action", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); /* Because we are loading from a hard-coded string, there is * no chance of I/O errors. Failure here implies a malformed * UI definition. Full stop. */ gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error); if (error != NULL) g_error ("%s", error->message); id = "org.gnome.evolution.webview"; e_plugin_ui_register_manager (ui_manager, id, web_view); e_plugin_ui_enable_manager (ui_manager, id); } GtkWidget * e_web_view_new (void) { return g_object_new (E_TYPE_WEB_VIEW, NULL); } void e_web_view_clear (EWebView *web_view) { GtkStyle *style; gchar *html; g_return_if_fail (E_IS_WEB_VIEW (web_view)); style = gtk_widget_get_style (GTK_WIDGET (web_view)); html = g_strdup_printf ( "", e_color_to_value (&style->base[GTK_STATE_NORMAL])); webkit_web_view_load_html_string ( WEBKIT_WEB_VIEW (web_view), html, NULL); g_free (html); } void e_web_view_load_string (EWebView *web_view, const gchar *string) { EWebViewClass *class; g_return_if_fail (E_IS_WEB_VIEW (web_view)); class = E_WEB_VIEW_GET_CLASS (web_view); g_return_if_fail (class->load_string != NULL); class->load_string (web_view, string); } void e_web_view_load_uri (EWebView *web_view, const gchar *uri) { EWebViewClass *class; g_return_if_fail (E_IS_WEB_VIEW (web_view)); class = E_WEB_VIEW_GET_CLASS (web_view); g_return_if_fail (class->load_uri != NULL); class->load_uri (web_view, uri); } void e_web_view_reload (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); webkit_web_view_reload (WEBKIT_WEB_VIEW (web_view)); } const gchar * e_web_view_get_uri (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); return webkit_web_view_get_uri (WEBKIT_WEB_VIEW (web_view)); } void e_web_view_frame_load_string (EWebView *web_view, const gchar *frame_name, const gchar *string) { EWebViewClass *class; g_return_if_fail (E_IS_WEB_VIEW (web_view)); g_return_if_fail (frame_name != NULL); class = E_WEB_VIEW_GET_CLASS (web_view); g_return_if_fail (class->frame_load_string != NULL); class->frame_load_string (web_view, frame_name, string); } void e_web_view_frame_load_uri (EWebView *web_view, const gchar *frame_name, const gchar *uri) { EWebViewClass *class; g_return_if_fail (E_IS_WEB_VIEW (web_view)); g_return_if_fail (frame_name != NULL); class = E_WEB_VIEW_GET_CLASS (web_view); g_return_if_fail (class->frame_load_uri != NULL); class->frame_load_uri (web_view, frame_name, uri); } const gchar * e_web_view_frame_get_uri (EWebView *web_view, const gchar *frame_name) { WebKitWebFrame *main_frame; g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); g_return_val_if_fail (frame_name != NULL, NULL); main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view)); if (main_frame != NULL) { WebKitWebFrame *frame; frame = webkit_web_frame_find_frame (main_frame, frame_name); if (frame != NULL) return webkit_web_frame_get_uri (frame); } return NULL; } gchar * e_web_view_get_html (EWebView *web_view) { WebKitDOMDocument *document; WebKitDOMElement *element; g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (web_view)); element = webkit_dom_document_get_document_element (document); return webkit_dom_html_element_get_outer_html ( WEBKIT_DOM_HTML_ELEMENT (element)); } gboolean e_web_view_get_caret_mode (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return web_view->priv->caret_mode; } void e_web_view_set_caret_mode (EWebView *web_view, gboolean caret_mode) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); if (web_view->priv->caret_mode == caret_mode) return; web_view->priv->caret_mode = caret_mode; g_object_notify (G_OBJECT (web_view), "caret-mode"); } GtkTargetList * e_web_view_get_copy_target_list (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); return webkit_web_view_get_copy_target_list ( WEBKIT_WEB_VIEW (web_view)); } gboolean e_web_view_get_disable_printing (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return web_view->priv->disable_printing; } void e_web_view_set_disable_printing (EWebView *web_view, gboolean disable_printing) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); if (web_view->priv->disable_printing == disable_printing) return; web_view->priv->disable_printing = disable_printing; g_object_notify (G_OBJECT (web_view), "disable-printing"); } gboolean e_web_view_get_disable_save_to_disk (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return web_view->priv->disable_save_to_disk; } void e_web_view_set_disable_save_to_disk (EWebView *web_view, gboolean disable_save_to_disk) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); if (web_view->priv->disable_save_to_disk == disable_save_to_disk) return; web_view->priv->disable_save_to_disk = disable_save_to_disk; g_object_notify (G_OBJECT (web_view), "disable-save-to-disk"); } gboolean e_web_view_get_enable_frame_flattening (EWebView *web_view) { WebKitWebSettings *settings; gboolean flattening; /* Return TRUE with fail since it's default value we set in _init(). */ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), TRUE); settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view)); g_return_val_if_fail (settings != NULL, TRUE); g_object_get ( G_OBJECT (settings), "enable-frame-flattening", &flattening, NULL); return flattening; } void e_web_view_set_enable_frame_flattening (EWebView *web_view, gboolean enable_frame_flattening) { WebKitWebSettings *settings; g_return_if_fail (E_IS_WEB_VIEW (web_view)); settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view)); g_return_if_fail (settings != NULL); g_object_set ( G_OBJECT (settings), "enable-frame-flattening", enable_frame_flattening, NULL); } gboolean e_web_view_get_editable (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return webkit_web_view_get_editable (WEBKIT_WEB_VIEW (web_view)); } void e_web_view_set_editable (EWebView *web_view, gboolean editable) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); webkit_web_view_set_editable (WEBKIT_WEB_VIEW (web_view), editable); } gboolean e_web_view_get_inline_spelling (EWebView *web_view) { #if 0 /* WEBKIT - XXX No equivalent property? */ /* XXX This is just here to maintain symmetry * with e_web_view_set_inline_spelling(). */ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return gtk_html_get_inline_spelling (GTK_HTML (web_view)); #endif return FALSE; } void e_web_view_set_inline_spelling (EWebView *web_view, gboolean inline_spelling) { #if 0 /* WEBKIT - XXX No equivalent property? */ /* XXX GtkHTML does not utilize GObject properties as well * as it could. This just wraps gtk_html_set_inline_spelling() * so we get a "notify::inline-spelling" signal. */ g_return_if_fail (E_IS_WEB_VIEW (web_view)); gtk_html_set_inline_spelling (GTK_HTML (web_view), inline_spelling); g_object_notify (G_OBJECT (web_view), "inline-spelling"); #endif } gboolean e_web_view_get_magic_links (EWebView *web_view) { #if 0 /* WEBKIT - XXX No equivalent property? */ /* XXX This is just here to maintain symmetry * with e_web_view_set_magic_links(). */ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return gtk_html_get_magic_links (GTK_HTML (web_view)); #endif return FALSE; } void e_web_view_set_magic_links (EWebView *web_view, gboolean magic_links) { #if 0 /* WEBKIT - XXX No equivalent property? */ /* XXX GtkHTML does not utilize GObject properties as well * as it could. This just wraps gtk_html_set_magic_links() * so we can get a "notify::magic-links" signal. */ g_return_if_fail (E_IS_WEB_VIEW (web_view)); gtk_html_set_magic_links (GTK_HTML (web_view), magic_links); g_object_notify (G_OBJECT (web_view), "magic-links"); #endif } gboolean e_web_view_get_magic_smileys (EWebView *web_view) { #if 0 /* WEBKIT - No equivalent property? */ /* XXX This is just here to maintain symmetry * with e_web_view_set_magic_smileys(). */ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return gtk_html_get_magic_smileys (GTK_HTML (web_view)); #endif return FALSE; } void e_web_view_set_magic_smileys (EWebView *web_view, gboolean magic_smileys) { #if 0 /* WEBKIT - No equivalent property? */ /* XXX GtkHTML does not utilize GObject properties as well * as it could. This just wraps gtk_html_set_magic_smileys() * so we can get a "notify::magic-smileys" signal. */ g_return_if_fail (E_IS_WEB_VIEW (web_view)); gtk_html_set_magic_smileys (GTK_HTML (web_view), magic_smileys); g_object_notify (G_OBJECT (web_view), "magic-smileys"); #endif } const gchar * 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)); if (g_strcmp0 (web_view->priv->selected_uri, selected_uri) == 0) return; g_free (web_view->priv->selected_uri); web_view->priv->selected_uri = g_strdup (selected_uri); g_object_notify (G_OBJECT (web_view), "selected-uri"); } GdkPixbufAnimation * e_web_view_get_cursor_image (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); return web_view->priv->cursor_image; } void e_web_view_set_cursor_image (EWebView *web_view, GdkPixbufAnimation *image) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); if (web_view->priv->cursor_image == image) return; if (image != NULL) g_object_ref (image); if (web_view->priv->cursor_image != NULL) g_object_unref (web_view->priv->cursor_image); web_view->priv->cursor_image = image; g_object_notify (G_OBJECT (web_view), "cursor-image"); } const gchar * e_web_view_get_cursor_image_src (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); return web_view->priv->cursor_image_src; } void e_web_view_set_cursor_image_src (EWebView *web_view, const gchar *src_uri) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); if (g_strcmp0 (web_view->priv->cursor_image_src, src_uri) == 0) return; g_free (web_view->priv->cursor_image_src); web_view->priv->cursor_image_src = g_strdup (src_uri); g_object_notify (G_OBJECT (web_view), "cursor-image-src"); } GtkAction * e_web_view_get_open_proxy (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return web_view->priv->open_proxy; } void e_web_view_set_open_proxy (EWebView *web_view, GtkAction *open_proxy) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); if (web_view->priv->open_proxy == open_proxy) return; if (open_proxy != NULL) { g_return_if_fail (GTK_IS_ACTION (open_proxy)); g_object_ref (open_proxy); } if (web_view->priv->open_proxy != NULL) g_object_unref (web_view->priv->open_proxy); web_view->priv->open_proxy = open_proxy; g_object_notify (G_OBJECT (web_view), "open-proxy"); } GtkTargetList * e_web_view_get_paste_target_list (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); return webkit_web_view_get_paste_target_list ( WEBKIT_WEB_VIEW (web_view)); } GtkAction * e_web_view_get_print_proxy (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return web_view->priv->print_proxy; } void e_web_view_set_print_proxy (EWebView *web_view, GtkAction *print_proxy) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); if (web_view->priv->print_proxy == print_proxy) return; if (print_proxy != NULL) { g_return_if_fail (GTK_IS_ACTION (print_proxy)); g_object_ref (print_proxy); } if (web_view->priv->print_proxy != NULL) g_object_unref (web_view->priv->print_proxy); web_view->priv->print_proxy = print_proxy; g_object_notify (G_OBJECT (web_view), "print-proxy"); } GtkAction * e_web_view_get_save_as_proxy (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return web_view->priv->save_as_proxy; } void e_web_view_set_save_as_proxy (EWebView *web_view, GtkAction *save_as_proxy) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); if (web_view->priv->save_as_proxy == save_as_proxy) return; if (save_as_proxy != NULL) { g_return_if_fail (GTK_IS_ACTION (save_as_proxy)); g_object_ref (save_as_proxy); } if (web_view->priv->save_as_proxy != NULL) g_object_unref (web_view->priv->save_as_proxy); web_view->priv->save_as_proxy = save_as_proxy; g_object_notify (G_OBJECT (web_view), "save-as-proxy"); } void e_web_view_add_highlight (EWebView *web_view, const gchar *highlight) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); g_return_if_fail (highlight && *highlight); g_queue_push_tail ( &web_view->priv->highlights, g_strdup (highlight)); webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (web_view), highlight, FALSE, 0); webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (web_view), TRUE); } void e_web_view_clear_highlights (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (web_view)); while (!g_queue_is_empty (&web_view->priv->highlights)) g_free (g_queue_pop_head (&web_view->priv->highlights)); } void e_web_view_update_highlights (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); web_view_update_document_highlights (web_view); } 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) { EWebViewClass *class; g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); 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); } void e_web_view_copy_clipboard (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (web_view)); } void e_web_view_cut_clipboard (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); webkit_web_view_cut_clipboard (WEBKIT_WEB_VIEW (web_view)); } gboolean e_web_view_is_selection_active (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (web_view)); } void e_web_view_paste_clipboard (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); webkit_web_view_paste_clipboard (WEBKIT_WEB_VIEW (web_view)); } gboolean e_web_view_scroll_forward (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); webkit_web_view_move_cursor ( WEBKIT_WEB_VIEW (web_view), GTK_MOVEMENT_PAGES, 1); return TRUE; /* XXX This means nothing. */ } gboolean e_web_view_scroll_backward (EWebView *web_view) { g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); webkit_web_view_move_cursor ( WEBKIT_WEB_VIEW (web_view), GTK_MOVEMENT_PAGES, -1); return TRUE; /* XXX This means nothing. */ } void e_web_view_select_all (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); webkit_web_view_select_all (WEBKIT_WEB_VIEW (web_view)); } void e_web_view_unselect_all (EWebView *web_view) { #if 0 /* WEBKIT */ g_return_if_fail (E_IS_WEB_VIEW (web_view)); gtk_html_command (GTK_HTML (web_view), "unselect-all"); #endif } void e_web_view_zoom_100 (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (web_view), 1.0); } void e_web_view_zoom_in (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); webkit_web_view_zoom_in (WEBKIT_WEB_VIEW (web_view)); } void e_web_view_zoom_out (EWebView *web_view) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); webkit_web_view_zoom_out (WEBKIT_WEB_VIEW (web_view)); } 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) { 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); gtk_menu_popup ( GTK_MENU (menu), NULL, NULL, NULL, NULL, 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); } static gchar * web_view_get_frame_selection_html (WebKitDOMElement *iframe) { WebKitDOMDocument *document; WebKitDOMDOMWindow *window; WebKitDOMDOMSelection *selection; WebKitDOMNodeList *frames; gulong ii, length; document = webkit_dom_html_iframe_element_get_content_document ( WEBKIT_DOM_HTML_IFRAME_ELEMENT (iframe)); window = webkit_dom_document_get_default_view (document); selection = webkit_dom_dom_window_get_selection (window); if (selection && (webkit_dom_dom_selection_get_range_count (selection) > 0)) { WebKitDOMRange *range; WebKitDOMElement *element; WebKitDOMDocumentFragment *fragment; range = webkit_dom_dom_selection_get_range_at (selection, 0, NULL); if (range != NULL) { fragment = webkit_dom_range_clone_contents ( range, NULL); element = webkit_dom_document_create_element ( document, "DIV", NULL); webkit_dom_node_append_child ( WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (fragment), NULL); return webkit_dom_html_element_get_inner_html ( WEBKIT_DOM_HTML_ELEMENT (element)); } } 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++) { WebKitDOMNode *node; gchar *text; node = webkit_dom_node_list_item (frames, ii); text = web_view_get_frame_selection_html ( WEBKIT_DOM_ELEMENT (node)); if (text != NULL) return text; } return NULL; } gchar * e_web_view_get_selection_html (EWebView *web_view) { WebKitDOMDocument *document; WebKitDOMNodeList *frames; gulong ii, length; g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); if (!webkit_web_view_has_selection (WEBKIT_WEB_VIEW (web_view))) return NULL; document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (web_view)); 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++) { gchar *text; WebKitDOMNode *node; node = webkit_dom_node_list_item (frames, ii); text = web_view_get_frame_selection_html ( WEBKIT_DOM_ELEMENT (node)); if (text != NULL) return text; } return NULL; } void e_web_view_set_settings (EWebView *web_view, WebKitWebSettings *settings) { g_return_if_fail (E_IS_WEB_VIEW (web_view)); if (settings == webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view))) return; g_object_bind_property ( settings, "enable-caret-browsing", web_view, "caret-mode", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); webkit_web_view_set_settings (WEBKIT_WEB_VIEW (web_view), settings); } WebKitWebSettings * e_web_view_get_default_settings (void) { WebKitWebSettings *settings; GObjectClass *class; GParamSpec *pspec; settings = webkit_web_settings_new (); g_object_set ( G_OBJECT (settings), "enable-frame-flattening", TRUE, "enable-java-applet", FALSE, "enable-html5-database", FALSE, "enable-html5-local-storage", FALSE, "enable-offline-web-application-cache", FALSE, "enable-site-specific-quirks", TRUE, "enable-scripts", FALSE, NULL); /* This property was introduced in WebKitGTK 2.0, * so check for it and enable it if it's present. */ class = G_OBJECT_GET_CLASS (settings); pspec = g_object_class_find_property ( class, "respect-image-orientation"); if (pspec != NULL) { g_object_set ( G_OBJECT (settings), pspec->name, TRUE, NULL); } return settings; } void e_web_view_update_fonts (EWebView *web_view) { EWebViewClass *class; GString *stylesheet; gchar *base64; gchar *aa = NULL; WebKitWebSettings *settings; PangoFontDescription *min_size, *ms, *vw; const gchar *styles[] = { "normal", "oblique", "italic" }; const gchar *smoothing = NULL; GtkStyleContext *context; GdkColor *link = NULL; GdkColor *visited = NULL; g_return_if_fail (E_IS_WEB_VIEW (web_view)); ms = NULL; vw = NULL; class = E_WEB_VIEW_GET_CLASS (web_view); if (class->set_fonts != NULL) class->set_fonts (web_view, &ms, &vw); if (ms == NULL) { gchar *font; font = g_settings_get_string ( web_view->priv->font_settings, "monospace-font-name"); ms = pango_font_description_from_string ( (font != NULL) ? font : "monospace 10"); g_free (font); } if (vw == NULL) { gchar *font; font = g_settings_get_string ( web_view->priv->font_settings, "font-name"); vw = pango_font_description_from_string ( (font != NULL) ? font : "serif 10"); g_free (font); } if (pango_font_description_get_size (ms) < pango_font_description_get_size (vw)) { min_size = ms; } else { min_size = vw; } stylesheet = g_string_new (""); g_string_append_printf ( stylesheet, "body {\n" " font-family: '%s';\n" " font-size: %dpt;\n" " font-weight: %d;\n" " font-style: %s;\n", pango_font_description_get_family (vw), pango_font_description_get_size (vw) / PANGO_SCALE, pango_font_description_get_weight (vw), styles[pango_font_description_get_style (vw)]); if (web_view->priv->aliasing_settings != NULL) aa = g_settings_get_string ( web_view->priv->aliasing_settings, "antialiasing"); if (g_strcmp0 (aa, "none") == 0) smoothing = "none"; else if (g_strcmp0 (aa, "grayscale") == 0) smoothing = "antialiased"; else if (g_strcmp0 (aa, "rgba") == 0) smoothing = "subpixel-antialiased"; if (smoothing != NULL) g_string_append_printf ( stylesheet, " -webkit-font-smoothing: %s;\n", smoothing); g_free (aa); g_string_append (stylesheet, "}\n"); g_string_append_printf ( stylesheet, "pre,code,.pre {\n" " font-family: '%s';\n" " font-size: %dpt;\n" " font-weight: %d;\n" " font-style: %s;\n" "}", pango_font_description_get_family (ms), pango_font_description_get_size (ms) / PANGO_SCALE, pango_font_description_get_weight (ms), styles[pango_font_description_get_style (ms)]); context = gtk_widget_get_style_context (GTK_WIDGET (web_view)); gtk_style_context_get_style ( context, "link-color", &link, "visited-link-color", &visited, NULL); if (link == NULL) { link = g_slice_new0 (GdkColor); link->blue = G_MAXINT16; } if (visited == NULL) { visited = g_slice_new0 (GdkColor); visited->red = G_MAXINT16; } g_string_append_printf ( stylesheet, "a {\n" " color: #%06x;\n" "}\n" "a:visited {\n" " color: #%06x;\n" "}\n", e_color_to_value (link), e_color_to_value (visited)); gdk_color_free (link); gdk_color_free (visited); base64 = g_base64_encode ((guchar *) stylesheet->str, stylesheet->len); g_string_free (stylesheet, TRUE); stylesheet = g_string_new ("data:text/css;charset=utf-8;base64,"); g_string_append (stylesheet, base64); g_free (base64); settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view)); g_object_set ( G_OBJECT (settings), "default-font-size", pango_font_description_get_size (vw) / PANGO_SCALE, "default-font-family", pango_font_description_get_family (vw), "monospace-font-family", pango_font_description_get_family (ms), "default-monospace-font-size", (pango_font_description_get_size (ms) / PANGO_SCALE), "minimum-font-size", (pango_font_description_get_size (min_size) / PANGO_SCALE), "user-stylesheet-uri", stylesheet->str, NULL); g_string_free (stylesheet, TRUE); pango_font_description_free (ms); pango_font_description_free (vw); } void e_web_view_install_request_handler (EWebView *web_view, GType handler_type) { SoupSession *session; SoupSessionFeature *feature; gboolean new; session = webkit_get_default_session (); feature = soup_session_get_feature (session, SOUP_TYPE_REQUESTER); new = FALSE; if (feature == NULL) { feature = SOUP_SESSION_FEATURE (soup_requester_new ()); soup_session_add_feature (session, feature); new = TRUE; } soup_session_feature_add_feature (feature, handler_type); if (new) { g_object_unref (feature); } }