aboutsummaryrefslogblamecommitdiffstats
path: root/widgets/misc/e-attachment-view.c
blob: e9e546e4498efbc1351e9e9cd08f1375abaaff26 (plain) (tree)


























                                                                               
                             








































                                                             
                                 
































                                                              
















                                                                     























                                                                       



                                         





                                                               



                                                                     
                                                           
 

                                                                    


           





























                                                                     
                        




                                                   


                                                              


                                                              


                                                        













                                                        
                                




                                                   


                                                              

                                                                     

                                                                





                                                
                   



                                            






                                          









































                                                 
                              






























                                                      
                        
















                                                                             


                                                              

                                                              


                                                        




















                                                                          
                        














                                                                            


                                                              

                                                              


                                                        













                                                                      
                        






                                                            


                                                              




                                                                      


                                                                


















                                                                      
                        















                                                                            


                                                              


                                                              


                                                        


























                                                                       









                                             












































                                                                            

                                                 
































                                                                         



                                                              








































































                                                                           

























                                                                  







                                                                  

                                                                 





















                                                                              
































                                                                      








































                                                                              































































                                                                             






















































































































                                                                       















































































































                                                                             

































































                                                                       




                                                                    

























                                                                        
















                                                                    

                                                            
                                                              

                                             
                                                                  







                                                       

                                                         



                                                             
                                 
 
                                                            

                       
                                                            







                                                               

                                                                   




























































































                                                                              



















                                                                  
                                
                          
                           

                          
                              



                                                       

                                                                 

                              
                                                       
                                                              

                                                              


                                  

         


                                                            


                                                               
                                                                   
                                                                  

                                                               
                                                                 

                                                                
                                                                 

                                                                       
                                                           




                                                                    
                                       
























































                                                                              
 
/*
 * e-attachment-view.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)
 *
 */

#include "e-attachment-view.h"

#include <config.h>
#include <glib/gi18n.h>
#include <camel/camel-stream-mem.h>

#include "e-util/e-binding.h"
#include "e-util/e-plugin-ui.h"
#include "e-util/e-util.h"
#include "e-attachment-dialog.h"

enum {
    DND_TYPE_MESSAGE_RFC822,
    DND_TYPE_X_UID_LIST,
    DND_TYPE_TEXT_URI_LIST,
    DND_TYPE_NETSCAPE_URL,
    DND_TYPE_TEXT_VCARD,
    DND_TYPE_TEXT_CALENDAR
};

static GtkTargetEntry drop_types[] = {
    { "message/rfc822", 0, DND_TYPE_MESSAGE_RFC822 },
    { "x-uid-list",     0, DND_TYPE_X_UID_LIST },
    { "text/uri-list",  0, DND_TYPE_TEXT_URI_LIST },
    { "_NETSCAPE_URL",  0, DND_TYPE_NETSCAPE_URL },
    { "text/x-vcard",   0, DND_TYPE_TEXT_VCARD },
    { "text/calendar",  0, DND_TYPE_TEXT_CALENDAR }
};

/* The atoms need initialized at runtime. */
static struct {
    const gchar *target;
    GdkAtom atom;
    GdkDragAction actions;
} drag_info[] = {
    { "message/rfc822", NULL,   GDK_ACTION_COPY },
    { "x-uid-list",     NULL,   GDK_ACTION_COPY |
                    GDK_ACTION_MOVE |
                    GDK_ACTION_ASK },
    { "text/uri-list",  NULL,   GDK_ACTION_COPY },
    { "_NETSCAPE_URL",  NULL,   GDK_ACTION_COPY },
    { "text/x-vcard",   NULL,   GDK_ACTION_COPY },
    { "text/calendar",  NULL,   GDK_ACTION_COPY }
};

static const gchar *ui =
"<ui>"
"  <popup name='context'>"
"    <menuitem action='cancel'/>"
"    <menuitem action='save-as'/>"
"    <menuitem action='set-background'/>"
"    <menuitem action='remove'/>"
"    <menuitem action='properties'/>"
"    <placeholder name='custom-actions'/>"
"    <separator/>"
"    <menuitem action='add'/>"
"    <separator/>"
"    <placeholder name='open-actions'/>"
"  </popup>"
"  <popup name='dnd'>"
"    <menuitem action='drag-copy'/>"
"    <menuitem action='drag-move'/>"
"    <separator/>"
"    <menuitem action='drag-cancel'/>"
"  </popup>"
"</ui>";

static void
action_add_cb (GtkAction *action,
               EAttachmentView *view)
{
    EAttachmentStore *store;
    gpointer parent;

    parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
    parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;

    store = e_attachment_view_get_store (view);
    e_attachment_store_run_load_dialog (store, parent);
}

static void
action_cancel_cb (GtkAction *action,
                  EAttachmentView *view)
{
    EAttachment *attachment;
    GList *selected;

    selected = e_attachment_view_get_selected_attachments (view);
    g_return_if_fail (g_list_length (selected) == 1);
    attachment = selected->data;

    e_attachment_cancel (attachment);

    g_list_foreach (selected, (GFunc) g_object_unref, NULL);
    g_list_free (selected);
}

static void
action_drag_cancel_cb (GtkAction *action,
                       EAttachmentView *view)
{
    EAttachmentViewPrivate *priv;

    priv = e_attachment_view_get_private (view);
    gtk_drag_finish (priv->drag_context, FALSE, FALSE, priv->time);
}

static void
action_drag_copy_cb (GtkAction *action,
                     EAttachmentView *view)
{
    e_attachment_view_drag_action (view, GDK_ACTION_COPY);
}

static void
action_drag_move_cb (GtkAction *action,
                     EAttachmentView *view)
{
    e_attachment_view_drag_action (view, GDK_ACTION_MOVE);
}

static void
action_open_in_cb (GtkAction *action,
                   EAttachmentView *view)
{
    GAppInfo *app_info;
    GtkTreePath *path;
    GList *selected;

    selected = e_attachment_view_get_selected_paths (view);
    g_return_if_fail (g_list_length (selected) == 1);
    path = selected->data;

    app_info = g_object_get_data (G_OBJECT (action), "app-info");
    g_return_if_fail (G_IS_APP_INFO (app_info));

    e_attachment_view_open_path (view, path, app_info);

    g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
    g_list_free (selected);
}

static void
action_properties_cb (GtkAction *action,
                      EAttachmentView *view)
{
    EAttachment *attachment;
    GtkWidget *dialog;
    GList *selected;
    gpointer parent;

    selected = e_attachment_view_get_selected_attachments (view);
    g_return_if_fail (g_list_length (selected) == 1);
    attachment = selected->data;

    parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
    parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;

    dialog = e_attachment_dialog_new (parent, attachment);
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);

    g_list_foreach (selected, (GFunc) g_object_unref, NULL);
    g_list_free (selected);
}

static void
action_recent_cb (GtkAction *action,
                  EAttachmentView *view)
{
    GtkRecentChooser *chooser;
    EAttachmentStore *store;
    EAttachment *attachment;
    gpointer parent;
    gchar *uri;

    chooser = GTK_RECENT_CHOOSER (action);
    store = e_attachment_view_get_store (view);

    parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
    parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;

    uri = gtk_recent_chooser_get_current_uri (chooser);
    attachment = e_attachment_new_for_uri (uri);
    e_attachment_store_add_attachment (store, attachment);
    e_attachment_load_async (
        attachment, (GAsyncReadyCallback)
        e_attachment_load_handle_error, parent);
    g_free (uri);
}

static void
action_remove_cb (GtkAction *action,
                  EAttachmentView *view)
{
    e_attachment_view_remove_selected (view, FALSE);
}

static void
action_save_as_cb (GtkAction *action,
                   EAttachmentView *view)
{
    EAttachmentStore *store;
    GList *selected;
    gpointer parent;

    store = e_attachment_view_get_store (view);

    parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
    parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;

    selected = e_attachment_view_get_selected_attachments (view);
    e_attachment_store_run_save_dialog (store, selected, parent);
    g_list_foreach (selected, (GFunc) g_object_unref, NULL);
    g_list_free (selected);
}

static void
action_set_background_cb (GtkAction *action,
                          EAttachmentView *view)
{
    /* FIXME */
}

static GtkActionEntry standard_entries[] = {

    { "cancel",
      GTK_STOCK_CANCEL,
      NULL,
      NULL,
      NULL,  /* XXX Add a tooltip! */
      G_CALLBACK (action_cancel_cb) },

    { "drag-cancel",
      NULL,
      N_("Cancel _Drag"),
      NULL,
      NULL,  /* XXX Add a tooltip! */
      G_CALLBACK (action_drag_cancel_cb) },

    { "drag-copy",
      NULL,
      N_("_Copy"),
      NULL,
      NULL,  /* XXX Add a tooltip! */
      G_CALLBACK (action_drag_copy_cb) },

    { "drag-move",
      NULL,
      N_("_Move"),
      NULL,
      NULL,  /* XXX Add a tooltip! */
      G_CALLBACK (action_drag_move_cb) },

    { "save-as",
      GTK_STOCK_SAVE_AS,
      NULL,
      NULL,
      NULL,  /* XXX Add a tooltip! */
      G_CALLBACK (action_save_as_cb) },

    { "set-background",
      NULL,
      N_("Set as _Background"),
      NULL,
      NULL,  /* XXX Add a tooltip! */
      G_CALLBACK (action_set_background_cb) }
};

static GtkActionEntry editable_entries[] = {

    { "add",
      GTK_STOCK_ADD,
      N_("A_dd Attachment..."),
      NULL,
      N_("Attach a file"),
      G_CALLBACK (action_add_cb) },

    { "properties",
      GTK_STOCK_PROPERTIES,
      NULL,
      NULL,
      NULL,  /* XXX Add a tooltip! */
      G_CALLBACK (action_properties_cb) },

    { "remove",
      GTK_STOCK_REMOVE,
      NULL,
      NULL,
      NULL,  /* XXX Add a tooltip! */
      G_CALLBACK (action_remove_cb) }
};

static void
drop_message_rfc822 (EAttachmentView *view,
                     GtkSelectionData *selection_data,
                     EAttachmentStore *store,
                     GdkDragAction action)
{
    EAttachmentViewPrivate *priv;
    EAttachment *attachment;
    CamelMimeMessage *message;
    CamelDataWrapper *wrapper;
    CamelStream *stream;
    const gchar *data;
    gboolean success = FALSE;
    gboolean delete = FALSE;
    gpointer parent;
    gint length;

    priv = e_attachment_view_get_private (view);

    data = (const gchar *) gtk_selection_data_get_data (selection_data);
    length = gtk_selection_data_get_length (selection_data);

    stream = camel_stream_mem_new ();
    camel_stream_write (stream, data, length);
    camel_stream_reset (stream);

    message = camel_mime_message_new ();
    wrapper = CAMEL_DATA_WRAPPER (message);

    if (camel_data_wrapper_construct_from_stream (wrapper, stream) == -1)
        goto exit;

    parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
    parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;

    attachment = e_attachment_new_for_message (message);
    e_attachment_store_add_attachment (store, attachment);
    e_attachment_load_async (
        attachment, (GAsyncReadyCallback)
        e_attachment_load_handle_error, parent);
    g_object_unref (attachment);

    success = TRUE;
    delete = (action == GDK_ACTION_MOVE);

exit:
    camel_object_unref (message);
    camel_object_unref (stream);

    gtk_drag_finish (priv->drag_context, success, delete, priv->time);
}

static void
drop_netscape_url (EAttachmentView *view,
                   GtkSelectionData *selection_data,
                   EAttachmentStore *store,
                   GdkDragAction action)
{
    EAttachmentViewPrivate *priv;
    EAttachment *attachment;
    const gchar *data;
    gpointer parent;
    gchar *copied_data;
    gchar **strv;
    gint length;

    /* _NETSCAPE_URL is represented as "URI\nTITLE" */

    priv = e_attachment_view_get_private (view);

    data = (const gchar *) gtk_selection_data_get_data (selection_data);
    length = gtk_selection_data_get_length (selection_data);

    copied_data = g_strndup (data, length);
    strv = g_strsplit (copied_data, "\n", 2);
    g_free (copied_data);

    parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
    parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;

    attachment = e_attachment_new_for_uri (strv[0]);
    e_attachment_store_add_attachment (store, attachment);
    e_attachment_load_async (
        attachment, (GAsyncReadyCallback)
        e_attachment_load_handle_error, parent);
    g_object_unref (attachment);

    g_strfreev (strv);

    gtk_drag_finish (priv->drag_context, TRUE, FALSE, priv->time);
}

static void
drop_text_uri_list (EAttachmentView *view,
                    GtkSelectionData *selection_data,
                    EAttachmentStore *store,
                    GdkDragAction action)
{
    EAttachmentViewPrivate *priv;
    gpointer parent;
    gchar **uris;
    gint ii;

    priv = e_attachment_view_get_private (view);

    uris = gtk_selection_data_get_uris (selection_data);

    parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
    parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;

    for (ii = 0; uris[ii] != NULL; ii++) {
        EAttachment *attachment;

        attachment = e_attachment_new_for_uri (uris[ii]);
        e_attachment_store_add_attachment (store, attachment);
        e_attachment_load_async (
            attachment, (GAsyncReadyCallback)
            e_attachment_load_handle_error, parent);
        g_object_unref (attachment);
    }

    g_strfreev (uris);

    gtk_drag_finish (priv->drag_context, TRUE, FALSE, priv->time);
}

static void
drop_text_generic (EAttachmentView *view,
                   GtkSelectionData *selection_data,
                   EAttachmentStore *store,
                   GdkDragAction action)
{
    EAttachmentViewPrivate *priv;
    EAttachment *attachment;
    CamelMimePart *mime_part;
    GdkAtom atom;
    const gchar *data;
    gpointer parent;
    gchar *content_type;
    gint length;

    priv = e_attachment_view_get_private (view);

    data = (const gchar *) gtk_selection_data_get_data (selection_data);
    length = gtk_selection_data_get_length (selection_data);
    atom = gtk_selection_data_get_data_type (selection_data);

    mime_part = camel_mime_part_new ();

    content_type = gdk_atom_name (atom);
    camel_mime_part_set_content (mime_part, data, length, content_type);
    camel_mime_part_set_disposition (mime_part, "inline");
    g_free (content_type);

    parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
    parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;

    attachment = e_attachment_new ();
    e_attachment_set_mime_part (attachment, mime_part);
    e_attachment_store_add_attachment (store, attachment);
    e_attachment_load_async (
        attachment, (GAsyncReadyCallback)
        e_attachment_load_handle_error, parent);
    g_object_unref (attachment);

    camel_object_unref (mime_part);

    gtk_drag_finish (priv->drag_context, TRUE, FALSE, priv->time);
}

static void
drop_x_uid_list (EAttachmentView *view,
                 GtkSelectionData *selection_data,
                 EAttachmentStore *store,
                 GdkDragAction action)
{
    EAttachmentViewPrivate *priv;

    /* FIXME  Ugh, this looks painful.  Requires mailer stuff. */

    priv = e_attachment_view_get_private (view);

    gtk_drag_finish (priv->drag_context, FALSE, FALSE, priv->time);
}

static void
attachment_view_class_init (EAttachmentViewIface *iface)
{
    gint ii;

    g_object_interface_install_property (
        iface,
        g_param_spec_boolean (
            "editable",
            "Editable",
            NULL,
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT));

    for (ii = 0; ii < G_N_ELEMENTS (drag_info); ii++) {
        const gchar *target = drag_info[ii].target;
        drag_info[ii].atom = gdk_atom_intern (target, FALSE);
    }
}

GType
e_attachment_view_get_type (void)
{
    static GType type = 0;

    if (G_UNLIKELY (type == 0)) {
        static const GTypeInfo type_info = {
            sizeof (EAttachmentViewIface),
            (GBaseInitFunc) NULL,
            (GBaseFinalizeFunc) NULL,
            (GClassInitFunc) attachment_view_class_init,
            (GClassFinalizeFunc) NULL,
            NULL,  /* class_data */
            0,     /* instance_size */
            0,     /* n_preallocs */
            (GInstanceInitFunc) NULL,
            NULL   /* value_table */
        };

        type = g_type_register_static (
            G_TYPE_INTERFACE, "EAttachmentView", &type_info, 0);

        g_type_interface_add_prerequisite (type, GTK_TYPE_WIDGET);
    }

    return type;
}

void
e_attachment_view_init (EAttachmentView *view)
{
    EAttachmentViewPrivate *priv;
    GtkUIManager *ui_manager;
    GtkActionGroup *action_group;
    const gchar *domain = GETTEXT_PACKAGE;
    GError *error = NULL;

    priv = e_attachment_view_get_private (view);

    e_attachment_view_drag_source_set (view);
    e_attachment_view_drag_dest_set (view);

    ui_manager = gtk_ui_manager_new ();
    priv->merge_id = gtk_ui_manager_new_merge_id (ui_manager);
    priv->ui_manager = ui_manager;

    action_group = gtk_action_group_new ("standard");
    gtk_action_group_set_translation_domain (action_group, domain);
    gtk_action_group_add_actions (
        action_group, standard_entries,
        G_N_ELEMENTS (standard_entries), view);
    gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
    priv->standard_actions = action_group;

    action_group = gtk_action_group_new ("editable");
    gtk_action_group_set_translation_domain (action_group, domain);
    gtk_action_group_add_actions (
        action_group, editable_entries,
        G_N_ELEMENTS (editable_entries), view);
    gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
    priv->editable_actions = action_group;

    action_group = gtk_action_group_new ("openwith");
    gtk_action_group_set_translation_domain (action_group, domain);
    gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
    priv->openwith_actions = action_group;

    /* Because we are loading from a hard-coded string, there is
     * no chance of I/O errors.  Failure here implies a malformed
     * UI definition.  Full stop. */
    gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
    if (error != NULL)
        g_error ("%s", error->message);

    e_mutual_binding_new (
        G_OBJECT (view), "editable",
        G_OBJECT (priv->editable_actions), "visible");

    e_plugin_ui_register_manager (ui_manager, "attachment-view", view);
}

void
e_attachment_view_dispose (EAttachmentView *view)
{
    EAttachmentViewPrivate *priv;

    priv = e_attachment_view_get_private (view);

    if (priv->ui_manager != NULL) {
        g_object_unref (priv->ui_manager);
        priv->ui_manager = NULL;
    }

    if (priv->standard_actions != NULL) {
        g_object_unref (priv->standard_actions);
        priv->standard_actions = NULL;
    }

    if (priv->editable_actions != NULL) {
        g_object_unref (priv->editable_actions);
        priv->editable_actions = NULL;
    }

    if (priv->openwith_actions != NULL) {
        g_object_unref (priv->openwith_actions);
        priv->openwith_actions = NULL;
    }

    if (priv->drag_context != NULL) {
        g_object_unref (priv->drag_context);
        priv->drag_context = NULL;
    }
}

void
e_attachment_view_finalize (EAttachmentView *view)
{
    EAttachmentViewPrivate *priv;

    priv = e_attachment_view_get_private (view);

    if (priv->selection_data != NULL)
        gtk_selection_data_free (priv->selection_data);
}

EAttachmentViewPrivate *
e_attachment_view_get_private (EAttachmentView *view)
{
    EAttachmentViewIface *iface;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);

    iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
    g_return_val_if_fail (iface->get_private != NULL, NULL);

    return iface->get_private (view);
}

EAttachmentStore *
e_attachment_view_get_store (EAttachmentView *view)
{
    EAttachmentViewIface *iface;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);

    iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
    g_return_val_if_fail (iface->get_store != NULL, NULL);

    return iface->get_store (view);
}

gboolean
e_attachment_view_get_editable (EAttachmentView *view)
{
    EAttachmentViewPrivate *priv;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);

    priv = e_attachment_view_get_private (view);

    return priv->editable;
}

void
e_attachment_view_set_editable (EAttachmentView *view,
                                gboolean editable)
{
    EAttachmentViewPrivate *priv;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));

    priv = e_attachment_view_get_private (view);
    priv->editable = editable;

    g_object_notify (G_OBJECT (view), "editable");
}

GList *
e_attachment_view_get_selected_attachments (EAttachmentView *view)
{
    EAttachmentStore *store;
    GtkTreeModel *model;
    GList *selected, *item;
    gint column_id;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);

    column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
    selected = e_attachment_view_get_selected_paths (view);
    store = e_attachment_view_get_store (view);
    model = GTK_TREE_MODEL (store);

    /* Convert the GtkTreePaths to EAttachments. */
    for (item = selected; item != NULL; item = item->next) {
        EAttachment *attachment;
        GtkTreePath *path;
        GtkTreeIter iter;

        path = item->data;

        gtk_tree_model_get_iter (model, &iter, path);
        gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
        gtk_tree_path_free (path);

        item->data = attachment;
    }

    return selected;
}

void
e_attachment_view_open_path (EAttachmentView *view,
                             GtkTreePath *path,
                             GAppInfo *app_info)
{
    EAttachmentStore *store;
    EAttachment *attachment;
    GtkTreeModel *model;
    GtkTreeIter iter;
    gpointer parent;
    gint column_id;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
    g_return_if_fail (path != NULL);

    column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
    store = e_attachment_view_get_store (view);
    model = GTK_TREE_MODEL (store);

    gtk_tree_model_get_iter (model, &iter, path);
    gtk_tree_model_get (model, &iter, column_id, &attachment, -1);

    parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
    parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL;

    e_attachment_open_async (
        attachment, app_info, (GAsyncReadyCallback)
        e_attachment_open_handle_error, parent);

    g_object_unref (attachment);
}

void
e_attachment_view_remove_selected (EAttachmentView *view,
                                   gboolean select_next)
{
    EAttachmentStore *store;
    GtkTreeModel *model;
    GList *selected, *item;
    gint column_id;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));

    column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
    selected = e_attachment_view_get_selected_paths (view);
    store = e_attachment_view_get_store (view);
    model = GTK_TREE_MODEL (store);

    for (item = selected; item != NULL; item = item->next) {
        EAttachment *attachment;
        GtkTreePath *path = item->data;
        GtkTreeIter iter;

        gtk_tree_model_get_iter (model, &iter, path);
        gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
        e_attachment_store_remove_attachment (store, attachment);
        g_object_unref (attachment);
    }

    /* If we only removed one attachment, try to select another. */
    if (select_next && g_list_length (selected) == 1) {
        GtkTreePath *path = selected->data;

        e_attachment_view_select_path (view, path);
        if (!e_attachment_view_path_is_selected (view, path))
            if (gtk_tree_path_prev (path))
                e_attachment_view_select_path (view, path);
    }

    g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
    g_list_free (selected);
}

gboolean
e_attachment_view_button_press_event (EAttachmentView *view,
                                      GdkEventButton *event)
{
    GtkTreePath *path;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    /* If the user clicked on a selected item, retain the current
     * selection.  If the user clicked on an unselected item, select
     * the clicked item only.  If the user did not click on an item,
     * clear the current selection. */
    path = e_attachment_view_get_path_at_pos (view, event->x, event->y);
    if (path != NULL) {
        if (!e_attachment_view_path_is_selected (view, path)) {
            e_attachment_view_unselect_all (view);
            e_attachment_view_select_path (view, path);
        }
        gtk_tree_path_free (path);
    } else
        e_attachment_view_unselect_all (view);

    /* Cancel drag and drop if there are no selected items,
     * or if any of the selected items are loading or saving. */
    if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
        GList *selected, *iter;
        gboolean busy = FALSE;

        selected = e_attachment_view_get_selected_attachments (view);
        for (iter = selected; iter != NULL; iter = iter->next) {
            EAttachment *attachment = iter->data;
            busy |= e_attachment_get_loading (attachment);
            busy |= e_attachment_get_saving (attachment);
        }
        if (selected == NULL || busy)
            e_attachment_view_drag_source_unset (view);
        g_list_foreach (selected, (GFunc) g_object_unref, NULL);
        g_list_free (selected);
    }

    if (event->button == 3 && event->type == GDK_BUTTON_PRESS) {
        e_attachment_view_show_popup_menu (view, event);
        return TRUE;
    }

    return FALSE;
}

gboolean
e_attachment_view_button_release_event (EAttachmentView *view,
                                        GdkEventButton *event)
{
    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    /* Restore the attachment view as a drag source, in case
     * we had to cancel during a button press event. */
    if (event->button == 1)
        e_attachment_view_drag_source_set (view);

    return FALSE;
}

GtkTreePath *
e_attachment_view_get_path_at_pos (EAttachmentView *view,
                                   gint x,
                                   gint y)
{
    EAttachmentViewIface *iface;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);

    iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
    g_return_val_if_fail (iface->get_path_at_pos != NULL, NULL);

    return iface->get_path_at_pos (view, x, y);
}

GList *
e_attachment_view_get_selected_paths (EAttachmentView *view)
{
    EAttachmentViewIface *iface;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);

    iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
    g_return_val_if_fail (iface->get_selected_paths != NULL, NULL);

    return iface->get_selected_paths (view);
}

gboolean
e_attachment_view_path_is_selected (EAttachmentView *view,
                                    GtkTreePath *path)
{
    EAttachmentViewIface *iface;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
    g_return_val_if_fail (path != NULL, FALSE);

    iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
    g_return_val_if_fail (iface->path_is_selected != NULL, FALSE);

    return iface->path_is_selected (view, path);
}

void
e_attachment_view_select_path (EAttachmentView *view,
                               GtkTreePath *path)
{
    EAttachmentViewIface *iface;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
    g_return_if_fail (path != NULL);

    iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
    g_return_if_fail (iface->select_path != NULL);

    iface->select_path (view, path);
}

void
e_attachment_view_unselect_path (EAttachmentView *view,
                                 GtkTreePath *path)
{
    EAttachmentViewIface *iface;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
    g_return_if_fail (path != NULL);

    iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
    g_return_if_fail (iface->unselect_path != NULL);

    iface->unselect_path (view, path);
}

void
e_attachment_view_select_all (EAttachmentView *view)
{
    EAttachmentViewIface *iface;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));

    iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
    g_return_if_fail (iface->select_all != NULL);

    iface->select_all (view);
}

void
e_attachment_view_unselect_all (EAttachmentView *view)
{
    EAttachmentViewIface *iface;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));

    iface = E_ATTACHMENT_VIEW_GET_IFACE (view);
    g_return_if_fail (iface->unselect_all != NULL);

    iface->unselect_all (view);
}

void
e_attachment_view_sync_selection (EAttachmentView *view,
                                  EAttachmentView *target)
{
    GList *selected, *iter;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
    g_return_if_fail (E_IS_ATTACHMENT_VIEW (target));

    selected = e_attachment_view_get_selected_paths (view);
    e_attachment_view_unselect_all (target);

    for (iter = selected; iter != NULL; iter = iter->next)
        e_attachment_view_select_path (target, iter->data);

    g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
    g_list_free (selected);
}

void
e_attachment_view_drag_source_set (EAttachmentView *view)
{
    GtkTargetEntry *targets;
    GtkTargetList *list;
    gint n_targets;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));

    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 (view), GDK_BUTTON1_MASK,
        targets, n_targets, GDK_ACTION_COPY);

    gtk_target_table_free (targets, n_targets);
    gtk_target_list_unref (list);
}

void
e_attachment_view_drag_source_unset (EAttachmentView *view)
{
    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));

    gtk_drag_source_unset (GTK_WIDGET (view));
}

void
e_attachment_view_drag_begin (EAttachmentView *view,
                              GdkDragContext *context)
{
    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
    g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
}

void
e_attachment_view_drag_end (EAttachmentView *view,
                            GdkDragContext *context)
{
    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
    g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
}

void
e_attachment_view_drag_data_get (EAttachmentView *view,
                                 GdkDragContext *context,
                                 GtkSelectionData *selection,
                                 guint info,
                                 guint time)
{
    GList *selected, *iter;
    gchar **uris;
    gint ii = 0;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
    g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
    g_return_if_fail (selection != NULL);

    selected = e_attachment_view_get_selected_attachments (view);
    if (selected == NULL)
        return;

    uris = g_malloc0 (sizeof (gchar *) * (g_list_length (selected) + 1));

    for (iter = selected; iter != NULL; iter = iter->next) {
        EAttachment *attachment = iter->data;
        GFile *file;

        /* FIXME Need to handle attachments with no GFile. */
        file = e_attachment_get_file (attachment);
        if (file == NULL)
            continue;

        uris[ii++] = g_file_get_uri (file);
    }

    gtk_selection_data_set_uris (selection, uris);

    g_strfreev (uris);
}

void
e_attachment_view_drag_dest_set (EAttachmentView *view)
{
    GtkTargetEntry *targets;
    GtkTargetList *list;
    gint n_targets;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));

    list = gtk_target_list_new (NULL, 0);
    /* FIXME Add targets here... */
    targets = gtk_target_table_new_from_list (list, &n_targets);

    gtk_drag_dest_set (
        GTK_WIDGET (view), GTK_DEST_DEFAULT_ALL,
        targets, n_targets, GDK_ACTION_COPY);

    gtk_target_table_free (targets, n_targets);
    gtk_target_list_unref (list);
}

void
e_attachment_view_drag_dest_unset (EAttachmentView *view)
{
    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));

    gtk_drag_dest_unset (GTK_WIDGET (view));
}

void
e_attachment_view_drag_action (EAttachmentView *view,
                               GdkDragAction action)
{
    EAttachmentViewPrivate *priv;
    GtkSelectionData *selection_data;
    EAttachmentStore *store;
    GdkAtom atom;
    gchar *name;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));

    priv = e_attachment_view_get_private (view);

    selection_data = priv->selection_data;
    store = e_attachment_view_get_store (view);
    atom = gtk_selection_data_get_data_type (selection_data);

    switch (priv->info) {
        case DND_TYPE_MESSAGE_RFC822:
            drop_message_rfc822 (
                view, selection_data, store, action);
            return;

        case DND_TYPE_NETSCAPE_URL:
            drop_netscape_url (
                view, selection_data, store, action);
            return;

        case DND_TYPE_TEXT_URI_LIST:
            drop_text_uri_list (
                view, selection_data, store, action);
            return;

        case DND_TYPE_TEXT_VCARD:
        case DND_TYPE_TEXT_CALENDAR:
            drop_text_generic (
                view, selection_data, store, action);
            return;

        case DND_TYPE_X_UID_LIST:
            drop_x_uid_list (
                view, selection_data, store, action);
            return;

        default:
            name = gdk_atom_name (atom);
            g_warning ("Unknown drag type: %s", name);
            g_free (name);
            break;
    }

    gtk_drag_finish (priv->drag_context, FALSE, FALSE, priv->time);
}

gboolean
e_attachment_view_drag_motion (EAttachmentView *view,
                               GdkDragContext *context,
                               gint x,
                               gint y,
                               guint time)
{
    GList *iter;
    GdkDragAction actions = 0;
    GdkDragAction chosen_action;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
    g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), FALSE);

    /* Disallow drops if we're not editable. */
    if (!e_attachment_view_get_editable (view))
        return FALSE;

    for (iter = context->targets; iter != NULL; iter = iter->next) {
        GdkAtom atom = iter->data;
        gint ii;

        for (ii = 0; ii < G_N_ELEMENTS (drag_info); ii++)
            if (atom == drag_info[ii].atom)
                actions |= drag_info[ii].actions;
    }

    actions &= context->actions;
    chosen_action = context->suggested_action;

    if (chosen_action == GDK_ACTION_ASK) {
        GdkDragAction mask;

        mask = GDK_ACTION_COPY | GDK_ACTION_MOVE;
        if ((actions & mask) != mask)
            chosen_action = GDK_ACTION_COPY;
    }

    gdk_drag_status (context, chosen_action, time);

    return (chosen_action != 0);
}

gboolean
e_attachment_view_drag_drop (EAttachmentView *view,
                             GdkDragContext *context,
                             gint x,
                             gint y,
                             guint time)
{
    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
    g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), FALSE);

    /* Disallow drops if we're not editable. */
    if (!e_attachment_view_get_editable (view))
        return FALSE;

    return TRUE;
}

void
e_attachment_view_drag_data_received (EAttachmentView *view,
                                      GdkDragContext *context,
                                      gint x,
                                      gint y,
                                      GtkSelectionData *selection,
                                      guint info,
                                      guint time)
{
    EAttachmentViewPrivate *priv;
    GtkUIManager *ui_manager;
    GdkDragAction action;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
    g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
    g_return_if_fail (selection != NULL);

    priv = e_attachment_view_get_private (view);
    ui_manager = e_attachment_view_get_ui_manager (view);

    action = context->action;

    if (gtk_selection_data_get_data (selection) == NULL)
        return;

    if (gtk_selection_data_get_length (selection) == -1)
        return;

    if (priv->drag_context != NULL)
        g_object_unref (priv->drag_context);

    if (priv->selection_data != NULL)
        gtk_selection_data_free (priv->selection_data);

    priv->drag_context = g_object_ref (context);
    priv->selection_data = gtk_selection_data_copy (selection);
    priv->info = info;
    priv->time = time;

    if (action == GDK_ACTION_ASK) {
        GtkWidget *menu;

        menu = gtk_ui_manager_get_widget (ui_manager, "/dnd");
        g_return_if_fail (GTK_IS_MENU (menu));

        gtk_menu_popup (
            GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, time);
    } else
        e_attachment_view_drag_action (view, action);
}

GtkAction *
e_attachment_view_get_action (EAttachmentView *view,
                              const gchar *action_name)
{
    GtkUIManager *ui_manager;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
    g_return_val_if_fail (action_name != NULL, NULL);

    ui_manager = e_attachment_view_get_ui_manager (view);

    return e_lookup_action (ui_manager, action_name);
}

GtkActionGroup *
e_attachment_view_get_action_group (EAttachmentView *view,
                                    const gchar *group_name)
{
    GtkUIManager *ui_manager;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
    g_return_val_if_fail (group_name != NULL, NULL);

    ui_manager = e_attachment_view_get_ui_manager (view);

    return e_lookup_action_group (ui_manager, group_name);
}

GtkUIManager *
e_attachment_view_get_ui_manager (EAttachmentView *view)
{
    EAttachmentViewPrivate *priv;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);

    priv = e_attachment_view_get_private (view);

    return priv->ui_manager;
}

GtkAction *
e_attachment_view_recent_action_new (EAttachmentView *view,
                                     const gchar *action_name,
                                     const gchar *action_label)
{
    GtkAction *action;
    GtkRecentChooser *chooser;

    g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
    g_return_val_if_fail (action_name != NULL, NULL);

    action = gtk_recent_action_new (
        action_name, action_label, NULL, NULL);
    gtk_recent_action_set_show_numbers (GTK_RECENT_ACTION (action), TRUE);

    chooser = GTK_RECENT_CHOOSER (action);
    gtk_recent_chooser_set_show_icons (chooser, TRUE);
    gtk_recent_chooser_set_show_not_found (chooser, FALSE);
    gtk_recent_chooser_set_show_private (chooser, FALSE);
    gtk_recent_chooser_set_show_tips (chooser, TRUE);
    gtk_recent_chooser_set_sort_type (chooser, GTK_RECENT_SORT_MRU);

    g_signal_connect (
        action, "item-activated",
        G_CALLBACK (action_recent_cb), view);

    return action;
}

void
e_attachment_view_show_popup_menu (EAttachmentView *view,
                                   GdkEventButton *event)
{
    GtkUIManager *ui_manager;
    GtkWidget *menu;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));

    e_attachment_view_update_actions (view);

    ui_manager = e_attachment_view_get_ui_manager (view);
    menu = gtk_ui_manager_get_widget (ui_manager, "/context");
    g_return_if_fail (GTK_IS_MENU (menu));

    if (event != NULL)
        gtk_menu_popup (
            GTK_MENU (menu), NULL, NULL, NULL, NULL,
            event->button, event->time);
    else
        gtk_menu_popup (
            GTK_MENU (menu), NULL, NULL, NULL, NULL,
            0, gtk_get_current_event_time ());
}

void
e_attachment_view_update_actions (EAttachmentView *view)
{
    EAttachmentViewPrivate *priv;
    EAttachment *attachment;
    GtkAction *action;
    GList *list, *iter;
    guint n_selected;
    gboolean is_image;
    gboolean busy = FALSE;

    g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));

    priv = e_attachment_view_get_private (view);
    list = e_attachment_view_get_selected_attachments (view);
    n_selected = g_list_length (list);

    if (n_selected == 1) {
        attachment = g_object_ref (list->data);
        is_image = e_attachment_is_image (attachment);
        busy |= e_attachment_get_loading (attachment);
        busy |= e_attachment_get_saving (attachment);
    } else {
        attachment = NULL;
        is_image = FALSE;
    }

    g_list_foreach (list, (GFunc) g_object_unref, NULL);
    g_list_free (list);

    action = e_attachment_view_get_action (view, "cancel");
    gtk_action_set_visible (action, busy);

    action = e_attachment_view_get_action (view, "properties");
    gtk_action_set_visible (action, !busy && n_selected == 1);

    action = e_attachment_view_get_action (view, "remove");
    gtk_action_set_visible (action, !busy && n_selected > 0);

    action = e_attachment_view_get_action (view, "save-as");
    gtk_action_set_visible (action, !busy && n_selected > 0);

    action = e_attachment_view_get_action (view, "set-background");
    gtk_action_set_visible (action, !busy && is_image);

    /* Clear out the "openwith" action group. */
    gtk_ui_manager_remove_ui (priv->ui_manager, priv->merge_id);
    e_action_group_remove_all_actions (priv->openwith_actions);

    if (attachment == NULL || busy)
        return;

    list = e_attachment_list_apps (attachment);

    for (iter = list; iter != NULL; iter = iter->next) {
        GAppInfo *app_info = iter->data;
        GtkAction *action;
        const gchar *app_executable;
        const gchar *app_name;
        gchar *action_tooltip;
        gchar *action_label;
        gchar *action_name;

        if (!g_app_info_should_show (app_info))
            continue;

        app_executable = g_app_info_get_executable (app_info);
        app_name = g_app_info_get_name (app_info);

        action_name = g_strdup_printf ("open-in-%s", app_executable);
        action_label = g_strdup_printf (_("Open in %s..."), app_name);

        action_tooltip = g_strdup_printf (
            _("Open this attachment in %s"), app_name);

        action = gtk_action_new (
            action_name, action_label, action_tooltip, NULL);

        g_object_set_data_full (
            G_OBJECT (action),
            "app-info", g_object_ref (app_info),
            (GDestroyNotify) g_object_unref);

        g_object_set_data_full (
            G_OBJECT (action),
            "attachment", g_object_ref (attachment),
            (GDestroyNotify) g_object_unref);

        g_signal_connect (
            action, "activate",
            G_CALLBACK (action_open_in_cb), view);

        gtk_action_group_add_action (priv->openwith_actions, action);

        gtk_ui_manager_add_ui (
            priv->ui_manager, priv->merge_id,
            "/context/open-actions", action_name,
            action_name, GTK_UI_MANAGER_AUTO, FALSE);

        g_free (action_name);
        g_free (action_label);
        g_free (action_tooltip);
    }

    g_object_unref (attachment);
    g_list_foreach (list, (GFunc) g_object_unref, NULL);
    g_list_free (list);
}