/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* mail-component.c * * Copyright (C) 2003 Ximian Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * 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 * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * * Author: Ettore Perazzoli */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "e-storage.h" #include "e-storage-set.h" #include "e-storage-browser.h" #include "e-storage-set-view.h" #include "em-folder-selector.h" #include "em-folder-selection.h" #include "folder-browser-factory.h" #include "mail-config.h" #include "mail-component.h" #include "mail-folder-cache.h" #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 "em-migrate.h" #include #include #include #include #include #include #include #include #include #define d(x) x #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; struct _MailComponentPrivate { char *base_directory; MailAsyncEvent *async_event; GHashTable *storages_hash; /* storage by store */ EFolderTypeRegistry *folder_type_registry; EStorageSet *storage_set; RuleContext *search_context; char *context_path; /* current path for right-click menu */ CamelStore *local_store; }; static int emc_tree_right_click(ETree *tree, gint row, ETreePath path, gint col, GdkEvent *event, MailComponent *component); /* Utility functions. */ /* EPFIXME: Eeek, this totally sucks. See comment in e-storage.h, async_open_folder() should NOT be a signal. */ struct _StorageConnectedData { EStorage *storage; char *path; EStorageDiscoveryCallback callback; void *callback_data; }; typedef struct _StorageConnectedData StorageConnectedData; static void storage_connected_callback (CamelStore *store, CamelFolderInfo *info, StorageConnectedData *data) { EStorageResult result; if (info != NULL) result = E_STORAGE_OK; else result = E_STORAGE_GENERICERROR; (* data->callback) (data->storage, result, data->path, data->callback_data); g_object_unref (data->storage); g_free (data->path); g_free (data); } static void storage_async_open_folder_callback (EStorage *storage, const char *path, EStorageDiscoveryCallback callback, void *callback_data, CamelStore *store) { StorageConnectedData *storage_connected_data = g_new0 (StorageConnectedData, 1); g_object_ref (storage); storage_connected_data->storage = storage; storage_connected_data->path = g_strdup (path); storage_connected_data->callback = callback; storage_connected_data->callback_data = callback_data; mail_note_store (store, NULL, storage, (void *) storage_connected_callback, storage_connected_data); } static void add_storage (MailComponent *component, const char *name, CamelService *store, CamelException *ex) { EStorage *storage; EFolder *root_folder; root_folder = e_folder_new (name, "noselect", ""); storage = e_storage_new (name, root_folder); e_storage_declare_has_subfolders(storage, "/", _("Connecting...")); camel_object_ref(store); g_object_set_data((GObject *)storage, "em-store", store); g_hash_table_insert (component->priv->storages_hash, store, storage); g_signal_connect(storage, "async_open_folder", G_CALLBACK (storage_async_open_folder_callback), store); #if 0 /* Some private test code - zed */ { static void *model; if (model == NULL) { model = em_store_model_new(); em_store_model_view_new(model); } em_store_model_add_store(model, store); } #endif #if 0 /* EPFIXME these are not needed anymore. */ g_signal_connect(storage, "create_folder", G_CALLBACK(storage_create_folder), store); g_signal_connect(storage, "remove_folder", G_CALLBACK(storage_remove_folder), store); g_signal_connect(storage, "xfer_folder", G_CALLBACK(storage_xfer_folder), store); #endif e_storage_set_add_storage (component->priv->storage_set, storage); mail_note_store ((CamelStore *) store, NULL, storage, NULL, NULL); g_object_unref (storage); } static void load_accounts(MailComponent *component, EAccountList *accounts) { EIterator *iter; /* Load each service (don't connect!). Check its provider and * see if this belongs in the shell's folder list. If so, add * it. */ iter = e_list_get_iterator ((EList *) accounts); while (e_iterator_is_valid (iter)) { EAccountService *service; EAccount *account; const char *name; account = (EAccount *) e_iterator_get (iter); service = account->source; name = account->name; if (account->enabled && service->url != NULL) mail_component_load_storage_by_uri (component, service->url, name); e_iterator_next (iter); } g_object_unref (iter); } static inline gboolean type_is_mail (const char *type) { return !strcmp (type, "mail") || !strcmp (type, "mail/public"); } static inline gboolean type_is_vtrash (const char *type) { return !strcmp (type, "vtrash"); } static inline gboolean type_is_vjunk (const char *type) { return !strcmp (type, "vjunk"); } static void storage_go_online (gpointer key, gpointer value, gpointer data) { CamelStore *store = key; CamelService *service = CAMEL_SERVICE (store); if (! (service->provider->flags & CAMEL_PROVIDER_IS_REMOTE) || (service->provider->flags & CAMEL_PROVIDER_IS_EXTERNAL)) return; if ((CAMEL_IS_DISCO_STORE (service) && camel_disco_store_status (CAMEL_DISCO_STORE (service)) == CAMEL_DISCO_STORE_OFFLINE) || service->status != CAMEL_SERVICE_DISCONNECTED) { mail_store_set_offline (store, FALSE, NULL, NULL); mail_note_store (store, NULL, NULL, NULL, NULL); } } static void go_online (MailComponent *component) { camel_session_set_online(session, TRUE); mail_session_set_interactive(TRUE); mail_component_storages_foreach(component, storage_go_online, NULL); } static void setup_search_context (MailComponent *component) { MailComponentPrivate *priv = component->priv; char *user = g_strdup_printf ("%s/evolution/searches.xml", g_get_home_dir ()); /* EPFIXME should be somewhere else. */ char *system = g_strdup (EVOLUTION_PRIVDATADIR "/vfoldertypes.xml"); priv->search_context = rule_context_new (); g_object_set_data_full (G_OBJECT (priv->search_context), "user", user, g_free); g_object_set_data_full (G_OBJECT (priv->search_context), "system", system, g_free); rule_context_add_part_set (priv->search_context, "partset", filter_part_get_type (), rule_context_add_part, rule_context_next_part); rule_context_add_rule_set (priv->search_context, "ruleset", filter_rule_get_type (), rule_context_add_rule, rule_context_next_rule); rule_context_load (priv->search_context, system, user); } /* Local store setup. */ char *default_drafts_folder_uri; CamelFolder *drafts_folder = NULL; char *default_sent_folder_uri; CamelFolder *sent_folder = NULL; char *default_outbox_folder_uri; CamelFolder *outbox_folder = NULL; char *default_inbox_folder_uri; CamelFolder *inbox_folder = NULL; static struct { char *base; char **uri; CamelFolder **folder; } default_folders[] = { { "Inbox", &default_inbox_folder_uri, &inbox_folder }, { "Drafts", &default_drafts_folder_uri, &drafts_folder }, { "Outbox", &default_outbox_folder_uri, &outbox_folder }, { "Sent", &default_sent_folder_uri, &sent_folder }, }; static void setup_local_store(MailComponent *component) { MailComponentPrivate *p = component->priv; CamelException ex; char *store_uri; int i; g_assert(p->local_store == NULL); /* EPFIXME It should use base_directory once we have moved it. */ store_uri = g_strconcat("mbox:", g_get_home_dir(), "/.evolution/mail/local", NULL); p->local_store = mail_component_load_storage_by_uri(component, store_uri, _("On this Computer")); camel_object_ref(p->local_store); camel_exception_init (&ex); for (i=0;ilocal_store, default_folders[i].base, CAMEL_STORE_FOLDER_CREATE, &ex); camel_exception_clear(&ex); } g_free(store_uri); } /* EStorageBrowser callbacks. */ static BonoboControl * create_noselect_control (void) { GtkWidget *label; label = gtk_label_new (_("This folder cannot contain messages.")); gtk_widget_show (label); return bonobo_control_new (label); } static GtkWidget * create_view_callback (EStorageBrowser *browser, const char *path, void *unused_data) { BonoboControl *control; EFolder *folder; const char *folder_type; const char *physical_uri; folder = e_storage_set_get_folder (e_storage_browser_peek_storage_set (browser), path); if (folder == NULL) { g_warning ("No folder at %s", path); return gtk_label_new ("(You should not be seeing this label)"); } folder_type = e_folder_get_type_string (folder); physical_uri = e_folder_get_physical_uri (folder); if (type_is_mail (folder_type)) { const char *noselect; CamelURL *url; url = camel_url_new (physical_uri, NULL); noselect = url ? camel_url_get_param (url, "noselect") : NULL; if (noselect && !strcasecmp (noselect, "yes")) control = create_noselect_control (); else control = folder_browser_factory_new_control (physical_uri); camel_url_free (url); } else if (type_is_vtrash (folder_type)) { if (!strncasecmp (physical_uri, "file:", 5)) control = folder_browser_factory_new_control ("vtrash:file:/"); else control = folder_browser_factory_new_control (physical_uri); } else if (type_is_vjunk (folder_type)) { if (!strncasecmp (physical_uri, "file:", 5)) control = folder_browser_factory_new_control ("vjunk:file:/"); else control = folder_browser_factory_new_control (physical_uri); } else return NULL; if (!control) return NULL; /* EPFIXME: This leaks the control. */ return bonobo_widget_new_control_from_objref (BONOBO_OBJREF (control), CORBA_OBJECT_NIL); } static void browser_page_switched_callback (EStorageBrowser *browser, GtkWidget *old_page, GtkWidget *new_page, BonoboControl *parent_control) { if (BONOBO_IS_WIDGET (old_page)) { BonoboControlFrame *control_frame = bonobo_widget_get_control_frame (BONOBO_WIDGET (old_page)); bonobo_control_frame_control_deactivate (control_frame); } if (BONOBO_IS_WIDGET (new_page)) { BonoboControlFrame *control_frame = bonobo_widget_get_control_frame (BONOBO_WIDGET (new_page)); Bonobo_UIContainer ui_container = bonobo_control_get_remote_ui_container (parent_control, NULL); /* This is necessary because we are not embedding the folder browser control directly; we are putting the folder browser control into a notebook which is then exported to the shell as a control. So we need to forward the notebook's UIContainer to the folder browser. */ bonobo_control_frame_set_ui_container (control_frame, ui_container, NULL); bonobo_control_frame_control_activate (control_frame); } } static CamelFolder * foo_get_folder (EStorageSetView *view, const char *path, CamelException *ex) { /* 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 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_uri; em_utils_selection_get_uidlist (selection, &src_uri, &uids); camel_exception_init (&ex); if (!(src = mail_tool_uri_to_folder (src_uri, 0, &ex))) { /* FIXME: report error to user? */ camel_exception_clear (&ex); em_utils_uids_free (uids); g_free (src_uri); return; } g_free (src_uri); 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); printf ("* dropped a x-uid-list\n"); break; case DND_DROP_TYPE_FOLDER: /* rename a folder */ drop_folder (view, path, move, selection, user_data); printf ("* dropped a x-folder\n"); break; case DND_DROP_TYPE_MESSAGE_RFC822: /* import a message/rfc822 stream */ drop_message_rfc822 (view, path, selection, user_data); printf ("* dropped a message/rfc822\n"); break; case DND_DROP_TYPE_TEXT_URI_LIST: /* import an mbox, maildir, or mh folder? */ drop_text_uri_list (view, path, selection, user_data); printf ("* dropped a text/uri-list\n"); break; default: g_assert_not_reached (); } gtk_drag_finish (context, TRUE, TRUE, time); } /* GObject methods. */ static void impl_dispose (GObject *object) { MailComponentPrivate *priv = MAIL_COMPONENT (object)->priv; if (priv->storage_set != NULL) { g_object_unref (priv->storage_set); priv->storage_set = NULL; } if (priv->folder_type_registry != NULL) { g_object_unref (priv->folder_type_registry); priv->folder_type_registry = NULL; } if (priv->search_context != NULL) { g_object_unref (priv->search_context); priv->search_context = NULL; } if (priv->local_store != NULL) { camel_object_unref (CAMEL_OBJECT (priv->local_store)); priv->local_store = NULL; } (* G_OBJECT_CLASS (parent_class)->dispose) (object); } static void impl_finalize (GObject *object) { MailComponentPrivate *priv = MAIL_COMPONENT (object)->priv; g_free (priv->base_directory); mail_async_event_destroy (priv->async_event); g_hash_table_destroy (priv->storages_hash); /* EPFIXME free the data within? */ if (mail_async_event_destroy (priv->async_event) == -1) { g_warning("Cannot destroy async event: would deadlock"); g_warning(" system may be unstable at exit"); } g_free(priv->context_path); g_free (priv); (* G_OBJECT_CLASS (parent_class)->finalize) (object); } /* Evolution::Component CORBA methods. */ static void impl_createControls (PortableServer_Servant servant, Bonobo_Control *corba_sidebar_control, Bonobo_Control *corba_view_control, CORBA_Environment *ev) { MailComponent *mail_component = MAIL_COMPONENT (bonobo_object_from_servant (servant)); MailComponentPrivate *priv = mail_component->priv; EStorageBrowser *browser; GtkWidget *tree_widget; GtkWidget *tree_widget_scrolled; GtkWidget *view_widget; BonoboControl *sidebar_control; BonoboControl *view_control; browser = e_storage_browser_new (priv->storage_set, "/", create_view_callback, NULL); tree_widget = e_storage_browser_peek_tree_widget (browser); tree_widget_scrolled = e_storage_browser_peek_tree_widget_scrolled (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_scrolled); gtk_widget_show (view_widget); sidebar_control = bonobo_control_new (tree_widget_scrolled); view_control = bonobo_control_new (view_widget); *corba_sidebar_control = CORBA_Object_duplicate (BONOBO_OBJREF (sidebar_control), ev); *corba_view_control = CORBA_Object_duplicate (BONOBO_OBJREF (view_control), ev); g_signal_connect_object (browser, "page_switched", G_CALLBACK (browser_page_switched_callback), view_control, 0); g_signal_connect(tree_widget, "right_click", G_CALLBACK(emc_tree_right_click), mail_component); } static GNOME_Evolution_CreatableItemTypeList * impl__get_userCreatableItems (PortableServer_Servant servant, CORBA_Environment *ev) { GNOME_Evolution_CreatableItemTypeList *list = GNOME_Evolution_CreatableItemTypeList__alloc (); list->_length = 1; list->_maximum = list->_length; list->_buffer = GNOME_Evolution_CreatableItemTypeList_allocbuf (list->_length); CORBA_sequence_set_release (list, FALSE); list->_buffer[0].id = "message"; list->_buffer[0].description = _("New Mail Message"); list->_buffer[0].menuDescription = _("_Mail Message"); list->_buffer[0].tooltip = _("Compose a new mail message"); list->_buffer[0].menuShortcut = 'm'; list->_buffer[0].iconName = "new-message.xpm"; return list; } static void impl_requestCreateItem (PortableServer_Servant servant, const CORBA_char *item_type_name, CORBA_Environment *ev) { if (strcmp (item_type_name, "message") != 0) { CORBA_exception_set (ev, CORBA_USER_EXCEPTION, ex_GNOME_Evolution_Component_UnknownType, NULL); return; } em_utils_compose_new_message (); } /* Initialization. */ static void mail_component_class_init (MailComponentClass *class) { POA_GNOME_Evolution_Component__epv *epv = &class->epv; GObjectClass *object_class = G_OBJECT_CLASS (class); parent_class = g_type_class_peek_parent (class); object_class->dispose = impl_dispose; object_class->finalize = impl_finalize; epv->createControls = impl_createControls; epv->_get_userCreatableItems = impl__get_userCreatableItems; epv->requestCreateItem = impl_requestCreateItem; } static void mail_component_init (MailComponent *component) { MailComponentPrivate *priv; EAccountList *accounts; struct stat st; char *mail_dir; priv = g_new0 (MailComponentPrivate, 1); component->priv = priv; priv->base_directory = g_build_filename (g_get_home_dir (), ".evolution", NULL); if (camel_mkdir (priv->base_directory, 0777) == -1 && errno != EEXIST) abort (); /* EPFIXME: Turn into an object? */ mail_session_init (priv->base_directory); priv->async_event = mail_async_event_new(); priv->storages_hash = g_hash_table_new (NULL, NULL); priv->folder_type_registry = e_folder_type_registry_new (); priv->storage_set = e_storage_set_new (priv->folder_type_registry); /* migrate evolution 1.x folders to 2.0's location/format */ mail_dir = g_strdup_printf ("%s/mail", priv->base_directory); if (stat (mail_dir, &st) == -1) { CamelException ex; camel_exception_init (&ex); if (em_migrate (component, &ex) == -1) { GtkWidget *dialog; dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, _("The following error occured while migrating your mail data:\n%s"), camel_exception_get_description (&ex)); g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); gtk_widget_show (dialog); camel_exception_clear (&ex); } } g_free (mail_dir); setup_local_store (component); accounts = mail_config_get_accounts (); load_accounts(component, accounts); /* mail_autoreceive_setup (); EPFIXME keep it off for testing */ setup_search_context (component); /* EPFIXME not sure about this. */ go_online (component); } /* Public API. */ MailComponent * mail_component_peek (void) { static MailComponent *component = NULL; if (component == NULL) { component = g_object_new (mail_component_get_type (), NULL); /* FIXME: this should all be initialised in a starutp routine, not from the peek function, this covers much of the ::init method's content too */ vfolder_load_storage(); } return component; } const char * mail_component_peek_base_directory (MailComponent *component) { return component->priv->base_directory; } RuleContext * mail_component_peek_search_context (MailComponent *component) { return component->priv->search_context; } void mail_component_add_store (MailComponent *component, CamelStore *store, const char *name) { CamelException ex; camel_exception_init (&ex); if (name == NULL) { char *service_name; service_name = camel_service_get_name ((CamelService *) store, TRUE); add_storage (component, service_name, (CamelService *) store, &ex); g_free (service_name); } else { add_storage (component, name, (CamelService *) store, &ex); } camel_exception_clear (&ex); } /** * mail_component_load_storage_by_uri: * @component: * @uri: * @name: * * * * Return value: Pointer to the newly added CamelStore. The caller is supposed * to ref the object if it wants to store it. **/ CamelStore * mail_component_load_storage_by_uri (MailComponent *component, const char *uri, const char *name) { CamelException ex; CamelService *store; CamelProvider *prov; camel_exception_init (&ex); /* Load the service (don't connect!). Check its provider and * see if this belongs in the shell's folder list. If so, add * it. */ prov = camel_session_get_provider (session, uri, &ex); if (prov == NULL) { /* EPFIXME: real error dialog */ g_warning ("couldn't get service %s: %s\n", uri, camel_exception_get_description (&ex)); camel_exception_clear (&ex); return NULL; } if (!(prov->flags & CAMEL_PROVIDER_IS_STORAGE) || (prov->flags & CAMEL_PROVIDER_IS_EXTERNAL)) return NULL; store = camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, &ex); if (store == NULL) { /* EPFIXME: real error dialog */ g_warning ("couldn't get service %s: %s\n", uri, camel_exception_get_description (&ex)); camel_exception_clear (&ex); return NULL; } if (name != NULL) { add_storage (component, name, store, &ex); } else { char *service_name; service_name = camel_service_get_name (store, TRUE); add_storage (component, service_name, store, &ex); g_free (service_name); } if (camel_exception_is_set (&ex)) { /* EPFIXME: real error dialog */ g_warning ("Cannot load storage: %s", camel_exception_get_description (&ex)); camel_exception_clear (&ex); } camel_object_unref (CAMEL_OBJECT (store)); return CAMEL_STORE (store); /* (Still has one ref in the hash.) */ } static void store_disconnect (CamelStore *store, void *event_data, void *data) { camel_service_disconnect (CAMEL_SERVICE (store), TRUE, NULL); camel_object_unref (CAMEL_OBJECT (store)); } void mail_component_remove_storage (MailComponent *component, CamelStore *store) { MailComponentPrivate *priv = component->priv; EStorage *storage; /* Because the storages_hash holds a reference to each store * used as a key in it, none of them will ever be gc'ed, meaning * any call to camel_session_get_{service,store} with the same * URL will always return the same object. So this works. */ storage = g_hash_table_lookup (priv->storages_hash, store); if (!storage) return; g_hash_table_remove (priv->storages_hash, store); /* so i guess potentially we could have a race, add a store while one being removed. ?? */ mail_note_store_remove (store); e_storage_set_remove_storage (priv->storage_set, storage); mail_async_event_emit(priv->async_event, MAIL_ASYNC_THREAD, (MailAsyncFunc) store_disconnect, store, NULL, NULL); } void mail_component_remove_storage_by_uri (MailComponent *component, const char *uri) { CamelProvider *prov; CamelService *store; prov = camel_session_get_provider (session, uri, NULL); if (!prov) return; if (!(prov->flags & CAMEL_PROVIDER_IS_STORAGE) || (prov->flags & CAMEL_PROVIDER_IS_EXTERNAL)) return; store = camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, NULL); if (store != NULL) { mail_component_remove_storage (component, CAMEL_STORE (store)); camel_object_unref (CAMEL_OBJECT (store)); } } EStorage * mail_component_lookup_storage (MailComponent *component, CamelStore *store) { EStorage *storage; /* Because the storages_hash holds a reference to each store * used as a key in it, none of them will ever be gc'ed, meaning * any call to camel_session_get_{service,store} with the same * URL will always return the same object. So this works. */ storage = g_hash_table_lookup (component->priv->storages_hash, store); if (storage) g_object_ref (storage); return storage; } int mail_component_get_storage_count (MailComponent *component) { return g_hash_table_size (component->priv->storages_hash); } EStorageSet * mail_component_peek_storage_set (MailComponent *component) { return component->priv->storage_set; } void mail_component_storages_foreach (MailComponent *component, GHFunc func, void *data) { g_hash_table_foreach (component->priv->storages_hash, func, data); } extern struct _CamelSession *session; char *em_uri_from_camel(const char *curi) { CamelURL *curl; EAccount *account; const char *uid, *path; char *euri; CamelProvider *provider; provider = camel_session_get_provider(session, curi, NULL); if (provider == NULL) return g_strdup(curi); curl = camel_url_new(curi, NULL); if (curl == NULL) return g_strdup(curi); account = mail_config_get_account_by_source_url(curi); uid = (account == NULL)?"local@local":account->uid; path = (provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH)?curl->fragment:curl->path; if (path[0] == '/') path++; euri = g_strdup_printf("email://%s/%s", uid, path); d(printf("em uri from camel '%s' -> '%s'\n", curi, euri)); return euri; } char *em_uri_to_camel(const char *euri) { EAccountList *accounts; const EAccount *account; EAccountService *service; CamelProvider *provider; CamelURL *eurl, *curl; char *uid, *curi; eurl = camel_url_new(euri, NULL); if (eurl == NULL) return g_strdup(euri); if (strcmp(eurl->protocol, "email") != 0) { camel_url_free(eurl); return g_strdup(euri); } g_assert(eurl->user != NULL); g_assert(eurl->host != NULL); if (strcmp(eurl->user, "local") == 0 && strcmp(eurl->host, "local") == 0) { curi = g_strdup_printf("mbox:%s/.evolution/mail/local#%s", g_get_home_dir(), eurl->path); camel_url_free(eurl); return curi; } uid = g_strdup_printf("%s@%s", eurl->user, eurl->host); accounts = mail_config_get_accounts(); account = e_account_list_find(accounts, E_ACCOUNT_FIND_UID, uid); g_free(uid); if (account == NULL) { camel_url_free(eurl); return g_strdup(euri); } service = account->source; provider = camel_session_get_provider(session, service->url, NULL); curl = camel_url_new(service->url, NULL); if (provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH) camel_url_set_fragment(curl, eurl->path); else camel_url_set_path(curl, eurl->path); curi = camel_url_to_string(curl, 0); camel_url_free(eurl); camel_url_free(curl); d(printf("em uri to camel '%s' -> '%s'\n", euri, curi)); return curi; } CamelFolder * mail_component_get_folder_from_evomail_uri (MailComponent *component, guint32 flags, const char *evomail_uri, CamelException *ex) { CamelException local_ex; EAccountList *accounts; EIterator *iter; const char *p; const char *q; const char *folder_name; char *uid; camel_exception_init (&local_ex); if (strncmp (evomail_uri, "evomail:", 8) != 0) return NULL; p = evomail_uri + 8; while (*p == '/') p ++; q = strchr (p, '/'); if (q == NULL) return NULL; uid = g_strndup (p, q - p); folder_name = q + 1; /* since we have no explicit account for 'local' folders, make one up */ if (strcmp(uid, "local") == 0) { g_free(uid); return camel_store_get_folder(component->priv->local_store, folder_name, flags, ex); } accounts = mail_config_get_accounts (); iter = e_list_get_iterator ((EList *) accounts); while (e_iterator_is_valid (iter)) { EAccount *account = (EAccount *) e_iterator_get (iter); EAccountService *service = account->source; CamelProvider *provider; CamelStore *store; if (strcmp (account->uid, uid) != 0) continue; provider = camel_session_get_provider (session, service->url, &local_ex); if (provider == NULL) goto fail; store = (CamelStore *) camel_session_get_service (session, service->url, CAMEL_PROVIDER_STORE, &local_ex); if (store == NULL) goto fail; g_free (uid); return camel_store_get_folder (store, folder_name, flags, ex); } fail: camel_exception_clear (&local_ex); g_free (uid); return NULL; } char * mail_component_evomail_uri_from_folder (MailComponent *component, CamelFolder *folder) { CamelStore *store = camel_folder_get_parent_store (folder); EAccount *account; char *service_url; char *evomail_uri; const char *uid; if (store == NULL) return NULL; service_url = camel_service_get_url (CAMEL_SERVICE (store)); account = mail_config_get_account_by_source_url (service_url); if (account == NULL) { /* since we have no explicit account for 'local' folders, make one up */ /* TODO: check the folder is really a local one, folder->parent_store == local_store? */ uid = "local"; /*g_free (service_url); return NULL;*/ } else { uid = account->uid; } evomail_uri = g_strconcat ("evomail:///", uid, "/", camel_folder_get_full_name (folder), NULL); g_free (service_url); return evomail_uri; } BONOBO_TYPE_FUNC_FULL (MailComponent, GNOME_Evolution_Component, PARENT_TYPE, mail_component) /* ********************************************************************** */ #if 0 static void emc_popup_view(GtkWidget *w, MailComponent *mc) { } static void emc_popup_open_new(GtkWidget *w, MailComponent *mc) { } #endif /* FIXME: This must be done in another thread */ static void em_copy_folders(CamelStore *tostore, const char *tobase, CamelStore *fromstore, const char *frombase, int delete) { GString *toname, *fromname; CamelFolderInfo *fi; GList *pending = NULL, *deleting = NULL, *l; guint32 flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE; CamelException *ex = camel_exception_new(); int fromlen; const char *tmp; if (camel_store_supports_subscriptions(fromstore)) flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED; fi = camel_store_get_folder_info(fromstore, frombase, flags, ex); if (camel_exception_is_set(ex)) goto done; pending = g_list_append(pending, fi); toname = g_string_new(""); fromname = g_string_new(""); tmp = strrchr(frombase, '/'); if (tmp == NULL) fromlen = 0; else fromlen = tmp-frombase+1; printf("top name is '%s'\n", fi->full_name); while (pending) { CamelFolderInfo *info = pending->data; pending = g_list_remove_link(pending, pending); while (info) { CamelFolder *fromfolder, *tofolder; GPtrArray *uids; if (info->child) pending = g_list_append(pending, info->child); if (tobase[0]) g_string_printf(toname, "%s/%s", tobase, info->full_name + fromlen); else g_string_printf(toname, "%s", info->full_name + fromlen); printf("Copying from '%s' to '%s'\n", info->full_name, toname->str); /* This makes sure we create the same tree, e.g. from a nonselectable source */ /* Not sure if this is really the 'right thing', e.g. for spool stores, but it makes the ui work */ if ((info->flags & CAMEL_FOLDER_NOSELECT) == 0) { printf("this folder is selectable\n"); fromfolder = camel_store_get_folder(fromstore, info->full_name, 0, ex); if (fromfolder == NULL) goto exception; tofolder = camel_store_get_folder(tostore, toname->str, CAMEL_STORE_FOLDER_CREATE, ex); if (tofolder == NULL) { camel_object_unref(fromfolder); goto exception; } if (camel_store_supports_subscriptions(tostore) && !camel_store_folder_subscribed(tostore, toname->str)) camel_store_subscribe_folder(tostore, toname->str, NULL); uids = camel_folder_get_uids(fromfolder); camel_folder_transfer_messages_to(fromfolder, uids, tofolder, NULL, delete, ex); camel_folder_free_uids(fromfolder, uids); camel_object_unref(fromfolder); camel_object_unref(tofolder); } if (camel_exception_is_set(ex)) goto exception; else if (delete) deleting = g_list_prepend(deleting, info); info = info->sibling; } } /* delete the folders in reverse order from how we copyied them, if we are deleting any */ l = deleting; while (l) { CamelFolderInfo *info = l->data; printf("deleting folder '%s'\n", info->full_name); if (camel_store_supports_subscriptions(fromstore)) camel_store_unsubscribe_folder(fromstore, info->full_name, NULL); camel_store_delete_folder(fromstore, info->full_name, NULL); l = l->next; } exception: camel_store_free_folder_info(fromstore, fi); g_list_free(deleting); g_string_free(toname, TRUE); g_string_free(fromname, TRUE); done: printf("exception: %s\n", ex->desc?ex->desc:""); camel_exception_free(ex); } struct _copy_folder_data { MailComponent *mc; int delete; }; static void emc_popup_copy_folder_selected(const char *uri, void *data) { struct _copy_folder_data *d = data; if (uri == NULL) { g_free(d); return; } if (uri) { EFolder *folder = e_storage_set_get_folder(d->mc->priv->storage_set, d->mc->priv->context_path); CamelException *ex = camel_exception_new(); CamelStore *fromstore, *tostore; char *tobase, *frombase; CamelURL *url; printf("copying folder '%s' to '%s'\n", d->mc->priv->context_path, uri); fromstore = camel_session_get_store(session, e_folder_get_physical_uri(folder), ex); frombase = strchr(d->mc->priv->context_path+1, '/')+1; tostore = camel_session_get_store(session, uri, ex); url = camel_url_new(uri, NULL); if (url->fragment) tobase = url->fragment; else if (url->path && url->path[0]) tobase = url->path+1; else tobase = ""; em_copy_folders(tostore, tobase, fromstore, frombase, d->delete); camel_url_free(url); camel_exception_free(ex); } g_free(d); } static void emc_popup_copy(GtkWidget *w, MailComponent *mc) { struct _copy_folder_data *d; d = g_malloc(sizeof(*d)); d->mc = mc; d->delete = 0; em_select_folder(NULL, _("Select folder"), _("Select destination to copy folder into"), NULL, emc_popup_copy_folder_selected, d); } static void emc_popup_move(GtkWidget *w, MailComponent *mc) { struct _copy_folder_data *d; d = g_malloc(sizeof(*d)); d->mc = mc; d->delete = 1; em_select_folder(NULL, _("Select folder"), _("Select destination to move folder into"), NULL, emc_popup_copy_folder_selected, d); } static void emc_popup_new_folder_create(EStorageSet *ess, EStorageResult result, void *data) { printf("folder created %s\n", result == E_STORAGE_OK?"ok":"failed"); } static void emc_popup_new_folder_response(EMFolderSelector *emfs, guint response, MailComponent *mc) { if (response == GTK_RESPONSE_OK) { char *path, *tmp, *name, *full; EStorage *storage; CamelStore *store; CamelException *ex; printf("Creating folder: %s (%s)\n", em_folder_selector_get_selected(emfs), em_folder_selector_get_selected_uri(emfs)); path = g_strdup(em_folder_selector_get_selected(emfs)); tmp = strchr(path+1, '/'); *tmp++ = 0; /* FIXME: camel_store_create_folder should just take full path names */ full = g_strdup(tmp); name = strrchr(tmp, '/'); if (name == NULL) { name = tmp; tmp = ""; } else *name++ = 0; storage = e_storage_set_get_storage(mc->priv->storage_set, path+1); store = g_object_get_data((GObject *)storage, "em-store"); printf("creating folder '%s' / '%s' on '%s'\n", tmp, name, path+1); ex = camel_exception_new(); camel_store_create_folder(store, tmp, name, ex); if (camel_exception_is_set(ex)) { printf("Create failed: %s\n", ex->desc); } else if (camel_store_supports_subscriptions(store)) { camel_store_subscribe_folder(store, full, ex); if (camel_exception_is_set(ex)) { printf("Subscribe failed: %s\n", ex->desc); } } camel_exception_free(ex); g_free(full); g_free(path); /* Blah, this should just use camel, we get better error reporting if we do too */ /*e_storage_set_async_create_folder(mc->priv->storage_set, path, "mail", "", emc_popup_new_folder_create, mc);*/ } gtk_widget_destroy((GtkWidget *)emfs); } static void emc_popup_new_folder (GtkWidget *w, MailComponent *mc) { GtkWidget *dialog; dialog = em_folder_selector_create_new(mc->priv->storage_set, 0, _("Create folder"), _("Specify where to create the folder:")); em_folder_selector_set_selected((EMFolderSelector *)dialog, mc->priv->context_path); g_signal_connect(dialog, "response", G_CALLBACK(emc_popup_new_folder_response), mc); gtk_widget_show(dialog); } static void em_delete_rec(CamelStore *store, CamelFolderInfo *fi, CamelException *ex) { while (fi) { CamelFolder *folder; if (fi->child) em_delete_rec(store, fi->child, ex); if (camel_exception_is_set(ex)) return; printf("deleting folder '%s'\n", fi->full_name); /* shouldn't camel do this itself? */ if (camel_store_supports_subscriptions(store)) camel_store_unsubscribe_folder(store, fi->full_name, NULL); folder = camel_store_get_folder(store, fi->full_name, 0, NULL); if (folder) { GPtrArray *uids = camel_folder_get_uids(folder); int i; camel_folder_freeze(folder); for (i = 0; i < uids->len; i++) camel_folder_delete_message(folder, uids->pdata[i]); camel_folder_sync(folder, TRUE, NULL); camel_folder_thaw(folder); camel_folder_free_uids(folder, uids); } camel_store_delete_folder(store, fi->full_name, ex); if (camel_exception_is_set(ex)) return; fi = fi->sibling; } } static void em_delete_folders(CamelStore *store, const char *base, CamelException *ex) { CamelFolderInfo *fi; guint32 flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE; if (camel_store_supports_subscriptions(store)) flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED; fi = camel_store_get_folder_info(store, base, flags, ex); if (camel_exception_is_set(ex)) return; em_delete_rec(store, fi, ex); camel_store_free_folder_info(store, fi); } static void emc_popup_delete_response(GtkWidget *w, guint response, MailComponent *mc) { gtk_widget_destroy(w); if (response == GTK_RESPONSE_OK) { const char *path = strchr(mc->priv->context_path+1, '/')+1; EFolder *folder = e_storage_set_get_folder(mc->priv->storage_set, mc->priv->context_path); CamelException *ex = camel_exception_new(); CamelStore *store; /* FIXME: need to hook onto store changed event and delete view as well, somewhere else tho */ store = camel_session_get_store(session, e_folder_get_physical_uri(folder), ex); if (camel_exception_is_set(ex)) goto exception; em_delete_folders(store, path, ex); if (!camel_exception_is_set(ex)) goto noexception; exception: e_notice(NULL, GTK_MESSAGE_ERROR, _("Could not delete folder: %s"), ex->desc); noexception: camel_exception_free(ex); if (store) camel_object_unref(store); } } static void emc_popup_delete_folder(GtkWidget *w, MailComponent *mc) { GtkWidget *dialog; char *title; const char *path = strchr(mc->priv->context_path+1, '/')+1; EFolder *folder; folder = e_storage_set_get_folder(mc->priv->storage_set, mc->priv->context_path); if (folder == NULL) return; dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Really delete folder \"%s\" and all of its subfolders?"), path); gtk_dialog_add_button((GtkDialog *)dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); gtk_dialog_add_button((GtkDialog *)dialog, GTK_STOCK_DELETE, GTK_RESPONSE_OK); gtk_dialog_set_default_response((GtkDialog *)dialog, GTK_RESPONSE_OK); gtk_container_set_border_width((GtkContainer *)dialog, 6); gtk_box_set_spacing((GtkBox *)((GtkDialog *)dialog)->vbox, 6); title = g_strdup_printf(_("Delete \"%s\""), path); gtk_window_set_title((GtkWindow *)dialog, title); g_free(title); g_signal_connect(dialog, "response", G_CALLBACK(emc_popup_delete_response), mc); gtk_widget_show(dialog); } static void emc_popup_rename_folder(GtkWidget *w, MailComponent *mc) { char *prompt, *new; EFolder *folder; const char *old, *why; int done = 0; folder = e_storage_set_get_folder(mc->priv->storage_set, mc->priv->context_path); if (folder == NULL) return; old = e_folder_get_name(folder); prompt = g_strdup_printf (_("Rename the \"%s\" folder to:"), e_folder_get_name(folder)); while (!done) { new = e_request_string(NULL, _("Rename Folder"), prompt, old); if (new == NULL || strcmp(old, new) == 0) done = 1; #if 0 else if (!e_shell_folder_name_is_valid(new, &why)) e_notice(NULL, GTK_MESSAGE_ERROR, _("The specified folder name is not valid: %s"), why); #endif else { char *base, *path; /* FIXME: we can't use the os independent path crap here, since we want to control the format */ base = g_path_get_dirname(mc->priv->context_path); path = g_build_filename(base, new, NULL); if (e_storage_set_get_folder(mc->priv->storage_set, path) != NULL) { e_notice(NULL, GTK_MESSAGE_ERROR, _("A folder named \"%s\" already exists. Please use a different name."), new); } else { CamelStore *store; CamelException *ex = camel_exception_new(); const char *oldpath, *newpath; oldpath = strchr(mc->priv->context_path+1, '/'); g_assert(oldpath); newpath = strchr(path+1, '/'); g_assert(newpath); oldpath++; newpath++; printf("renaming %s to %s\n", oldpath, newpath); store = camel_session_get_store(session, e_folder_get_physical_uri(folder), ex); if (camel_exception_is_set(ex)) goto exception; camel_store_rename_folder(store, oldpath, newpath, ex); if (!camel_exception_is_set(ex)) goto noexception; exception: e_notice(NULL, GTK_MESSAGE_ERROR, _("Could not rename folder: %s"), ex->desc); noexception: if (store) camel_object_unref(store); camel_exception_free(ex); done = 1; } g_free(path); g_free(base); } g_free(new); } } struct _prop_data { void *object; CamelArgV *argv; GtkWidget **widgets; }; static void emc_popup_properties_response(GtkWidget *dialog, int response, struct _prop_data *prop_data) { int i; CamelArgV *argv = prop_data->argv; if (response != GTK_RESPONSE_OK) { gtk_widget_destroy(dialog); return; } for (i=0;iargc;i++) { CamelArg *arg = &argv->argv[i]; switch (arg->tag & CAMEL_ARG_TYPE) { case CAMEL_ARG_BOO: arg->ca_int = gtk_toggle_button_get_active ((GtkToggleButton *) prop_data->widgets[i]); break; case CAMEL_ARG_STR: g_free(arg->ca_str); arg->ca_str = gtk_entry_get_text ((GtkEntry *) prop_data->widgets[i]); break; default: printf("unknown property type set\n"); } } camel_object_setv(prop_data->object, NULL, argv); gtk_widget_destroy(dialog); } static void emc_popup_properties_free(void *data) { struct _prop_data *prop_data = data; int i; for (i=0; iargv->argc; i++) { if ((prop_data->argv->argv[i].tag & CAMEL_ARG_TYPE) == CAMEL_ARG_STR) g_free(prop_data->argv->argv[i].ca_str); } camel_object_unref(prop_data->object); g_free(prop_data->argv); g_free(prop_data); } static void emc_popup_properties_got_folder (char *uri, CamelFolder *folder, void *data) { if (folder) { GtkWidget *dialog, *w, *table, *label; GSList *list, *l; char *name; int row = 1; gint32 count, i; struct _prop_data *prop_data; CamelArgV *argv; CamelArgGetV *arggetv; camel_object_get(folder, NULL, CAMEL_FOLDER_PROPERTIES, &list, CAMEL_FOLDER_NAME, &name, NULL); dialog = gtk_dialog_new_with_buttons(_("Folder properties"), NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); /* TODO: maybe we want some basic properties here, like message counts/approximate size/etc */ w = gtk_frame_new(_("Properties")); gtk_box_pack_start ((GtkBox *) ((GtkDialog *)dialog)->vbox, w, TRUE, TRUE, 6); table = gtk_table_new(g_slist_length(list)+1, 2, FALSE); gtk_container_add((GtkContainer *)w, table); label = gtk_label_new(_("Folder Name")); gtk_misc_set_alignment ((GtkMisc *) label, 1.0, 0.5); gtk_table_attach ((GtkTable *) table, label, 0, 1, 0, 1, GTK_FILL|GTK_EXPAND, 0, 3, 0); label = gtk_label_new(name); gtk_misc_set_alignment ((GtkMisc *) label, 0.0, 0.5); gtk_table_attach ((GtkTable *) table, label, 1, 2, 0, 1, GTK_FILL|GTK_EXPAND, 0, 3, 0); /* build an arggetv/argv to retrieve/store the results */ count = g_slist_length(list); arggetv = g_malloc0(sizeof(*arggetv) + (count - CAMEL_ARGV_MAX) * sizeof(arggetv->argv[0])); arggetv->argc = count; argv = g_malloc0(sizeof(*argv) + (count - CAMEL_ARGV_MAX) * sizeof(argv->argv[0])); argv->argc = count; i = 0; l = list; while (l) { CamelProperty *prop = l->data; argv->argv[i].tag = prop->tag; arggetv->argv[i].tag = prop->tag; arggetv->argv[i].ca_ptr = &argv->argv[i].ca_ptr; l = l->next; i++; } camel_object_getv(folder, NULL, arggetv); g_free(arggetv); prop_data = g_malloc0(sizeof(*prop_data)); prop_data->widgets = g_malloc0(sizeof(prop_data->widgets[0]) * count); prop_data->argv = argv; /* setup the ui with the values retrieved */ l = list; i = 0; while (l) { CamelProperty *prop = l->data; switch (prop->tag & CAMEL_ARG_TYPE) { case CAMEL_ARG_BOO: w = gtk_check_button_new_with_label(prop->description); gtk_toggle_button_set_active((GtkToggleButton *)w, argv->argv[i].ca_int != 0); gtk_table_attach ((GtkTable *) table, w, 0, 2, row, row+1, 0, 0, 3, 3); prop_data->widgets[i] = w; break; case CAMEL_ARG_STR: label = gtk_label_new(prop->description); gtk_misc_set_alignment ((GtkMisc *) label, 1.0, 0.5); gtk_table_attach ((GtkTable *) table, label, 0, 1, row, row+1, GTK_FILL|GTK_EXPAND, 0, 3, 3); w = gtk_entry_new(); if (argv->argv[i].ca_str) { gtk_entry_set_text((GtkEntry *)w, argv->argv[i].ca_str); camel_object_free(folder, argv->argv[i].tag, argv->argv[i].ca_str); argv->argv[i].ca_str = NULL; } gtk_table_attach ((GtkTable *) table, w, 1, 2, row, row+1, GTK_FILL, 0, 3, 3); prop_data->widgets[i] = w; break; default: w = gtk_label_new("CamelFolder error: unsupported propery type"); gtk_table_attach ((GtkTable *) table, w, 0, 2, row, row+1, 0, 0, 3, 3); break; } row++; l = l->next; } prop_data->object = folder; camel_object_ref(folder); camel_object_free(folder, CAMEL_FOLDER_PROPERTIES, list); camel_object_free(folder, CAMEL_FOLDER_NAME, name); /* we do 'apply on ok' ... since instant apply may apply some very long running tasks */ g_signal_connect(dialog, "response", G_CALLBACK(emc_popup_properties_response), prop_data); g_object_set_data_full((GObject *)dialog, "e-prop-data", prop_data, emc_popup_properties_free); gtk_widget_show_all(dialog); } } static void emc_popup_properties(GtkWidget *w, MailComponent *mc) { EFolder *efolder; /* TODO: Make sure we only have one dialog open for any given folder */ efolder = e_storage_set_get_folder(mc->priv->storage_set, mc->priv->context_path); if (efolder == NULL) return; mail_get_folder(e_folder_get_physical_uri(efolder), 0, emc_popup_properties_got_folder, mc, mail_thread_new); } static EMPopupItem emc_popup_menu[] = { #if 0 { EM_POPUP_ITEM, "00.emc.00", N_("_View"), G_CALLBACK(emc_popup_view), NULL, NULL, 0 }, { EM_POPUP_ITEM, "00.emc.01", N_("Open in _New Window"), G_CALLBACK(emc_popup_open_new), NULL, NULL, 0 }, { EM_POPUP_BAR, "10.emc" }, #endif { EM_POPUP_ITEM, "10.emc.00", N_("_Copy"), G_CALLBACK(emc_popup_copy), NULL, "folder-copy-16.png", 0 }, { EM_POPUP_ITEM, "10.emc.01", N_("_Move"), G_CALLBACK(emc_popup_move), NULL, "folder-move-16.png", 0 }, { EM_POPUP_BAR, "20.emc" }, { EM_POPUP_ITEM, "20.emc.00", N_("_New Folder..."), G_CALLBACK(emc_popup_new_folder), NULL, "folder-mini.png", 0 }, { EM_POPUP_ITEM, "20.emc.01", N_("_Delete"), G_CALLBACK(emc_popup_delete_folder), NULL, "evolution-trash-mini.png", 0 }, { EM_POPUP_ITEM, "20.emc.01", N_("_Rename"), G_CALLBACK(emc_popup_rename_folder), NULL, NULL, 0 }, { EM_POPUP_BAR, "80.emc" }, { EM_POPUP_ITEM, "80.emc.00", N_("_Properties..."), G_CALLBACK(emc_popup_properties), NULL, "configure_16_folder.xpm", 0 }, }; static int emc_tree_right_click(ETree *tree, gint row, ETreePath path, gint col, GdkEvent *event, MailComponent *component) { char *name; ETreeModel *model = e_tree_get_model(tree); EMPopup *emp; int i; GSList *menus = NULL; struct _GtkMenu *menu; name = e_tree_memory_node_get_data((ETreeMemory *)model, path); g_free(component->priv->context_path); component->priv->context_path = g_strdup(name); printf("right click, path = '%s'\n", name); emp = em_popup_new("com.ximian.mail.storageset.popup.select"); for (i=0;iactivate_data = component; menus = g_slist_prepend(menus, item); } em_popup_add_items(emp, menus, (GDestroyNotify)g_slist_free); menu = em_popup_create_menu_once(emp, NULL, 0, 0); if (event == NULL || event->type == GDK_KEY_PRESS) { /* FIXME: menu pos function */ gtk_menu_popup(menu, NULL, NULL, NULL, NULL, 0, event->key.time); } else { gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button.button, event->button.time); } return TRUE; }