diff options
-rw-r--r-- | mail/ChangeLog | 6 | ||||
-rw-r--r-- | mail/mail-component.c | 489 |
2 files changed, 495 insertions, 0 deletions
diff --git a/mail/ChangeLog b/mail/ChangeLog index 826ee496e6..ea233bacd9 100644 --- a/mail/ChangeLog +++ b/mail/ChangeLog @@ -1,5 +1,11 @@ 2003-10-21 Jeffrey Stedfast <fejj@ximian.com> + * mail-component.c (drag_text_uri_list, folder_dragged_cb) + (drop_uid_list, drop_folder, import_message_rfc822) + (drop_message_rfc822, drop_text_uri_list, folder_receive_drop_cb): + New functions to handle drag & drop to/from the folder tree. + (impl_createControls): Setup drag & drop support. + * em-format.c (emf_multipart_mixed): Put an <hr> between parts of a multipart. diff --git a/mail/mail-component.c b/mail/mail-component.c index 80eeeb91af..4bffcb1e9b 100644 --- a/mail/mail-component.c +++ b/mail/mail-component.c @@ -26,6 +26,11 @@ #endif #include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> #include "e-storage.h" #include "e-storage-set.h" @@ -41,13 +46,17 @@ #include "mail-vfolder.h" #include "mail-mt.h" #include "mail-ops.h" +#include "mail-tools.h" #include "mail-send-recv.h" #include "mail-session.h" #include "em-popup.h" +#include "em-utils.h" #include <gtk/gtklabel.h> +#include <e-util/e-mktemp.h> + #include <gal/e-table/e-tree.h> #include <gal/e-table/e-tree-memory.h> @@ -57,6 +66,41 @@ #include <bonobo/bonobo-widget.h> +#define MESSAGE_RFC822_TYPE "message/rfc822" +#define TEXT_URI_LIST_TYPE "text/uri-list" +#define UID_LIST_TYPE "x-uid-list" +#define FOLDER_TYPE "x-folder" + +/* Drag & Drop types */ +enum DndDragType { + DND_DRAG_TYPE_FOLDER, /* drag an evo folder */ + DND_DRAG_TYPE_TEXT_URI_LIST, /* drag to an mbox file */ +}; + +enum DndDropType { + DND_DROP_TYPE_UID_LIST, /* drop a list of message uids */ + DND_DROP_TYPE_FOLDER, /* drop an evo folder */ + DND_DROP_TYPE_MESSAGE_RFC822, /* drop a message/rfc822 stream */ + DND_DROP_TYPE_TEXT_URI_LIST, /* drop an mbox file */ +}; + +static GtkTargetEntry drag_types[] = { + { UID_LIST_TYPE, 0, DND_DRAG_TYPE_FOLDER }, + { TEXT_URI_LIST_TYPE, 0, DND_DRAG_TYPE_TEXT_URI_LIST }, +}; + +static const int num_drag_types = sizeof (drag_types) / sizeof (drag_types[0]); + +static GtkTargetEntry drop_types[] = { + { UID_LIST_TYPE, 0, DND_DROP_TYPE_UID_LIST }, + { FOLDER_TYPE, 0, DND_DROP_TYPE_FOLDER }, + { MESSAGE_RFC822_TYPE, 0, DND_DROP_TYPE_MESSAGE_RFC822 }, + { TEXT_URI_LIST_TYPE, 0, DND_DROP_TYPE_TEXT_URI_LIST }, +}; + +static const int num_drop_types = sizeof (drop_types) / sizeof (drop_types[0]); + + #define PARENT_TYPE bonobo_object_get_type () static BonoboObjectClass *parent_class = NULL; @@ -383,6 +427,444 @@ browser_page_switched_callback (EStorageBrowser *browser, } } +static CamelFolder * +foo_get_folder (EStorageSetView *view, const char *path, CamelException *ex) +{ + /* <NotZed> either do + mail_tool_uri_to_folder(ess_get_folder(path).physicaluri), + or split the path into 'path' and 'storage name' and do get + ess_get_storage() -> store -> open_folder + */ + CamelFolder *folder; + EStorageSet *set; + EFolder *efolder; + const char *uri; + + set = e_storage_set_view_get_storage_set (view); + efolder = e_storage_set_get_folder (set, path); + uri = e_folder_get_physical_uri (efolder); + + folder = mail_tool_uri_to_folder (uri, 0, ex); + + return folder; +} + +static void +drag_text_uri_list (EStorageSetView *view, const char *path, GtkSelectionData *selection, gpointer user_data) +{ + CamelFolder *src, *dest; + const char *tmpdir; + CamelStore *store; + CamelException ex; + GtkWidget *dialog; + GPtrArray *uids; + char *uri; + + camel_exception_init (&ex); + + if (!(src = foo_get_folder (view, path, &ex))) { + dialog = gtk_message_dialog_new ((GtkWindow *) view, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + _("Could not open source folder: %s"), + camel_exception_get_description (&ex)); + + gtk_dialog_run ((GtkDialog *) dialog); + gtk_widget_destroy (dialog); + + camel_exception_clear (&ex); + + return; + } + + if (!(tmpdir = e_mkdtemp ("drag-n-drop-XXXXXX"))) { + dialog = gtk_message_dialog_new ((GtkWindow *) view, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + _("Could not create temporary directory: %s"), + g_strerror (errno)); + + gtk_dialog_run ((GtkDialog *) dialog); + gtk_widget_destroy (dialog); + + camel_object_unref (src); + + return; + } + + uri = g_strdup_printf ("mbox:%s", tmpdir); + if (!(store = camel_session_get_store (session, uri, &ex))) { + dialog = gtk_message_dialog_new ((GtkWindow *) view, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + _("Could not create temporary mbox store: %s"), + camel_exception_get_description (&ex)); + + gtk_dialog_run ((GtkDialog *) dialog); + gtk_widget_destroy (dialog); + + camel_exception_clear (&ex); + camel_object_unref (src); + g_free (uri); + + return; + } + + if (!(dest = camel_store_get_folder (store, "mbox", CAMEL_STORE_FOLDER_CREATE, &ex))) { + dialog = gtk_message_dialog_new ((GtkWindow *) view, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + _("Could not create temporary mbox folder: %s"), + camel_exception_get_description (&ex)); + + gtk_dialog_run ((GtkDialog *) dialog); + gtk_widget_destroy (dialog); + + camel_exception_clear (&ex); + camel_object_unref (store); + camel_object_unref (src); + g_free (uri); + + return; + } + + camel_object_unref (store); + uids = camel_folder_get_uids (src); + + camel_folder_transfer_messages_to (src, uids, dest, NULL, FALSE, &ex); + if (camel_exception_is_set (&ex)) { + dialog = gtk_message_dialog_new ((GtkWindow *) view, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + _("Could not copy messages to temporary mbox folder: %s"), + camel_exception_get_description (&ex)); + + gtk_dialog_run ((GtkDialog *) dialog); + gtk_widget_destroy (dialog); + + camel_folder_free_uids (src, uids); + camel_exception_clear (&ex); + camel_object_unref (dest); + camel_object_unref (src); + g_free (uri); + + return; + } + + camel_folder_free_uids (src, uids); + camel_object_unref (dest); + camel_object_unref (src); + + memcpy (uri, "file", 4); + + gtk_selection_data_set (selection, selection->target, 8, + uri, strlen (uri)); + + g_free (uri); +} + +static void +folder_dragged_cb (EStorageSetView *view, const char *path, GdkDragContext *context, + GtkSelectionData *selection, guint info, guint time, gpointer user_data) +{ + printf ("dragging folder `%s'\n", path); + + switch (info) { + case DND_DRAG_TYPE_FOLDER: + /* dragging @path to a new location in the folder tree */ + gtk_selection_data_set (selection, selection->target, 8, path, strlen (path) + 1); + break; + case DND_DRAG_TYPE_TEXT_URI_LIST: + /* dragging @path to some place external to evolution */ + drag_text_uri_list (view, path, selection, user_data); + break; + default: + g_assert_not_reached (); + } +} + +static gboolean +parse_uid_list (const char *in, int inlen, char **path, GPtrArray **uids) +{ + const char *inptr, *inend; + + inend = in + inlen; + + *path = g_strdup (in); + + *uids = g_ptr_array_new (); + + inptr = in + inlen + 1; + while (inptr < inend) { + g_ptr_array_add (*uids, g_strdup (inptr)); + inptr += strlen (inptr) + 1; + } + + if ((*uids)->len == 0) { + g_ptr_array_free (*uids, TRUE); + g_free (*path); + + return FALSE; + } + + return TRUE; +} + +static void +drop_uid_list (EStorageSetView *view, const char *path, gboolean move, GtkSelectionData *selection, gpointer user_data) +{ + CamelFolder *src, *dest; + CamelException ex; + GPtrArray *uids; + char *src_path; + + if (!parse_uid_list (selection->data, selection->length, &src_path, &uids)) + return; + + camel_exception_init (&ex); + + if (!(src = foo_get_folder (view, src_path, &ex))) { + /* FIXME: report error to user? */ + camel_exception_clear (&ex); + em_utils_uids_free (uids); + g_free (src_path); + return; + } + + g_free (src_path); + + if (!(dest = foo_get_folder (view, path, &ex))) { + /* FIXME: report error to user? */ + camel_exception_clear (&ex); + em_utils_uids_free (uids); + camel_object_unref (src); + return; + } + + camel_folder_transfer_messages_to (src, uids, dest, NULL, move, &ex); + if (camel_exception_is_set (&ex)) { + /* FIXME: report error to user? */ + camel_exception_clear (&ex); + em_utils_uids_free (uids); + camel_object_unref (dest); + camel_object_unref (src); + return; + } + + em_utils_uids_free (uids); + camel_object_unref (dest); + camel_object_unref (src); +} + +static void +drop_folder (EStorageSetView *view, const char *path, gboolean move, GtkSelectionData *selection, gpointer user_data) +{ + CamelFolder *src, *dest; + CamelFolder *store; + CamelException ex; + + camel_exception_init (&ex); + + /* get the destination folder (where the user dropped). this + * will become the parent folder of the folder that got + * dragged */ + if (!(dest = foo_get_folder (view, path, &ex))) { + /* FIXME: report error to user? */ + camel_exception_clear (&ex); + return; + } + + /* get the folder being dragged */ + if (!(src = foo_get_folder (view, selection->data, &ex))) { + /* FIXME: report error to user? */ + camel_exception_clear (&ex); + camel_object_unref (dest); + return; + } + + if (src->parent_store == dest->parent_store && move) { + /* simple rename() action */ + char *old_name, *new_name; + + old_name = g_strdup (src->full_name); + new_name = g_strdup_printf ("%s/%s", dest->full_name, src->name); + camel_object_unref (src); + + camel_store_rename_folder (dest->parent_store, old_name, new_name, &ex); + if (camel_exception_is_set (&ex)) { + /* FIXME: report error to user? */ + camel_exception_clear (&ex); + camel_object_unref (dest); + g_free (old_name); + g_free (new_name); + return; + } + + camel_object_unref (dest); + g_free (old_name); + g_free (new_name); + } else { + /* copy the folder */ + camel_object_unref (dest); + camel_object_unref (src); + } +} + +static gboolean +import_message_rfc822 (CamelFolder *dest, CamelStream *stream, gboolean scan_from, CamelException *ex) +{ + CamelMimeParser *mp; + + mp = camel_mime_parser_new (); + camel_mime_parser_scan_from (mp, scan_from); + camel_mime_parser_init_with_stream (mp, stream); + + while (camel_mime_parser_step (mp, 0, 0) == CAMEL_MIME_PARSER_STATE_FROM) { + CamelMessageInfo *info; + CamelMimeMessage *msg; + + msg = camel_mime_message_new (); + if (camel_mime_part_construct_from_parser (CAMEL_MIME_PART (msg), mp) == -1) { + camel_object_unref (msg); + camel_object_unref (mp); + return FALSE; + } + + /* append the message to the folder... */ + info = g_new0 (CamelMessageInfo, 1); + camel_folder_append_message (dest, msg, info, NULL, ex); + camel_object_unref (msg); + + if (camel_exception_is_set (ex)) { + camel_object_unref (mp); + return FALSE; + } + + /* skip over the FROM_END state */ + camel_mime_parser_step (mp, 0, 0); + } + + camel_object_unref (mp); + + return TRUE; +} + +static void +drop_message_rfc822 (EStorageSetView *view, const char *path, GtkSelectionData *selection, gpointer user_data) +{ + CamelFolder *folder; + CamelStream *stream; + CamelException ex; + gboolean scan_from; + + camel_exception_init (&ex); + + if (!(folder = foo_get_folder (view, path, &ex))) { + /* FIXME: report error to user? */ + camel_exception_clear (&ex); + return; + } + + scan_from = selection->length > 5 && !strncmp (selection->data, "From ", 5); + stream = camel_stream_mem_new_with_buffer (selection->data, selection->length); + + if (!import_message_rfc822 (folder, stream, scan_from, &ex)) { + /* FIXME: report to user? */ + } + + camel_exception_clear (&ex); + + camel_object_unref (stream); + camel_object_unref (folder); +} + +static void +drop_text_uri_list (EStorageSetView *view, const char *path, GtkSelectionData *selection, gpointer user_data) +{ + CamelFolder *folder; + CamelStream *stream; + CamelException ex; + char **urls, *tmp; + int i; + + camel_exception_init (&ex); + + if (!(folder = foo_get_folder (view, path, &ex))) { + /* FIXME: report to user? */ + camel_exception_clear (&ex); + return; + } + + tmp = g_strndup (selection->data, selection->length); + urls = g_strsplit (tmp, "\n", 0); + g_free (tmp); + + for (i = 0; urls[i] != NULL; i++) { + CamelURL *uri; + char *url; + int fd; + + /* get the path component */ + url = g_strstrip (urls[i]); + uri = camel_url_new (url, NULL); + g_free (url); + + if (!uri || strcmp (uri->protocol, "file") != 0) { + camel_url_free (uri); + continue; + } + + url = uri->path; + uri->path = NULL; + camel_url_free (uri); + + if ((fd = open (url, O_RDONLY)) == -1) { + g_free (url); + continue; + } + + stream = camel_stream_fs_new_with_fd (fd); + if (!import_message_rfc822 (folder, stream, TRUE, &ex)) { + /* FIXME: report to user? */ + } + + camel_exception_clear (&ex); + camel_object_unref (stream); + g_free (url); + } + + camel_object_unref (folder); + g_free (urls); +} + +static void +folder_receive_drop_cb (EStorageSetView *view, const char *path, GdkDragContext *context, + GtkSelectionData *selection, guint info, guint time, gpointer user_data) +{ + gboolean move = context->action == GDK_ACTION_MOVE; + + /* this means we are receiving no data */ + if (!selection->data || selection->length == -1) + return; + + switch (info) { + case DND_DROP_TYPE_UID_LIST: + /* import a list of uids from another evo folder */ + drop_uid_list (view, path, move, selection, user_data); + break; + case DND_DROP_TYPE_FOLDER: + /* rename a folder */ + drop_folder (view, path, move, selection, user_data); + break; + case DND_DROP_TYPE_MESSAGE_RFC822: + /* import a message/rfc822 stream */ + drop_message_rfc822 (view, path, selection, user_data); + break; + case DND_DROP_TYPE_TEXT_URI_LIST: + /* import an mbox, maildir, or mh folder? */ + drop_text_uri_list (view, path, selection, user_data); + break; + default: + g_assert_not_reached (); + } +} + + /* GObject methods. */ static void @@ -457,6 +939,13 @@ impl_createControls (PortableServer_Servant servant, tree_widget = e_storage_browser_peek_tree_widget (browser); view_widget = e_storage_browser_peek_view_widget (browser); + e_storage_set_view_set_drag_types ((EStorageSetView *) tree_widget, drag_types, num_drag_types); + e_storage_set_view_set_drop_types ((EStorageSetView *) tree_widget, drop_types, num_drop_types); + e_storage_set_view_set_allow_dnd ((EStorageSetView *) tree_widget, TRUE); + + g_signal_connect (tree_widget, "folder_dragged", G_CALLBACK (folder_dragged_cb), browser); + g_signal_connect (tree_widget, "folder_receive_drop", G_CALLBACK (folder_receive_drop_cb), browser); + gtk_widget_show (tree_widget); gtk_widget_show (view_widget); |