/* * image-inline.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) * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include static gint org_gnome_image_inline_classid; /* Forward Declarations */ void org_gnome_image_inline_format (gpointer ep, EMFormatHookTarget *target); typedef struct _ImageInlinePObject ImageInlinePObject; struct _ImageInlinePObject { EMFormatHTMLPObject object; CamelMimePart *mime_part; GdkPixbuf *pixbuf; GtkWidget *widget; }; static void auto_rotate (ImageInlinePObject *image_object) { GdkPixbuf *pixbuf; GdkPixbufRotation rotation; const gchar *orientation; gboolean flip; /* Check for an "orientation" pixbuf option and honor it. */ pixbuf = image_object->pixbuf; orientation = gdk_pixbuf_get_option (pixbuf, "orientation"); if (orientation == NULL) return; switch (strtol (orientation, NULL, 10)) { case 1: /* top - left */ rotation = GDK_PIXBUF_ROTATE_NONE; flip = FALSE; break; case 2: /* top - right */ rotation = GDK_PIXBUF_ROTATE_NONE; flip = TRUE; break; case 3: /* bottom - right */ rotation = GDK_PIXBUF_ROTATE_UPSIDEDOWN; flip = FALSE; break; case 4: /* bottom - left */ rotation = GDK_PIXBUF_ROTATE_UPSIDEDOWN; flip = TRUE; break; case 5: /* left/top */ rotation = GDK_PIXBUF_ROTATE_CLOCKWISE; flip = TRUE; break; case 6: /* right/top */ rotation = GDK_PIXBUF_ROTATE_CLOCKWISE; flip = FALSE; break; case 7: /* right/bottom */ rotation = GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE; flip = TRUE; break; case 8: /* left/bottom */ rotation = GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE; flip = FALSE; break; default: g_return_if_reached (); } if (rotation != GDK_PIXBUF_ROTATE_NONE) { pixbuf = gdk_pixbuf_rotate_simple (pixbuf, rotation); g_return_if_fail (pixbuf != NULL); g_object_unref (image_object->pixbuf); image_object->pixbuf = pixbuf; } if (flip) { pixbuf = gdk_pixbuf_flip (pixbuf, TRUE); g_return_if_fail (pixbuf != NULL); g_object_unref (image_object->pixbuf); image_object->pixbuf = pixbuf; } } static void set_drag_source (GtkImageView *image_view) { GtkTargetEntry *targets; GtkTargetList *list; gint n_targets; list = gtk_target_list_new (NULL, 0); gtk_target_list_add_uri_targets (list, 0); targets = gtk_target_table_new_from_list (list, &n_targets); gtk_drag_source_set ( GTK_WIDGET (image_view), GDK_BUTTON1_MASK, targets, n_targets, GDK_ACTION_COPY); gtk_target_table_free (targets, n_targets); gtk_target_list_unref (list); } static gboolean button_press_press_cb (GtkImageView *image_view, GdkEventButton *event, ImageInlinePObject *image_object) { if (event->type != GDK_2BUTTON_PRESS) return FALSE; if (gtk_image_view_get_zoom (image_view) < 1.0) { gtk_image_view_set_zoom (image_view, 1.0); gtk_drag_source_unset (GTK_WIDGET (image_view)); } else { gtk_image_view_set_fitting (image_view, TRUE); set_drag_source (image_view); } return TRUE; } static void drag_data_get_cb (GtkImageView *image_view, GdkDragContext *context, GtkSelectionData *selection, guint info, guint time, ImageInlinePObject *image_object) { EMFormatHTMLDisplay *html_display; EAttachmentStore *store; EAttachmentView *view; EAttachment *attachment = NULL; GtkTreeRowReference *reference; GtkTreePath *path; GList *list, *iter; /* XXX This illustrates the lack of integration between EMFormat * and EAttachment, in that we now have to search through the * attachment store to find an attachment whose CamelMimePart * matches ours. This allows us to defer to EAttachmentView * for the drag-data-get implementation. */ html_display = EM_FORMAT_HTML_DISPLAY (image_object->object.format); view = em_format_html_display_get_attachment_view (html_display); store = e_attachment_view_get_store (view); list = e_attachment_store_get_attachments (store); for (iter = list; iter != NULL; iter = iter->next) { CamelMimePart *mime_part; attachment = E_ATTACHMENT (iter->data); mime_part = e_attachment_get_mime_part (attachment); if (mime_part == image_object->mime_part) { g_object_ref (attachment); break; } attachment = NULL; } g_list_foreach (list, (GFunc) g_object_unref, NULL); g_list_free (list); /* Make sure we found an EAttachment to select. */ g_return_if_fail (E_IS_ATTACHMENT (attachment)); /* Now select its path in the attachment store. */ reference = e_attachment_get_reference (attachment); g_return_if_fail (gtk_tree_row_reference_valid (reference)); path = gtk_tree_row_reference_get_path (reference); e_attachment_view_unselect_all (view); e_attachment_view_select_path (view, path); gtk_tree_path_free (path); /* Let EAttachmentView handle the rest. */ e_attachment_view_drag_data_get ( view, context, selection, info, time); } static void size_allocate_cb (GtkHTMLEmbedded *embedded, GtkAllocation *allocation, ImageInlinePObject *image_object) { GtkWidget *widget; gint pixbuf_width; gint pixbuf_height; gint widget_width; gint widget_height; gdouble zoom = 1.0; widget = GTK_WIDGET (image_object->object.format->html); widget_width = widget->allocation.width - 12; pixbuf_width = gdk_pixbuf_get_width (image_object->pixbuf); pixbuf_height = gdk_pixbuf_get_height (image_object->pixbuf); if (pixbuf_width > widget_width) zoom = (gdouble) widget_width / pixbuf_width; widget_width = MIN (widget_width, pixbuf_width); widget_height = (gint) (zoom * pixbuf_height); gtk_widget_set_size_request ( image_object->widget, widget_width, widget_height); } static void mouse_wheel_scroll_cb (GtkWidget *image_view, GdkScrollDirection direction, ImageInlinePObject *image_object) { GtkOrientation orientation; GtkScrollType scroll_type; GtkHTML *html; gint steps = 2; html = image_object->object.format->html; switch (direction) { case GDK_SCROLL_UP: orientation = GTK_ORIENTATION_VERTICAL; scroll_type = GTK_SCROLL_STEP_BACKWARD; break; case GDK_SCROLL_DOWN: orientation = GTK_ORIENTATION_VERTICAL; scroll_type = GTK_SCROLL_STEP_FORWARD; break; case GDK_SCROLL_LEFT: orientation = GTK_ORIENTATION_HORIZONTAL; scroll_type = GTK_SCROLL_STEP_BACKWARD; break; case GDK_SCROLL_RIGHT: orientation = GTK_ORIENTATION_HORIZONTAL; scroll_type = GTK_SCROLL_STEP_FORWARD; break; default: g_return_if_reached (); } while (steps > 0) { g_signal_emit_by_name ( html, "scroll", orientation, scroll_type, 2.0, NULL); steps--; } } static void org_gnome_image_inline_pobject_free (EMFormatHTMLPObject *object) { ImageInlinePObject *image_object; image_object = (ImageInlinePObject *) object; if (image_object->mime_part != NULL) { camel_object_unref (image_object->mime_part); image_object->mime_part = NULL; } if (image_object->pixbuf != NULL) { g_object_unref (image_object->pixbuf); image_object->pixbuf = NULL; } if (image_object->widget != NULL) { GtkWidget *parent; g_signal_handlers_disconnect_by_func ( image_object->widget, button_press_press_cb, image_object); g_signal_handlers_disconnect_by_func ( image_object->widget, drag_data_get_cb, image_object); g_signal_handlers_disconnect_by_func ( image_object->widget, mouse_wheel_scroll_cb, image_object); parent = gtk_widget_get_parent (image_object->widget); if (parent != NULL) g_signal_handlers_disconnect_by_func ( parent, size_allocate_cb, image_object); g_object_unref (image_object->widget); image_object->widget = NULL; } } static void org_gnome_image_inline_decode (ImageInlinePObject *image_object) { GdkPixbuf *pixbuf; GdkPixbufLoader *loader; CamelDataWrapper *data_wrapper; CamelMimePart *mime_part; CamelMedium *medium; CamelStream *stream; GByteArray *array; GError *error = NULL; array = g_byte_array_new (); mime_part = image_object->mime_part; medium = CAMEL_MEDIUM (mime_part); /* Stream takes ownership of the byte array. */ stream = camel_stream_mem_new_with_byte_array (array); data_wrapper = camel_medium_get_content (medium); camel_data_wrapper_decode_to_stream (data_wrapper, stream); /* Don't trust the content type in the MIME part. It could * be lying or it could be "application/octet-stream". Let * the GtkPixbufLoader figure it out. */ loader = gdk_pixbuf_loader_new (); gdk_pixbuf_loader_write (loader, array->data, array->len, &error); if (error != NULL) { g_warning ("%s", error->message); g_error_free (error); goto exit; } pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); if (pixbuf != NULL) { image_object->pixbuf = g_object_ref (pixbuf); auto_rotate (image_object); } gdk_pixbuf_loader_close (loader, &error); if (error != NULL) { g_warning ("%s", error->message); g_error_free (error); goto exit; } exit: camel_object_unref (stream); g_object_unref (loader); } static gboolean org_gnome_image_inline_embed (EMFormatHTML *format, GtkHTMLEmbedded *embedded, EMFormatHTMLPObject *object) { ImageInlinePObject *image_object; GtkImageView *image_view; GtkWidget *container; GtkWidget *widget; image_object = (ImageInlinePObject *) object; if (image_object->pixbuf == NULL) return FALSE; container = GTK_WIDGET (embedded); widget = gtk_image_view_new (); gtk_container_add (GTK_CONTAINER (container), widget); image_object->widget = g_object_ref (widget); gtk_widget_show (widget); image_view = GTK_IMAGE_VIEW (widget); gtk_image_view_set_pixbuf ( image_view, image_object->pixbuf, TRUE); set_drag_source (image_view); g_signal_connect ( image_view, "button-press-event", G_CALLBACK (button_press_press_cb), image_object); g_signal_connect ( image_view, "drag-data-get", G_CALLBACK (drag_data_get_cb), image_object); g_signal_connect ( embedded, "size-allocate", G_CALLBACK (size_allocate_cb), image_object); g_signal_connect ( image_view, "mouse-wheel-scroll", G_CALLBACK (mouse_wheel_scroll_cb), image_object); return TRUE; } void org_gnome_image_inline_format (gpointer ep, EMFormatHookTarget *target) { ImageInlinePObject *image_object; gchar *classid; classid = g_strdup_printf ( "org-gnome-image-inline-display-%d", org_gnome_image_inline_classid++); image_object = (ImageInlinePObject *) em_format_html_add_pobject ( EM_FORMAT_HTML (target->format), sizeof (ImageInlinePObject), classid, target->part, org_gnome_image_inline_embed); camel_object_ref (target->part); image_object->mime_part = target->part; image_object->object.free = org_gnome_image_inline_pobject_free; org_gnome_image_inline_decode (image_object); camel_stream_printf ( target->stream, "", classid); g_free (classid); }