/*
* 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 <http://www.gnu.org/licenses/>
*
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <gtk/gtk.h>
#include <glib/gi18n-lib.h>
#include <camel/camel-medium.h>
#include <camel/camel-mime-part.h>
#include <camel/camel-stream-mem.h>
#include <gtkhtml/gtkhtml-embedded.h>
#include <gtkimageview/gtkimagescrollwin.h>
#include <mail/em-format-hook.h>
#include <mail/em-format-html-display.h>
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
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) {
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_object (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);
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, "<object classid=%s></object>", classid);
g_free (classid);
}