/* * 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 * * * Authors: * Michael Zucchi * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #include #include #include #include #include #include #include #include #ifdef G_OS_WIN32 /* Work around 'DATADIR' and 'interface' lossage in */ #define DATADIR crap_DATADIR #include #undef DATADIR #undef interface #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "menus/gal-view-etable.h" #include "menus/gal-view-factory-etable.h" #include "menus/gal-view-instance.h" #include "misc/e-charset-picker.h" #include #include "e-util/e-error.h" #include "e-util/e-dialog-utils.h" #include "e-util/e-icon-factory.h" #include "e-util/e-print.h" #include "e-util/e-profile-event.h" #include "e-util/e-util-private.h" #include "e-util/e-util-labels.h" #include "shell/e-shell.h" #include "filter/filter-rule.h" #include "em-format-html-display.h" #include "em-format-html-print.h" #include "em-folder-selection.h" #include "em-folder-view.h" #include "em-folder-browser.h" #include "em-mailer-prefs.h" #include "em-folder-browser.h" #include "message-list.h" #include "em-utils.h" #include "em-composer-utils.h" #include "em-menu.h" #include "em-event.h" #include "e-mail-shell-module.h" #include "mail-mt.h" #include "mail-ops.h" #include "mail-config.h" #include "mail-autofilter.h" #include "mail-vfolder.h" #include "mail-tools.h" #ifdef HAVE_XFREE #include #endif /* this is added to emfv->enable_map in :init() */ static const EMFolderViewEnable emfv_enable_map[] = { { "EditCut", EM_POPUP_SELECT_MANY }, { "EditCopy", EM_FOLDER_VIEW_SELECT_SELECTION }, { "EditPaste", EM_POPUP_SELECT_FOLDER }, // { "SelectAllText", EM_POPUP_SELECT_ONE }, /* FIXME: should these be single-selection? */ { "MailNext", EM_POPUP_SELECT_MANY|EM_FOLDER_VIEW_SELECT_NEXT_MSG }, // { "MailNextFlagged", EM_POPUP_SELECT_MANY }, // { "MailNextUnread", EM_POPUP_SELECT_MANY }, // { "MailNextThread", EM_POPUP_SELECT_MANY }, { "MailPrevious", EM_POPUP_SELECT_MANY|EM_FOLDER_VIEW_SELECT_PREV_MSG }, // { "MailPreviousFlagged", EM_POPUP_SELECT_MANY }, // { "MailPreviousUnread", EM_POPUP_SELECT_MANY }, { "AddSenderToAddressbook", EM_POPUP_SELECT_ADD_SENDER }, // { "MessageApplyFilters", EM_POPUP_SELECT_MANY }, // { "MessageFilterJunk", EM_POPUP_SELECT_MANY }, // { "MessageCopy", EM_POPUP_SELECT_MANY }, // { "MessageDelete", EM_POPUP_SELECT_MANY|EM_POPUP_SELECT_DELETE }, // { "MessageDeleteKey", EM_POPUP_SELECT_MANY}, // { "MessageForward", EM_POPUP_SELECT_MANY }, // { "MessageForwardAttached", EM_POPUP_SELECT_MANY }, // { "MessageForwardInline", EM_POPUP_SELECT_ONE }, // { "MessageForwardQuoted", EM_POPUP_SELECT_ONE }, // { "MessageRedirect", EM_POPUP_SELECT_ONE }, // { "MessageMarkAsRead", EM_POPUP_SELECT_MANY|EM_POPUP_SELECT_MARK_READ }, // { "MessageMarkAsUnRead", EM_POPUP_SELECT_MANY|EM_POPUP_SELECT_MARK_UNREAD }, // { "MessageMarkAsImportant", EM_POPUP_SELECT_MANY|EM_POPUP_SELECT_MARK_IMPORTANT }, // { "MessageMarkAsUnimportant", EM_POPUP_SELECT_MANY|EM_POPUP_SELECT_MARK_UNIMPORTANT }, // { "MessageMarkAsJunk", EM_POPUP_SELECT_MANY|EM_POPUP_SELECT_JUNK }, // { "MessageMarkAsNotJunk", EM_POPUP_SELECT_MANY}, { "MessageFollowUpFlag", EM_POPUP_SELECT_MANY|EM_POPUP_SELECT_FLAG_FOLLOWUP }, { "MessageFollowUpComplete", EM_POPUP_SELECT_MANY|EM_POPUP_SELECT_FLAG_COMPLETED }, { "MessageFollowUpClear", EM_POPUP_SELECT_MANY|EM_POPUP_SELECT_FLAG_CLEAR }, // { "MessageMove", EM_POPUP_SELECT_MANY }, // { "MessageOpen", EM_POPUP_SELECT_MANY }, // { "MessagePostReply", EM_POPUP_SELECT_ONE }, // { "MessageReplyAll", EM_POPUP_SELECT_ONE }, // { "MessageReplyList", EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_MAILING_LIST }, // { "MessageReplySender", EM_POPUP_SELECT_ONE }, // { "MessageEdit", EM_POPUP_SELECT_ONE }, // { "MessageSaveAs", EM_POPUP_SELECT_MANY }, { "MessageSearch", EM_POPUP_SELECT_ONE| EM_FOLDER_VIEW_PREVIEW_PRESENT }, // { "MessageUndelete", EM_POPUP_SELECT_MANY|EM_POPUP_SELECT_UNDELETE }, // { "PrintMessage", EM_POPUP_SELECT_ONE }, // { "PrintPreviewMessage", EM_POPUP_SELECT_ONE }, // { "TextZoomIn", EM_POPUP_SELECT_ONE }, // { "TextZoomOut", EM_POPUP_SELECT_ONE }, // { "TextZoomReset", EM_POPUP_SELECT_ONE }, { "ToolsFilterMailingList", EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_MAILING_LIST}, { "ToolsFilterRecipient", EM_POPUP_SELECT_ONE }, { "ToolsFilterSender", EM_POPUP_SELECT_ONE }, { "ToolsFilterSubject", EM_POPUP_SELECT_ONE }, { "ToolsVFolderMailingList", EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_MAILING_LIST}, { "ToolsVFolderRecipient", EM_POPUP_SELECT_ONE }, { "ToolsVFolderSender", EM_POPUP_SELECT_ONE }, { "ToolsVFolderSubject", EM_POPUP_SELECT_ONE }, // { "ViewLoadImages", EM_POPUP_SELECT_ONE }, // { "ViewSource", EM_POPUP_SELECT_ONE }, // /* always enabled */ // { "MailStop", 0 }, { NULL }, }; struct _EMFolderViewPrivate { guint setting_notify_id; guint selected_id; guint nomarkseen:1; guint destroyed:1; GtkWidget *invisible; char *selection_uri; char *selected_uid; }; static GtkVBoxClass *emfv_parent; enum { EMFV_ON_URL, EMFV_LOADED, EMFV_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; static void emfv_selection_get(GtkWidget *widget, GtkSelectionData *data, guint info, guint time_stamp, EMFolderView *emfv); static void emfv_selection_clear_event(GtkWidget *widget, GdkEventSelection *event, EMFolderView *emfv); //#ifdef ENABLE_PROFILING //static void //emfv_format_complete(EMFormat *emf, EMFolderView *emfv) //{ // e_profile_event_emit("goto.done", emf->uid?emf->uid:"", 0); //} //#endif static void emfv_init(GObject *o) { EMFolderView *emfv = (EMFolderView *)o; struct _EMFolderViewPrivate *p; extern CamelSession *session; // gtk_box_set_homogeneous (GTK_BOX (emfv), FALSE); // // p = emfv->priv = g_malloc0(sizeof(struct _EMFolderViewPrivate)); // // emfv->statusbar_active = TRUE; // emfv->list_active = FALSE; // // emfv->ui_files = g_slist_append(NULL, // g_build_filename (EVOLUTION_UIDIR, // "evolution-mail-message.xml", // NULL)); // // emfv->ui_app_name = "evolution-mail"; emfv->enable_map = g_slist_prepend(NULL, (void *)emfv_enable_map); // emfv->list = (MessageList *)message_list_new(); // g_signal_connect(emfv->list, "message_selected", G_CALLBACK(emfv_list_message_selected), emfv); // g_signal_connect(emfv->list, "message_list_built", G_CALLBACK(emfv_list_built), emfv); // // /* FIXME: should this hang off message-list instead? */ // g_signal_connect(emfv->list->tree, "right_click", G_CALLBACK(emfv_list_right_click), emfv); // g_signal_connect(emfv->list->tree, "double_click", G_CALLBACK(emfv_list_double_click), emfv); // g_signal_connect(emfv->list->tree, "key_press", G_CALLBACK(emfv_list_key_press), emfv); // g_signal_connect(emfv->list->tree, "selection_change", G_CALLBACK(emfv_list_selection_change), emfv); // // emfv->preview = (EMFormatHTMLDisplay *)em_format_html_display_new(); // /* FIXME: set_session should NOT be called here. Should it be a constructor attribute? */ // em_format_set_session ((EMFormat *) emfv->preview, session); // g_signal_connect(emfv->preview, "link_clicked", G_CALLBACK(emfv_format_link_clicked), emfv); g_signal_connect(emfv->preview, "popup_event", G_CALLBACK(emfv_format_popup_event), emfv); g_signal_connect (emfv->preview, "on_url", G_CALLBACK (emfv_on_url_cb), emfv); // g_signal_connect (((EMFormatHTML *)emfv->preview)->html, "button-release-event", G_CALLBACK (emfv_on_html_button_released_cb), emfv); //#ifdef ENABLE_PROFILING // g_signal_connect(emfv->preview, "complete", G_CALLBACK (emfv_format_complete), emfv); //#endif p->invisible = gtk_invisible_new(); g_signal_connect(p->invisible, "selection_get", G_CALLBACK(emfv_selection_get), emfv); g_signal_connect(p->invisible, "selection_clear_event", G_CALLBACK(emfv_selection_clear_event), emfv); gtk_selection_add_target(p->invisible, GDK_SELECTION_PRIMARY, GDK_SELECTION_TYPE_STRING, 0); gtk_selection_add_target(p->invisible, GDK_SELECTION_CLIPBOARD, GDK_SELECTION_TYPE_STRING, 1); emfv->async = mail_async_event_new(); emfv_setting_setup(emfv); } static void emfv_class_init(GObjectClass *klass) { klass->finalize = emfv_finalise; ((GtkObjectClass *) klass)->destroy = emfv_destroy; ((GtkWidgetClass *) klass)->popup_menu = emfv_popup_menu; ((EMFolderViewClass *) klass)->update_message_style = TRUE; ((EMFolderViewClass *)klass)->set_folder = emfv_set_folder; ((EMFolderViewClass *)klass)->set_folder_uri = emfv_set_folder_uri; ((EMFolderViewClass *)klass)->set_message = emfv_set_message; ((EMFolderViewClass *)klass)->activate = emfv_activate; ((EMFolderViewClass *)klass)->on_url = emfv_on_url; signals[EMFV_ON_URL] = g_signal_new ("on-url", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMFolderViewClass, on_url), NULL, NULL, e_marshal_VOID__STRING_STRING, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); signals[EMFV_LOADED] = g_signal_new("loaded", G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(EMFolderViewClass, loaded), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[EMFV_CHANGED] = g_signal_new("changed", G_OBJECT_CLASS_TYPE(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(EMFolderViewClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void emfv_selection_get(GtkWidget *widget, GtkSelectionData *data, guint info, guint time_stamp, EMFolderView *emfv) { struct _EMFolderViewPrivate *p = emfv->priv; if (p->selection_uri == NULL) return; gtk_selection_data_set(data, data->target, 8, (unsigned char *)p->selection_uri, strlen(p->selection_uri)); } static void emfv_selection_clear_event(GtkWidget *widget, GdkEventSelection *event, EMFolderView *emfv) { #if 0 /* do i care? */ struct _EMFolderViewPrivate *p = emfv->priv; g_free(p->selection_uri); p->selection_uri = NULL; #endif } /* ********************************************************************** */ /* Popup menu In many cases these are the functions called by the bonobo callbacks too */ /* ********************************************************************** */ /* Bonobo menu's */ /* a lot of stuff maps directly to the popup menu equivalent */ #define EMFV_MAP_CALLBACK(from, to) \ static void \ from(BonoboUIComponent *uid, void *data, const char *path) \ { \ to(NULL, NULL, data); \ } static void prepare_offline(void *key, void *value, void *data) { CamelService *service = key; if (CAMEL_IS_DISCO_STORE(service) || CAMEL_IS_OFFLINE_STORE(service)) { mail_store_prepare_offline((CamelStore *)service); } } static void emfv_prepare_offline(BonoboUIComponent *uid, void *data, const char *path) { e_mail_shell_module_stores_foreach ( mail_shell_module, prepare_offline, NULL); } static void emfv_edit_cut(BonoboUIComponent *uid, void *data, const char *path) { EMFolderView *emfv = data; if (GTK_WIDGET_HAS_FOCUS(emfv->preview->formathtml.html)) em_format_html_display_cut(emfv->preview); else message_list_copy(emfv->list, TRUE); } static void emfv_edit_copy(BonoboUIComponent *uid, void *data, const char *path) { EMFolderView *emfv = data; if (GTK_WIDGET_HAS_FOCUS(emfv->preview->formathtml.html)) em_format_html_display_copy(emfv->preview); else message_list_copy(emfv->list, FALSE); } static void emfv_edit_paste(BonoboUIComponent *uid, void *data, const char *path) { EMFolderView *emfv = data; message_list_paste(emfv->list); } //static void //emfv_select_all_text(BonoboUIComponent *uid, void *data, const char *path) //{ // EMFolderView *emfv = data; // gboolean selected; // // gtk_html_select_all (((EMFormatHTML *)emfv->preview)->html); // selected = gtk_html_command (((EMFormatHTML *)emfv->preview)->html, "is-selection-active"); // bonobo_ui_component_set_prop(emfv->uic, "/commands/EditCopy", "sensitive", selected?"1":"0", NULL); // //} static void emfv_message_search(BonoboUIComponent *uic, void *data, const char *path) { EMFolderView *emfv = data; if (!emfv->list_active) /* We are in new mail window */ em_format_html_display_search(emfv->preview); else { /* We are in top level. Just grab focus to Search Bar */ gtk_widget_grab_focus (((ESearchBar *)((EMFolderBrowser *) emfv)->search)->entry); gtk_option_menu_set_history (GTK_OPTION_MENU (((ESearchBar *)((EMFolderBrowser *) emfv)->search)->scopeoption), 3); } } static void emp_uri_popup_vfolder_sender(EPopup *ep, EPopupItem *pitem, void *data) { EMFolderView *emfv = data; EMPopupTargetURI *t = (EMPopupTargetURI *)ep->target; CamelURL *url; CamelInternetAddress *addr; url = camel_url_new(t->uri, NULL); if (url == NULL) { g_warning("cannot parse url '%s'", t->uri); return; } if (url->path && url->path[0]) { /* ensures vfolder is running */ vfolder_load_storage (); addr = camel_internet_address_new (); camel_address_decode (CAMEL_ADDRESS (addr), url->path); vfolder_gui_add_from_address (addr, AUTO_FROM, emfv->folder_uri); camel_object_unref (addr); } camel_url_free(url); } static void emp_uri_popup_vfolder_recipient(EPopup *ep, EPopupItem *pitem, void *data) { EMFolderView *emfv = data; EMPopupTargetURI *t = (EMPopupTargetURI *)ep->target; CamelURL *url; CamelInternetAddress *addr; url = camel_url_new(t->uri, NULL); if (url == NULL) { g_warning("cannot parse url '%s'", t->uri); return; } if (url->path && url->path[0]) { /* ensures vfolder is running */ vfolder_load_storage (); addr = camel_internet_address_new (); camel_address_decode (CAMEL_ADDRESS (addr), url->path); vfolder_gui_add_from_address (addr, AUTO_TO, emfv->folder_uri); camel_object_unref (addr); } camel_url_free(url); } /* ********************************************************************** */ static BonoboUIVerb emfv_message_verbs[] = { BONOBO_UI_UNSAFE_VERB ("PrepareForOffline", emfv_prepare_offline), BONOBO_UI_UNSAFE_VERB ("EditCut", emfv_edit_cut), BONOBO_UI_UNSAFE_VERB ("EditCopy", emfv_edit_copy), BONOBO_UI_UNSAFE_VERB ("EditPaste", emfv_edit_paste), // BONOBO_UI_UNSAFE_VERB ("SelectAllText", emfv_select_all_text), // BONOBO_UI_UNSAFE_VERB ("MessageDelete", emfv_message_delete), // BONOBO_UI_UNSAFE_VERB ("MessageDeleteKey", emfv_message_delete), // BONOBO_UI_UNSAFE_VERB ("MessageOpen", emfv_message_open), BONOBO_UI_UNSAFE_VERB ("MessageSearch", emfv_message_search), // BONOBO_UI_UNSAFE_VERB ("ViewSource", emfv_message_source), BONOBO_UI_VERB_END }; static void emfv_enable_menus(EMFolderView *emfv) { guint32 disable_mask; GString *name; GSList *l; if (emfv->uic == NULL) return; { if (emfv->menu) { EMMenuTargetSelect *t; t = em_menu_target_new_select(emfv->menu, emfv->folder, emfv->folder_uri, message_list_get_selected(emfv->list)); t->target.widget = GTK_WIDGET (emfv); e_menu_update_target((EMenu *)emfv->menu, t); } } if (emfv->folder) { EMPopup *emp = em_popup_new("dummy"); EMPopupTargetSelect *t; t = em_folder_view_get_popup_target(emfv, emp, FALSE); disable_mask = t->target.mask; e_popup_target_free((EPopup *)emp, t); g_object_unref(emp); } else { disable_mask = ~0; } name = g_string_new(""); for (l = emfv->enable_map; l; l = l->next) { EMFolderViewEnable *map = l->data; int i; for (i=0;map[i].name;i++) { int state = (map[i].mask & disable_mask) == 0; g_string_printf(name, "/commands/%s", map[i].name); bonobo_ui_component_set_prop(emfv->uic, name->str, "sensitive", state?"1":"0", NULL); } } g_string_free(name, TRUE); } static void emfv_activate(EMFolderView *emfv, BonoboUIComponent *uic, int act) { if (act) { em_format_mode_t style; gboolean state; GSList *l; emfv->uic = uic; for (l = emfv->ui_files;l;l = l->next) bonobo_ui_util_set_ui(uic, PREFIX, (char *)l->data, emfv->ui_app_name, NULL); bonobo_ui_component_add_verb_list_with_data(uic, emfv_message_verbs, emfv); /* must do plugin menu's after main ones because of bonobo bustedness */ if (emfv->menu) e_menu_activate((EMenu *)emfv->menu, uic, act); // state = emfv->preview->caret_mode; // bonobo_ui_component_set_prop(uic, "/commands/CaretMode", "state", state?"1":"0", NULL); // bonobo_ui_component_add_listener(uic, "CaretMode", emfv_caret_mode, emfv); // style = ((EMFormat *)emfv->preview)->mode?EM_FORMAT_ALLHEADERS:EM_FORMAT_NORMAL; // if (style) // bonobo_ui_component_set_prop(uic, "/commands/ViewFullHeaders", "state", "1", NULL); // bonobo_ui_component_add_listener(uic, "ViewFullHeaders", emfv_view_mode, emfv); // em_format_set_mode((EMFormat *)emfv->preview, style); if (emfv->folder) bonobo_ui_component_set_prop(uic, "/commands/MessageEdit", "sensitive", "0", NULL); // /* default charset used in mail view */ // e_charset_picker_bonobo_ui_populate (uic, "/menu/View", _("Default"), emfv_charset_changed, emfv); emfv_enable_menus(emfv); if (emfv->statusbar_active) bonobo_ui_component_set_translate (uic, "/", "", NULL); /* We need to set this up to get the right view options for the message-list, even if we're not showing it */ if (emfv->folder) emfv_setup_view_instance(emfv); } else { const BonoboUIVerb *v; if (emfv->menu) e_menu_activate((EMenu *)emfv->menu, uic, act); /* TODO: Should this just rm /? */ for (v = &emfv_message_verbs[0]; v->cname; v++) bonobo_ui_component_remove_verb(uic, v->cname); if (emfv->folder) mail_sync_folder(emfv->folder, NULL, NULL); emfv->uic = NULL; } } EMPopupTargetSelect * em_folder_view_get_popup_target(EMFolderView *emfv, EMPopup *emp, int on_display) { EMPopupTargetSelect *t; t = em_popup_target_new_select(emp, emfv->folder, emfv->folder_uri, message_list_get_selected(emfv->list)); t->target.widget = (GtkWidget *)emfv; if (emfv->list->threaded) t->target.mask &= ~EM_FOLDER_VIEW_SELECT_THREADED; if (message_list_hidden(emfv->list) != 0) t->target.mask &= ~EM_FOLDER_VIEW_SELECT_HIDDEN; if (message_list_can_select(emfv->list, MESSAGE_LIST_SELECT_NEXT, 0, 0)) t->target.mask &= ~EM_FOLDER_VIEW_SELECT_NEXT_MSG; if (message_list_can_select(emfv->list, MESSAGE_LIST_SELECT_PREVIOUS, 0, 0)) t->target.mask &= ~EM_FOLDER_VIEW_SELECT_PREV_MSG; if (on_display) t->target.mask &= ~EM_FOLDER_VIEW_SELECT_DISPLAY; else t->target.mask &= ~EM_FOLDER_VIEW_SELECT_LISTONLY; if (gtk_html_command (((EMFormatHTML *)emfv->preview)->html, "is-selection-active")) t->target.mask &= ~EM_FOLDER_VIEW_SELECT_SELECTION; else t->target.mask &= ~EM_FOLDER_VIEW_SELECT_NOSELECTION; if (emfv->preview_active) t->target.mask &= ~EM_FOLDER_VIEW_PREVIEW_PRESENT; /* See bug 352980 */ /* See bug #54770 */ /* if (!emfv->hide_deleted) t->target.mask &= ~EM_POPUP_SELECT_DELETE;*/ return t; } void em_folder_view_set_statusbar (EMFolderView *emfv, gboolean statusbar) { g_return_if_fail (emfv); emfv->statusbar_active = statusbar; if (statusbar && emfv->uic) bonobo_ui_component_set_translate (emfv->uic, "/", "", NULL); } void em_folder_view_set_hide_deleted(EMFolderView *emfv, gboolean status) { if (emfv->folder && (emfv->folder->folder_flags & CAMEL_FOLDER_IS_TRASH)) status = FALSE; emfv->hide_deleted = status; if (emfv->folder) { message_list_set_hidedeleted(emfv->list, status); g_signal_emit(emfv, signals[EMFV_CHANGED], 0); } } /* ********************************************************************** */ struct mst_t { EMFolderView *emfv; char *uid; }; static void mst_free (struct mst_t *mst) { mst->emfv->list->seen_id = 0; g_free (mst->uid); g_free (mst); } static int do_mark_seen (gpointer user_data) { struct mst_t *mst = user_data; EMFolderView *emfv = mst->emfv; MessageList *list = emfv->list; if (mst->uid && list->cursor_uid && !strcmp (mst->uid, list->cursor_uid)) emfv_set_seen (emfv, mst->uid); return FALSE; } static void emfv_list_done_message_selected(CamelFolder *folder, const char *uid, CamelMimeMessage *msg, void *data, CamelException *ex) { EMFolderView *emfv = data; EMEvent *eme; EMEventTargetMessage *target; EShell *shell; if (emfv->preview == NULL) { emfv->priv->nomarkseen = FALSE; emfv_enable_menus(emfv); g_object_unref (emfv); return; } e_profile_event_emit("goto.loaded", emfv->displayed_uid, 0); shell = e_shell_module_get_shell (mail_shell_module); e_shell_event (shell, "mail-icon", "evolution-mail"); /** @Event: message.reading * @Title: Viewing a message * @Target: EMEventTargetMessage * * message.reading is emitted whenever a user views a message. */ /* TODO: do we emit a message.reading with no message when we're looking at nothing or don't care? */ eme = em_event_peek(); target = em_event_target_new_message(eme, folder, msg, uid, 0); e_event_emit((EEvent *)eme, "message.reading", (EEventTarget *)target); em_format_format((EMFormat *)emfv->preview, folder, uid, msg); if (emfv->list->seen_id) g_source_remove(emfv->list->seen_id); if (msg && emfv->mark_seen && !emfv->priv->nomarkseen) { if (emfv->mark_seen_timeout > 0) { struct mst_t *mst; mst = g_new (struct mst_t, 1); mst->emfv = emfv; mst->uid = g_strdup (uid); emfv->list->seen_id = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, emfv->mark_seen_timeout, (GSourceFunc)do_mark_seen, mst, (GDestroyNotify)mst_free); } else { emfv_set_seen (emfv, uid); } } else if (camel_exception_is_set(ex)) { GtkHTMLStream *hstream = gtk_html_begin(((EMFormatHTML *)emfv->preview)->html); /* Display the error inline rather than popping up an annoying box. We also clear the exception, this stops the box popping up */ gtk_html_stream_printf(hstream, "

%s

%s

", _("Unable to retrieve message"), ex->desc); gtk_html_stream_close(hstream, GTK_HTML_STREAM_OK); camel_exception_clear(ex); } emfv->priv->nomarkseen = FALSE; emfv_enable_menus(emfv); g_object_unref (emfv); } static gboolean emfv_spin(EMFormatHTML *efh, GtkHTMLEmbedded *eb, EMFormatHTMLPObject *pobject) { GtkWidget *ep; if (!strcmp(eb->classid, "spinner")) { GtkWidget *box, *label; gchar *msg = g_strdup_printf("%s", _("Retrieving Message...")); label = gtk_label_new (NULL); gtk_label_set_markup ((GtkLabel *)label, msg); box = gtk_hbox_new (FALSE, 0); g_free (msg); ep = e_spinner_new_spinning_small_shown (); gtk_box_pack_start ((GtkBox *)box, ep, FALSE, FALSE, 0); gtk_box_pack_start ((GtkBox *)box, label, FALSE, FALSE, 0); gtk_container_add ((GtkContainer *)eb, box); gtk_widget_show_all ((GtkWidget *)eb); g_signal_handlers_disconnect_by_func(efh, emfv_spin, NULL); } return TRUE; } static gboolean emfv_message_selected_timeout(void *data) { EMFolderView *emfv = data; if (emfv->priv->selected_uid) { if (emfv->displayed_uid == NULL || strcmp(emfv->displayed_uid, emfv->priv->selected_uid) != 0) { /*GtkHTMLStream *hstream;*/ g_free(emfv->displayed_uid); emfv->displayed_uid = emfv->priv->selected_uid; emfv->priv->selected_uid = NULL; g_object_ref (emfv); /* TODO: we should manage our own thread stuff, would make cancelling outstanding stuff easier */ e_profile_event_emit("goto.load", emfv->displayed_uid, 0); /* hstream = gtk_html_begin(((EMFormatHTML *)emfv->preview)->html); g_signal_connect(((EMFormatHTML *)emfv->preview)->html, "object_requested", G_CALLBACK(emfv_spin), NULL); gtk_html_stream_printf(hstream, "

%s

%s

", _("Retrieving Message"), emfv->displayed_uid); gtk_html_stream_close(hstream, GTK_HTML_STREAM_OK); */ mail_get_messagex(emfv->folder, emfv->displayed_uid, emfv_list_done_message_selected, emfv, mail_msg_fast_ordered_push); } else { e_profile_event_emit("goto.empty", "", 0); g_free(emfv->priv->selected_uid); emfv->priv->selected_uid = NULL; } } else { e_profile_event_emit("goto.empty", "", 0); g_free(emfv->displayed_uid); emfv->displayed_uid = NULL; em_format_format((EMFormat *)emfv->preview, NULL, NULL, NULL); emfv->priv->nomarkseen = FALSE; } emfv->priv->selected_id = 0; return FALSE; } static gboolean emfv_popup_menu (GtkWidget *widget) { gboolean ret = FALSE; EMFolderView *emfv = (EMFolderView *)widget; /* Try to bring up menu for preview html object. Currently we cannot directly connect to html's "popup_menu" signal since it doesn't work. */ if (GTK_WIDGET_HAS_FOCUS (emfv->preview->formathtml.html)) ret = em_format_html_display_popup_menu (emfv->preview); if (!ret) emfv_popup (emfv, NULL, FALSE); return TRUE; } static void emp_uri_popup_link_copy(EPopup *ep, EPopupItem *pitem, void *data) { EMFolderView *emfv = data; struct _EMFolderViewPrivate *p = emfv->priv; g_free(p->selection_uri); p->selection_uri = em_utils_url_unescape_amp(pitem->user_data); gtk_selection_owner_set(p->invisible, GDK_SELECTION_PRIMARY, gtk_get_current_event_time()); gtk_selection_owner_set(p->invisible, GDK_SELECTION_CLIPBOARD, gtk_get_current_event_time()); } static EPopupItem emfv_uri_popups[] = { { E_POPUP_ITEM, "00.uri.11", N_("C_all To..."), emp_uri_popup_link_copy, NULL, NULL, EM_POPUP_URI_CALLTO }, { E_POPUP_ITEM, "00.uri.15", N_("_Copy Link Location"), emp_uri_popup_link_copy, NULL, "edit-copy", EM_POPUP_URI_NOT_MAILTO }, { E_POPUP_SUBMENU, "99.uri.00", N_("Create _Search Folder"), NULL, NULL, NULL, EM_POPUP_URI_MAILTO }, { E_POPUP_ITEM, "99.uri.00/00.10", N_("_From this Address"), emp_uri_popup_vfolder_sender, NULL, NULL, EM_POPUP_URI_MAILTO }, { E_POPUP_ITEM, "99.uri.00/00.00", N_("_To this Address"), emp_uri_popup_vfolder_recipient, NULL, NULL, EM_POPUP_URI_MAILTO }, }; static void emfv_uri_popup_free(EPopup *ep, GSList *list, void *data) { while (list) { GSList *n = list->next; struct _EPopupItem *item = list->data; g_free(item->user_data); item->user_data = NULL; g_free (item); g_slist_free_1(list); list = n; } } static void emfv_free_em_popup (gpointer emp) { EPopup *ep = (EPopup *)emp; if (!ep) return; if (ep->target) { /* without this the next unref on ep does nothing */ e_popup_target_free (ep, ep->target); ep->target = NULL; } g_object_unref (ep); } static GtkMenu * emfv_append_menu (EMPopup *des_emp, GtkMenu *des_menu, EMPopup *src_emp, GtkMenu *src_menu) { GtkWidget *separator; GList *children, *p; char *name; if (!src_menu) return des_menu; if (!des_menu) return src_menu; separator = gtk_separator_menu_item_new (); gtk_widget_show (separator); gtk_menu_shell_append (GTK_MENU_SHELL (des_menu), separator); children = gtk_container_get_children (GTK_CONTAINER (src_menu)); for (p = children; p; p = p->next) { g_object_ref (p->data); gtk_container_remove (GTK_CONTAINER (src_menu), p->data); gtk_menu_shell_append (GTK_MENU_SHELL (des_menu), p->data); g_object_unref (p->data); } g_list_free (children); gtk_widget_destroy (GTK_WIDGET (src_menu)); /* free src_emp together with des_emp; name contains unique identifier */ name = g_strdup_printf ("emp_%p", src_emp); g_object_set_data_full (G_OBJECT (des_emp), name, src_emp, emfv_free_em_popup); g_free (name); return des_menu; } static int emfv_format_popup_event(EMFormatHTMLDisplay *efhd, GdkEventButton *event, const char *uri, CamelMimePart *part, EMFolderView *emfv) { GtkMenu *menu = NULL; EMPopup *main_emp = NULL; if (uri == NULL && part == NULL) { /* So we don't try and popup with nothing selected - rather odd result! */ GPtrArray *uids = message_list_get_selected(emfv->list); int doit = uids->len > 0; message_list_free_uids(emfv->list, uids); if (doit) emfv_popup(emfv, (GdkEvent *)event, TRUE); return doit; } /* FIXME: this maybe should just fit on em-html-display, it has access to the snooped part type */ /** @HookPoint-EMPopup: Inline URI Context Menu * @Id: org.gnome.evolution.mail.folderview.popup * @Class: org.gnome.evolution.mail.popup:1.0 * @Target: EMPopupTargetURI * * This is the context menu shown when clicking on inline URIs, * including addresses or normal HTML links that are displayed inside * the message view. */ /** @HookPoint-EMPopup: Inline Object Context Menu * @Id: org.gnome.evolution.mail.folderview.popup * @Class: org.gnome.evolution.mail.popup:1.0 * @Target: EMPopupTargetPart * * This is the context menu shown when clicking on inline * content such as a picture. */ if (uri) { gboolean have_more_uris = strchr (uri, '\n') != NULL; const char *act, *next; for (act = uri; act; act = next) { char *u; next = strchr (act, '\n'); if (next) { u = g_strndup (act, next - act); next++; } else u = g_strdup (act); if (u && *u) { GSList *menus = NULL; int i; EMPopupTargetURI *t; EMPopup *emp; EPopupTarget *target; GtkMenu *mymenu; emp = em_popup_new ("org.gnome.evolution.mail.folderview.popup"); t = em_popup_target_new_uri(emp, u); target = (EPopupTarget *)t; for (i = 0; i < sizeof (emfv_uri_popups)/sizeof (emfv_uri_popups[0]); i++) { EPopupItem *itm = g_malloc0 (sizeof (EPopupItem)); memcpy (itm, &emfv_uri_popups[i], sizeof (EPopupItem)); itm->user_data = g_strdup (t->uri); menus = g_slist_prepend (menus, itm); } e_popup_add_items ((EPopup *)emp, menus, NULL, emfv_uri_popup_free, emfv); mymenu = e_popup_create_menu_once ((EPopup *)emp, target, 0); if (have_more_uris) { GtkWidget *item; if (strlen (u) > 100) { GString *str; char *c; /* the url should be in the form of http://a.b.c/... and we want to see where the image comes from, so skip first 10 characters and find the first '/' there */ c = strchr (u + 10, '/'); if (!c) str = g_string_new_len (u, 40); else str = g_string_new_len (u, MAX (c - u + 1, 40)); g_string_append (str, "..."); g_string_append (str, u + strlen (u) - 40); item = gtk_menu_item_new_with_label (str->str); g_string_free (str, TRUE); } else item = gtk_menu_item_new_with_label (u); gtk_widget_set_sensitive (item, FALSE); gtk_widget_show (item); gtk_menu_shell_insert (GTK_MENU_SHELL (mymenu), item, 0); } menu = emfv_append_menu (main_emp, menu, emp, mymenu); if (!main_emp) main_emp = emp; } g_free (u); } } if (part) { EMPopup *emp; EPopupTarget *target; emp = em_popup_new ("org.gnome.evolution.mail.folderview.popup"); target = (EPopupTarget *)em_popup_target_new_part(emp, part, NULL); menu = emfv_append_menu (main_emp, menu, emp, e_popup_create_menu_once ((EPopup *)emp, target, 0)); if (!main_emp) main_emp = emp; } if (event == NULL) gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time()); else gtk_menu_popup (menu, NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; } static void emfv_set_seen(EMFolderView *emfv, const char *uid) { guint32 old_flags = camel_folder_get_message_flags(emfv->folder, uid); /* If we're setting the SEEN flag on a message, handle receipt requests */ if (!(old_flags & CAMEL_MESSAGE_SEEN)) em_utils_handle_receipt(emfv->folder, uid, (CamelMimeMessage *)((EMFormat *)emfv->preview)->message); camel_folder_set_message_flags(emfv->folder, uid, CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN); } /* keep these two tables in sync */ enum { EMFV_ANIMATE_IMAGES = 1, EMFV_CHARSET, EMFV_CITATION_COLOUR, EMFV_CITATION_MARK, EMFV_CARET_MODE, EMFV_MESSAGE_STYLE, EMFV_MARK_SEEN, EMFV_MARK_SEEN_TIMEOUT, EMFV_LOAD_HTTP, EMFV_HEADERS, EMFV_SHOW_PREVIEW, EMFV_SHOW_DELETED, EMFV_THREAD_LIST, EMFV_PANED_SIZE, EMFV_SENDER_PHOTO, EMFV_PHOTO_LOCAL, EMFV_SETTINGS /* last, for loop count */ }; /* IF these get too long, update key field */ static const char * const emfv_display_keys[] = { "animate_images", "charset", "citation_colour", "mark_citations", "caret_mode", "message_style", "mark_seen", "mark_seen_timeout", "load_http_images", "headers", "show_preview", "show_deleted", "thread_list", "paned_size", "sender_photo", "photo_local", }; static GHashTable *emfv_setting_key; static void emfv_setting_notify(GConfClient *gconf, guint cnxn_id, GConfEntry *entry, EMFolderView *emfv) { GConfValue *value; char *tkey; g_return_if_fail (gconf_entry_get_key (entry) != NULL); if (!(value = gconf_entry_get_value (entry))) return; tkey = strrchr(entry->key, '/'); g_return_if_fail (tkey != NULL); switch(GPOINTER_TO_INT(g_hash_table_lookup(emfv_setting_key, tkey+1))) { case EMFV_ANIMATE_IMAGES: em_format_html_display_set_animate(emfv->preview, gconf_value_get_bool (value)); break; case EMFV_CHARSET: em_format_set_default_charset((EMFormat *)emfv->preview, gconf_value_get_string (value)); break; case EMFV_CITATION_COLOUR: { const char *s; GdkColor colour; guint32 rgb; s = gconf_value_get_string (value); gdk_color_parse(s?s:"#737373", &colour); rgb = ((colour.red & 0xff00) << 8) | (colour.green & 0xff00) | ((colour.blue & 0xff00) >> 8); em_format_html_set_mark_citations((EMFormatHTML *)emfv->preview, ((EMFormatHTML *)emfv->preview)->mark_citations, rgb); break; } case EMFV_CITATION_MARK: em_format_html_set_mark_citations((EMFormatHTML *)emfv->preview, gconf_value_get_bool (value), ((EMFormatHTML *)emfv->preview)->citation_colour); break; case EMFV_CARET_MODE: em_format_html_display_set_caret_mode(emfv->preview, gconf_value_get_bool (value)); break; case EMFV_MESSAGE_STYLE: if (EM_FOLDER_VIEW_GET_CLASS (emfv)->update_message_style) { int style = gconf_value_get_int (value); if (style < EM_FORMAT_NORMAL || style > EM_FORMAT_SOURCE) style = EM_FORMAT_NORMAL; em_format_set_mode((EMFormat *)emfv->preview, style); } break; case EMFV_MARK_SEEN: emfv->mark_seen = gconf_value_get_bool (value); break; case EMFV_MARK_SEEN_TIMEOUT: emfv->mark_seen_timeout = gconf_value_get_int (value); break; case EMFV_LOAD_HTTP: em_format_html_set_load_http((EMFormatHTML *)emfv->preview, gconf_value_get_int(value)); break; case EMFV_HEADERS: { GSList *header_config_list, *p; EMFormat *emf = (EMFormat *)emfv->preview; header_config_list = gconf_client_get_list(gconf, "/apps/evolution/mail/display/headers", GCONF_VALUE_STRING, NULL); em_format_clear_headers((EMFormat *)emfv->preview); p = header_config_list; while (p) { EMMailerPrefsHeader *h; char *xml = (char *)p->data; h = em_mailer_prefs_header_from_xml(xml); if (h && h->enabled) { em_format_add_header(emf, h->name, EM_FORMAT_HEADER_BOLD); } em_mailer_prefs_header_free(h); p = g_slist_next(p); } g_slist_foreach(header_config_list, (GFunc) g_free, NULL); g_slist_free(header_config_list); /* force a redraw */ if (emf->message) em_format_redraw(emf); break; } case EMFV_SENDER_PHOTO: { EMFormat *emf = (EMFormat *)emfv->preview; emf->show_photo = gconf_value_get_bool (value); if (emf->message) em_format_redraw(emf); break; } case EMFV_PHOTO_LOCAL: { EMFormat *emf = (EMFormat *)emfv->preview; emf->photo_local = gconf_value_get_bool (value); break; } case EMFV_SHOW_PREVIEW: { gboolean state_gconf, state_camel; char *ret; /* If emfv->folder hasn't been initialized, do nothing */ if (!emfv->folder) return; state_gconf = gconf_value_get_bool (value); if (state_gconf == FALSE) emfv_enable_menus (emfv); if ((ret = camel_object_meta_get (emfv->folder, "evolution:show_preview"))) { state_camel = (ret[0] != '0'); g_free (ret); if (state_gconf == state_camel) return; } if (camel_object_meta_set (emfv->folder, "evolution:show_preview", state_gconf ? "1" : "0")) camel_object_state_write (emfv->folder); if (emfv->list_active) em_folder_browser_show_preview ((EMFolderBrowser *)emfv, state_gconf); bonobo_ui_component_set_prop (emfv->uic, "/commands/ViewPreview", "state", state_gconf ? "1" : "0", NULL); break; } case EMFV_SHOW_DELETED: { gboolean state; state = gconf_value_get_bool (value); em_folder_view_set_hide_deleted (emfv, !state); /* Set the prop only if the component has already been * activated. */ if (emfv->uic) bonobo_ui_component_set_prop (emfv->uic, "/commands/HideDeleted", "state", state ? "0" : "1", NULL); break; } case EMFV_THREAD_LIST: { gboolean state_gconf, state_camel; char *ret; /* If emfv->folder or emfv->list hasn't been initialized, do nothing */ if (!emfv->folder || !emfv->list) return; state_gconf = gconf_value_get_bool (value); if ((ret = camel_object_meta_get (emfv->folder, "evolution:thread_list"))) { state_camel = (ret[0] != '0'); g_free (ret); if (state_gconf == state_camel) return; } if (camel_object_meta_set (emfv->folder, "evolution:thread_list", state_gconf ? "1" : "0")) camel_object_state_write (emfv->folder); message_list_set_threaded (emfv->list, state_gconf); bonobo_ui_component_set_prop (emfv->uic, "/commands/ViewThreaded", "state", state_gconf ? "1" : "0", NULL); break; } case EMFV_PANED_SIZE: { EMFolderBrowser *emfb = (EMFolderBrowser *)emfv; int paned_size; if (!emfv->list_active || !emfb->vpane || !emfv->preview_active) return; paned_size = gconf_value_get_int (value); if (paned_size == gtk_paned_get_position (GTK_PANED (emfb->vpane))) return; gtk_paned_set_position (GTK_PANED (emfb->vpane), paned_size); break; } } } static void emfv_setting_setup(EMFolderView *emfv) { GConfClient *gconf = gconf_client_get_default(); GConfEntry *entry; GError *err = NULL; int i; char key[64]; if (emfv_setting_key == NULL) { emfv_setting_key = g_hash_table_new(g_str_hash, g_str_equal); for (i=1;imessage); g_error_free(err); } emfv->priv->setting_notify_id = gconf_client_notify_add(gconf, "/apps/evolution/mail/display", (GConfClientNotifyFunc)emfv_setting_notify, emfv, NULL, NULL); g_object_unref(gconf); } static void emfv_on_url (EMFolderView *emfv, const char *uri, const char *nice_uri) { if (emfv->statusbar_active) { if (emfv->uic) { bonobo_ui_component_set_status (emfv->uic, nice_uri, NULL); /* Make sure the node keeps existing if nice_url == NULL */ if (!nice_uri) bonobo_ui_component_set_translate ( emfv->uic, "/", "", NULL); } } } static void emfv_on_url_cb (GObject *emitter, const char *url, EMFolderView *emfv) { char *nice_url = NULL; if (url) { if (strncmp (url, "mailto:", 7) == 0) { CamelInternetAddress *cia = camel_internet_address_new(); CamelURL *curl; char *addr; curl = camel_url_new(url, NULL); camel_address_decode((CamelAddress *)cia, curl->path); addr = camel_address_format((CamelAddress *)cia); nice_url = g_strdup_printf (_("Click to mail %s"), addr&&addr[0]?addr:(url + 7)); g_free(addr); camel_url_free(curl); camel_object_unref(cia); } else if (strncmp (url, "callto:", 7) == 0 || strncmp (url, "h323:", 5) == 0 || strncmp (url, "sip:", 4) == 0) { CamelInternetAddress *cia = camel_internet_address_new(); CamelURL *curl; char *addr; curl = camel_url_new(url, NULL); camel_address_decode((CamelAddress *)cia, curl->path); addr = camel_address_format((CamelAddress *)cia); nice_url = g_strdup_printf (_("Click to call %s"), addr&&addr[0]?addr:(url + 7)); g_free(addr); camel_url_free(curl); camel_object_unref(cia); } else if (!strncmp (url, "##", 2)) { nice_url = g_strdup (_("Click to hide/unhide addresses")); } else nice_url = g_strdup_printf (_("Click to open %s"), url); } g_signal_emit (emfv, signals[EMFV_ON_URL], 0, url, nice_url); g_free (nice_url); }