Files
evolution/plugins/image-inline/image-inline.c
Matthew Barnes d2fb5ee1a8 Avoid using GdkEventButton directly in certain places.
Prefer dealing with GdkEvent pointers and using accessor functions like
gdk_event_get_button().

This is complicated by the fact that some GtkWidget method declarations
still use GdkEventButton pointers, and synthesizing button events pretty
much requires direct GdkEventButton access.  But GDK seems to be nudging
itself toward sealing the GdkEvent union.  Likely to happen in GDK4.

Mainly clean up signal handlers and leave method overrides alone for now.
2012-11-29 13:24:24 -05:00

477 lines
12 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 <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,
GdkEvent *button_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)
{
GtkAllocation image_allocation;
EWebView *web_view;
gint pixbuf_width;
gint pixbuf_height;
gint widget_width;
gint widget_height;
gdouble zoom = 1.0;
web_view = em_format_html_get_web_view (image_object->object.format);
gtk_widget_get_allocation (GTK_WIDGET (web_view), &image_allocation);
widget_width = image_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;
EWebView *web_view;
gint steps = 2;
web_view = em_format_html_get_web_view (image_object->object.format);
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 (
web_view, "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) {
g_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_sync (
data_wrapper, stream, NULL, NULL);
/* 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:
g_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;
gchar *content;
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);
g_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);
content = g_strdup_printf ("<object classid=%s></object>", classid);
camel_stream_write_string (target->stream, content, NULL, NULL);
g_free (content);
g_free (classid);
}