417 lines
10 KiB
C
417 lines
10 KiB
C
/*
|
|
* 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) {
|
|
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);
|
|
|
|
parent = gtk_widget_get_parent (image_object->widget);
|
|
if (parent)
|
|
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_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);
|
|
}
|