aboutsummaryrefslogblamecommitdiffstats
path: root/plugins/image-inline/image-inline.c
blob: 994726d47bd6c16572e220f200facaa170daaaca (plain) (tree)
























                                                                             
                   

                           


                                           

                                        










                                                                             
                                 




                          











































































                                                                      







































































































                                                                            
                                            
                                            

                                                   
                                       
                           



                           
                           
 

                                                                             
                                                   



                                                                     
                                        









                                                                   


                                                        
 

                                   
                           

                       
                                                                             
 
























                                                                 
                           
                                       

                                                             




                        





                                                                 
                                              
                                                         


                                               





                                                      

                                  





                                                             


                                                             

                                                                      


                                                                        
 





                                                      
                                                                


                                
                                       
                                 


                            


                                    
                                            



                                                              
                                                         

                                                  
 



                                                                   








                                                                          
                             
                                                             

                                           









                                                 
                                
                                



















                                                          



                                                              

                                             


                                                        









                                                                  



                                                             



                                                                  



















                                                                       
                                    
                                               

                                                                        
                                                     





                                                                         
/*
 * 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,
                       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)
{
    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;

    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);

    camel_stream_printf (
        target->stream, "<object classid=%s></object>", classid);

    g_free (classid);
}