diff options
author | Not Zed <NotZed@Ximian.com> | 2003-09-18 05:19:04 +0800 |
---|---|---|
committer | Michael Zucci <zucchi@src.gnome.org> | 2003-09-18 05:19:04 +0800 |
commit | 81a0ff5bc44a3bd11399e6b3c985735737606c8c (patch) | |
tree | 54b5ed4342a6843c1db4c7e75f2e1b1fe9b82dff | |
parent | a36a1bb70b6ebcb51ac39304370c89bda63e11b9 (diff) | |
download | gsoc2013-evolution-81a0ff5bc44a3bd11399e6b3c985735737606c8c.tar gsoc2013-evolution-81a0ff5bc44a3bd11399e6b3c985735737606c8c.tar.gz gsoc2013-evolution-81a0ff5bc44a3bd11399e6b3c985735737606c8c.tar.bz2 gsoc2013-evolution-81a0ff5bc44a3bd11399e6b3c985735737606c8c.tar.lz gsoc2013-evolution-81a0ff5bc44a3bd11399e6b3c985735737606c8c.tar.xz gsoc2013-evolution-81a0ff5bc44a3bd11399e6b3c985735737606c8c.tar.zst gsoc2013-evolution-81a0ff5bc44a3bd11399e6b3c985735737606c8c.zip |
cvs removed.
2003-09-17 Not Zed <NotZed@Ximian.com>
* folder-browser.c, folder-browser.h, folder-browser-ui.c
folder-browser-ui.h, mail-callbacks.c, mail-callbacks.h
mail-display.c, mail-display.h, mail-display-stream.c
mail-display-stream.h, mail-format.c, mail-format.h
mail-identify.c, mail-search.c, mail-search.h
message-browser.c, message-browser.h, subscribe-dialog.c
subscribe-dialog.h, mail-font-prefs.c, mail-font-prefs.h: cvs
removed.
* Makefile.am: Removed mail-font-prefs.[ch], hasn't been built for
ages.
* em-*.c: killed a bunch of printfs.
* em-format-html-display.c (efhd_html_button_press_event): update
for html object api chagnes.
** Merge in mail-refactor-2 branch.
svn path=/trunk/; revision=22602
77 files changed, 16729 insertions, 15950 deletions
diff --git a/mail/ChangeLog b/mail/ChangeLog index c443170f80..ff1d644a98 100644 --- a/mail/ChangeLog +++ b/mail/ChangeLog @@ -1,3 +1,24 @@ +2003-09-17 Not Zed <NotZed@Ximian.com> + + * folder-browser.c, folder-browser.h, folder-browser-ui.c + folder-browser-ui.h, mail-callbacks.c, mail-callbacks.h + mail-display.c, mail-display.h, mail-display-stream.c + mail-display-stream.h, mail-format.c, mail-format.h + mail-identify.c, mail-search.c, mail-search.h + message-browser.c, message-browser.h, subscribe-dialog.c + subscribe-dialog.h, mail-font-prefs.c, mail-font-prefs.h: cvs + removed. + + * Makefile.am: Removed mail-font-prefs.[ch], hasn't been built for + ages. + + * em-*.c: killed a bunch of printfs. + + * em-format-html-display.c (efhd_html_button_press_event): update + for html object api chagnes. + + ** Merge in mail-refactor-2 branch. + 2003-09-17 Jeffrey Stedfast <fejj@ximian.com> * evolution-mbox-upgrade.c: New source file to migrate from the @@ -168,6 +189,847 @@ CamelOperation argument, for overriding the status handler. Fixed most calles to pass NULL to use the default. +2003-09-15 Not Zed <NotZed@Ximian.com> + + * em-folder-browser.c (emfb_activate): remove warning about folder + not being loaded yet - it basically never is. + + * em-popup.c (emp_standard_menu_factory): dont setup any global + select menu's yet. they're all handled by folderview atm. + +2003-09-11 Not Zed <NotZed@Ximian.com> + + * em-format-html.c (efh_finalise, efh_format_timeout): Use a + proper hash free func, otherwise it dont work. + +2003-09-11 Not Zed <NotZed@Ximian.com> + + * em-inline-filter.[ch]: A new class which implements an inline + snooper via a mime filter, so it is fully streamable. contents + merely passes through the filter. + + * em-format-html.c (efh_finalise): free text substitute parts + table. + (efh_text_plain): transform a text part into a multipart, scanning + for inline data. Keep the multipart around for redraws. + (efh_format_timeout): clear the text substitute parts table. + +2003-09-10 Not Zed <NotZed@Ximian.com> + + * em-format-html-display.c (efhd_init): hook onto realise so we + get the real theme-applied style. + (efhd_gtkhtml_realise): get the theme data for colour defaults. + Tweak the colour selection to make it work better with dark + themes. + + * em-format-quote.c (emfq_format_source): we need to implement + this. + (emfq_format_error): we need not to call parent, it doesn't + implement it. + + * message-list.c (message_list_select_uid): don't emit + changedhere, let it go through the table cursor change. + + * em-folder-browser.c (em_folder_browser_show_preview): use + folderview.preview_active for this state. + (em_folder_browser_show_preview): clear the current message when + we turn off the message view, and load the current one when turn it on. + + * em-folder-view.c (emfv_destroy): zero out preview + list. + (emfv_list_message_selected): check preview_active before doing + anything. + (emfv_edit_cut): + (emfv_edit_copy): only run if preview active. + + * em-format-html.c (efh_format_do): output the proper html + headers, etc. + +2003-09-10 Jeffrey Stedfast <fejj@ximian.com> + + * mail-account-gui.c (mail_account_gui_save): Allow the user to + select any fodler for his/her Drafts and Sent folders. Fixes bug + #45412. + +2003-09-09 Not Zed <NotZed@Ximian.com> + + * em-utils.c (forward_non_attached): implement forward quoted. + + * em-format-quote.[ch]: New class, em-format-html-quote wont cut + it. Sigh. + + * em-format-html-quote.c (efhq_base_init): move type init stuff + here. + (efhq_complete): remove, this is a signal. + (efhq_multipart_related): we need to override the base class, we + don't want to output any attachment html. + + * em-format-html-print.c (efhp_base_init): move builtin type init + to here. + + * em-format-html.c (efh_init): get xmailer mask from gconf (sigh). + (efh_format_header): inmplement most of xmailer mask thing. + rupert icon not done yet, probably needs to be done as part of + em-format-html-display, sigh. + (type_builtin_table[]): add image/svg to supported image formats. + (efhd_base_init): move type init to here. + (efh_text_enriched): write to the filtered_stream, not stream. + + * em-format.c (em_format_format_text): oops, actually use any + supplied charset. + (emf_base_init): move hashtable init into base_init, so we get a + new copy for each derived class too. + + * mail-send-recv.c (build_dialogue): use an eclippedlabel for + status. Fixed all uses. + +2003-09-08 Not Zed <NotZed@Ximian.com> + + * em-format-html.h (EMFormatHTML): added a simple_headers option, + only output headers in basic format. Added hide_headers option, + to disable all header output. + + * em-format-html-quote.c (efhq_format_message): blockquote the + contnet, thats how you cite it! + (efhq_init): turn on simple headers for html output. + (efhq_format_message): output headers and part directly, bypassing + parent format_message. + (efhq_format_message): implement hide_headers. + + * em-format-html.c (efh_busy): implement busy. + (efh_format_message): implement simple_headers option. + (efh_format_text_header): implement simple_headers option. + (efh_format_message): move the header formatting stuff into + exported em_format_html_format_headers. + (efh_format_message): only output headers if not hidden. + + * em-format.c (emf_busy): base implementation of a new virtual + method, returns TRUE if the object is still busy + rendering/downloading stuff. + + * em-utils.c (em_utils_message_to_html): renamed from + em_utils_quote_message. Also make sure the html conversion is + complete before getting the data. + (em_utils_part_to_html): similar, but for parts. + (composer_set_body): put in David Woodhouse's timezone in + attribution patch. + (composer_set_body): we want to quote the part (content), we don't + want message headers. + (em_utils_message_to_html): add a 'show headers' argument. + + * folder-browser-factory.c (control_activate): removed. + (control_deactivate): removed. + + * mail-identify.c: + * folder-browser.[ch], folder-browser-ui.[ch]: + * mail-callbacks.[ch], mail-search.[ch]: + * mail-display.[ch], mail-format.[ch], mail-display-stream.[ch]: + * message-browser.[ch]: Removed, fixed all users. + + * component-factory.c (factory): change callbacks to em_utils + ones. + (configure_folder_popup): comment out some of the reconfigure + stuff. Wont have it in 1.6? + (handle_external_uri_cb): use em_utils stuff. + (user_create_new_item_cb): " + (owner_unset_cb): " + + * em-composer-utils.c (composer_send_cb): rename to + em_utils_composer_send_cb, and export, sigh, needed for factory. + (composer_save_draft_cb): Same. + +2003-09-05 Not Zed <NotZed@Ximian.com> + + * em-format.c (type_builtin_table[]): Add a fallback multipart/* + for other types (e.g. multipart/report) + (em_format_fallback_handler): fix some bad logic. + + * em-folder-browser.c: track the pane size if the user changes it. + (emfb_set_folder): Added a bit of a mess that will select the + first unread message the first time you visit a folder. + (emfb_destroy): impelment. clear up outstanding signal handlers. + (emfb_list_built): Select the first unread message. this isn't + entirely reliable as yet, and not configurable at all. + + * em-format-html-display.c (em_format_html_display_set_animate) + (em_format_html_display_set_caret_mode): guess? :) + (efhd_attachment_button): dont desensitise the button, just dont + hook onto it, otherwise it looks fugly. + + * em-folder-view.c (emfv_list_done_message_selected): dont lookup + gconf values every time. use g_timeout_add rather than + gtk_timeout, remove fixme's. + (emfv_setting_notify): listner for gconf changes, reflect + internally. + (emfv_setting_setup): setup listner for gconf, and read initial + settings. + (emfv_activate): use local copy of settings rather than snooping + gconf. + (emfv_caret_mode): propagate caret-mode to display + + * em-format-html-quote.c (efhq_format_message): remove gconf + stuff, our parent already has citation colour. + + * em-format-html.c (efh_format_timeout): remove gconf stuff. + (em_format_html_set_load_http, em_format_html_set_mark_citations): + set options on formatter, re-renders if required. + (type_builtin_table[]): text/* should go to text/plain, not + text/enriched. + +2003-09-04 Not Zed <NotZed@Ximian.com> + + * em-utils.c (confirm_expunge): rename it to emu_confirm_expunge + and remove leading whitespace before function. + (em_utils_expunge_folder): we want to expunge the folder, not + empty the trash. Jeff didn't even run this once ... + + * em-popup.c: Lots of new features, 'global' popup menu's on a + per-selection type, via factories, popup selections (targets), a + standard factory for many menu items. + (emp_apps_open_in): duh, fix uri using logic + (emp_standard_menu_factory): only add apps to app list if + !requires_terminal. + + * em-format-html-display.c (efhd_open_in, efhd_popup_free_items): + moved to em-popup.c + (efhd_popup_save_attachment, efhd_popup_save_message) + (efhd_popup_reply_sender, efhd_popup_reply_list) + (efhd_popup_reply_all, efhd_popup_forward): (re)moved to em-popup.c + (efhd_attachment_popup): use enew popu stuff. + (efhd_attachment_button): Scale the icons for mime-type icons. + + * em-folder-view.c (em_folder_view_disable_mask): removed + ... moved to em-popup. + (em_folder_view_get_popup_target): new method, get the selection + target for the folder view. + (emfv_html_popup_saveas, emfv_html_popup_link_open) + (emfv_html_popup_link_copy, emfv_html_popup_address_send) + (emfv_html_popup_address_add, emfv_format_popup_free_items): moved + to em-popup.c + (emfv_format_popup_event): use new popup stuff. + (emfv_popup): use new popup stuff, but still just use all local + menu's. + +2003-09-03 Not Zed <NotZed@Ximian.com> + + * em-folder-view.c (emfv_format_popup_event): implement, a popup + menu for right-clicking on links and images. + (emfv_html_popup_link_copy): implement. + (emfv_init): setup an invisible for selection stuff. + (emfv_destroy): free invisible + + * em-utils.c (em_utils_get_proxy_uri): utility to get the current + system proxy setting. + (emu_set_proxy): implementation. + + * em-camel-stream.[ch]: removed. + + * em-format-html.c (efh_url_requested, efh_format_timeout): use + em_html_stream rather than em_camel_stream. + (emfh_gethttp): set the system proxy on the new stream. + (emfh_multipart_related_check): use puri rather than purin inside + the loop - duh. + (emfh_multipart_related_check): removed 'unrelated part' warning, + they can be (and normally are) added by the callbacks. + + * em-format-html.h (EMFormatHTMLJob): s/estream/stream/ + + * em-html-stream.[ch]: New subclass of emsyncstream, replacement + for em-camel-stream. + +2003-09-04 Jeffrey Stedfast <fejj@ximian.com> + + * em-folder-browser.c (emfb_folder_expunge): Call + em_utils_expunge_folder instead. + + * em-utils.c (em_utils_expunge_folder): New function. + (confirm_expunge): Make private. + +2003-09-04 Jeffrey Stedfast <fejj@ximian.com> + + * em-folder-browser.c (emfb_folder_expunge): Confirm hat the user + wants to expunge. + (emfb_empty_trash): Implemented. + + * em-utils.c (em_utils_prompt_user): Make public (used to be + e_question). + (em_utils_confirm_expunge): New function to confirm that the user + wants to expunge. + (em_utils_empty_trash): New function to empty all Trash folders. + + * em-composer-utils.c: Get rid of e_question and use em-utils' + em_utils_prompt_user() function instead. + + * em-format-html-quote.[c,h]: New formatter for quoting + replies/forwards/etc. + + * em-utils.c (em_utils_quote_message): New function. + (composer_set_body): Use the new em_utils_quote_message() + function. + (em_utils_temp_save_part): Only g_free mfilename if it was + malloc'd. + + * mail-tools.c (mail_tool_quote_message): Removed. + (mail_tool_forward_message): Removed. + +2003-09-03 Jeffrey Stedfast <fejj@ximian.com> + + * em-format.c (em_format_class_remove_handler): New function to + remove a mime-type handler from a class. + + * em-format-html.c (efh_init): Set the CITATION bit for the + default html flags. + + * em-format-html-display.c: Fixed some compiler warnings by adding + appropriate includes. + (efhd_multipart_signed): Don't write "inlined signature...". + +2003-09-03 Not Zed <NotZed@Ximian.com> + + * em-icon-stream.[ch]: New subclass of emsyncstream, write camel + stream one side, creates an icon on the other. + + * em-sync-stream.[ch]: New file, an abstract class for creating + write-any-thread-act-gui-thread stuff. 'em-camel-stream' will + subclass this. + + * em-format-html-display.c (efhd_attachment_button): setup a job + to write out an icon if the type is an image type, otherwise try + and get the icon directly. no caching yet. the system icons + aren't scaled properly either. + (efhd_write_icon_job): async job to write out image content. + +2003-09-02 Not Zed <NotZed@Ximian.com> + + * em-format-html.c (emfh_new_job): renamed to + em_format_html_job_new and made public. + (emfh_queue_job): renamed to em_format_html_job_queue, and made + public. + + * em-format-html.h: Made EMFormatHTMLJob a public structure. + +2003-09-02 Not Zed <NotZed@Ximian.com> + + * em-folder-view.h (struct _EMFolderView): track the uicomponent + while we're activated. + + * em-message-browser.c (em_message_browser_window_new): kill + warning. + (emmb_init, emmb_finalise): kill printf + + * em-format-html.c (efh_format_header): Converted code from head + from David Woodhouse <dwmw2@infradead.org>'s timezone display + patch. + (efh_format_text_header): support new flag, HEADER_HTML - header + alredy in html format. + + * em-format-html-print.c (em_format_html_print_print): only ref + print_config if != NULL. + + * em-folder-browser.c (emfb_tree_key_press): handle + space/backspace in messagelist to scroll the message view. + (emfb_create_view_menus): setup view menu's, this should probably + live in message-list. + (emfb_init): setup the folderbrowser enable map into the list. + (emfb_enable_map): folder browser enable map + + * em-utils.c (em_utils_adjustment_page): new helper to scroll an + adjustment up/down 1 page. + + * em-folder-view.c (emfv_list_double_click): implement, open + window. + (emfv_list_key_press): implement keybinding overrides. Enhance + delete key to undelete if everything is already deleted. + (emfv_build_enable_mask): separate out enable mask creation. + (emfv_popup): use above to get mask. + (emfv_enable_menus): enable/sensitize menus, use the same disable + mask system as used for the popups. + (emfv_destroy): change to use g_source_remove on seen_id. + (emfv_finalise): free up folders, clean up async event thing. + (emfv_init): setup an async event handler + (emfv_set_folder): handle hook/unhook of folder_changed events. + (emfv_folder_changed): proxy folder changed to main thread, ignore + the details of what changed. + (emfv_gui_folder_changed): update the menu's to reflect any folder + changes. + (emfv_build_disable_mask): added CAN_THREADED. + (em_folder_view_disable_mask): make public (rename from + emfv_build_disable_mask). + (emfv_enable_menus): changed to work on a list of arrays of + enablers, so they can be subclassed. + (emfv_init): add our enable map to the ui. + (em_folder_view_disable_mask): added support for can hidden (there + are hidden messages). + +2003-09-01 Not Zed <NotZed@Ximian.com> + + * em-popup.c: New, simple menu-merging popup menu implementation. + NOTE: should be temporary, but needs something that has similar + merging facilities. + + * em-folder-view.c (emfv_popup*): added popup callbacks, implement + a popup menu, using em_popup. + (emfv_message_*): replaced a whole bunch of one-line, or simple + functions with macro's to map to the popup implementation. + (emfv_tools_vfolder*, emfv_tools_filter*): map to popup + equivalents. + (emfv_init): drop printf + + * em-format-html-display.c (efhd_attachment_popup): use the + em_popup stuff to build a dynamic menu. + + * em-utils.c (em_utils_temp_save_part): change assignment order ot + kill warning. + (emu_get_save_filesel): handle null/empty name by appending / to + the filename. + +2003-08-30 Not Zed <NotZed@Ximian.com> + + * mail-search.glade: forgot to add this yesterday. + + * em-utils.h: don't include stuff we dont need to. + + * em-folder-view.c (emfv_message_forward): just call + em_utils_forward_messages. + + * em-format-html-display.c (em_format_html_display_search): + removed unused. + (efhd_drag_data_get): cleanup, use em_utils_temp_save_part. + (efhd_attachment_popup): quick hack, setup a bunch more menu + items, for forwarding inline messages, hook up saving parts, and + messages, and hook up the 'open in' menu. + (efhd_open_in): implement. + + * em-utils.c (em_utils_save_message): Renamed to + em_utils_save_part. + (em_utils_filesel_prompt): removed, it just makes things more + complex than having a single response handler. + (em_utils_save_part): move dialog stuff here, it also creates a + name based on the type of part its given. + (emu_get_save_filesel): new method to create a fileselector with + standard options. + (emu_save_part_response): handle file selector response for save + part. + (can_save): renamed to emu_can_save + (em_utils_save_messages): use get_save_filesel + (emu_can_save): handle the path="" case + (em_utils_save_part): Add a prompt argument. + (filesel_ok_cb): removed. + (emu_update_save_path): update the gconf save_dir setting. + (em_utils_forward_messages): helper to forward using default + style. + (forward_non_attached): remove uids argument. + (em_utils_forward_message): helper to forward a message using the + default forward style. + (forward_non_attached): removed folder argument. + (em_utils_temp_save_part): helper to save a part to a temporary + file, e.g. for dnd, app launch, etc. + +2003-08-29 Not Zed <NotZed@Ximian.com> + + * em-folder-view.c: set 'outgoing' properly. + + * em-folder-browser.c (emfb_tools_subscriptions): enforce a single + instance of the subscribe editor. + (emfb_subscribe_editor_destroy): clear subscribe editor handle. + +2003-08-29 Not Zed <NotZed@Ximian.com> + + * em-camel-stream.c (em_camel_stream_new): Added some optional + logging code. + (stream_close): and here. + (stream_write): and here. + + * em-folder-browser.c (emfb_init): remove fixme about search bar, + its there now. also fixme's about dnd/selection, they are handled + in lower-level widgets. + (em_folder_browser_show_preview): dont exit if show preview set, + but only if it hasn't changed. + (emfb_view_hide_selected, emfb_view_show_all) + (emfb_view_hide_read): removed some spurious printfs. + + * Makefile.am (glade_DATA): Added mail-search.glade. FIXME: + should all glade files be merged into 1? + + * em-format-html-display.c (efhd_format_clone): remove search + match count code from here - wont be finished rendering at this + point anyway. + (em_format_html_display_search): new api for running an + interactive search popup. + (efhd_update_matches, efhd_update_search) + (efhd_search_entry_activate, efhd_search_case_toggled) + (efhd_search_response): helpers/callbacks for search popup. + (efhd_class_init): hook into complete signal on EMFormat. + (efhd_complete): complete rendering handler, update match count. + + * em-folder-view.c: removed fixme about api's - yes, do need two + set_folder api's. + (emfv_edit_cut, emfv_edit_copy): removed printfs + (emfv_edit_paste): removed commented call to html_paste, we never + want to do that. + +2003-08-29 Jeffrey Stedfast <fejj@ximian.com> + + * em-folder-view.c (emfv_message_mark_unread): If there is a + mark-as-read timeout handler registered, unregister it here. + (emfv_tools_filter_mlist): Implemented. + (emfv_tools_filter_recipient): Implemented. + (emfv_tools_filter_sender): Implemented. + (emfv_tools_filter_subject): Implemented. + (emfv_tools_vfolder_mlist): Implemented. + (emfv_tools_vfolder_recipient): Implemented. + (emfv_tools_vfolder_sender): Implemented. + (emfv_tools_vfolder_subject): Implemented. + +2003-08-28 Not Zed <NotZed@Ximian.com> + + * em-folder-browser.c (emfb_search_menu_activated) + (emfb_search_config_search, emfb_search_search_activated) + (emfb_search__query_changed): Implement search-bar callbacks. + (emfb_init): setup search bar. + +2003-08-28 Not Zed <NotZed@Ximian.com> + + * em-folder-view.c (emfv_message_reply): common reply code entry + point, also implement simple reply-to-highlighted text (currently + disabled). + (emfv_activate): disable resend message on non-sent folders. + (emfv_message_reply_all, emfv_message_reply_list) + (emfv_message_reply_sender): use message_reply for common code. + (em_folder_view_open_selected): in drafts or outbox, edit the + message instead. + (emfv_activate): force a sync on deactivate. + + * em-utils.c (em_utils_selection_get_mailbox): get mailbox + (message/rfc822?) selection data. + (em_utils_read_messages_from_stream): helper to move stuff from a + mbox stream to a folder. + (em_utils_folder_is_drafts, em_utils_folder_is_sent) + (em_utils_folder_is_outbox): from folder browser helpers for + customising the user experience. + + * message-list.c (message_list_construct): hook onto dnd stufd. + (ml_tree_drag_data_get): implement drag sending. + (ml_tree_drag_data_received): implement drag recieving. + + * em-format-html-display.c (efhd_drag_data_get): implemented. + (efhd_drag_data_delete): implemented. + (efhd_attachment_button): setup dnd callbacks. + +2003-08-28 Jeffrey Stedfast <fejj@ximian.com> + + * em-folder-view.c (emfv_destroy): override the destroy method for + GtkObject - unregister the mark-as-seen timeout if one is + currently registered. + (emfv_list_done_message_selected): Add the mark-as-seen timeout + functionality here. + (emfv_format_link_clicked): Implemented. + +2003-08-27 Jeffrey Stedfast <fejj@ximian.com> + + * em-utils.c (get_reply_list): Implemented. + +2003-08-27 Not Zed <NotZed@Ximian.com> + + * em-message-browser.c (emmb_activate): disable Edit->Paste menu + always. + + * em-folder-browser.c (emfb_edit_paste): do a message-list paste, + not a html one. + (emfb_edit_cut, emfb_edit_copy, emfb_edit_paste): Moved to folder-view. + + * message-list.c (message_list_paste): trigger a paste action. + (message_list_set_folder): added a uri argument, and save it + internally, fixed all callers. + (message_list_finalise): free the folder uri. + + * em-utils.c (em_utils_selection_set_mailbox): New helper to set + the current selection as text in a berkely mailbox format. + (em_utils_write_messages): helper to write stuff to a stream in + mbox format. + +2003-08-27 Not Zed <NotZed@Ximian.com> + + * message-list.c (on_selection_changed_cmd): own/deown the primary + selection when it changes. + (message_list_init): init private data and invisible for + selection. + (message_list_destroy): free invisible. + (message_list_finalise): free private data. + (get_selected_cb): removed. + (message_list_copy): new method to do copy and cut. cut/copy to + the clipboard. + (ml_selection_clear_event): clear the right selection when + requested. + (message_list_has_primary_selection): helper to find out if the + message-list has the selection. is there a gtk way for this? + +2003-08-26 Not Zed <NotZed@Ximian.com> + + * mail-local.c (mlf_meta_set, mlf_meta_get): proxy meta-data stuff + to subservient folder. + +2003-08-23 Not Zed <NotZed@Ximian.com> + + * em-folder-view.c (emfv_init): init preview here always. + +2003-08-25 Jeffrey Stedfast <fejj@ximian.com> + + * em-folder-view.c (em_folder_view_print): Use + e_dialog_set_transient_for(). + (emfv_message_delete): Fixed a FIXME. + + * em-folder-browser.c (emfb_edit_cut): Implemented. + (emfb_edit_copy): Implemented. + (emfb_edit_paste): Implemented. + + * em-format-html-display.c (em_format_html_display_cut): New function. + (em_format_html_display_copy): New. + (em_format_html_display_paste): New. + +2003-08-25 Jeffrey Stedfast <fejj@ximian.com> + + * em-utils.c (em_utils_flag_for_followup): Use + e_dialog_set_transient_for(). + (em_utils_filesel_prompt): Same. + (post_reply_to_message): Here too. + (em_utils_edit_filters): Same. + (create_new_composer): And here. + (em_utils_compose_new_message_with_mailto): Here too. + (em_utils_post_to_url): " + (redirect_get_composer): Same. + (reply_get_composer): Again... + + * em-folder-browser.c (emfb_tools_filters): Implemented. + + * em-utils.c (em_utils_edit_filters): New function to open the + filter editor dialog. + +2003-08-22 Jeffrey Stedfast <fejj@ximian.com> + + * em-utils.c (em_utils_flag_for_followup): Implemented. + (em_utils_flag_for_followup_clear): Implemented. + (em_utils_flag_for_followup_completed): Implemented. + + * em-folder-view.c (emfv_message_followup_flag): Implemented. + (emfv_message_followup_clear): Implemented. + (emfv_message_followup_completed): Implemented. + +2003-08-22 Not Zed <NotZed@Ximian.com> + + * em-camel-stream.c (em_camel_stream_new): now take the gtkhtml + too, and hook onto it's destroy so we don't try writing anymore + after its gone. + (stream_write, stream_flush, stream_close, emcs_gui_received): + NOOP if the gtkhtml has been destroyed. + (emcs_gtkhtml_destroy): null out the stream when the gtkhtml gets + destroyed, it is no longer valid. + (emcs_gui_received): dont try to soak all outstanding events, it + always runs synchronous anyway, just get one and exit. + + * em-format-html.c (efh_gtkhtml_destroy): if the gtkhtml gets + destroyed, abort any pending timeouts/processing. + (efh_format_source): fixed implementation to write out all + headers. + (efh_multipart_related, emfh_multipart_related_check): separate + checking for unused parts into a separate job, which is run after + previous ones are executed. keep track of visibility tree level + in job, etc. + +2003-08-22 Jeffrey Stedfast <fejj@ximian.com> + + * em-folder-browser.c (emfb_mail_compose): Implemented. + (emfb_mail_post): Implemented. + + * em-utils.c (em_utils_compose_new_message): New function. + (em_utils_compose_new_message_with_mailto): New. + (em_utils_post_to_url): New. + +2003-08-21 Not Zed <NotZed@Ximian.com> + + * subscribe-dialog.glade: removed the text in the progress thing. + It never showed up anyway and caused weird resizing stuff when the + progress bar was active. + + * em-subscribe-editor.c: Found the correct version of the new + subscribe code (on branch, duh!), and integrated it. + (sub_selection_changed): Sensitise buttons based on selection. + + * em-format-html.c (efh_text_plain, efh_text_enriched) + (efh_write_text_html): Use format_text for text output. + (efh_write_image): use explicit image writer. + (emfh_gethttp): added some progress stuff. + (efh_format_do): maintain the accessible uri tree during jobs. + (efh_url_requested): store the current uri accessibility tree node + in the job, so it can be properly set for sub-jobs. + (emh_multipart_related): moved here, can't use super-class version + as it doesn't know about async jobs. + (type_buildin_table[]): Added image/jpg and image/jpeg for the + brokenmailers out there and to reduce the whinge. + + * em-format.c (em_format_format_content): For text parts, perform + default charset/charset snooping/decoding. No longer closes the + stream once complete. + (emf_write_related): close stream ourselves. + +2003-08-21 Jeffrey Stedfast <fejj@ximian.com> + + * em-folder-view.c (emfv_message_forward): Implemented. + (emfv_message_forward_attached): Implemented. + (emfv_message_forward_inline): Implemented. + (emfv_message_forward_quoted): Implemented. + (emfv_message_redirect): Implemented. + (emfv_message_post_reply): Implemented. + (emfv_message_reply_all): Implemented. + (emfv_message_reply_list): Implemented. + (emfv_message_reply_sender): Implemented. + (emfv_message_resend): Implemented. + (emfv_message_saveas): Implemented. + + * em-composer-utils.c: New source file containing all the composer + send/draft callback mess. + + * em-utils.c (em_utils_uids_copy): New convenience function to + copy a list of uids. + (em_utils_uids_free): New convenience function to free a list of + uids. + (em_utils_save_message): New function to save a CamelMimeMessage + (prompts the user for a location to save). + (em_utils_save_messages): New function to save a list of messages + (given a folder and list of uids). + (em_utils_configure_account): Configure a new account... + (em_utils_check_user_can_send_mail): Make sure the user has a + transport setup. + (em_utils_edit_message): New function to edit a message object. + (em_utils_edit_messages): New function to open a composer to edit + each message. + (em_utils_forward_attached): New function to forward messages as + an attachment, + (em_utils_forward_inline): Forward a bunch of messages inline. + (em_utils_forward_quoted): Forward a bunch of messages quoted. + (em_utils_redirect_message): Redirect a message object. + (em_utils_redirect_message_by_uid): Redirect a message given a + folder and uid. + (em_utils_reply_to_message): Reply to a message object. + (em_utils_reply_to_message_by_uid): Reply to a message given a + folder and uid. + (em_utils_post_reply_to_message_by_uid): Post a reply to a message + given a folder and uid. + + * mail-ops.c (filter_folder_free): Use em_utils_uids_free(). + (transfer_messages_free): Same. + (get_messages_free): Here too. + (save_messages_free): Same. + +2003-08-20 Not Zed <NotZed@Ximian.com> + + * em-subscribe-editor.[ch]: new widget, a dialog for editing + subscriptions. + + * em-format-html.c (efh_format_done): emit a complete when done. + + * em-format.c (emf_class_init): Added a 'complete' signal, so that + printing knows when to print. + + * em-format-html-print.c (em_format_html_print_print): Changed to + take the message and source formatter too. Runs an async render + then prints. + +2003-08-19 Not Zed <NotZed@Ximian.com> + + * em-*.c: stacks more changes, added some bonobo menu setup, and + implemented the trivial functions. + + * em-message-browser.[ch]: New message browser, inherits from + em-folder-view. Basically works. + + * message-list.c (message_list_select_uid): if we're selecting + while still loading, setup a pending select. + (regen_list_free): Check for a pending select, and select the + message if we're now idle. + + * em-folder-view.c (em_folder_view_set_message, + em_folder_view_set_folder): Make virtual macro's. + (emfv_control_activate): added hook to enable bonobo state when setup. + + * em-format.c (emf_format_clone): base implementation, just clears + state data. + + * em-format.h: change ::format to ::format_clone. Make + em_format_format_clone a macro/virtual method. + + * mail-mt.c (em_channel_setup): new function to setup i/o + channels, so we can control the recursive flag. leave off for + now. + (mail_msg_init): setup MsgPort channels using above. + + * em-format-html.c (efh_format): serialise/de-recursify formatting + via a timeout function. + (efh_format_timeout): keep polling to find out if cancellation is + complete, then kick off a new render. + +2003-08-18 Not Zed <NotZed@Ximian.com> + + * em-*.c: more updates, incl threaded formatting queue. + +2003-08-18 Jeffrey Stedfast <fejj@ximian.com> + + * em-format-html-display.c (em_format_html_display_zoom_in): New + method to zoom-in on the gtkhtml contents. + (em_format_html_display_zoom_out): Same but for zoom-out + (em_format_html_display_zoom_reset): You get the idea. + +2003-08-12 Jeffrey Stedfast <fejj@ximian.com> + + * mail-callbacks.c (invert_selection): Use + message_list_invert_selection(). + (select_thread): Use message_list_select_thread(). + (select_all): Use message_list_select_all(). + + * message-list.c (message_list_select_all): New function. + (message_list_select_thread): New function. + (message_list_invert_selection): New function. + +2003-08-12 Jeffrey Stedfast <fejj@ximian.com> + + * mail-session.c: Synced up with HEAD. + + * component-factory.c: + + * mail-folder-cache.[c,h]: + + * mail-tools.h: + + * mail-ops.[c,h]: + + * mail-send-recv.c: + + * mail-format.[c,h]: + + * mail-display.c: + + * mail-account-gui.c: + + * mail-local.c: + + * mail-offline-handler.c: + + * subscribe-dialog.c: + +2003-08-12 Jeffrey Stedfast <fejj@ximian.com> + + * em-format-html-display.c: Fixed some compiler warnings. + + * em-format.c: Updated for new mime-parser changes made to HEAD. + + * em-folder-view.c: Fixed some compiler warnings. + + * em-format-html.c: Fixed some compiler warnings. + +2003-08-06 Not Zed <NotZed@Ximian.com> + + * em-format-html-display.c (efhd_format_attachment): Added bonobo + embeddables. + 2003-08-01 Harry Lu <harry.lu@sun.com> *Fix for bug #6951 diff --git a/mail/Makefile.am b/mail/Makefile.am index b1b93266cb..d7d2d347cc 100644 --- a/mail/Makefile.am +++ b/mail/Makefile.am @@ -70,12 +70,44 @@ libevolution_mail_la_SOURCES = \ component-factory.h \ e-searching-tokenizer.c \ e-searching-tokenizer.h \ - folder-browser.c \ - folder-browser.h \ + em-inline-filter.c \ + em-inline-filter.h \ + em-folder-view.c \ + em-folder-view.h \ + em-folder-browser.c \ + em-folder-browser.h \ + em-format.c \ + em-format.h \ + em-format-html.c \ + em-format-html.h \ + em-format-html-display.c \ + em-format-html-display.h \ + em-format-html-print.c \ + em-format-html-print.h \ + em-format-html-quote.c \ + em-format-html-quote.h \ + em-format-quote.c \ + em-format-quote.h \ + em-marshal.c \ + em-marshal.h \ + em-message-browser.c \ + em-message-browser.h \ + em-composer-utils.c \ + em-composer-utils.h \ + em-popup.c \ + em-popup.h \ + em-utils.c \ + em-utils.h \ + em-subscribe-editor.c \ + em-subscribe-editor.h \ + em-sync-stream.c \ + em-sync-stream.h \ + em-icon-stream.c \ + em-icon-stream.h \ + em-html-stream.c \ + em-html-stream.h \ folder-browser-factory.c \ folder-browser-factory.h \ - folder-browser-ui.c \ - folder-browser-ui.h \ folder-info.c \ folder-info.h \ mail-account-editor.c \ @@ -86,8 +118,6 @@ libevolution_mail_la_SOURCES = \ mail-accounts.h \ mail-autofilter.c \ mail-autofilter.h \ - mail-callbacks.c \ - mail-callbacks.h \ mail-composer-prefs.c \ mail-composer-prefs.h \ mail-config.c \ @@ -100,15 +130,8 @@ libevolution_mail_la_SOURCES = \ mail-config-factory.h \ mail-preferences.c \ mail-preferences.h \ - mail-display.c \ - mail-display.h \ - mail-display-stream.c \ - mail-display-stream.h \ mail-folder-cache.c \ mail-folder-cache.h \ - mail-format.h \ - mail-format.c \ - mail-identify.c \ mail-importer.c \ mail-importer.h \ mail-local.c \ @@ -119,8 +142,6 @@ libevolution_mail_la_SOURCES = \ mail-offline-handler.h \ mail-ops.c \ mail-ops.h \ - mail-search.c \ - mail-search.h \ mail-send-recv.c \ mail-send-recv.h \ mail-session.c \ @@ -132,22 +153,14 @@ libevolution_mail_la_SOURCES = \ mail-types.h \ mail-vfolder.c \ mail-vfolder.h \ - message-browser.c \ - message-browser.h \ message-list.c \ message-list.h \ message-tag-editor.c \ message-tag-editor.h \ message-tag-followup.c \ message-tag-followup.h \ - subscribe-dialog.c \ - subscribe-dialog.h \ mail.h -# needs gtkhtml prop manager ported -# mail-font-prefs.c \ -# mail-font-prefs.h - libevolution_mail_la_LIBADD = \ $(top_builddir)/shell/importer/libevolution-importer.la \ $(top_builddir)/camel/libcamel.la \ @@ -177,7 +190,10 @@ server_DATA = $(server_in_files:.server.in.in=.server) sed -e "s|\@COMPONENTDIR\@|$(componentdir)|" $< > $@ @INTLTOOL_SERVER_RULE@ -glade_DATA = mail-config.glade local-config.glade subscribe-dialog.glade message-tags.glade +MARSHAL_GENERATED = em-marshal.c em-marshal.h +@EVO_MARSHAL_RULE@ + +glade_DATA = mail-config.glade local-config.glade subscribe-dialog.glade message-tags.glade mail-search.glade etspec_DATA = mail-accounts.etspec message-list.etspec subscribe-dialog.etspec diff --git a/mail/component-factory.c b/mail/component-factory.c index 8789bcde8a..9fda34bec0 100644 --- a/mail/component-factory.c +++ b/mail/component-factory.c @@ -49,7 +49,6 @@ #include "folder-browser-factory.h" #include "evolution-shell-component.h" #include "evolution-shell-component-dnd.h" -#include "folder-browser.h" #include "folder-info.h" #include "mail.h" #include "mail-config.h" @@ -64,7 +63,7 @@ #include "mail-mt.h" #include "mail-importer.h" #include "mail-folder-cache.h" - +#include "em-utils.h" #include "component-factory.h" #include "mail-send-recv.h" @@ -417,17 +416,21 @@ static void configure_folder_popup(BonoboUIComponent *component, void *user_data, const char *cname) { char *uri = user_data; + + /* FIXME: re-implement */ if (strncmp(uri, "vfolder:", 8) == 0) vfolder_edit_rule(uri); +#if 0 else { - FolderBrowser *fb = folder_browser_factory_get_browser(uri); + struct _EMFolderBrowser *fb = folder_browser_factory_get_browser(uri); if (fb) configure_folder(component, fb, cname); else mail_local_reconfigure_folder(uri, NULL, NULL); } +#endif } static void @@ -801,7 +804,7 @@ owner_set_cb (EvolutionShellComponent *shell_component, { /* setup the global quick-search context */ char *user = g_strdup_printf ("%s/searches.xml", evolution_dir); - char *system = g_strdup (EVOLUTION_PRIVDATADIR "/vfoldertypes.xml"); + char *system = g_strdup (EVOLUTION_PRIVDATADIR "/searchtypes.xml"); search_context = rule_context_new (); g_object_set_data_full(G_OBJECT(search_context), "user", user, g_free); @@ -876,7 +879,7 @@ handle_external_uri_cb (EvolutionShellComponent *shell_component, /* FIXME: Sigh. This shouldn't be here. But the code is messy, so I'll just put it here anyway. */ - send_to_url (uri, NULL); + em_utils_compose_new_message_with_mailto(NULL, uri); } static void @@ -887,10 +890,11 @@ user_create_new_item_cb (EvolutionShellComponent *shell_component, gpointer data) { if (!strcmp (id, "message")) { - send_to_url (NULL, parent_folder_physical_uri); + em_utils_compose_new_message_with_mailto(NULL, NULL); + /*send_to_url (NULL, parent_folder_physical_uri);*/ return; } else if (!strcmp (id, "post")) { - post_to_url (parent_folder_physical_uri); + em_utils_post_to_url (NULL, parent_folder_physical_uri); return; } @@ -928,7 +932,7 @@ owner_unset_cb (EvolutionShellComponent *shell_component, gpointer user_data) g_signal_handler_disconnect((GtkObject *)shell_component, shell_component_handlers[i].hand); if (gconf_client_get_bool (gconf, "/apps/evolution/mail/trash/empty_on_exit", NULL)) - empty_trash (NULL, NULL, NULL); + em_utils_empty_trash(NULL); unref_standard_folders (); mail_local_storage_shutdown (); @@ -1617,7 +1621,7 @@ factory (BonoboGenericFactory *factory, || strcmp (component_id, MAIL_COMPOSER_PREFS_CONTROL_ID) == 0) return mail_config_control_factory_cb (factory, component_id, evolution_shell_client_corba_objref (global_shell_client)); else if (strcmp(component_id, COMPOSER_IID) == 0) - return (BonoboObject *)evolution_composer_new(composer_send_cb, composer_save_draft_cb); + return (BonoboObject *)evolution_composer_new(em_utils_composer_send_cb, em_utils_composer_save_draft_cb); g_warning (FACTORY_ID ": Don't know what to do with %s", component_id); return NULL; diff --git a/mail/em-camel-stream.c b/mail/em-camel-stream.c new file mode 100644 index 0000000000..a1274d634a --- /dev/null +++ b/mail/em-camel-stream.c @@ -0,0 +1,326 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * Michael Zucchi <notzed@ximian.com> + * + * Copyright 2001 Ximian, Inc. (www.ximian.com) + * + * 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. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdio.h> +#include <camel/camel-stream.h> +#include <camel/camel-object.h> +#include <gtkhtml/gtkhtml.h> +#include <gtkhtml/gtkhtml-stream.h> +#include <gtk/gtkmain.h> +#include "em-camel-stream.h" + +#include "mail-mt.h" + +#define EMCS_BUFFER_SIZE (4096) + +/*#define LOG_STREAM*/ + +#define d(x) + +enum _write_msg_t { + EMCS_WRITE, + EMCS_FLUSH, + EMCS_CLOSE_OK, + EMCS_CLOSE_ERROR, +}; + +struct _write_msg { + EMsg msg; + + enum _write_msg_t op; + + const char *data; + size_t n; +}; + +static void em_camel_stream_class_init (EMCamelStreamClass *klass); +static void em_camel_stream_init (CamelObject *object); +static void em_camel_stream_finalize (CamelObject *object); + +static ssize_t stream_write(CamelStream *stream, const char *buffer, size_t n); +static int stream_close(CamelStream *stream); +static int stream_flush(CamelStream *stream); + +static CamelStreamClass *parent_class = NULL; + +CamelType +em_camel_stream_get_type (void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + type = camel_type_register (CAMEL_STREAM_TYPE, + "EMCamelStream", + sizeof (EMCamelStream), + sizeof (EMCamelStreamClass), + (CamelObjectClassInitFunc) em_camel_stream_class_init, + NULL, + (CamelObjectInitFunc) em_camel_stream_init, + (CamelObjectFinalizeFunc) em_camel_stream_finalize); + } + + return type; +} + +static void +em_camel_stream_class_init (EMCamelStreamClass *klass) +{ + CamelStreamClass *stream_class = CAMEL_STREAM_CLASS (klass); + + parent_class = (CamelStreamClass *) CAMEL_STREAM_TYPE; + + /* virtual method overload */ + stream_class->write = stream_write; + stream_class->flush = stream_flush; + stream_class->close = stream_close; +} + +static gboolean +emcs_gui_received(GIOChannel *source, GIOCondition cond, void *data) +{ + EMCamelStream *estream = data; + struct _write_msg *msg; + + d(printf("%p: gui sync op job waiting\n", estream)); + + msg = (struct _write_msg *)e_msgport_get(estream->data_port); + /* Should never happen ... */ + if (msg == NULL) + return TRUE; + + d(printf("%p: running sync op %d\n", estream, msg->op)); + + /* force out any pending data before doing anything else */ + if (estream->used > 0) { + d(printf("sync write %d\n", estream->used)); + + if (estream->html_stream) + gtk_html_stream_write(estream->html_stream, estream->buffer, estream->used); + estream->used = 0; + } + + switch (msg->op) { + case EMCS_WRITE: + d(printf("sync write %d\n", msg->n)); + if (estream->html_stream) + gtk_html_stream_write(estream->html_stream, msg->data, msg->n); + break; + case EMCS_FLUSH: + stream_flush((CamelStream *)estream); + break; + case EMCS_CLOSE_OK: + if (estream->html_stream) { + gtk_html_stream_close(estream->html_stream, GTK_HTML_STREAM_OK); + estream->html_stream = NULL; + } + break; + case EMCS_CLOSE_ERROR: + if (estream->html_stream) { + gtk_html_stream_close(estream->html_stream, GTK_HTML_STREAM_ERROR); + estream->html_stream = NULL; + } + break; + } + + e_msgport_reply((EMsg *)msg); + d(printf("%p: gui sync op jobs done\n", estream)); + + return TRUE; +} + +static void +em_camel_stream_init (CamelObject *object) +{ + EMCamelStream *estream = (EMCamelStream *)object; + + estream->data_port = e_msgport_new(); + estream->reply_port = e_msgport_new(); + + estream->gui_channel = g_io_channel_unix_new(e_msgport_fd(estream->data_port)); + estream->gui_watch = g_io_add_watch(estream->gui_channel, G_IO_IN, emcs_gui_received, estream); + + estream->used = 0; + estream->buffer = g_malloc(EMCS_BUFFER_SIZE); + + d(printf("%p: new estream\n", estream)); +} + +static void +sync_op(EMCamelStream *estream, enum _write_msg_t op, const char *data, size_t n) +{ + struct _write_msg msg; + + d(printf("%p: launching sync op %d\n", estream, op)); + /* we do everything synchronous, we should never have any locks, and + this prevents overflow from banked up data */ + msg.msg.reply_port = estream->reply_port; + msg.op = op; + msg.data = data; + msg.n = n; + e_msgport_put(estream->data_port, &msg.msg); + e_msgport_wait(estream->reply_port); + g_assert(e_msgport_get(msg.msg.reply_port) == &msg.msg); + d(printf("%p: returned sync op %d\n", estream, op)); +} + +static void +em_camel_stream_finalize (CamelObject *object) +{ + EMCamelStream *estream = (EMCamelStream *)object; + + d(printf("%p: finalising stream\n", object)); + if (estream->html_stream) { + d(printf("%p: html stream still open - error\n", object)); + if (pthread_self() == mail_gui_thread) + gtk_html_stream_close(estream->html_stream, GTK_HTML_STREAM_ERROR); + else + sync_op(estream, EMCS_CLOSE_ERROR, NULL, 0); + } + + /* TODO: is this stuff safe to do in another thread? */ + g_source_remove(estream->gui_watch); + g_io_channel_unref(estream->gui_channel); + e_msgport_destroy(estream->data_port); + estream->data_port = NULL; + e_msgport_destroy(estream->reply_port); + estream->reply_port = NULL; + g_free(estream->buffer); +} + +static ssize_t +stream_write (CamelStream *stream, const char *buffer, size_t n) +{ + EMCamelStream *estream = EM_CAMEL_STREAM (stream); + + if (estream->html_stream == NULL) + return -1; + +#ifdef LOG_STREAM + if (estream->save) + fwrite(buffer, sizeof(char), n, estream->save); +#endif + + if (pthread_self() == mail_gui_thread) + gtk_html_stream_write(estream->html_stream, buffer, n); + else { +#if 1 + size_t left = EMCS_BUFFER_SIZE-estream->used; + + /* A super-simple buffer, if we get too much to fit, just do a sync + write, which will implicitly clear our previous writes first */ + d(printf("thread write '%d'\n", n)); + + if (n >= left) { + sync_op(estream, EMCS_WRITE, buffer, n); + } else { + memcpy(estream->buffer + estream->used, buffer, n); + estream->used += n; + } +#else + sync_op(estream, EMCS_WRITE, buffer, n); +#endif + } + return (ssize_t) n; +} + +static int +stream_flush(CamelStream *stream) +{ + EMCamelStream *estream = (EMCamelStream *)stream; + + if (estream->html_stream) { + if (pthread_self() == mail_gui_thread) { + /* FIXME: flush html stream via gtkhtml_stream_flush which doens't exist yet ... */ + while (gtk_events_pending ()) + gtk_main_iteration (); + } else { + sync_op(estream, EMCS_FLUSH, NULL, 0); + } + } + + return 0; +} + +static int +stream_close(CamelStream *stream) +{ + EMCamelStream *estream = (EMCamelStream *)stream; + + d(printf("%p: closing stream\n", stream)); + +#ifdef LOG_STREAM + if (estream->save) { + fclose(estream->save); + estream->save = NULL; + } +#endif + + if (estream->html_stream) { + if (pthread_self() == mail_gui_thread) { + gtk_html_stream_close(estream->html_stream, GTK_HTML_STREAM_OK); + estream->html_stream = NULL; + } else { + sync_op(estream, EMCS_CLOSE_OK, NULL, 0); + } + } + + return 0; +} + +static void +emcs_gtkhtml_destroy(struct _GtkHTML *html, EMCamelStream *emcs) +{ + d(printf("%p: emcs gtkhtml destroy\n", emcs)); + emcs->html = NULL; + emcs->html_stream = NULL; +} + +/* TODO: Could pass NULL for html_stream, and do a gtk_html_begin + on first data -> less flashing */ +CamelStream * +em_camel_stream_new(struct _GtkHTML *html, struct _GtkHTMLStream *html_stream) +{ + EMCamelStream *new; + + new = EM_CAMEL_STREAM (camel_object_new (EM_CAMEL_STREAM_TYPE)); + new->html_stream = html_stream; + g_signal_connect(html, "destroy", G_CALLBACK(emcs_gtkhtml_destroy), new); + +#ifdef LOG_STREAM + { + static int count; + char name[32]; + + sprintf(name, "camel-stream.%d.html", count++); + printf("saving raw html to '%s'\n", name); + new->save = fopen(name, "w"); + } +#endif + return CAMEL_STREAM (new); +} diff --git a/mail/em-camel-stream.h b/mail/em-camel-stream.h new file mode 100644 index 0000000000..955a15a5e2 --- /dev/null +++ b/mail/em-camel-stream.h @@ -0,0 +1,71 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2001 Ximian, Inc. (www.ximian.com) + * + * 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. + * + */ + +#ifndef EM_CAMEL_STREAM_H +#define EM_CAMEL_STREAM_H + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#define EM_CAMEL_STREAM_TYPE (em_camel_stream_get_type ()) +#define EM_CAMEL_STREAM(obj) (CAMEL_CHECK_CAST((obj), EM_CAMEL_STREAM_TYPE, EMCamelStream)) +#define EM_CAMEL_STREAM_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), EM_CAMEL_STREAM_TYPE, EMCamelStreamClass)) +#define MAIL_IS_DISPLAY_STREAM(o) (CAMEL_CHECK_TYPE((o), EM_CAMEL_STREAM_TYPE)) + +struct _GtkHTML; +struct _GtkHTMLStream; + +#include <camel/camel-stream.h> +#include "e-util/e-msgport.h" + +typedef struct _EMCamelStream { + CamelStream parent_stream; + + struct _GtkHTML *html; + struct _GtkHTMLStream *html_stream; + + struct _EMsgPort *data_port, *reply_port; + struct _GIOChannel *gui_channel; + guint gui_watch; + char *buffer; + int used; + void *save; +} EMCamelStream; + +typedef struct { + CamelStreamClass parent_class; + +} EMCamelStreamClass; + + +CamelType em_camel_stream_get_type (void); + +/* the html_stream is closed when we are finalised (with an error), or closed (ok) */ +CamelStream *em_camel_stream_new(struct _GtkHTML *html, struct _GtkHTMLStream *html_stream); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* EM_CAMEL_STREAM_H */ diff --git a/mail/em-composer-utils.c b/mail/em-composer-utils.c new file mode 100644 index 0000000000..e181c2cb33 --- /dev/null +++ b/mail/em-composer-utils.c @@ -0,0 +1,623 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "mail-mt.h" +#include "mail-ops.h" +#include "mail-tools.h" +#include "mail-config.h" +#include "mail-session.h" +#include "mail-send-recv.h" + +#include <e-util/e-dialog-utils.h> /* e_notice */ + +#include "em-utils.h" +#include "em-composer-utils.h" + +struct emcs_t { + unsigned int ref_count; + + CamelFolder *drafts_folder; + char *drafts_uid; + + CamelFolder *folder; + guint32 flags, set; + char *uid; +}; + +static struct emcs_t * +emcs_new (void) +{ + struct emcs_t *emcs; + + emcs = g_new (struct emcs_t, 1); + emcs->ref_count = 1; + emcs->drafts_folder = NULL; + emcs->drafts_uid = NULL; + emcs->folder = NULL; + emcs->flags = 0; + emcs->set = 0; + emcs->uid = NULL; + + return emcs; +} + +static void +free_emcs (struct emcs_t *emcs) +{ + if (emcs->drafts_folder) + camel_object_unref (emcs->drafts_folder); + g_free (emcs->drafts_uid); + + if (emcs->folder) + camel_object_unref (emcs->folder); + g_free (emcs->uid); + g_free (emcs); +} + +static void +emcs_ref (struct emcs_t *emcs) +{ + emcs->ref_count++; +} + +static void +emcs_unref (struct emcs_t *emcs) +{ + emcs->ref_count--; + if (emcs->ref_count == 0) + free_emcs (emcs); +} + +static void +composer_destroy_cb (gpointer user_data, GObject *deadbeef) +{ + emcs_unref (user_data); +} + +static gboolean +ask_confirm_for_unwanted_html_mail (EMsgComposer *composer, EDestination **recipients) +{ + gboolean show_again, res; + GConfClient *gconf; + GString *str; + int i; + + gconf = mail_config_get_gconf_client (); + + if (!gconf_client_get_bool (gconf, "/apps/evolution/mail/prompts/unwanted_html", NULL)) + return TRUE; + + /* FIXME: this wording sucks */ + str = g_string_new (_("You are sending an HTML-formatted message. Please make sure that\n" + "the following recipients are willing and able to receive HTML mail:\n")); + for (i = 0; recipients[i] != NULL; ++i) { + if (!e_destination_get_html_mail_pref (recipients[i])) { + const char *name; + + name = e_destination_get_textrep (recipients[i], FALSE); + + g_string_append_printf (str, " %s\n", name); + } + } + + g_string_append (str, _("Send anyway?")); + res = em_utils_prompt_user ((GtkWindow *) composer, GTK_RESPONSE_YES, &show_again, "%s", str->str); + g_string_free (str, TRUE); + + gconf_client_set_bool (gconf, "/apps/evolution/mail/prompts/unwanted_html", show_again, NULL); + + return res; +} + +static gboolean +ask_confirm_for_empty_subject (EMsgComposer *composer) +{ + gboolean show_again, res; + GConfClient *gconf; + + gconf = mail_config_get_gconf_client (); + + if (!gconf_client_get_bool (gconf, "/apps/evolution/mail/prompts/empty_subject", NULL)) + return TRUE; + + res = em_utils_prompt_user ((GtkWindow *) composer, GTK_RESPONSE_YES, &show_again, + _("This message has no subject.\nReally send?")); + + gconf_client_set_bool (gconf, "/apps/evolution/mail/prompts/empty_subject", show_again, NULL); + + return res; +} + +static gboolean +ask_confirm_for_only_bcc (EMsgComposer *composer, gboolean hidden_list_case) +{ + gboolean show_again, res; + const char *first_text; + GConfClient *gconf; + + gconf = mail_config_get_gconf_client (); + + if (!gconf_client_get_bool (gconf, "/apps/evolution/mail/prompts/only_bcc", NULL)) + return TRUE; + + /* If the user is mailing a hidden contact list, it is possible for + them to create a message with only Bcc recipients without really + realizing it. To try to avoid being totally confusing, I've changed + this dialog to provide slightly different text in that case, to + better explain what the hell is going on. */ + + if (hidden_list_case) { + first_text = _("Since the contact list you are sending to " + "is configured to hide the list's addresses, " + "this message will contain only Bcc recipients."); + } else { + first_text = _("This message contains only Bcc recipients."); + } + + res = em_utils_prompt_user ((GtkWindow *) composer, GTK_RESPONSE_YES, &show_again, + "%s\n%s", first_text, + _("It is possible that the mail server may reveal the recipients " + "by adding an Apparently-To header.\nSend anyway?")); + + gconf_client_set_bool (gconf, "/apps/evolution/mail/prompts/only_bcc", show_again, NULL); + + return res; +} + + +struct _send_data { + struct emcs_t *emcs; + EMsgComposer *composer; + gboolean send; +}; + +static void +composer_send_queued_cb (CamelFolder *folder, CamelMimeMessage *msg, CamelMessageInfo *info, + int queued, const char *appended_uid, void *data) +{ + struct emcs_t *emcs; + struct _send_data *send = data; + + emcs = send->emcs; + + if (queued) { + if (emcs && emcs->drafts_folder) { + /* delete the old draft message */ + camel_folder_set_message_flags (emcs->drafts_folder, emcs->drafts_uid, + CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, + CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN); + camel_object_unref (emcs->drafts_folder); + emcs->drafts_folder = NULL; + g_free (emcs->drafts_uid); + emcs->drafts_uid = NULL; + } + + if (emcs && emcs->folder) { + /* set any replied flags etc */ + camel_folder_set_message_flags (emcs->folder, emcs->uid, emcs->flags, emcs->set); + camel_object_unref (emcs->folder); + emcs->folder = NULL; + g_free (emcs->uid); + emcs->uid = NULL; + } + + gtk_widget_destroy (GTK_WIDGET (send->composer)); + + if (send->send && camel_session_is_online (session)) { + /* queue a message send */ + mail_send (); + } + } else { + if (!emcs) { + /* disconnect the previous signal handlers */ + g_signal_handlers_disconnect_matched (send->composer, G_SIGNAL_MATCH_FUNC, 0, + 0, NULL, em_utils_composer_send_cb, NULL); + g_signal_handlers_disconnect_matched (send->composer, G_SIGNAL_MATCH_FUNC, 0, + 0, NULL, em_utils_composer_save_draft_cb, NULL); + + /* reconnect to the signals using a non-NULL emcs for the callback data */ + em_composer_utils_setup_default_callbacks (send->composer); + } + + e_msg_composer_set_enable_autosave (send->composer, TRUE); + gtk_widget_show (GTK_WIDGET (send->composer)); + } + + camel_message_info_free (info); + + if (send->emcs) + emcs_unref (send->emcs); + + g_object_unref (send->composer); + g_free (send); +} + +static CamelMimeMessage * +composer_get_message (EMsgComposer *composer, gboolean post, gboolean save_html_object_data) +{ + CamelMimeMessage *message = NULL; + EDestination **recipients, **recipients_bcc; + gboolean send_html, confirm_html; + CamelInternetAddress *cia; + int hidden = 0, shown = 0; + int num = 0, num_bcc = 0; + const char *subject; + GConfClient *gconf; + EAccount *account; + int i; + + gconf = mail_config_get_gconf_client (); + + /* We should do all of the validity checks based on the composer, and not on + the created message, as extra interaction may occur when we get the message + (e.g. to get a passphrase to sign a message) */ + + /* get the message recipients */ + recipients = e_msg_composer_get_recipients (composer); + + cia = camel_internet_address_new (); + + /* see which ones are visible/present, etc */ + if (recipients) { + for (i = 0; recipients[i] != NULL; i++) { + const char *addr = e_destination_get_address (recipients[i]); + + if (addr && addr[0]) { + camel_address_decode ((CamelAddress *) cia, addr); + if (camel_address_length ((CamelAddress *) cia) > 0) { + camel_address_remove ((CamelAddress *) cia, -1); + num++; + if (e_destination_is_evolution_list (recipients[i]) + && !e_destination_list_show_addresses (recipients[i])) { + hidden++; + } else { + shown++; + } + } + } + } + } + + recipients_bcc = e_msg_composer_get_bcc (composer); + if (recipients_bcc) { + for (i = 0; recipients_bcc[i] != NULL; i++) { + const char *addr = e_destination_get_address (recipients_bcc[i]); + + if (addr && addr[0]) { + camel_address_decode ((CamelAddress *) cia, addr); + if (camel_address_length ((CamelAddress *) cia) > 0) { + camel_address_remove ((CamelAddress *) cia, -1); + num_bcc++; + } + } + } + + e_destination_freev (recipients_bcc); + } + + camel_object_unref (cia); + + /* I'm sensing a lack of love, er, I mean recipients. */ + if (num == 0 && !post) { + e_notice ((GtkWindow *) composer, GTK_MESSAGE_WARNING, + _("You must specify recipients in order to send this message.")); + goto finished; + } + + if (num > 0 && (num == num_bcc || shown == 0)) { + /* this means that the only recipients are Bcc's */ + if (!ask_confirm_for_only_bcc (composer, shown == 0)) + goto finished; + } + + send_html = gconf_client_get_bool (gconf, "/apps/evolution/mail/composer/send_html", NULL); + confirm_html = gconf_client_get_bool (gconf, "/apps/evolution/mail/prompts/unwanted_html", NULL); + + /* Only show this warning if our default is to send html. If it isn't, we've + manually switched into html mode in the composer and (presumably) had a good + reason for doing this. */ + if (e_msg_composer_get_send_html (composer) && send_html && confirm_html) { + gboolean html_problem = FALSE; + + if (recipients) { + for (i = 0; recipients[i] != NULL && !html_problem; i++) { + if (!e_destination_get_html_mail_pref (recipients[i])) + html_problem = TRUE; + } + } + + if (html_problem) { + html_problem = !ask_confirm_for_unwanted_html_mail (composer, recipients); + if (html_problem) + goto finished; + } + } + + /* Check for no subject */ + subject = e_msg_composer_get_subject (composer); + if (subject == NULL || subject[0] == '\0') { + if (!ask_confirm_for_empty_subject (composer)) + goto finished; + } + + /* actually get the message now, this will sign/encrypt etc */ + message = e_msg_composer_get_message (composer, save_html_object_data); + if (message == NULL) + goto finished; + + /* Add info about the sending account */ + account = e_msg_composer_get_preferred_account (composer); + + if (account) { + camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Account", account->name); + camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Transport", account->transport->url); + camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Fcc", account->sent_folder_uri); + if (account->id->organization && *account->id->organization) + camel_medium_set_header (CAMEL_MEDIUM (message), "Organization", account->id->organization); + } + + /* Get the message recipients and 'touch' them, boosting their use scores */ + if (recipients) + e_destination_touchv (recipients); + + finished: + + if (recipients) + e_destination_freev (recipients); + + return message; +} + +static void +got_post_folder (char *uri, CamelFolder *folder, void *data) +{ + CamelFolder **fp = data; + + *fp = folder; + + if (folder) + camel_object_ref (folder); +} + +void +em_utils_composer_send_cb (EMsgComposer *composer, gpointer user_data) +{ + extern CamelFolder *outbox_folder; + CamelMimeMessage *message; + CamelMessageInfo *info; + struct _send_data *send; + gboolean post = FALSE; + CamelFolder *folder; + XEvolution *xev; + char *url; + + url = e_msg_composer_hdrs_get_post_to ((EMsgComposerHdrs *) composer->hdrs); + if (url && *url) { + post = TRUE; + + mail_msg_wait (mail_get_folder (url, 0, got_post_folder, &folder, mail_thread_new)); + + if (!folder) { + g_free (url); + return; + } + } else { + folder = outbox_folder; + camel_object_ref (folder); + } + + g_free (url); + + message = composer_get_message (composer, post, FALSE); + if (!message) + return; + + if (post) { + /* Remove the X-Evolution* headers if we are in Post-To mode */ + xev = mail_tool_remove_xevolution_headers (message); + mail_tool_destroy_xevolution (xev); + } + + info = camel_message_info_new (); + info->flags = CAMEL_MESSAGE_SEEN; + + send = g_malloc (sizeof (*send)); + send->emcs = user_data; + if (send->emcs) + emcs_ref (send->emcs); + send->send = !post; + send->composer = composer; + g_object_ref (composer); + gtk_widget_hide (GTK_WIDGET (composer)); + + e_msg_composer_set_enable_autosave (composer, FALSE); + + mail_append_mail (folder, message, info, composer_send_queued_cb, send); + camel_object_unref (message); + camel_object_unref (folder); +} + +struct _save_draft_info { + struct emcs_t *emcs; + EMsgComposer *composer; + int quit; +}; + +static void +save_draft_done (CamelFolder *folder, CamelMimeMessage *msg, CamelMessageInfo *info, int ok, + const char *appended_uid, void *user_data) +{ + struct _save_draft_info *sdi = user_data; + struct emcs_t *emcs; + CORBA_Environment ev; + + if (!ok) + goto done; + CORBA_exception_init (&ev); + GNOME_GtkHTML_Editor_Engine_runCommand (sdi->composer->editor_engine, "saved", &ev); + CORBA_exception_free (&ev); + + if ((emcs = sdi->emcs) == NULL) { + emcs = emcs_new (); + + /* disconnect the previous signal handlers */ + g_signal_handlers_disconnect_by_func (sdi->composer, G_CALLBACK (em_utils_composer_send_cb), NULL); + g_signal_handlers_disconnect_by_func (sdi->composer, G_CALLBACK (em_utils_composer_save_draft_cb), NULL); + + /* reconnect to the signals using a non-NULL emcs for the callback data */ + em_composer_utils_setup_default_callbacks (sdi->composer); + } + + if (emcs->drafts_folder) { + /* delete the original draft message */ + camel_folder_set_message_flags (emcs->drafts_folder, emcs->drafts_uid, + CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, + CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN); + camel_object_unref (emcs->drafts_folder); + emcs->drafts_folder = NULL; + g_free (emcs->drafts_uid); + emcs->drafts_uid = NULL; + } + + if (emcs->folder) { + /* set the replied flags etc */ + camel_folder_set_message_flags (emcs->folder, emcs->uid, emcs->flags, emcs->set); + camel_object_unref (emcs->folder); + emcs->folder = NULL; + g_free (emcs->uid); + emcs->uid = NULL; + } + + if (appended_uid) { + camel_object_ref (folder); + emcs->drafts_folder = folder; + emcs->drafts_uid = g_strdup (appended_uid); + } + + if (sdi->quit) + gtk_widget_destroy (GTK_WIDGET (sdi->composer)); + + done: + g_object_unref (sdi->composer); + if (sdi->emcs) + emcs_unref (sdi->emcs); + g_free (info); + g_free (sdi); +} + +static void +save_draft_folder (char *uri, CamelFolder *folder, gpointer data) +{ + CamelFolder **save = data; + + if (folder) { + *save = folder; + camel_object_ref (folder); + } +} + +void +em_utils_composer_save_draft_cb (EMsgComposer *composer, int quit, gpointer user_data) +{ + extern char *default_drafts_folder_uri; + extern CamelFolder *drafts_folder; + struct _save_draft_info *sdi; + CamelFolder *folder = NULL; + CamelMimeMessage *msg; + CamelMessageInfo *info; + EAccount *account; + + account = e_msg_composer_get_preferred_account (composer); + if (account && account->drafts_folder_uri && + strcmp (account->drafts_folder_uri, default_drafts_folder_uri) != 0) { + int id; + + id = mail_get_folder (account->drafts_folder_uri, 0, save_draft_folder, &folder, mail_thread_new); + mail_msg_wait (id); + + if (!folder) { + if (!em_utils_prompt_user ((GtkWindow *) composer, GTK_RESPONSE_YES, NULL, + _("Unable to open the drafts folder for this account.\n" + "Would you like to use the default drafts folder?"))) + return; + + folder = drafts_folder; + camel_object_ref (drafts_folder); + } + } else { + folder = drafts_folder; + camel_object_ref (folder); + } + + msg = e_msg_composer_get_message_draft (composer); + + info = g_new0 (CamelMessageInfo, 1); + info->flags = CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN; + + sdi = g_malloc (sizeof (struct _save_draft_info)); + sdi->composer = composer; + g_object_ref (composer); + sdi->emcs = user_data; + if (sdi->emcs) + emcs_ref (sdi->emcs); + sdi->quit = quit; + + mail_append_mail (folder, msg, info, save_draft_done, sdi); + camel_object_unref (folder); + camel_object_unref (msg); +} + + +void +em_composer_utils_setup_callbacks (EMsgComposer *composer, CamelFolder *folder, const char *uid, + guint32 flags, guint32 set, CamelFolder *drafts, const char *drafts_uid) +{ + struct emcs_t *emcs; + + emcs = emcs_new (); + + if (folder && uid) { + camel_object_ref (folder); + emcs->folder = folder; + emcs->uid = g_strdup (uid); + emcs->flags = flags; + emcs->set = set; + } + + if (drafts && drafts_uid) { + camel_object_ref (drafts); + emcs->drafts_folder = drafts; + emcs->drafts_uid = g_strdup (drafts_uid); + } + + g_signal_connect (composer, "send", G_CALLBACK (em_utils_composer_send_cb), emcs); + g_signal_connect (composer, "save-draft", G_CALLBACK (em_utils_composer_save_draft_cb), emcs); + + g_object_weak_ref ((GObject *) composer, (GWeakNotify) composer_destroy_cb, emcs); +} diff --git a/mail/em-composer-utils.h b/mail/em-composer-utils.h new file mode 100644 index 0000000000..7e4aeb0274 --- /dev/null +++ b/mail/em-composer-utils.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifndef __EM_COMPOSER_UTILS_H__ +#define __EM_COMPOSER_UTILS_H__ + +#include <composer/e-msg-composer.h> + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +void em_composer_utils_setup_callbacks (EMsgComposer *composer, CamelFolder *folder, const char *uid, + guint32 flags, guint32 set, CamelFolder *drafts, const char *drafts_uid); + +#define em_composer_utils_setup_default_callbacks(composer) em_composer_utils_setup_callbacks (composer, NULL, NULL, 0, 0, NULL, NULL) + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __EM_COMPOSER_UTILS_H__ */ diff --git a/mail/em-folder-browser.c b/mail/em-folder-browser.c new file mode 100644 index 0000000000..7341f91543 --- /dev/null +++ b/mail/em-folder-browser.c @@ -0,0 +1,933 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <gtk/gtkvbox.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkbutton.h> +#include <gtk/gtkvpaned.h> +#include <gtkhtml/gtkhtml.h> +#include <gdk/gdkkeysyms.h> + +#include <libgnomeprintui/gnome-print-dialog.h> + +#include "mail-mt.h" +#include "mail-ops.h" +#include "mail-tools.h" +#include "mail-config.h" + +#include <e-util/e-passwords.h> +#include <e-util/e-dialog-utils.h> + +#include <camel/camel-mime-message.h> +#include <camel/camel-stream.h> +#include <camel/camel-stream-filter.h> +#include <camel/camel-mime-filter.h> +#include <camel/camel-mime-filter-tohtml.h> +#include <camel/camel-mime-filter-enriched.h> +#include <camel/camel-multipart.h> +#include <camel/camel-stream-mem.h> +#include <camel/camel-url.h> + +#include <bonobo/bonobo-main.h> +#include <bonobo/bonobo-object.h> +#include <bonobo/bonobo-generic-factory.h> +#include <bonobo/bonobo-control.h> +#include <bonobo/bonobo-ui-component.h> +#include <bonobo/bonobo-ui-util.h> + +/* for efilterbar stuff */ +#include <e-util/e-sexp.h> +#include "mail-vfolder.h" +#include "filter/vfolder-rule.h" +#include <widgets/misc/e-filter-bar.h> +#include <camel/camel-search-private.h> + +/* gal view crap */ +#include <gal/menus/gal-view-etable.h> +#include <gal/menus/gal-view-instance.h> +#include <gal/menus/gal-view-factory-etable.h> +#include "widgets/menus/gal-view-menus.h" + +#include "e-util/e-dialog-utils.h" +#include "em-utils.h" +#include "em-format-html-display.h" +#include "em-format-html-print.h" +#include "em-folder-browser.h" +#include "em-subscribe-editor.h" +#include "message-list.h" + +#include "mail-ops.h" + +#include "evolution-shell-component-utils.h" /* Pixmap stuff, sigh */ + +#define d(x) + +struct _EMFolderBrowserPrivate { + GtkWidget *preview; /* container for message display */ + + GtkWidget *subscribe_editor; + + GalViewInstance *view_instance; + GalViewMenus *view_menus; + + guint vpane_resize_id; + guint list_built_id; /* hook onto list-built for delayed 'select first unread' stuff */ +}; + +static void emfb_activate(EMFolderView *emfv, BonoboUIComponent *uic, int state); +static void emfb_set_folder(EMFolderView *emfv, CamelFolder *folder, const char *uri); + +/* FilterBar stuff ... */ +static void emfb_search_config_search(EFilterBar *efb, FilterRule *rule, int id, const char *query, void *data); +static void emfb_search_menu_activated(ESearchBar *esb, int id, EMFolderBrowser *fb); +static void emfb_search_search_activated(ESearchBar *esb, EMFolderBrowser *emfb); +static void emfb_search_query_changed(ESearchBar *esb, EMFolderBrowser *fb); + +static int emfb_list_key_press(ETree *tree, int row, ETreePath path, int col, GdkEvent *ev, EMFolderBrowser *fb); + +static const EMFolderViewEnable emfb_enable_map[]; + +enum { + ESB_SAVE, +}; + +static ESearchBarItem emfb_search_items[] = { + E_FILTERBAR_ADVANCED, + { NULL, 0, NULL }, + E_FILTERBAR_SAVE, + E_FILTERBAR_EDIT, + { NULL, 0, NULL }, + { N_("Create _Virtual Folder From Search..."), ESB_SAVE, NULL }, + { NULL, -1, NULL } +}; + +static EMFolderViewClass *emfb_parent; + +/* Needed since the paned wont take the position its given otherwise ... */ +static void +emfb_pane_realised(GtkWidget *w, EMFolderBrowser *emfb) +{ + GConfClient *gconf; + + gconf = mail_config_get_gconf_client (); + gtk_paned_set_position((GtkPaned *)emfb->vpane, gconf_client_get_int(gconf, "/apps/evolution/mail/display/paned_size", NULL)); +} + +static gboolean +emfb_pane_button_release_event(GtkWidget *w, GdkEventButton *e, EMFolderBrowser *emfb) +{ + GConfClient *gconf = mail_config_get_gconf_client (); + + if (GTK_WIDGET_REALIZED (w)) + gconf_client_set_int(gconf, "/apps/evolution/mail/display/paned_size", + gtk_paned_get_position(GTK_PANED(w)), NULL); + + return FALSE; +} + +static void +emfb_init(GObject *o) +{ + EMFolderBrowser *emfb = (EMFolderBrowser *)o; + struct _EMFolderBrowserPrivate *p; + /* FIXME ... */ + extern RuleContext *search_context; + + p = emfb->priv = g_malloc0(sizeof(struct _EMFolderBrowserPrivate)); + + emfb->view.preview_active = TRUE; + + g_slist_free(emfb->view.ui_files); + emfb->view.ui_files = g_slist_append(NULL, EVOLUTION_UIDIR "/evolution-mail-global.xml"); + emfb->view.ui_files = g_slist_append(emfb->view.ui_files, EVOLUTION_UIDIR "/evolution-mail-list.xml"); + emfb->view.ui_files = g_slist_append(emfb->view.ui_files, EVOLUTION_UIDIR "/evolution-mail-message.xml"); + + emfb->view.enable_map = g_slist_prepend(emfb->view.enable_map, (void *)emfb_enable_map); + + if (search_context) { + const char *systemrules = g_object_get_data (G_OBJECT (search_context), "system"); + const char *userrules = g_object_get_data (G_OBJECT (search_context), "user"); + + emfb->search = e_filter_bar_new(search_context, systemrules, userrules, emfb_search_config_search, emfb); + e_search_bar_set_menu ((ESearchBar *)emfb->search, emfb_search_items); + gtk_widget_show((GtkWidget *)emfb->search); + + g_signal_connect(emfb->search, "menu_activated", G_CALLBACK(emfb_search_menu_activated), emfb); + g_signal_connect(emfb->search, "search_activated", G_CALLBACK(emfb_search_search_activated), emfb); + g_signal_connect(emfb->search, "query_changed", G_CALLBACK(emfb_search_query_changed), emfb); + + gtk_box_pack_start((GtkBox *)emfb, (GtkWidget *)emfb->search, FALSE, TRUE, 0); + } + + emfb->vpane = gtk_vpaned_new(); + g_signal_connect(emfb->vpane, "realize", G_CALLBACK(emfb_pane_realised), emfb); + emfb->priv->vpane_resize_id = g_signal_connect(emfb->vpane, "button_release_event", G_CALLBACK(emfb_pane_button_release_event), emfb); + + gtk_widget_show(emfb->vpane); + + gtk_box_pack_start_defaults((GtkBox *)emfb, emfb->vpane); + + gtk_paned_add1((GtkPaned *)emfb->vpane, (GtkWidget *)emfb->view.list); + gtk_widget_show((GtkWidget *)emfb->view.list); + + /* currently: just use a scrolledwindow for preview widget */ + p->preview = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy((GtkScrolledWindow *)p->preview, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type((GtkScrolledWindow *)p->preview, GTK_SHADOW_IN); + gtk_widget_show(p->preview); + + gtk_container_add((GtkContainer *)p->preview, (GtkWidget *)emfb->view.preview->formathtml.html); + gtk_widget_show((GtkWidget *)emfb->view.preview->formathtml.html); + + gtk_paned_add2((GtkPaned *)emfb->vpane, p->preview); + gtk_widget_show(p->preview); + + g_signal_connect(emfb->view.list->tree, "key_press", G_CALLBACK(emfb_list_key_press), emfb); +} + +static void +emfb_finalise(GObject *o) +{ + EMFolderBrowser *emfb = (EMFolderBrowser *)o; + + g_free(emfb->priv); + + ((GObjectClass *)emfb_parent)->finalize(o); +} + +static void +emfb_destroy(GtkObject *o) +{ + EMFolderBrowser *emfb = (EMFolderBrowser *)o; + + if (emfb->priv->list_built_id) { + g_signal_handler_disconnect(((EMFolderView *)emfb)->list, emfb->priv->list_built_id); + emfb->priv->list_built_id = 0; + } + + ((GtkObjectClass *)emfb_parent)->destroy(o); +} + +static void +emfb_class_init(GObjectClass *klass) +{ + klass->finalize = emfb_finalise; + ((GtkObjectClass *)klass)->destroy = emfb_destroy; + ((EMFolderViewClass *)klass)->set_folder = emfb_set_folder; + ((EMFolderViewClass *)klass)->activate = emfb_activate; +} + +GType +em_folder_browser_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMFolderBrowserClass), + NULL, NULL, + (GClassInitFunc)emfb_class_init, + NULL, NULL, + sizeof(EMFolderBrowser), 0, + (GInstanceInitFunc)emfb_init + }; + emfb_parent = g_type_class_ref(em_folder_view_get_type()); + type = g_type_register_static(em_folder_view_get_type(), "EMFolderBrowser", &info, 0); + } + + return type; +} + +GtkWidget *em_folder_browser_new(void) +{ + EMFolderBrowser *emfb = g_object_new(em_folder_browser_get_type(), 0); + + return (GtkWidget *)emfb; +} + +void em_folder_browser_show_preview(EMFolderBrowser *emfb, gboolean state) +{ + if ((emfb->view.preview_active ^ state) == 0 + || emfb->view.list == NULL) + return; + + emfb->view.preview_active = state; + + if (state) { + GConfClient *gconf = mail_config_get_gconf_client (); + int paned_size /*, y*/; + + paned_size = gconf_client_get_int(gconf, "/apps/evolution/mail/display/paned_size", NULL); + + /*y = save_cursor_pos (emfb);*/ + gtk_paned_set_position (GTK_PANED (emfb->vpane), paned_size); + gtk_widget_show (GTK_WIDGET (emfb->priv->preview)); + + if (emfb->view.list->cursor_uid) + message_list_select_uid(emfb->view.list, emfb->view.list->cursor_uid); + + /* need to load/show the current message? */ + /*do_message_selected (emfb);*/ + /*set_cursor_pos (emfb, y);*/ + } else { + em_format_format((EMFormat *)emfb->view.preview, NULL); + gtk_widget_hide(emfb->priv->preview); + /* + mail_display_set_message (emfb->mail_display, NULL, NULL, NULL); + emfb_ui_message_loaded (emfb);*/ + } + + /* FIXME: need to update menu's to reflect ui changes */ +} + +/* ********************************************************************** */ + +/* FIXME: Need to separate system rules from user ones */ +/* FIXME: Ugh! */ + +static void +emfb_search_menu_activated(ESearchBar *esb, int id, EMFolderBrowser *emfb) +{ + EFilterBar *efb = (EFilterBar *)esb; + + d(printf("menu activated\n")); + + switch (id) { + case ESB_SAVE: + d(printf("Save vfolder\n")); + if (efb->current_query) { + FilterRule *rule = vfolder_clone_rule(efb->current_query); + char *name, *text; + + text = e_search_bar_get_text(esb); + name = g_strdup_printf("%s %s", rule->name, (text&&text[0])?text:"''"); + g_free (text); + filter_rule_set_name(rule, name); + g_free (name); + + filter_rule_set_source(rule, FILTER_SOURCE_INCOMING); + vfolder_rule_add_source((VfolderRule *)rule, emfb->view.folder_uri); + vfolder_gui_add_rule((VfolderRule *)rule); + } + break; + } +} + +static void +emfb_search_config_search(EFilterBar *efb, FilterRule *rule, int id, const char *query, void *data) +{ + EMFolderBrowser *emfb = data; + GList *partl; + struct _camel_search_words *words; + int i; + GSList *strings = NULL; + + /* we scan the parts of a rule, and set all the types we know about to the query string */ + partl = rule->parts; + while (partl) { + FilterPart *part = partl->data; + + if (!strcmp(part->name, "subject")) { + FilterInput *input = (FilterInput *)filter_part_find_element(part, "subject"); + if (input) + filter_input_set_value(input, query); + } else if (!strcmp(part->name, "body")) { + FilterInput *input = (FilterInput *)filter_part_find_element(part, "word"); + if (input) + filter_input_set_value(input, query); + + words = camel_search_words_split(query); + for (i=0;i<words->len;i++) + strings = g_slist_prepend(strings, g_strdup(words->words[i]->word)); + camel_search_words_free (words); + } else if(!strcmp(part->name, "sender")) { + FilterInput *input = (FilterInput *)filter_part_find_element(part, "sender"); + if (input) + filter_input_set_value(input, query); + } else if(!strcmp(part->name, "to")) { + FilterInput *input = (FilterInput *)filter_part_find_element(part, "recipient"); + if (input) + filter_input_set_value(input, query); + } + + partl = partl->next; + } + + em_format_html_display_set_search(emfb->view.preview, + EM_FORMAT_HTML_DISPLAY_SEARCH_SECONDARY|EM_FORMAT_HTML_DISPLAY_SEARCH_ICASE, + strings); + while (strings) { + GSList *n = strings->next; + + g_free(strings->data); + g_slist_free_1(strings); + strings = n; + } +} + +static void +emfb_search_search_activated(ESearchBar *esb, EMFolderBrowser *emfb) +{ + char *search_word; + + if (emfb->view.list == NULL) + return; + + g_object_get (esb, "query", &search_word, NULL); + message_list_set_search(emfb->view.list, search_word); + g_free(search_word); +} + +static void +emfb_search_query_changed(ESearchBar *esb, EMFolderBrowser *emfb) +{ + int id; + + id = e_search_bar_get_item_id(esb); + if (id == E_FILTERBAR_ADVANCED_ID) + emfb_search_search_activated(esb, emfb); +} + +/* ********************************************************************** */ + +static int +emfb_list_key_press(ETree *tree, int row, ETreePath path, int col, GdkEvent *ev, EMFolderBrowser *emfb) +{ + if ((ev->key.state & GDK_CONTROL_MASK) != 0) + return FALSE; + + switch (ev->key.keyval) { + case GDK_space: + em_utils_adjustment_page(gtk_scrolled_window_get_vadjustment((GtkScrolledWindow *)emfb->priv->preview), TRUE); + break; + case GDK_BackSpace: + em_utils_adjustment_page(gtk_scrolled_window_get_vadjustment((GtkScrolledWindow *)emfb->priv->preview), FALSE); + break; + default: + return FALSE; + } + + return TRUE; +} + +/* ********************************************************************** */ + +static void +emfb_edit_invert_selection(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_invert_selection(emfv->list); +} + +static void +emfb_edit_select_all(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_select_all(emfv->list); +} + +static void +emfb_edit_select_thread(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_select_thread(emfv->list); +} + +static void +emfb_folder_properties(BonoboUIComponent *uid, void *data, const char *path) +{ + /* If only we could remove this ... */ + /* Should it be part of the factory? */ + printf("FIXME: folderproperties\n"); +} + +static void +emfb_folder_expunge(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderBrowser *emfb = data; + + em_utils_expunge_folder ((GtkWidget *) emfb, emfb->view.folder); +} + +static void +emfb_mark_all_read(BonoboUIComponent *uid, void *data, const char *path) +{ + /* FIXME: make a 'mark messages' function? */ + EMFolderView *emfv = data; + GPtrArray *uids; + int i; + + uids = camel_folder_get_uids(emfv->folder); + camel_folder_freeze(emfv->folder); + for (i=0;i<uids->len;i++) + camel_folder_set_message_flags(emfv->folder, uids->pdata[i], CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN); + camel_folder_thaw(emfv->folder); + camel_folder_free_uids(emfv->folder, uids); +} + +static void +emfb_view_hide_read(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_hide_add(emfv->list, "(match-all (system-flag \"seen\"))", ML_HIDE_SAME, ML_HIDE_SAME); +} + +static void +emfb_view_hide_selected(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + GPtrArray *uids; + + /* TODO: perhaps this should sit directly on message_list? */ + /* is it worth it, it's so trivial */ + uids = message_list_get_selected(emfv->list); + message_list_hide_uids(emfv->list, uids); + message_list_free_uids(emfv->list, uids); +} + +static void +emfb_view_show_all(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_hide_clear(emfv->list); +} + +/* ********************************************************************** */ + +static void +emfb_empty_trash(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + em_utils_empty_trash ((GtkWidget *) emfv); +} + +static void +emfb_forget_passwords(BonoboUIComponent *uid, void *data, const char *path) +{ + e_passwords_forget_passwords(); +} + +static void +emfb_mail_compose(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderBrowser *emfb = data; + + em_utils_compose_new_message ((GtkWidget *) emfb); +} + +static void +emfb_mail_stop(BonoboUIComponent *uid, void *data, const char *path) +{ + camel_operation_cancel(NULL); +} + +static void +emfb_mail_post(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + char *url; + + url = mail_tools_folder_to_url (emfv->folder); + em_utils_post_to_url ((GtkWidget *) emfv, url); + g_free (url); +} + +static void +emfb_tools_filters(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderBrowser *emfb = data; + + em_utils_edit_filters ((GtkWidget *) emfb); +} + +static void +emfb_subscribe_editor_destroy(GtkWidget *w, EMFolderBrowser *emfb) +{ + emfb->priv->subscribe_editor = NULL; +} + +static void +emfb_tools_subscriptions(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderBrowser *emfb = data; + + if (emfb->priv->subscribe_editor) { + gdk_window_show(emfb->priv->subscribe_editor->window); + } else { + emfb->priv->subscribe_editor = (GtkWidget *)em_subscribe_editor_new(); + e_dialog_set_transient_for((GtkWindow *)emfb->priv->subscribe_editor, (GtkWidget *)emfb); + g_signal_connect(emfb->priv->subscribe_editor, "destroy", G_CALLBACK(emfb_subscribe_editor_destroy), emfb); + gtk_widget_show(emfb->priv->subscribe_editor); + } +} + +static void +emfb_tools_vfolders(BonoboUIComponent *uid, void *data, const char *path) +{ + /* FIXME: rename/refactor this */ + vfolder_edit(); +} + +static BonoboUIVerb emfb_verbs[] = { + BONOBO_UI_UNSAFE_VERB ("EditInvertSelection", emfb_edit_invert_selection), + BONOBO_UI_UNSAFE_VERB ("EditSelectAll", emfb_edit_select_all), + BONOBO_UI_UNSAFE_VERB ("EditSelectThread", emfb_edit_select_thread), + BONOBO_UI_UNSAFE_VERB ("ChangeFolderProperties", emfb_folder_properties), + BONOBO_UI_UNSAFE_VERB ("FolderExpunge", emfb_folder_expunge), + /* HideDeleted is a toggle */ + BONOBO_UI_UNSAFE_VERB ("MessageMarkAllAsRead", emfb_mark_all_read), + BONOBO_UI_UNSAFE_VERB ("ViewHideRead", emfb_view_hide_read), + BONOBO_UI_UNSAFE_VERB ("ViewHideSelected", emfb_view_hide_selected), + BONOBO_UI_UNSAFE_VERB ("ViewShowAll", emfb_view_show_all), + /* ViewThreaded is a toggle */ + + BONOBO_UI_UNSAFE_VERB ("EmptyTrash", emfb_empty_trash), + BONOBO_UI_UNSAFE_VERB ("ForgetPasswords", emfb_forget_passwords), + BONOBO_UI_UNSAFE_VERB ("MailCompose", emfb_mail_compose), + BONOBO_UI_UNSAFE_VERB ("MailPost", emfb_mail_post), + BONOBO_UI_UNSAFE_VERB ("MailStop", emfb_mail_stop), + BONOBO_UI_UNSAFE_VERB ("ToolsFilters", emfb_tools_filters), + BONOBO_UI_UNSAFE_VERB ("ToolsSubscriptions", emfb_tools_subscriptions), + BONOBO_UI_UNSAFE_VERB ("ToolsVFolders", emfb_tools_vfolders), + /* ViewPreview is a toggle */ + + BONOBO_UI_VERB_END +}; + +static EPixmap emfb_pixmaps[] = { + E_PIXMAP ("/commands/ChangeFolderProperties", "configure_16_folder.xpm"), + E_PIXMAP ("/commands/ViewHideRead", "hide_read_messages.xpm"), + E_PIXMAP ("/commands/ViewHideSelected", "hide_selected_messages.xpm"), + E_PIXMAP ("/commands/ViewShowAll", "show_all_messages.xpm"), + + E_PIXMAP ("/commands/MailCompose", "new-message.xpm"), + + E_PIXMAP_END +}; + +static const EMFolderViewEnable emfb_enable_map[] = { + { "EditSelectThread", EM_FOLDER_VIEW_SELECT_THREADED }, + { "ViewHideSelected", EM_POPUP_SELECT_MANY }, + { "ViewShowAll", EM_FOLDER_VIEW_SELECT_HIDDEN }, + { NULL }, +}; + +static void +emfb_hide_deleted(BonoboUIComponent *uic, const char *path, Bonobo_UIComponent_EventType type, const char *state, void *data) +{ + GConfClient *gconf; + EMFolderView *emfv = data; + + if (type != Bonobo_UIComponent_STATE_CHANGED) + return; + + gconf = mail_config_get_gconf_client (); + gconf_client_set_bool(gconf, "/apps/evolution/mail/display/show_deleted", state[0] == '0', NULL); + if (!(emfv->folder && (emfv->folder->folder_flags & CAMEL_FOLDER_IS_TRASH))) + message_list_set_hidedeleted(emfv->list, state[0] != '0'); +} + +static void +emfb_view_threaded(BonoboUIComponent *uic, const char *path, Bonobo_UIComponent_EventType type, const char *state, void *data) +{ + GConfClient *gconf; + EMFolderView *emfv = data; + + if (type != Bonobo_UIComponent_STATE_CHANGED) + return; + + gconf = mail_config_get_gconf_client (); + gconf_client_set_bool(gconf, "/apps/evolution/mail/display/thread_list", state[0] != '0', NULL); + + if (camel_object_meta_set(emfv->folder, "evolution:thread_list", state)) + camel_object_state_write(emfv->folder); + + /* FIXME: do set_threaded via meta-data listener on folder? */ + message_list_set_threaded(emfv->list, state[0] != '0'); + + /* FIXME: update selection state? */ +} + +static void +emfb_view_preview(BonoboUIComponent *uic, const char *path, Bonobo_UIComponent_EventType type, const char *state, void *data) +{ + GConfClient *gconf; + EMFolderView *emfv = data; + + if (type != Bonobo_UIComponent_STATE_CHANGED) + return; + + gconf = mail_config_get_gconf_client (); + gconf_client_set_bool(gconf, "/apps/evolution/mail/display/show_preview", state[0] != '0', NULL); + + if (camel_object_meta_set(emfv->folder, "evolution:show_preview", state)) + camel_object_state_write(emfv->folder); + + /* FIXME: do this via folder listener */ + em_folder_browser_show_preview((EMFolderBrowser *)emfv, state[0] != '0'); +} + +/* TODO: This should probably be handled by message-list, by storing/queueing + up the select operation if its busy rebuilding the message-list */ +static void +emfb_list_built(MessageList *ml, EMFolderBrowser *emfb) +{ + g_signal_handler_disconnect(ml, emfb->priv->list_built_id); + emfb->priv->list_built_id = 0; + + if (((EMFolderView *)emfb)->list->cursor_uid == NULL) + message_list_select(((EMFolderView *)emfb)->list, + MESSAGE_LIST_SELECT_NEXT, 0, CAMEL_MESSAGE_SEEN, TRUE); +} + +static void +emfb_set_folder(EMFolderView *emfv, CamelFolder *folder, const char *uri) +{ + /* This is required since we get activated the first time + before the folder is open and need to override the + defaults */ + if (folder) { + char *sstate; + + if ((sstate = camel_object_meta_get(folder, "evolution:show_preview"))) + em_folder_browser_show_preview((EMFolderBrowser *)emfv, sstate[0] != '0'); + + if ((sstate = camel_object_meta_get(folder, "evolution:thread_list"))) + message_list_set_threaded(emfv->list, sstate[0] == '1'); + + if (emfv->list->cursor_uid == NULL && ((EMFolderBrowser *)emfv)->priv->list_built_id == 0) + ((EMFolderBrowser *)emfv)->priv->list_built_id = + g_signal_connect(emfv->list, "message_list_built", G_CALLBACK(emfb_list_built), emfv); + } + + emfb_parent->set_folder(emfv, folder, uri); +} + +/* TODO: All this mess should sit directly on MessageList, but it would + need to become BonoboUIComponent aware ... */ + +static void +emfb_list_display_view(GalViewInstance *instance, GalView *view, EMFolderBrowser *emfb) +{ + if (GAL_IS_VIEW_ETABLE(view)) + gal_view_etable_attach_tree(GAL_VIEW_ETABLE(view), emfb->view.list->tree); +} + +static void +emfb_create_view_menus(EMFolderBrowser *emfb, BonoboUIComponent *uic) +{ + struct _EMFolderBrowserPrivate *p = emfb->priv; + static GalViewCollection *collection = NULL; + char *id; + gboolean outgoing; + + g_assert(p->view_instance == NULL); + g_assert(p->view_menus == NULL); + + outgoing = em_utils_folder_is_drafts(emfb->view.folder, emfb->view.folder_uri) + || em_utils_folder_is_sent(emfb->view.folder, emfb->view.folder_uri) + || em_utils_folder_is_outbox(emfb->view.folder, emfb->view.folder_uri); + + if (collection == NULL) { + ETableSpecification *spec; + char *dir; + GalViewFactory *factory; + + collection = gal_view_collection_new(); + + gal_view_collection_set_title(collection, _("Mail")); + + dir = g_build_filename(g_get_home_dir(), "/evolution/views/mail/", NULL); + gal_view_collection_set_storage_directories(collection, EVOLUTION_GALVIEWSDIR "/mail/", dir); + g_free(dir); + + spec = e_table_specification_new(); + e_table_specification_load_from_file(spec, EVOLUTION_ETSPECDIR "/message-list.etspec"); + + factory = gal_view_factory_etable_new(spec); + g_object_unref(spec); + gal_view_collection_add_factory(collection, factory); + g_object_unref(factory); + + gal_view_collection_load(collection); + } + + /* TODO: should this go through mail-config api? */ + id = mail_config_folder_to_safe_url(emfb->view.folder); + p->view_instance = gal_view_instance_new(collection, id); + g_free(id); + + if (outgoing) + gal_view_instance_set_default_view(p->view_instance, "As_Sent_Folder"); + + if (!gal_view_instance_exists(p->view_instance)) { + char *path; + struct stat st; + + gal_view_instance_load(p->view_instance); + + path = mail_config_folder_to_cachename(emfb->view.folder, "et-header-"); + if (path && stat (path, &st) == 0 && st.st_size > 0 && S_ISREG (st.st_mode)) { + ETableSpecification *spec; + ETableState *state; + GalView *view; + + spec = e_table_specification_new(); + e_table_specification_load_from_file(spec, EVOLUTION_ETSPECDIR "/message-list.etspec"); + view = gal_view_etable_new(spec, ""); + g_object_unref(spec); + + state = e_table_state_new(); + e_table_state_load_from_file(state, path); + gal_view_etable_set_state(GAL_VIEW_ETABLE (view), state); + g_object_unref(state); + + gal_view_instance_set_custom_view(p->view_instance, view); + g_object_unref(view); + } + g_free(path); + } + + p->view_menus = gal_view_menus_new(p->view_instance); + gal_view_menus_apply(p->view_menus, uic, NULL); + + /* Due to CORBA reentrancy, the view could be gone now. */ + if (p->view_instance == NULL) + return; + + g_signal_connect(p->view_instance, "display_view", G_CALLBACK(emfb_list_display_view), emfb); + emfb_list_display_view(p->view_instance, gal_view_instance_get_current_view(p->view_instance), emfb); +} + +static void +emfb_activate(EMFolderView *emfv, BonoboUIComponent *uic, int act) +{ + struct _EMFolderBrowserPrivate *p = ((EMFolderBrowser *)emfv)->priv; + + if (act) { + GConfClient *gconf; + gboolean state; + char *sstate; + + gconf = mail_config_get_gconf_client (); + + /* parent loads all ui files via ui_files */ + emfb_parent->activate(emfv, uic, act); + + bonobo_ui_component_add_verb_list_with_data(uic, emfb_verbs, emfv); + e_pixmaps_update(uic, emfb_pixmaps); + +#if 0 + /* FIXME: finish */ + /* (Pre)view pane size (do this first because it affects the + preview settings - see folder_browser_set_message_preview() + internals for details) */ + g_signal_handler_block(emfb->vpane, emfb->priv->vpane_resize_id); + gtk_paned_set_position((GtkPaned *)emfb->vpane, gconf_client_get_int (gconf, "/apps/evolution/mail/display/paned_size", NULL)); + g_signal_handler_unblock(emfb->vpane, emfb->priv->vpane_resize_id); +#endif + + /* (Pre)view toggle */ + if (emfv->folder + && (sstate = camel_object_meta_get(emfv->folder, "evolution:show_preview"))) { + state = sstate[0] == '1'; + g_free(sstate); + } else { + state = gconf_client_get_bool(gconf, "/apps/evolution/mail/display/show_preview", NULL); + } + + bonobo_ui_component_set_prop(uic, "/commands/ViewPreview", "state", state?"1":"0", NULL); + em_folder_browser_show_preview((EMFolderBrowser *)emfv, state); + bonobo_ui_component_add_listener(uic, "ViewPreview", emfb_view_preview, emfv); + + /* Stop button */ + state = mail_msg_active((unsigned int)-1); + bonobo_ui_component_set_prop(uic, "/commands/MailStop", "sensitive", state?"1":"0", NULL); + + /* HideDeleted */ + state = !gconf_client_get_bool(gconf, "/apps/evolution/mail/display/show_deleted", NULL); + bonobo_ui_component_set_prop(uic, "/commands/HideDeleted", "state", state ? "1" : "0", NULL); + bonobo_ui_component_add_listener(uic, "HideDeleted", emfb_hide_deleted, emfv); + if (!(emfv->folder && (emfv->folder->folder_flags & CAMEL_FOLDER_IS_TRASH))) + message_list_set_hidedeleted (emfv->list, state); + else + bonobo_ui_component_set_prop(uic, "/commands/HideDeleted", "sensitive", state?"1":"0", NULL); + + /* FIXME: If we have no folder, we can't do a few of the lookups we need, + perhaps we should postpone till we can */ + + /* ViewThreaded */ + if (emfv->folder + && (sstate = camel_object_meta_get(emfv->folder, "evolution:thread_list"))) { + state = sstate[0] == '1'; + g_free(sstate); + } else { + state = gconf_client_get_bool(gconf, "/apps/evolution/mail/display/thread_list", NULL); + } + + bonobo_ui_component_set_prop(uic, "/commands/ViewThreaded", "state", state?"1":"0", NULL); + bonobo_ui_component_add_listener(uic, "ViewThreaded", emfb_view_threaded, emfv); + message_list_set_threaded(emfv->list, state); + + /* FIXME: Selection state */ + + /* FIXME: property menu customisation */ + /*folder_browser_setup_property_menu (fb, fb->uicomp);*/ + + if (((EMFolderBrowser *)emfv)->search) + e_search_bar_set_ui_component((ESearchBar *)((EMFolderBrowser *)emfv)->search, uic); + + if (emfv->folder) + emfb_create_view_menus((EMFolderBrowser *)emfv, uic); + } else { + const BonoboUIVerb *v; + + for (v = &emfb_verbs[0]; v->cname; v++) + bonobo_ui_component_remove_verb(uic, v->cname); + + if (p->view_instance) { + g_object_unref(p->view_instance); + p->view_instance = NULL; + g_object_unref(p->view_menus); + p->view_menus = NULL; + } + + if (((EMFolderBrowser *)emfv)->search) + e_search_bar_set_ui_component((ESearchBar *)((EMFolderBrowser *)emfv)->search, NULL); + + emfb_parent->activate(emfv, uic, act); + } +} diff --git a/mail/em-folder-browser.h b/mail/em-folder-browser.h new file mode 100644 index 0000000000..248deaf9b4 --- /dev/null +++ b/mail/em-folder-browser.h @@ -0,0 +1,60 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifndef _EM_FOLDER_BROWSER_H +#define _EM_FOLDER_BROWSER_H + +#include "em-folder-view.h" + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +typedef struct _EMFolderBrowser EMFolderBrowser; +typedef struct _EMFolderBrowserClass EMFolderBrowserClass; + +struct _EMFolderBrowser { + EMFolderView view; + + struct _EMFolderBrowserPrivate *priv; + + GtkWidget *vpane; + struct _EFilterBar *search; +}; + +struct _EMFolderBrowserClass { + EMFolderViewClass parent_class; +}; + +GType em_folder_browser_get_type(void); + +GtkWidget *em_folder_browser_new(void); + +void em_folder_browser_show_preview(EMFolderBrowser *emfv, gboolean state); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* ! _EM_FOLDER_BROWSER_H */ diff --git a/mail/em-folder-view.c b/mail/em-folder-view.c new file mode 100644 index 0000000000..7122f3da36 --- /dev/null +++ b/mail/em-folder-view.c @@ -0,0 +1,1866 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <gtk/gtkvbox.h> +#include <gtk/gtkbutton.h> +#include <gtk/gtkvpaned.h> +#include <gdk/gdkkeysyms.h> + +#include <gtkhtml/gtkhtml.h> + +#include <libgnome/gnome-url.h> + +#include <libgnomeprintui/gnome-print-dialog.h> + +#include <camel/camel-mime-message.h> +#include <camel/camel-stream.h> +#include <camel/camel-stream-filter.h> +#include <camel/camel-mime-filter.h> +#include <camel/camel-mime-filter-tohtml.h> +#include <camel/camel-mime-filter-enriched.h> +#include <camel/camel-multipart.h> +#include <camel/camel-stream-mem.h> +#include <camel/camel-url.h> + +#include <bonobo/bonobo-main.h> +#include <bonobo/bonobo-object.h> +#include <bonobo/bonobo-generic-factory.h> +#include <bonobo/bonobo-control.h> +#include <bonobo/bonobo-ui-component.h> +#include <bonobo/bonobo-ui-util.h> + +#include "widgets/misc/e-charset-picker.h" + +#include <e-util/e-dialog-utils.h> + +#include "em-format-html-display.h" +#include "em-format-html-print.h" +#include "em-folder-view.h" +#include "em-message-browser.h" +#include "message-list.h" +#include "em-utils.h" + +#include <gtkhtml/gtkhtml.h> +#include <gtkhtml/htmlobject.h> +#include <gtkhtml/htmlengine.h> +#include <gtkhtml/htmlengine-save.h> + +#include "mail-mt.h" +#include "mail-ops.h" +#include "mail-config.h" /* hrm, pity we need this ... */ +#include "mail-autofilter.h" +#include "mail-vfolder.h" + +#include "evolution-shell-component-utils.h" /* Pixmap stuff, sigh */ + +static void emfv_folder_changed(CamelFolder *folder, CamelFolderChangeInfo *changes, EMFolderView *emfv); + +static void emfv_list_message_selected(MessageList *ml, const char *uid, EMFolderView *emfv); +static int emfv_list_right_click(ETree *tree, gint row, ETreePath path, gint col, GdkEvent *event, EMFolderView *emfv); +static void emfv_list_double_click(ETree *tree, gint row, ETreePath path, gint col, GdkEvent *event, EMFolderView *emfv); +static int emfv_list_key_press(ETree *tree, int row, ETreePath path, int col, GdkEvent *ev, EMFolderView *emfv); + +static void emfv_format_link_clicked(EMFormatHTMLDisplay *efhd, const char *uri, EMFolderView *); +static int emfv_format_popup_event(EMFormatHTMLDisplay *efhd, GdkEventButton *event, const char *uri, CamelMimePart *part, EMFolderView *); + +static void emfv_enable_menus(EMFolderView *emfv); + +static void emfv_set_folder(EMFolderView *emfv, CamelFolder *folder, const char *uri); +static void emfv_set_folder_uri(EMFolderView *emfv, const char *uri); +static void emfv_set_message(EMFolderView *emfv, const char *uid); +static void emfv_activate(EMFolderView *emfv, BonoboUIComponent *uic, int state); + +static void emfv_message_reply(EMFolderView *emfv, int mode); +static void vfolder_type_current (EMFolderView *emfv, int type); +static void filter_type_current (EMFolderView *emfv, int type); + +static void emfv_setting_setup(EMFolderView *emfv); + +static const EMFolderViewEnable emfv_enable_map[]; + +struct _EMFolderViewPrivate { + guint seen_id; + guint setting_notify_id; + + CamelObjectHookID folder_changed_id; + + GtkWidget *invisible; + char *selection_uri; +}; + +static GtkVBoxClass *emfv_parent; + +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); + +static void +emfv_init(GObject *o) +{ + EMFolderView *emfv = (EMFolderView *)o; + struct _EMFolderViewPrivate *p; + + gtk_box_set_homogeneous (GTK_BOX (emfv), FALSE); + + p = emfv->priv = g_malloc0(sizeof(struct _EMFolderViewPrivate)); + + emfv->ui_files = g_slist_append(NULL, EVOLUTION_UIDIR "/evolution-mail-message.xml"); + 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); + + /* 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); + + emfv->preview = (EMFormatHTMLDisplay *)em_format_html_display_new(); + 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); + + p->invisible = gtk_invisible_new(); + g_object_ref(p->invisible); + gtk_object_sink((GtkObject *)p->invisible); + 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_PRIMARY, GDK_SELECTION_TYPE_STRING, 1); + + emfv->async = mail_async_event_new(); + + emfv_setting_setup(emfv); +} + +static void +emfv_finalise(GObject *o) +{ + EMFolderView *emfv = (EMFolderView *)o; + struct _EMFolderViewPrivate *p = emfv->priv; + + if (emfv->async) + mail_async_event_destroy(emfv->async); + + if (emfv->folder) { + if (p->folder_changed_id) + camel_object_remove_event(emfv->folder, p->folder_changed_id); + camel_object_unref(emfv->folder); + g_free(emfv->folder_uri); + } + + g_slist_free(emfv->ui_files); + g_slist_free(emfv->enable_map); + + g_free(p); + + ((GObjectClass *)emfv_parent)->finalize(o); +} + +static void +emfv_destroy (GtkObject *o) +{ + EMFolderView *emfv = (EMFolderView *) o; + struct _EMFolderViewPrivate *p = emfv->priv; + + if (p->seen_id) { + g_source_remove(p->seen_id); + p->seen_id = 0; + } + + if (p->setting_notify_id) { + GConfClient *gconf = gconf_client_get_default(); + + gconf_client_notify_remove(gconf, p->setting_notify_id); + p->setting_notify_id = 0; + g_object_unref(gconf); + } + + if (p->invisible) { + g_object_unref(p->invisible); + p->invisible = NULL; + } + + emfv->preview = NULL; + emfv->list = NULL; + emfv->preview_active = FALSE; + emfv->uic = NULL; + + ((GtkObjectClass *) emfv_parent)->destroy (o); +} + +static void +emfv_class_init(GObjectClass *klass) +{ + klass->finalize = emfv_finalise; + + ((GtkObjectClass *) klass)->destroy = emfv_destroy; + + ((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; +} + +GType +em_folder_view_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMFolderViewClass), + NULL, NULL, + (GClassInitFunc)emfv_class_init, + NULL, NULL, + sizeof(EMFolderView), 0, + (GInstanceInitFunc)emfv_init + }; + emfv_parent = g_type_class_ref(gtk_vbox_get_type()); + type = g_type_register_static(gtk_vbox_get_type(), "EMFolderView", &info, 0); + } + + return type; +} + +GtkWidget *em_folder_view_new(void) +{ + EMFolderView *emfv = g_object_new(em_folder_view_get_type(), 0); + + return (GtkWidget *)emfv; +} + +/* flag all selected messages. Return number flagged */ +/* FIXME: Should this be part of message-list instead? */ +int +em_folder_view_mark_selected(EMFolderView *emfv, guint32 mask, guint32 set) +{ + GPtrArray *uids; + int i; + + if (emfv->folder == NULL) + return 0; + + uids = message_list_get_selected(emfv->list); + camel_folder_freeze(emfv->folder); + + for (i=0; i<uids->len; i++) + camel_folder_set_message_flags(emfv->folder, uids->pdata[i], mask, set); + + message_list_free_uids(emfv->list, uids); + camel_folder_thaw(emfv->folder); + + return i; +} + +/* should this be elsewhere/take a uid list? */ +int +em_folder_view_open_selected(EMFolderView *emfv) +{ + GPtrArray *uids; + int i = 0; + + /* FIXME: handle editing message? Should be a different method? editing handled by 'Resend' method already */ + + uids = message_list_get_selected(emfv->list); + + if (em_utils_folder_is_drafts(emfv->folder, emfv->folder_uri) + || em_utils_folder_is_outbox(emfv->folder, emfv->folder_uri)) { + em_utils_edit_messages((GtkWidget *)emfv, emfv->folder, uids); + } else { + /* TODO: have an em_utils_open_messages call? */ + + /* FIXME: 'are you sure' for > 10 messages; is this even necessary? */ + + for (i=0; i<uids->len; i++) { + EMMessageBrowser *emmb; + + emmb = (EMMessageBrowser *)em_message_browser_window_new(); + /* FIXME: session needs to be passed easier than this */ + em_format_set_session((EMFormat *)((EMFolderView *)emmb)->preview, ((EMFormat *)emfv->preview)->session); + em_folder_view_set_folder((EMFolderView *)emmb, emfv->folder, emfv->folder_uri); + em_folder_view_set_message((EMFolderView *)emmb, uids->pdata[i]); + gtk_widget_show(emmb->window); + } + + message_list_free_uids(emfv->list, uids); + } + + return i; +} + +/* ********************************************************************** */ + +static void +emfv_set_folder(EMFolderView *emfv, CamelFolder *folder, const char *uri) +{ + int isout = (folder && uri + && (em_utils_folder_is_drafts(folder, uri) + || em_utils_folder_is_sent(folder, uri) + || em_utils_folder_is_outbox(folder, uri))); + + message_list_set_folder(emfv->list, folder, uri, isout); + g_free(emfv->folder_uri); + emfv->folder_uri = g_strdup(uri); + if (folder != emfv->folder) { + if (emfv->folder) { + if (emfv->priv->folder_changed_id) + camel_object_remove_event(emfv->folder, emfv->priv->folder_changed_id); + camel_object_unref(emfv->folder); + } + emfv->folder = folder; + if (folder) { + emfv->priv->folder_changed_id = camel_object_hook_event(folder, "folder_changed", + (CamelObjectEventHookFunc)emfv_folder_changed, emfv); + camel_object_ref(folder); + } + } + + emfv_enable_menus(emfv); +} + +static void +emfv_got_folder(char *uri, CamelFolder *folder, void *data) +{ + EMFolderView *emfv = data; + + em_folder_view_set_folder(emfv, folder, uri); +} + +static void +emfv_set_folder_uri(EMFolderView *emfv, const char *uri) +{ + if (emfv->preview) + em_format_format((EMFormat *)emfv->preview, NULL); + + mail_get_folder(uri, 0, emfv_got_folder, emfv, mail_thread_new); +} + +static void +emfv_set_message(EMFolderView *emfv, const char *uid) +{ + message_list_select_uid(emfv->list, uid); +} + +/* ********************************************************************** */ + +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, 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 */ + +struct _emfv_label_item { + EMPopupItem item; + + EMFolderView *emfv; + const char *label; +}; + +static void +emfv_popup_open(GtkWidget *w, EMFolderView *emfv) +{ + em_folder_view_open_selected(emfv); +} + +static void +emfv_popup_resend(GtkWidget *w, EMFolderView *emfv) +{ + GPtrArray *uids; + + if (!em_utils_check_user_can_send_mail((GtkWidget *)emfv)) + return; + + uids = message_list_get_selected(emfv->list); + em_utils_edit_messages((GtkWidget *)emfv, emfv->folder, uids); +} + +static void +emfv_popup_saveas(GtkWidget *w, EMFolderView *emfv) +{ + GPtrArray *uids; + + uids = message_list_get_selected(emfv->list); + em_utils_save_messages((GtkWidget *)emfv, emfv->folder, uids); +} + +static void +emfv_popup_print(GtkWidget *w, EMFolderView *emfv) +{ + em_folder_view_print(emfv, FALSE); +} + +static void +emfv_popup_reply_sender(GtkWidget *w, EMFolderView *emfv) +{ + emfv_message_reply(emfv, REPLY_MODE_SENDER); +} + +static void +emfv_popup_reply_list(GtkWidget *w, EMFolderView *emfv) +{ + emfv_message_reply(emfv, REPLY_MODE_LIST); +} + +static void +emfv_popup_reply_all(GtkWidget *w, EMFolderView *emfv) +{ + emfv_message_reply(emfv, REPLY_MODE_ALL); +} + +static void +emfv_popup_forward(GtkWidget *w, EMFolderView *emfv) +{ + GPtrArray *uids; + + if (!em_utils_check_user_can_send_mail((GtkWidget *)emfv)) + return; + + uids = message_list_get_selected(emfv->list); + em_utils_forward_messages((GtkWidget *)emfv, emfv->folder, uids); +} + +static void +emfv_popup_flag_followup(GtkWidget *w, EMFolderView *emfv) +{ + GPtrArray *uids = message_list_get_selected(emfv->list); + + em_utils_flag_for_followup((GtkWidget *)emfv, emfv->folder, uids); +} + +static void +emfv_popup_flag_completed(GtkWidget *w, EMFolderView *emfv) +{ + GPtrArray *uids; + + uids = message_list_get_selected(emfv->list); + em_utils_flag_for_followup_completed((GtkWidget *)emfv, emfv->folder, uids); +} + +static void +emfv_popup_flag_clear(GtkWidget *w, EMFolderView *emfv) +{ + GPtrArray *uids = message_list_get_selected(emfv->list); + + em_utils_flag_for_followup_clear((GtkWidget *)emfv, emfv->folder, uids); +} + +static void +emfv_popup_mark_read(GtkWidget *w, EMFolderView *emfv) +{ + em_folder_view_mark_selected(emfv, CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN); +} + +static void +emfv_popup_mark_unread(GtkWidget *w, EMFolderView *emfv) +{ + em_folder_view_mark_selected(emfv, CAMEL_MESSAGE_SEEN|CAMEL_MESSAGE_DELETED, 0); + + if (emfv->priv->seen_id) { + g_source_remove(emfv->priv->seen_id); + emfv->priv->seen_id = 0; + } +} + +static void +emfv_popup_mark_important(GtkWidget *w, EMFolderView *emfv) +{ + em_folder_view_mark_selected(emfv, CAMEL_MESSAGE_FLAGGED|CAMEL_MESSAGE_DELETED, CAMEL_MESSAGE_FLAGGED); +} + +static void +emfv_popup_mark_unimportant(GtkWidget *w, EMFolderView *emfv) +{ + em_folder_view_mark_selected(emfv, CAMEL_MESSAGE_FLAGGED, 0); +} + +static void +emfv_popup_delete(GtkWidget *w, EMFolderView *emfv) +{ + GPtrArray *uids; + + uids = message_list_get_selected (emfv->list); + em_folder_view_mark_selected (emfv, CAMEL_MESSAGE_SEEN|CAMEL_MESSAGE_DELETED, CAMEL_MESSAGE_SEEN|CAMEL_MESSAGE_DELETED); + + if (uids->len == 1) + message_list_select (emfv->list, MESSAGE_LIST_SELECT_NEXT, 0, 0, FALSE); + + em_utils_uids_free (uids); +} + +static void +emfv_popup_undelete(GtkWidget *w, EMFolderView *emfv) +{ + em_folder_view_mark_selected(emfv, CAMEL_MESSAGE_DELETED, 0); +} + +static void +emfv_popup_move(GtkWidget *w, EMFolderView *emfv) +{ + /* FIXME */ +} + +static void +emfv_popup_copy(GtkWidget *w, EMFolderView *emfv) +{ + /* FIXME */ +} + +static void +emfv_set_label(EMFolderView *emfv, const char *label) +{ + GPtrArray *uids = message_list_get_selected(emfv->list); + int i; + + for (i=0;i<uids->len;i++) + camel_folder_set_message_user_tag(emfv->folder, uids->pdata[i], "label", label); + + message_list_free_uids(emfv->list, uids); +} + +static void +emfv_popup_label_clear(GtkWidget *w, EMFolderView *emfv) +{ + emfv_set_label(emfv, NULL); +} + +static void +emfv_popup_label_set(GtkWidget *w, struct _emfv_label_item *item) +{ + emfv_set_label(item->emfv, item->label); +} + +static void +emfv_popup_add_sender(GtkWidget *w, EMFolderView *emfv) +{ + /* FIXME */ + printf("UNIMPLEMENTED: add sender to addressbook\n"); +} + +static void +emfv_popup_apply_filters(GtkWidget *w, EMFolderView *emfv) +{ + GPtrArray *uids = message_list_get_selected(emfv->list); + + mail_filter_on_demand(emfv->folder, uids); +} + +/* filter callbacks, this will eventually be a wizard, see + filter_type_current/vfolder_type_current for implementation */ + +#define EMFV_POPUP_AUTO_TYPE(autotype, name, type) \ +static void \ +name(GtkWidget *w, EMFolderView *emfv) \ +{ \ + autotype(emfv, type); \ +} + +EMFV_POPUP_AUTO_TYPE(vfolder_type_current, emfv_popup_vfolder_subject, AUTO_SUBJECT) +EMFV_POPUP_AUTO_TYPE(vfolder_type_current, emfv_popup_vfolder_sender, AUTO_FROM) +EMFV_POPUP_AUTO_TYPE(vfolder_type_current, emfv_popup_vfolder_recipients, AUTO_TO) +EMFV_POPUP_AUTO_TYPE(vfolder_type_current, emfv_popup_vfolder_mlist, AUTO_MLIST) + +EMFV_POPUP_AUTO_TYPE(filter_type_current, emfv_popup_filter_subject, AUTO_SUBJECT) +EMFV_POPUP_AUTO_TYPE(filter_type_current, emfv_popup_filter_sender, AUTO_FROM) +EMFV_POPUP_AUTO_TYPE(filter_type_current, emfv_popup_filter_recipients, AUTO_TO) +EMFV_POPUP_AUTO_TYPE(filter_type_current, emfv_popup_filter_mlist, AUTO_MLIST) + +/* TODO: Move some of these to be 'standard' menu's */ + +static EMPopupItem emfv_popup_menu[] = { + { EM_POPUP_ITEM, "00.emfv.00", N_("_Open"), G_CALLBACK(emfv_popup_open), NULL, NULL, 0 }, + { EM_POPUP_ITEM, "00.emfv.01", N_("_Edit as New Message..."), G_CALLBACK(emfv_popup_resend), NULL, NULL, EM_POPUP_SELECT_RESEND }, + { EM_POPUP_ITEM, "00.emfv.02", N_("_Save As..."), G_CALLBACK(emfv_popup_saveas), NULL, "save-as-16.png", 0 }, + { EM_POPUP_ITEM, "00.emfv.03", N_("_Print"), G_CALLBACK(emfv_popup_print), NULL, "print.xpm", 0 }, + + { EM_POPUP_BAR, "10.emfv" }, + { EM_POPUP_ITEM, "10.emfv.00", N_("_Reply to Sender"), G_CALLBACK(emfv_popup_reply_sender), NULL, "reply.xpm", EM_POPUP_SELECT_ONE }, + { EM_POPUP_ITEM, "10.emfv.01", N_("Reply to _List"), G_CALLBACK(emfv_popup_reply_list), NULL, NULL, EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_MAILING_LIST }, + { EM_POPUP_ITEM, "10.emfv.02", N_("Reply to _All"), G_CALLBACK(emfv_popup_reply_all), NULL, "reply_to_all.xpm", EM_POPUP_SELECT_ONE }, + { EM_POPUP_ITEM, "10.emfv.03", N_("_Forward"), G_CALLBACK(emfv_popup_forward), NULL, "forward.xpm", EM_POPUP_SELECT_MANY }, + + { EM_POPUP_BAR, "20.emfv", NULL, NULL, NULL, NULL, EM_POPUP_SELECT_FLAG_FOLLOWUP|EM_POPUP_SELECT_FLAG_COMPLETED|EM_POPUP_SELECT_FLAG_CLEAR }, + { EM_POPUP_ITEM, "20.emfv.00", N_("Follo_w Up..."), G_CALLBACK(emfv_popup_flag_followup), NULL, "flag-for-followup-16.png", EM_POPUP_SELECT_FLAG_FOLLOWUP }, + { EM_POPUP_ITEM, "20.emfv.01", N_("Fla_g Completed"), G_CALLBACK(emfv_popup_flag_completed), NULL, NULL, EM_POPUP_SELECT_FLAG_COMPLETED }, + { EM_POPUP_ITEM, "20.emfv.02", N_("Cl_ear Flag"), G_CALLBACK(emfv_popup_flag_clear), NULL, NULL, EM_POPUP_SELECT_FLAG_CLEAR }, + + { EM_POPUP_BAR, "30.emfv" }, + { EM_POPUP_ITEM, "30.emfv.00", N_("Mar_k as Read"), G_CALLBACK(emfv_popup_mark_read), NULL, "mail-read.xpm", EM_POPUP_SELECT_MARK_READ }, + { EM_POPUP_ITEM, "30.emfv.01", N_("Mark as _Unread"), G_CALLBACK(emfv_popup_mark_unread), NULL, "mail-new.xpm", EM_POPUP_SELECT_MARK_UNREAD }, + { EM_POPUP_ITEM, "30.emfv.02", N_("Mark as _Important"), G_CALLBACK(emfv_popup_mark_important), NULL, "priority-high.xpm", EM_POPUP_SELECT_MARK_IMPORTANT }, + { EM_POPUP_ITEM, "30.emfv.03", N_("_Mark as Unimportant"), G_CALLBACK(emfv_popup_mark_unimportant), NULL, NULL, EM_POPUP_SELECT_MARK_UNIMPORTANT }, + + { EM_POPUP_BAR, "40.emfv" }, + { EM_POPUP_ITEM, "40.emfv.00", N_("_Delete"), G_CALLBACK(emfv_popup_delete), NULL, "evolution-trash-mini.png", EM_POPUP_SELECT_DELETE }, + { EM_POPUP_ITEM, "40.emfv.01", N_("U_ndelete"), G_CALLBACK(emfv_popup_undelete), NULL, "undelete_message-16.png", EM_POPUP_SELECT_UNDELETE }, + + { EM_POPUP_BAR, "50.emfv" }, + { EM_POPUP_ITEM, "50.emfv.00", N_("Mo_ve to Folder..."), G_CALLBACK(emfv_popup_move) }, + { EM_POPUP_ITEM, "50.emfv.01", N_("_Copy to Folder..."), G_CALLBACK(emfv_popup_copy) }, + + { EM_POPUP_BAR, "60.label" }, + { EM_POPUP_SUBMENU, "60.label.00", N_("Label") }, + { EM_POPUP_IMAGE, "60.label.00/00.label", N_("None"), G_CALLBACK(emfv_popup_label_clear) }, + { EM_POPUP_BAR, "60.label.00/00.label.00" }, + + { EM_POPUP_BAR, "70.emfv", NULL, NULL, NULL, NULL, EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_ADD_SENDER }, + { EM_POPUP_ITEM, "70.emfv.00", N_("Add Sender to Address_book"), G_CALLBACK(emfv_popup_add_sender), NULL, NULL, EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_ADD_SENDER }, + + { EM_POPUP_BAR, "80.emfv" }, + { EM_POPUP_ITEM, "80.emfv.00", N_("Appl_y Filters"), G_CALLBACK(emfv_popup_apply_filters) }, + + { EM_POPUP_BAR, "90.filter" }, + { EM_POPUP_SUBMENU, "90.filter.00", N_("Crea_te Rule From Message"), NULL, NULL, NULL, EM_POPUP_SELECT_ONE }, + { EM_POPUP_ITEM, "90.filter.00/00.00", N_("VFolder on _Subject"), G_CALLBACK(emfv_popup_vfolder_subject), NULL, NULL, EM_POPUP_SELECT_ONE }, + { EM_POPUP_ITEM, "90.filter.00/00.01", N_("VFolder on Se_nder"), G_CALLBACK(emfv_popup_vfolder_sender), NULL, NULL, EM_POPUP_SELECT_ONE }, + { EM_POPUP_ITEM, "90.filter.00/00.02", N_("VFolder on _Recipients"), G_CALLBACK(emfv_popup_vfolder_recipients), NULL, NULL, EM_POPUP_SELECT_ONE }, + { EM_POPUP_ITEM, "90.filter.00/00.03", N_("VFolder on Mailing _List"), + G_CALLBACK(emfv_popup_vfolder_mlist), NULL, NULL, EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_MAILING_LIST }, + + { EM_POPUP_BAR, "90.filter.00/10" }, + { EM_POPUP_ITEM, "90.filter.00/10.00", N_("Filter on Sub_ject"), G_CALLBACK(emfv_popup_filter_subject), NULL, NULL, EM_POPUP_SELECT_ONE }, + { EM_POPUP_ITEM, "90.filter.00/10.01", N_("Filter on Sen_der"), G_CALLBACK(emfv_popup_filter_sender), NULL, NULL, EM_POPUP_SELECT_ONE }, + { EM_POPUP_ITEM, "90.filter.00/10.02", N_("Filter on Re_cipients"), G_CALLBACK(emfv_popup_filter_recipients), NULL, NULL, EM_POPUP_SELECT_ONE }, + { EM_POPUP_ITEM, "90.filter.00/10.03", N_("Filter on _Mailing List"), + G_CALLBACK(emfv_popup_filter_mlist), NULL, NULL, EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_MAILING_LIST }, +}; + +static void +emfv_popup_labels_free(void *data) +{ + GSList *l = data; + + while (l) { + GSList *n = l->next; + struct _emfv_label_item *item = l->data; + + g_free(item->item.path); + g_free(item); + + g_slist_free_1(l); + l = n; + } +} + +static void +emfv_popup(EMFolderView *emfv, GdkEvent *event) +{ + GSList *menus = NULL, *l, *label_list = NULL; + GtkMenu *menu; + EMPopup *emp; + EMPopupTarget *target; + int i; + + emp = em_popup_new("com.ximian.mail.folderview.popup.select"); + target = em_folder_view_get_popup_target(emfv); + + for (i=0;i<sizeof(emfv_popup_menu)/sizeof(emfv_popup_menu[0]);i++) { + EMPopupItem *item = &emfv_popup_menu[i]; + + item->activate_data = emfv; + menus = g_slist_prepend(menus, item); + } + + em_popup_add_items(emp, menus, (GDestroyNotify)g_slist_free); + + i = 1; + for (l = mail_config_get_labels(); l; l = l->next) { + struct _emfv_label_item *item; + MailConfigLabel *label = l->data; + GdkPixmap *pixmap; + GdkColor colour; + GdkGC *gc; + + item = g_malloc0(sizeof(*item)); + item->item.type = EM_POPUP_IMAGE; + item->item.path = g_strdup_printf("60.label.00/00.label.%02d", i++); + item->item.label = label->name; + item->item.activate = G_CALLBACK(emfv_popup_label_set); + item->item.activate_data = item; + item->emfv = emfv; + item->label = label->tag; + + gdk_color_parse(label->colour, &colour); + gdk_color_alloc(gdk_colormap_get_system(), &colour); + + pixmap = gdk_pixmap_new(((GtkWidget *)emfv)->window, 16, 16, -1); + gc = gdk_gc_new(((GtkWidget *)emfv)->window); + gdk_gc_set_foreground(gc, &colour); + gdk_draw_rectangle(pixmap, gc, TRUE, 0, 0, 16, 16); + gdk_gc_unref(gc); + + item->item.image = gtk_image_new_from_pixmap(pixmap, NULL); + gtk_widget_show(item->item.image); + + label_list = g_slist_prepend(label_list, item); + } + + em_popup_add_items(emp, label_list, emfv_popup_labels_free); + + menu = em_popup_create_menu_once(emp, target, target->mask, target->mask); + + 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); + } +} + +/* ********************************************************************** */ + +/* 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, (EMFolderView *)data); \ +} + +EMFV_MAP_CALLBACK(emfv_message_apply_filters, emfv_popup_apply_filters) +EMFV_MAP_CALLBACK(emfv_message_copy, emfv_popup_copy) +EMFV_MAP_CALLBACK(emfv_message_move, emfv_popup_move) +EMFV_MAP_CALLBACK(emfv_message_forward, emfv_popup_forward) +EMFV_MAP_CALLBACK(emfv_message_reply_all, emfv_popup_reply_all) +EMFV_MAP_CALLBACK(emfv_message_reply_list, emfv_popup_reply_list) +EMFV_MAP_CALLBACK(emfv_message_reply_sender, emfv_popup_reply_sender) +EMFV_MAP_CALLBACK(emfv_message_mark_read, emfv_popup_mark_read) +EMFV_MAP_CALLBACK(emfv_message_mark_unread, emfv_popup_mark_unread) +EMFV_MAP_CALLBACK(emfv_message_mark_important, emfv_popup_mark_important) +EMFV_MAP_CALLBACK(emfv_message_mark_unimportant, emfv_popup_mark_unimportant) +EMFV_MAP_CALLBACK(emfv_message_delete, emfv_popup_delete) +EMFV_MAP_CALLBACK(emfv_message_undelete, emfv_popup_undelete) +EMFV_MAP_CALLBACK(emfv_message_followup_flag, emfv_popup_flag_followup) +/*EMFV_MAP_CALLBACK(emfv_message_followup_clear, emfv_popup_flag_clear) + EMFV_MAP_CALLBACK(emfv_message_followup_completed, emfv_popup_flag_completed)*/ +EMFV_MAP_CALLBACK(emfv_message_open, emfv_popup_open) +EMFV_MAP_CALLBACK(emfv_message_resend, emfv_popup_resend) +EMFV_MAP_CALLBACK(emfv_message_saveas, emfv_popup_saveas) +EMFV_MAP_CALLBACK(emfv_print_message, emfv_popup_print) + +static void +emfv_edit_cut(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + if (message_list_has_primary_selection(emfv->list)) + message_list_copy(emfv->list, TRUE); + else if (emfv->preview_active) + em_format_html_display_cut(emfv->preview); +} + +static void +emfv_edit_copy(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + if (message_list_has_primary_selection(emfv->list)) + message_list_copy(emfv->list, FALSE); + else if (emfv->preview_active) + em_format_html_display_copy(emfv->preview); +} + +static void +emfv_edit_paste(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_paste(emfv->list); +} + +static void +emfv_mail_next(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_select(emfv->list, MESSAGE_LIST_SELECT_NEXT, 0, 0, FALSE); +} + +static void +emfv_mail_next_flagged(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_select(emfv->list, MESSAGE_LIST_SELECT_NEXT, CAMEL_MESSAGE_FLAGGED, CAMEL_MESSAGE_FLAGGED, FALSE); +} + +static void +emfv_mail_next_unread(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_select(emfv->list, MESSAGE_LIST_SELECT_NEXT, 0, CAMEL_MESSAGE_SEEN, TRUE); +} + +static void +emfv_mail_next_thread(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_select_next_thread(emfv->list); +} + +static void +emfv_mail_previous(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_select(emfv->list, MESSAGE_LIST_SELECT_PREVIOUS, 0, 0, FALSE); +} + +static void +emfv_mail_previous_flagged(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_select(emfv->list, MESSAGE_LIST_SELECT_PREVIOUS, CAMEL_MESSAGE_FLAGGED, CAMEL_MESSAGE_FLAGGED, TRUE); +} + +static void +emfv_mail_previous_unread(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + message_list_select(emfv->list, MESSAGE_LIST_SELECT_PREVIOUS, 0, CAMEL_MESSAGE_SEEN, TRUE); +} + +static void +emfv_add_sender_addressbook(BonoboUIComponent *uid, void *data, const char *path) +{ + EMFolderView *emfv = data; + + emfv = emfv; + /* FIXME: need to find out what the new addressbook API is for this... */ +} + +static void +emfv_message_forward_attached (BonoboUIComponent *uic, void *data, const char *path) +{ + EMFolderView *emfv = data; + GPtrArray *uids; + + if (!em_utils_check_user_can_send_mail ((GtkWidget *) emfv)) + return; + + uids = message_list_get_selected (emfv->list); + em_utils_forward_attached ((GtkWidget *) emfv, emfv->folder, uids); +} + +static void +emfv_message_forward_inline (BonoboUIComponent *uic, void *data, const char *path) +{ + EMFolderView *emfv = data; + GPtrArray *uids; + + if (!em_utils_check_user_can_send_mail ((GtkWidget *) emfv)) + return; + + uids = message_list_get_selected (emfv->list); + em_utils_forward_inline ((GtkWidget *) emfv, emfv->folder, uids); +} + +static void +emfv_message_forward_quoted (BonoboUIComponent *uic, void *data, const char *path) +{ + EMFolderView *emfv = data; + GPtrArray *uids; + + if (!em_utils_check_user_can_send_mail ((GtkWidget *) emfv)) + return; + + uids = message_list_get_selected (emfv->list); + em_utils_forward_quoted ((GtkWidget *) emfv, emfv->folder, uids); +} + +static void +emfv_message_redirect (BonoboUIComponent *uic, void *data, const char *path) +{ + EMFolderView *emfv = data; + + if (emfv->list->cursor_uid == NULL) + return; + + if (!em_utils_check_user_can_send_mail ((GtkWidget *) emfv)) + return; + + em_utils_redirect_message_by_uid ((GtkWidget *) emfv, emfv->folder, emfv->list->cursor_uid); +} + +static void +emfv_message_post_reply (BonoboUIComponent *uic, void *data, const char *path) +{ + EMFolderView *emfv = data; + + if (emfv->list->cursor_uid == NULL) + return; + + if (!em_utils_check_user_can_send_mail ((GtkWidget *) emfv)) + return; + + em_utils_post_reply_to_message_by_uid ((GtkWidget *) emfv, emfv->folder, emfv->list->cursor_uid); +} + +static void +emfv_message_reply(EMFolderView *emfv, int mode) +{ + /* GtkClipboard *clip; */ + + if (emfv->list->cursor_uid == NULL) + return; + + if (!em_utils_check_user_can_send_mail ((GtkWidget *) emfv)) + return; + + /* Look away! Look away! */ + + /* HACK: Nasty internal gtkhtml poking going on here */ + + /* Disabled since there's no simple way to find out if + gtkhtml has the primary selection right now */ + + /* Ugh, to use the clipboard we need to request the selection + and have an async callback - painful to deal with */ + + /*clip = gtk_clipboard_get(GDK_SELECTION_PRIMARY);*/ + if (FALSE /*gtk_clipboard_get_owner(clip) == (GObject *)emfv->preview*/ + && ((EMFormatHTML *)emfv->preview)->html->engine->primary) { + CamelMimeMessage *msg, *src; + struct _header_raw *header; + HTMLEngineSaveState *state; + + src = (CamelMimeMessage *)((EMFormat *)emfv->preview)->message; + msg = camel_mime_message_new(); + + header = ((CamelMimePart *)src)->headers; + while (header) { + /* FIXME: shouldn't we strip out *all* Content-* headers? */ + if (g_ascii_strcasecmp(header->name, "content-type") != 0) + camel_medium_add_header((CamelMedium *)msg, header->name, header->value); + header = header->next; + } + + state = html_engine_save_buffer_new(((EMFormatHTML *)emfv->preview)->html->engine, TRUE); + html_object_save(((EMFormatHTML *)emfv->preview)->html->engine->primary, state); + camel_mime_part_set_content((CamelMimePart *)msg, + ((GString *)state->user_data)->str, + ((GString *)state->user_data)->len, + "text/html"); + + html_engine_save_buffer_free(state); + + em_utils_reply_to_message((GtkWidget *)emfv, msg, mode); + camel_object_unref(msg); + } else { + em_utils_reply_to_message_by_uid ((GtkWidget *) emfv, emfv->folder, emfv->list->cursor_uid, mode); + } + + /*g_object_unref(clip);*/ +} + +static void +emfv_message_search(BonoboUIComponent *uic, void *data, const char *path) +{ + EMFolderView *emfv = data; + + em_format_html_display_search(emfv->preview); +} + +static void +emfv_print_preview_message(BonoboUIComponent *uic, void *data, const char *path) +{ + EMFolderView *emfv = data; + + em_folder_view_print(emfv, TRUE); +} + +static void +emfv_text_zoom_in(BonoboUIComponent *uic, void *data, const char *path) +{ + EMFolderView *emfv = data; + + if (emfv->preview) + em_format_html_display_zoom_in(emfv->preview); +} + +static void +emfv_text_zoom_out(BonoboUIComponent *uic, void *data, const char *path) +{ + EMFolderView *emfv = data; + + if (emfv->preview) + em_format_html_display_zoom_out(emfv->preview); +} + +static void +emfv_text_zoom_reset(BonoboUIComponent *uic, void *data, const char *path) +{ + EMFolderView *emfv = data; + + if (emfv->preview) + em_format_html_display_zoom_reset(emfv->preview); +} + +/* ********************************************************************** */ + +struct _filter_data { + CamelFolder *folder; + const char *source; + char *uid; + int type; + char *uri; + char *mlist; +}; + +static void +filter_data_free (struct _filter_data *fdata) +{ + g_free (fdata->uid); + g_free (fdata->uri); + if (fdata->folder) + camel_object_unref (fdata->folder); + g_free (fdata->mlist); + g_free (fdata); +} + +static void +filter_type_got_message (CamelFolder *folder, const char *uid, CamelMimeMessage *msg, void *user_data) +{ + struct _filter_data *data = user_data; + + if (msg) + filter_gui_add_from_message (msg, data->source, data->type); + + filter_data_free (data); +} + +static void +filter_type_uid (CamelFolder *folder, const char *uid, const char *source, int type) +{ + struct _filter_data *data; + + data = g_malloc0 (sizeof (*data)); + data->type = type; + data->source = source; + + mail_get_message (folder, uid, filter_type_got_message, data, mail_thread_new); +} + +static void +filter_type_current (EMFolderView *emfv, int type) +{ + const char *source; + GPtrArray *uids; + + if (em_utils_folder_is_sent (emfv->folder, emfv->folder_uri) + || em_utils_folder_is_outbox (emfv->folder, emfv->folder_uri)) + source = FILTER_SOURCE_OUTGOING; + else + source = FILTER_SOURCE_INCOMING; + + uids = message_list_get_selected (emfv->list); + + if (uids->len == 1) + filter_type_uid (emfv->folder, (char *) uids->pdata[0], source, type); + + em_utils_uids_free (uids); +} + +EMFV_MAP_CALLBACK(emfv_tools_filter_subject, emfv_popup_filter_subject) +EMFV_MAP_CALLBACK(emfv_tools_filter_sender, emfv_popup_filter_sender) +EMFV_MAP_CALLBACK(emfv_tools_filter_recipient, emfv_popup_filter_recipients) +EMFV_MAP_CALLBACK(emfv_tools_filter_mlist, emfv_popup_filter_mlist) + +static void +vfolder_type_got_message (CamelFolder *folder, const char *uid, CamelMimeMessage *msg, void *user_data) +{ + struct _filter_data *data = user_data; + + if (msg) + vfolder_gui_add_from_message (msg, data->type, data->uri); + + filter_data_free (data); +} + +static void +vfolder_type_uid (CamelFolder *folder, const char *uid, const char *uri, int type) +{ + struct _filter_data *data; + + data = g_malloc0 (sizeof (*data)); + data->type = type; + data->uri = g_strdup (uri); + + mail_get_message (folder, uid, vfolder_type_got_message, data, mail_thread_new); +} + +static void +vfolder_type_current (EMFolderView *emfv, int type) +{ + GPtrArray *uids; + + uids = message_list_get_selected (emfv->list); + + if (uids->len == 1) + vfolder_type_uid (emfv->folder, (char *) uids->pdata[0], emfv->folder_uri, type); + + em_utils_uids_free (uids); +} + +EMFV_MAP_CALLBACK(emfv_tools_vfolder_subject, emfv_popup_vfolder_subject) +EMFV_MAP_CALLBACK(emfv_tools_vfolder_sender, emfv_popup_vfolder_sender) +EMFV_MAP_CALLBACK(emfv_tools_vfolder_recipient, emfv_popup_vfolder_recipients) +EMFV_MAP_CALLBACK(emfv_tools_vfolder_mlist, emfv_popup_vfolder_mlist) + +/* ********************************************************************** */ + +static void +emfv_view_load_images(BonoboUIComponent *uic, void *data, const char *path) +{ + EMFolderView *emfv = data; + + if (emfv->preview) + em_format_html_load_http((EMFormatHTML *)emfv->preview); +} + +static BonoboUIVerb emfv_message_verbs[] = { + 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 ("MailNext", emfv_mail_next), + BONOBO_UI_UNSAFE_VERB ("MailNextFlagged", emfv_mail_next_flagged), + BONOBO_UI_UNSAFE_VERB ("MailNextUnread", emfv_mail_next_unread), + BONOBO_UI_UNSAFE_VERB ("MailNextThread", emfv_mail_next_thread), + BONOBO_UI_UNSAFE_VERB ("MailPrevious", emfv_mail_previous), + BONOBO_UI_UNSAFE_VERB ("MailPreviousFlagged", emfv_mail_previous_flagged), + BONOBO_UI_UNSAFE_VERB ("MailPreviousUnread", emfv_mail_previous_unread), + + BONOBO_UI_UNSAFE_VERB ("AddSenderToAddressbook", emfv_add_sender_addressbook), + + BONOBO_UI_UNSAFE_VERB ("MessageApplyFilters", emfv_message_apply_filters), + BONOBO_UI_UNSAFE_VERB ("MessageCopy", emfv_message_copy), + BONOBO_UI_UNSAFE_VERB ("MessageDelete", emfv_message_delete), + BONOBO_UI_UNSAFE_VERB ("MessageForward", emfv_message_forward), + BONOBO_UI_UNSAFE_VERB ("MessageForwardAttached", emfv_message_forward_attached), + BONOBO_UI_UNSAFE_VERB ("MessageForwardInline", emfv_message_forward_inline), + BONOBO_UI_UNSAFE_VERB ("MessageForwardQuoted", emfv_message_forward_quoted), + BONOBO_UI_UNSAFE_VERB ("MessageRedirect", emfv_message_redirect), + BONOBO_UI_UNSAFE_VERB ("MessageMarkAsRead", emfv_message_mark_read), + BONOBO_UI_UNSAFE_VERB ("MessageMarkAsUnRead", emfv_message_mark_unread), + BONOBO_UI_UNSAFE_VERB ("MessageMarkAsImportant", emfv_message_mark_important), + BONOBO_UI_UNSAFE_VERB ("MessageMarkAsUnimportant", emfv_message_mark_unimportant), + BONOBO_UI_UNSAFE_VERB ("MessageFollowUpFlag", emfv_message_followup_flag), + BONOBO_UI_UNSAFE_VERB ("MessageMove", emfv_message_move), + BONOBO_UI_UNSAFE_VERB ("MessageOpen", emfv_message_open), + BONOBO_UI_UNSAFE_VERB ("MessagePostReply", emfv_message_post_reply), + BONOBO_UI_UNSAFE_VERB ("MessageReplyAll", emfv_message_reply_all), + BONOBO_UI_UNSAFE_VERB ("MessageReplyList", emfv_message_reply_list), + BONOBO_UI_UNSAFE_VERB ("MessageReplySender", emfv_message_reply_sender), + BONOBO_UI_UNSAFE_VERB ("MessageResend", emfv_message_resend), + BONOBO_UI_UNSAFE_VERB ("MessageSaveAs", emfv_message_saveas), + BONOBO_UI_UNSAFE_VERB ("MessageSearch", emfv_message_search), + BONOBO_UI_UNSAFE_VERB ("MessageUndelete", emfv_message_undelete), + + BONOBO_UI_UNSAFE_VERB ("PrintMessage", emfv_print_message), + BONOBO_UI_UNSAFE_VERB ("PrintPreviewMessage", emfv_print_preview_message), + + BONOBO_UI_UNSAFE_VERB ("TextZoomIn", emfv_text_zoom_in), + BONOBO_UI_UNSAFE_VERB ("TextZoomOut", emfv_text_zoom_out), + BONOBO_UI_UNSAFE_VERB ("TextZoomReset", emfv_text_zoom_reset), + + /* TODO: This stuff should just be 1 item that runs a wizard */ + BONOBO_UI_UNSAFE_VERB ("ToolsFilterMailingList", emfv_tools_filter_mlist), + BONOBO_UI_UNSAFE_VERB ("ToolsFilterRecipient", emfv_tools_filter_recipient), + BONOBO_UI_UNSAFE_VERB ("ToolsFilterSender", emfv_tools_filter_sender), + BONOBO_UI_UNSAFE_VERB ("ToolsFilterSubject", emfv_tools_filter_subject), + BONOBO_UI_UNSAFE_VERB ("ToolsVFolderMailingList", emfv_tools_vfolder_mlist), + BONOBO_UI_UNSAFE_VERB ("ToolsVFolderRecipient", emfv_tools_vfolder_recipient), + BONOBO_UI_UNSAFE_VERB ("ToolsVFolderSender", emfv_tools_vfolder_sender), + BONOBO_UI_UNSAFE_VERB ("ToolsVFolderSubject", emfv_tools_vfolder_subject), + + BONOBO_UI_UNSAFE_VERB ("ViewLoadImages", emfv_view_load_images), + /* ViewHeaders stuff is a radio */ + /* CaretMode is a toggle */ + + BONOBO_UI_VERB_END +}; +static EPixmap emfv_message_pixmaps[] = { + E_PIXMAP ("/commands/EditCut", "16_cut.png"), + E_PIXMAP ("/commands/EditCopy", "16_copy.png"), + E_PIXMAP ("/commands/EditPaste", "16_paste.png"), + + E_PIXMAP ("/commands/PrintMessage", "print.xpm"), + E_PIXMAP ("/commands/PrintPreviewMessage", "print-preview.xpm"), + E_PIXMAP ("/commands/MessageDelete", "evolution-trash-mini.png"), + E_PIXMAP ("/commands/MessageUndelete", "undelete_message-16.png"), + E_PIXMAP ("/commands/MessageCopy", "copy_16_message.xpm"), + E_PIXMAP ("/commands/MessageMove", "move_message.xpm"), + E_PIXMAP ("/commands/MessageReplyAll", "reply_to_all.xpm"), + E_PIXMAP ("/commands/MessageReplySender", "reply.xpm"), + E_PIXMAP ("/commands/MessageForward", "forward.xpm"), + E_PIXMAP ("/commands/MessageApplyFilters", "apply-filters-16.xpm"), + E_PIXMAP ("/commands/MessageSearch", "search-16.png"), + E_PIXMAP ("/commands/MessageSaveAs", "save-as-16.png"), + E_PIXMAP ("/commands/MessageMarkAsRead", "mail-read.xpm"), + E_PIXMAP ("/commands/MessageMarkAsUnRead", "mail-new.xpm"), + E_PIXMAP ("/commands/MessageMarkAsImportant", "priority-high.xpm"), + E_PIXMAP ("/commands/MessageFollowUpFlag", "flag-for-followup-16.png"), + + E_PIXMAP ("/Toolbar/MailMessageToolbar/MessageReplySender", "buttons/reply.png"), + E_PIXMAP ("/Toolbar/MailMessageToolbar/MessageReplyAll", "buttons/reply-to-all.png"), + E_PIXMAP ("/Toolbar/MailMessageToolbar/MessageForward", "buttons/forward.png"), + E_PIXMAP ("/Toolbar/MailMessageToolbar/PrintMessage", "buttons/print.png"), + E_PIXMAP ("/Toolbar/MailMessageToolbar/MessageMove", "buttons/move-message.png"), + E_PIXMAP ("/Toolbar/MailMessageToolbar/MessageCopy", "buttons/copy-message.png"), + E_PIXMAP ("/Toolbar/MailMessageToolbar/MessageDelete", "buttons/delete-message.png"), + + E_PIXMAP ("/Toolbar/MailNextButtons/MailNext", "buttons/next-message.png"), + E_PIXMAP ("/Toolbar/MailNextButtons/MailPrevious", "buttons/previous-message.png"), + + E_PIXMAP_END +}; + +/* this is added to emfv->enable_map in :init() */ +static const EMFolderViewEnable emfv_enable_map[] = { + { "EditCut", EM_POPUP_SELECT_MANY }, + { "EditCopy", EM_POPUP_SELECT_MANY }, + { "EditPaste", 0 }, + + /* FIXME: should these be single-selection? */ + { "MailNext", EM_POPUP_SELECT_MANY }, + { "MailNextFlagged", EM_POPUP_SELECT_MANY }, + { "MailNextUnread", EM_POPUP_SELECT_MANY }, + { "MailNextThread", EM_POPUP_SELECT_MANY }, + { "MailPrevious", EM_POPUP_SELECT_MANY }, + { "MailPreviousFlagged", EM_POPUP_SELECT_MANY }, + { "MailPreviousUnread", EM_POPUP_SELECT_MANY }, + + { "AddSenderToAddressbook", EM_POPUP_SELECT_ADD_SENDER }, + + { "MessageApplyFilters", EM_POPUP_SELECT_MANY }, + { "MessageCopy", EM_POPUP_SELECT_MANY }, + { "MessageDelete", EM_POPUP_SELECT_MANY|EM_POPUP_SELECT_DELETE }, + { "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 }, + { "MessageFollowUpFlag", EM_POPUP_SELECT_MANY }, + { "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 }, + { "MessageResend", EM_POPUP_SELECT_RESEND }, + { "MessageSaveAs", EM_POPUP_SELECT_MANY }, + { "MessageSearch", EM_POPUP_SELECT_ONE }, + { "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 }, + { "ToolsFilterRecipient", EM_POPUP_SELECT_ONE }, + { "ToolsFilterSender", EM_POPUP_SELECT_ONE }, + { "ToolsFilterSubject", EM_POPUP_SELECT_ONE }, + { "ToolsVFolderMailingList", EM_POPUP_SELECT_ONE }, + { "ToolsVFolderRecipient", EM_POPUP_SELECT_ONE }, + { "ToolsVFolderSender", EM_POPUP_SELECT_ONE }, + { "ToolsVFolderSubject", EM_POPUP_SELECT_ONE }, + + { "ViewLoadImages", EM_POPUP_SELECT_ONE }, + + { NULL }, + + /* always enabled + + { "ViewFullHeaders", IS_0MESSAGE, 0 }, + { "ViewNormal", IS_0MESSAGE, 0 }, + { "ViewSource", IS_0MESSAGE, 0 }, + { "CaretMode", IS_0MESSAGE, 0 }, */ +}; + +static void +emfv_enable_menus(EMFolderView *emfv) +{ + guint32 disable_mask; + GString *name; + GSList *l; + EMPopupTarget *t; + + if (emfv->uic == NULL) + return; + + if (emfv->folder) { + t = em_folder_view_get_popup_target(emfv); + disable_mask = t->mask; + em_popup_target_free(t); + } 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); +} + +/* must match em_format_mode_t order */ +static const char * const emfv_display_styles[] = { + "/commands/ViewNormal", + "/commands/ViewFullHeaders", + "/commands/ViewSource" +}; + +static void +emfv_view_mode(BonoboUIComponent *uic, const char *path, Bonobo_UIComponent_EventType type, const char *state, void *data) +{ + EMFolderView *emfv = data; + int i; + + if (type != Bonobo_UIComponent_STATE_CHANGED + || state[0] == '0') + return; + + /* TODO: I don't like this stuff much, is there any way we can move listening for such events + elsehwere? Probably not I guess, unless there's a EMFolderViewContainer for bonobo usage + of a folder view */ + + for (i=0;i<= EM_FORMAT_SOURCE;i++) { + if (strcmp(emfv_display_styles[i]+strlen("/commands/"), path) == 0) { + em_format_set_mode((EMFormat *)emfv->preview, i); + + if (TRUE /* set preferences but not for EMMessageBrowser? */) { + GConfClient *gconf = mail_config_get_gconf_client (); + + gconf_client_set_int (gconf, "/apps/evolution/mail/display/message_style", i, NULL); + } + break; + } + } +} + +static void +emfv_caret_mode(BonoboUIComponent *uic, const char *path, Bonobo_UIComponent_EventType type, const char *state, void *data) +{ + EMFolderView *emfv = data; + + if (type != Bonobo_UIComponent_STATE_CHANGED) + return; + + em_format_html_display_set_caret_mode(emfv->preview, state[0] != '0'); + + gconf_client_set_bool(mail_config_get_gconf_client(), "/apps/evolution/mail/display/caret_mode", state[0] != '0', NULL); +} + +static void +emfv_charset_changed(BonoboUIComponent *uic, const char *path, Bonobo_UIComponent_EventType type, const char *state, void *data) +{ + EMFolderView *emfv = data; + + if (type != Bonobo_UIComponent_STATE_CHANGED) + return; + + /* menu items begin with "Charset-" = 8 characters */ + if (state[0] != '0' && strlen(path) > 8) { + path += 8; + /* default charset used in mail view */ + if (!strcmp(path, _("Default"))) + path = NULL; + + em_format_set_charset((EMFormat *)emfv->preview, path); + } +} + +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); + e_pixmaps_update(uic, emfv_message_pixmaps); + + 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; + bonobo_ui_component_set_prop(uic, emfv_display_styles[style], "state", "1", NULL); + bonobo_ui_component_add_listener(uic, "ViewNormal", emfv_view_mode, emfv); + bonobo_ui_component_add_listener(uic, "ViewFullHeaders", emfv_view_mode, emfv); + bonobo_ui_component_add_listener(uic, "ViewSource", emfv_view_mode, emfv); + em_format_set_mode((EMFormat *)emfv->preview, style); + + if (emfv->folder && !em_utils_folder_is_sent(emfv->folder, emfv->folder_uri)) + bonobo_ui_component_set_prop(uic, "/commands/MessageResend", "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); + } else { + const BonoboUIVerb *v; + + /* 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; + } +} + +int em_folder_view_print(EMFolderView *emfv, int preview) +{ + /*struct _EMFolderViewPrivate *p = emfv->priv;*/ + EMFormatHTMLPrint *print; + GnomePrintConfig *config = NULL; + int res; + struct _CamelMedium *msg; + + /* FIXME: need to load the message first */ + if (!emfv->preview_active) + return 0; + + msg = emfv->preview->formathtml.format.message; + if (msg == NULL) + return 0; + + if (!preview) { + GtkDialog *dialog = (GtkDialog *)gnome_print_dialog_new(NULL, _("Print Message"), GNOME_PRINT_DIALOG_COPIES); + + gtk_dialog_set_default_response(dialog, GNOME_PRINT_DIALOG_RESPONSE_PRINT); + e_dialog_set_transient_for ((GtkWindow *) dialog, (GtkWidget *) emfv); + + switch (gtk_dialog_run(dialog)) { + case GNOME_PRINT_DIALOG_RESPONSE_PRINT: + break; + case GNOME_PRINT_DIALOG_RESPONSE_PREVIEW: + preview = TRUE; + break; + default: + gtk_widget_destroy((GtkWidget *)dialog); + return 0; + } + + config = gnome_print_dialog_get_config((GnomePrintDialog *)dialog); + gtk_widget_destroy((GtkWidget *)dialog); + } + + print = em_format_html_print_new(); + res = em_format_html_print_print(print, msg, (EMFormatHTML *)emfv->preview, config, preview); + g_object_unref(print); + if (config) + g_object_unref(config); + + return res; +} + +EMPopupTarget * +em_folder_view_get_popup_target(EMFolderView *emfv) +{ + EMPopupTarget *t; + + t = em_popup_target_new_select(emfv->folder, emfv->folder_uri, message_list_get_selected(emfv->list)); + t->widget = (GtkWidget *)emfv; + + if (emfv->list->threaded) + t->mask &= ~EM_FOLDER_VIEW_SELECT_THREADED; + + if (message_list_hidden(emfv->list) != 0) + t->mask &= ~EM_FOLDER_VIEW_SELECT_HIDDEN; + + return t; +} + +/* ********************************************************************** */ + +struct mst_t { + EMFolderView *emfv; + char *uid; +}; + +static void +mst_free (struct mst_t *mst) +{ + mst->emfv->priv->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)) + camel_folder_set_message_flags (emfv->folder, mst->uid, CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN); + + return FALSE; +} + +static void +emfv_list_done_message_selected(CamelFolder *folder, const char *uid, CamelMimeMessage *msg, void *data) +{ + EMFolderView *emfv = data; + + em_format_format((EMFormat *) emfv->preview, (struct _CamelMedium *)msg); + + if (emfv->priv->seen_id) + g_source_remove(emfv->priv->seen_id); + + if (msg && emfv->mark_seen) { + 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->priv->seen_id = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, emfv->mark_seen_timeout, + (GSourceFunc)do_mark_seen, mst, (GDestroyNotify)mst_free); + } else { + camel_folder_set_message_flags(emfv->folder, uid, CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN); + } + } +} + +static void +emfv_list_message_selected(MessageList *ml, const char *uid, EMFolderView *emfv) +{ + /* FIXME: ui stuff based on messageinfo, if available */ + + if (emfv->preview_active) { + if (uid) + mail_get_message(emfv->folder, uid, emfv_list_done_message_selected, emfv, mail_thread_new); + else + em_format_format((EMFormat *)emfv->preview, NULL); + } + + emfv_enable_menus(emfv); +} + +static void +emfv_list_double_click(ETree *tree, gint row, ETreePath path, gint col, GdkEvent *event, EMFolderView *emfv) +{ + /* Ignore double-clicks on columns that handle thier own state */ + if (MESSAGE_LIST_COLUMN_IS_ACTIVE (col)) + return; + + em_folder_view_open_selected(emfv); +} + +static int +emfv_list_right_click(ETree *tree, gint row, ETreePath path, gint col, GdkEvent *event, EMFolderView *emfv) +{ + emfv_popup(emfv, event); + + return TRUE; +} + +static int +emfv_list_key_press(ETree *tree, int row, ETreePath path, int col, GdkEvent *ev, EMFolderView *emfv) +{ + GPtrArray *uids; + int i; + guint32 flags; + + if ((ev->key.state & GDK_CONTROL_MASK) != 0) + return FALSE; + + switch (ev->key.keyval) { + case GDK_Return: + case GDK_KP_Enter: + case GDK_ISO_Enter: + em_folder_view_open_selected(emfv); + break; + case GDK_Delete: + case GDK_KP_Delete: + /* If any messages are undeleted, run delete, if all are deleted, run undelete */ + flags = 0; + uids = message_list_get_selected(emfv->list); + for (i = 0; i < uids->len; i++) { + if ((camel_folder_get_message_flags(emfv->folder, uids->pdata[i]) & CAMEL_MESSAGE_DELETED) == 0) + break; + } + message_list_free_uids(emfv->list, uids); + if (i == uids->len) + emfv_popup_undelete(NULL, emfv); + else + emfv_popup_delete(NULL, emfv); + break; + case GDK_Menu: + /* FIXME: location of popup */ + emfv_popup(emfv, NULL); + break; + case '!': + uids = message_list_get_selected(emfv->list); + + camel_folder_freeze(emfv->folder); + for (i = 0; i < uids->len; i++) { + flags = camel_folder_get_message_flags(emfv->folder, uids->pdata[i]) ^ CAMEL_MESSAGE_FLAGGED; + if (flags & CAMEL_MESSAGE_FLAGGED) + flags &= ~CAMEL_MESSAGE_DELETED; + camel_folder_set_message_flags(emfv->folder, uids->pdata[i], + CAMEL_MESSAGE_FLAGGED|CAMEL_MESSAGE_DELETED, flags); + } + camel_folder_thaw(emfv->folder); + + message_list_free_uids(emfv->list, uids); + break; + default: + return FALSE; + } + + return TRUE; +} + +static void +emfv_format_link_clicked(EMFormatHTMLDisplay *efhd, const char *uri, EMFolderView *emfv) +{ + if (!strncasecmp (uri, "mailto:", 7)) { + em_utils_compose_new_message_with_mailto ((GtkWidget *) efhd, uri); + } else if (*uri == '#') { + gtk_html_jump_to_anchor (((EMFormatHTML *) efhd)->html, uri + 1); + } else if (!strncasecmp (uri, "thismessage:", 12)) { + /* ignore */ + } else if (!strncasecmp (uri, "cid:", 4)) { + /* ignore */ + } else { + GError *err = NULL; + + gnome_url_show (uri, &err); + + if (err) { + g_warning ("gnome_url_show: %s", err->message); + g_error_free (err); + } + } +} + +static int +emfv_format_popup_event(EMFormatHTMLDisplay *efhd, GdkEventButton *event, const char *uri, CamelMimePart *part, EMFolderView *emfv) +{ + EMPopup *emp; + EMPopupTarget *target; + GtkMenu *menu; + + /* FIXME: this maybe should just fit on em-html-display, it has access to the + snooped part type */ + + emp = em_popup_new("com.ximian.mail.folderview.popup.uri"); + if (part) + target = em_popup_target_new_part(part, NULL); + else + target = em_popup_target_new_uri(uri); + + menu = em_popup_create_menu_once(emp, target, target->mask, target->mask); + gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time); + + return TRUE; +} + +static void +emfv_gui_folder_changed(CamelFolder *folder, void *dummy, EMFolderView *emfv) +{ + emfv_enable_menus(emfv); + g_object_unref(emfv); +} + +static void +emfv_folder_changed(CamelFolder *folder, CamelFolderChangeInfo *changes, EMFolderView *emfv) +{ + g_object_ref(emfv); + mail_async_event_emit(emfv->async, MAIL_ASYNC_GUI, (MailAsyncFunc)emfv_gui_folder_changed, folder, NULL, emfv); +} + +/* keep these two tables in sync */ +enum { + EMFV_ANIMATE_IMAGES = 1, + EMFV_CITATION_COLOUR, + EMFV_CITATION_MARK, + EMFV_CARET_MODE, + EMFV_MESSAGE_STYLE, + EMFV_MARK_SEEN, + EMFV_MARK_SEEN_TIMEOUT, + EMFV_SETTINGS /* last, for loop count */ +}; + +/* IF these get too long, update key field */ +static const char * const emfv_display_keys[] = { + "animate_images", + "citation_colour", + "mark_citations", + "caret_mode", + "message_style", + "mark_seen", + "mark_seen_timeout" +}; + +static GHashTable *emfv_setting_key; + +static void +emfv_setting_notify(GConfClient *gconf, guint cnxn_id, GConfEntry *entry, EMFolderView *emfv) +{ + char *tkey; + + g_return_if_fail (gconf_entry_get_key (entry) != NULL); + g_return_if_fail (gconf_entry_get_value (entry) != NULL); + + 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(gconf_entry_get_value(entry))); + break; + case EMFV_CITATION_COLOUR: { + const char *s; + GdkColor colour; + guint32 rgb; + + s = gconf_value_get_string(gconf_entry_get_value(entry)); + 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(gconf_entry_get_value(entry)), + ((EMFormatHTML *)emfv->preview)->citation_colour); + break; + case EMFV_CARET_MODE: + em_format_html_display_set_caret_mode(emfv->preview, gconf_value_get_bool(gconf_entry_get_value(entry))); + break; + case EMFV_MESSAGE_STYLE: { + int style = gconf_value_get_int(gconf_entry_get_value(entry)); + + 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(gconf_entry_get_value(entry)); + break; + case EMFV_MARK_SEEN_TIMEOUT: + emfv->mark_seen_timeout = gconf_value_get_int(gconf_entry_get_value(entry)); + 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;i<EMFV_SETTINGS;i++) + g_hash_table_insert(emfv_setting_key, (void *)emfv_display_keys[i-1], GINT_TO_POINTER(i)); + } + + gconf_client_add_dir(gconf, "/apps/evolution/mail/display", GCONF_CLIENT_PRELOAD_NONE, NULL); + + for (i=1;err == NULL && i<EMFV_SETTINGS;i++) { + sprintf(key, "/apps/evolution/mail/display/%s", emfv_display_keys[i-1]); + entry = gconf_client_get_entry(gconf, key, NULL, TRUE, &err); + if (entry) { + emfv_setting_notify(gconf, 0, entry, emfv); + gconf_entry_free(entry); + } + } + + if (err) { + g_warning("Could not load display settings: %s", err->message); + 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); +} diff --git a/mail/em-folder-view.h b/mail/em-folder-view.h new file mode 100644 index 0000000000..284de69c1b --- /dev/null +++ b/mail/em-folder-view.h @@ -0,0 +1,117 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifndef _EM_FOLDER_VIEW_H +#define _EM_FOLDER_VIEW_H + +#include <gtk/gtkvbox.h> +#include "em-popup.h" + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +struct _MessageList; +struct _EMFormatHTMLDisplay; +struct _CamelFolder; +struct _CamelMedium; + +typedef struct _EMFolderView EMFolderView; +typedef struct _EMFolderViewClass EMFolderViewClass; + +typedef struct _EMFolderViewEnable EMFolderViewEnable; + +enum { + EM_FOLDER_VIEW_SELECT_THREADED = EM_POPUP_SELECT_LAST, + EM_FOLDER_VIEW_SELECT_HIDDEN = EM_POPUP_SELECT_LAST<<1, + EM_FOLDER_VIEW_SELECT_LAST = EM_POPUP_SELECT_LAST<<2, +}; + +struct _EMFolderViewEnable { + const char *name; /* bonobo name, relative to /commands/ */ + guint32 mask; /* disable mask, see EM_FOLDER_VIEW_CAN* flags */ +}; + +struct _EMFolderView { + GtkVBox parent; + + struct _EMFolderViewPrivate *priv; + + struct _MessageList *list; + struct _EMFormatHTMLDisplay *preview; + + struct _CamelFolder *folder; + char *folder_uri; + + /* used to load ui from base activate implementation */ + GSList *ui_files; /* const char * list, TODO: should this be on class? */ + const char *ui_app_name; + + /* for proxying jobs to main or other threads */ + struct _MailAsyncEvent *async; + + struct _BonoboUIComponent *uic; /* if we're active, this will be set */ + GSList *enable_map; /* bonobo menu enable map, entries are 0-terminated EMFolderViewEnable arryas + TODO: should this be on class? */ + + int mark_seen_timeout; /* local copy of gconf stuff */ + int mark_seen:1; + int preview_active:1; /* is preview being used */ +}; + +struct _EMFolderViewClass { + GtkVBoxClass parent_class; + + /* if used as a control, used to activate/deactivate custom menu's */ + void (*activate)(EMFolderView *, struct _BonoboUIComponent *uic, int state); + + void (*set_folder_uri)(EMFolderView *emfv, const char *uri); + void (*set_folder)(EMFolderView *emfv, struct _CamelFolder *folder, const char *uri); + void (*set_message)(EMFolderView *emfv, const char *uid); +}; + +GType em_folder_view_get_type(void); + +GtkWidget *em_folder_view_new(void); + +#define em_folder_view_activate(emfv, uic, state) ((EMFolderViewClass *)G_OBJECT_GET_CLASS(emfv))->activate((emfv), (uic), (state)) +#define em_folder_view_set_folder(emfv, folder, uri) ((EMFolderViewClass *)G_OBJECT_GET_CLASS(emfv))->set_folder((emfv), (folder), (uri)) +#define em_folder_view_set_folder_uri(emfv, uri) ((EMFolderViewClass *)G_OBJECT_GET_CLASS(emfv))->set_folder_uri((emfv), (uri)) +#define em_folder_view_set_message(emfv, uid) ((EMFolderViewClass *)G_OBJECT_GET_CLASS(emfv))->set_message((emfv), (uid)) + +struct _EMPopupTarget *em_folder_view_get_popup_target(EMFolderView *emfv); + +int em_folder_view_mark_selected(EMFolderView *emfv, guint32 mask, guint32 set); +int em_folder_view_open_selected(EMFolderView *emfv); + +int em_folder_view_print(EMFolderView *emfv, int preview); + +/* this could be on message-list */ +guint32 em_folder_view_disable_mask(EMFolderView *emfv); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* ! _EM_FOLDER_VIEW_H */ diff --git a/mail/em-format-html-display.c b/mail/em-format-html-display.c new file mode 100644 index 0000000000..2b59f09e85 --- /dev/null +++ b/mail/em-format-html-display.c @@ -0,0 +1,1159 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <gtkhtml/gtkhtml.h> +#include <gtkhtml/htmlengine.h> +#include <gtkhtml/htmlobject.h> +#include <gtkhtml/htmliframe.h> +#include <gtkhtml/htmlinterval.h> +#include <gtkhtml/gtkhtml-embedded.h> +#include <gtkhtml/gtkhtml-search.h> + +#include <gtk/gtkvbox.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkbutton.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkimage.h> +#include <gtk/gtkarrow.h> + +#include <gtk/gtkmenu.h> +#include <gtk/gtkmenuitem.h> + +#include <glade/glade.h> + +#include <libgnomevfs/gnome-vfs-mime-handlers.h> + +#if 0 +#include <libgnomevfs/gnome-vfs-utils.h> +#include <libgnomevfs/gnome-vfs-mime-utils.h> +#include <libgnomevfs/gnome-vfs-mime.h> +#endif + +#include <bonobo/bonobo-control-frame.h> +#include <bonobo/bonobo-stream-memory.h> +#include <bonobo/bonobo-widget.h> + +#include <camel/camel-stream.h> +#include <camel/camel-stream-mem.h> +#include <camel/camel-mime-filter-tohtml.h> +#include <camel/camel-mime-part.h> +#include <camel/camel-multipart.h> +#include <camel/camel-multipart-signed.h> +#include <camel/camel-internet-address.h> +#include <camel/camel-mime-message.h> +#include <camel/camel-gpg-context.h> + +#include <e-util/e-msgport.h> +#include <e-util/e-gui-utils.h> +#include <e-util/e-dialog-utils.h> + +#include "mail-config.h" + +#include "em-format-html-display.h" +#include "em-marshal.h" +#include "e-searching-tokenizer.h" +#include "em-icon-stream.h" +#include "em-utils.h" +#include "em-popup.h" +#include "em-icon-stream.h" + +#define d(x) + +#define EFHD_TABLE_OPEN "<table>" + +struct _EMFormatHTMLDisplayPrivate { + /* For the interactive search dialogue */ + /* TODO: Should this be more subtle, like the mozilla one? */ + GtkDialog *search_dialog; + GtkWidget *search_entry; + GtkWidget *search_matches_label; + GtkWidget *search_case_check; + char *search_text; + int search_wrap; /* are we doing a wrap search */ +}; + +static int efhd_html_button_press_event (GtkWidget *widget, GdkEventButton *event, EMFormatHTMLDisplay *efh); +static void efhd_html_link_clicked (GtkHTML *html, const char *url, EMFormatHTMLDisplay *efhd); + +struct _attach_puri { + EMFormatPURI puri; + + const EMFormatHandler *handle; + + /* for the > and V buttons */ + GtkWidget *forward, *down; + /* currently no way to correlate this data to the frame :( */ + GtkHTML *frame; + CamelStream *output; + unsigned int shown:1; +}; + +static void efhd_iframe_created(GtkHTML *html, GtkHTML *iframe, EMFormatHTMLDisplay *efh); +/*static void efhd_url_requested(GtkHTML *html, const char *url, GtkHTMLStream *handle, EMFormatHTMLDisplay *efh); + static gboolean efhd_object_requested(GtkHTML *html, GtkHTMLEmbedded *eb, EMFormatHTMLDisplay *efh);*/ + +static void efhd_format_clone(EMFormat *, CamelMedium *, EMFormat *); +static void efhd_format_error(EMFormat *emf, CamelStream *stream, const char *txt); +static void efhd_format_message(EMFormat *, CamelStream *, CamelMedium *); +static void efhd_format_source(EMFormat *, CamelStream *, CamelMimePart *); +static void efhd_format_attachment(EMFormat *, CamelStream *, CamelMimePart *, const char *, const EMFormatHandler *); +static void efhd_complete(EMFormat *); + +static void efhd_builtin_init(EMFormatHTMLDisplayClass *efhc); + +enum { + EFHD_LINK_CLICKED, + EFHD_POPUP_EVENT, + EFHD_LAST_SIGNAL, +}; + +static guint efhd_signals[EFHD_LAST_SIGNAL] = { 0 }; + + +static EMFormatHTMLClass *efhd_parent; + +static void +efhd_gtkhtml_realise(GtkHTML *html, EMFormatHTMLDisplay *efhd) +{ + GtkStyle *style; + + /* FIXME: does this have to be re-done every time we draw? */ + + /* My favorite thing to do... muck around with colors so we respect people's stupid themes. + However, we only do this if we are rendering to the screen -- we ignore the theme + when we are printing. */ + style = gtk_widget_get_style((GtkWidget *)html); + if (style) { + int state = GTK_WIDGET_STATE(html); + gushort r, g, b; +#define SCALE (238) + + /* choose a suitably darker or lighter colour */ + r = style->base[state].red >> 8; + g = style->base[state].green >> 8; + b = style->base[state].blue >> 8; + + if (r+b+g > 128*3) { + r = (r*SCALE) >> 8; + g = (g*SCALE) >> 8; + b = (b*SCALE) >> 8; + } else { + r = 128 - ((SCALE * r) >> 9); + g = 128 - ((SCALE * g) >> 9); + b = 128 - ((SCALE * b) >> 9); + } + + efhd->formathtml.header_colour = ((r<<16) | (g<< 8) | b) & 0xffffff; + + r = style->text[state].red >> 8; + g = style->text[state].green >> 8; + b = style->text[state].blue >> 8; + + efhd->formathtml.text_colour = ((r<<16) | (g<< 8) | b) & 0xffffff; + } +#undef SCALE +} + +static void +efhd_init(GObject *o) +{ + EMFormatHTMLDisplay *efhd = (EMFormatHTMLDisplay *)o; +#define efh ((EMFormatHTML *)efhd) + + efhd->priv = g_malloc0(sizeof(*efhd->priv)); + + efhd->search_tok = (ESearchingTokenizer *)e_searching_tokenizer_new(); + html_engine_set_tokenizer(efh->html->engine, (HTMLTokenizer *)efhd->search_tok); + + g_signal_connect(efh->html, "realize", G_CALLBACK(efhd_gtkhtml_realise), o); + + /* we want to convert url's etc */ + efh->text_html_flags |= CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES; +#undef efh +} + +static void +efhd_finalise(GObject *o) +{ + EMFormatHTMLDisplay *efhd = (EMFormatHTMLDisplay *)o; + + /* check pending stuff */ + + g_free(efhd->priv->search_text); + g_free(efhd->priv); + + ((GObjectClass *)efhd_parent)->finalize(o); +} + +static gboolean +efhd_bool_accumulator(GSignalInvocationHint *ihint, GValue *out, const GValue *in, void *data) +{ + gboolean val = g_value_get_boolean(in); + + g_value_set_boolean(out, val); + + return !val; +} + +static void +efhd_class_init(GObjectClass *klass) +{ + ((EMFormatClass *)klass)->format_clone = efhd_format_clone; + ((EMFormatClass *)klass)->format_error = efhd_format_error; + ((EMFormatClass *)klass)->format_message = efhd_format_message; + ((EMFormatClass *)klass)->format_source = efhd_format_source; + ((EMFormatClass *)klass)->format_attachment = efhd_format_attachment; + ((EMFormatClass *)klass)->complete = efhd_complete; + + klass->finalize = efhd_finalise; + + efhd_signals[EFHD_LINK_CLICKED] = + g_signal_new("link_clicked", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(EMFormatHTMLDisplayClass, link_clicked), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + + efhd_signals[EFHD_POPUP_EVENT] = + g_signal_new("popup_event", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(EMFormatHTMLDisplayClass, popup_event), + efhd_bool_accumulator, NULL, + em_marshal_BOOLEAN__BOXED_POINTER_POINTER, + G_TYPE_BOOLEAN, 3, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE, + G_TYPE_POINTER, G_TYPE_POINTER); + + efhd_builtin_init((EMFormatHTMLDisplayClass *)klass); +} + +GType +em_format_html_display_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMFormatHTMLDisplayClass), + NULL, NULL, + (GClassInitFunc)efhd_class_init, + NULL, NULL, + sizeof(EMFormatHTMLDisplay), 0, + (GInstanceInitFunc)efhd_init + }; + efhd_parent = g_type_class_ref(em_format_html_get_type()); + type = g_type_register_static(em_format_html_get_type(), "EMFormatHTMLDisplay", &info, 0); + } + + return type; +} + +EMFormatHTMLDisplay *em_format_html_display_new(void) +{ + EMFormatHTMLDisplay *efhd; + + efhd = g_object_new(em_format_html_display_get_type(), 0); + + g_signal_connect(efhd->formathtml.html, "iframe_created", G_CALLBACK(efhd_iframe_created), efhd); + g_signal_connect(efhd->formathtml.html, "link_clicked", G_CALLBACK(efhd_html_link_clicked), efhd); + g_signal_connect(efhd->formathtml.html, "button_press_event", G_CALLBACK(efhd_html_button_press_event), efhd); + + return efhd; +} + +void em_format_html_display_goto_anchor(EMFormatHTMLDisplay *efhd, const char *name) +{ + printf("FIXME: go to anchor '%s'\n", name); +} + +void em_format_html_display_set_animate(EMFormatHTMLDisplay *efhd, gboolean state) +{ + efhd->animate = state; + gtk_html_set_animate(((EMFormatHTML *)efhd)->html, state); +} + +void em_format_html_display_set_caret_mode(EMFormatHTMLDisplay *efhd, gboolean state) +{ + efhd->caret_mode = state; + gtk_html_set_caret_mode(((EMFormatHTML *)efhd)->html, state); +} + +void +em_format_html_display_set_search(EMFormatHTMLDisplay *efhd, int type, GSList *strings) +{ + switch(type&3) { + case EM_FORMAT_HTML_DISPLAY_SEARCH_PRIMARY: + e_searching_tokenizer_set_primary_case_sensitivity(efhd->search_tok, (type&EM_FORMAT_HTML_DISPLAY_SEARCH_ICASE) == 0); + e_searching_tokenizer_set_primary_search_string(efhd->search_tok, NULL); + while (strings) { + e_searching_tokenizer_add_primary_search_string(efhd->search_tok, strings->data); + strings = strings->next; + } + break; + case EM_FORMAT_HTML_DISPLAY_SEARCH_SECONDARY: + default: + e_searching_tokenizer_set_secondary_case_sensitivity(efhd->search_tok, (type&EM_FORMAT_HTML_DISPLAY_SEARCH_ICASE) == 0); + e_searching_tokenizer_set_secondary_search_string(efhd->search_tok, NULL); + while (strings) { + e_searching_tokenizer_add_secondary_search_string(efhd->search_tok, strings->data); + strings = strings->next; + } + break; + } + + d(printf("redrawing with search\n")); + em_format_format_clone((EMFormat *)efhd, ((EMFormat *)efhd)->message, (EMFormat *)efhd); +} + +static void +efhd_update_matches(EMFormatHTMLDisplay *efhd) +{ + struct _EMFormatHTMLDisplayPrivate *p = efhd->priv; + char *str; + /* message-search popup match count string */ + char *fmt = _("Matches: %d"); + + if (p->search_dialog) { + str = alloca(strlen(fmt)+32); + sprintf(str, fmt, e_searching_tokenizer_match_count(efhd->search_tok)); + gtk_label_set_text((GtkLabel *)p->search_matches_label, str); + } +} + +static void +efhd_update_search(EMFormatHTMLDisplay *efhd) +{ + struct _EMFormatHTMLDisplayPrivate *p = efhd->priv; + GSList *words = NULL; + int flags = 0; + + if (p->search_text == NULL) + return; + + if (!gtk_toggle_button_get_active((GtkToggleButton *)p->search_case_check)) + flags = EM_FORMAT_HTML_DISPLAY_SEARCH_ICASE | EM_FORMAT_HTML_DISPLAY_SEARCH_PRIMARY; + else + flags = EM_FORMAT_HTML_DISPLAY_SEARCH_PRIMARY; + + if (p->search_text) + words = g_slist_append(words, p->search_text); + + em_format_html_display_set_search(efhd, flags, words); + g_slist_free(words); +} + +static void +efhd_search_response(GtkWidget *w, int button, EMFormatHTMLDisplay *efhd) +{ + struct _EMFormatHTMLDisplayPrivate *p = efhd->priv; + + if (button == GTK_RESPONSE_ACCEPT) { + char *txt = g_strdup(gtk_entry_get_text((GtkEntry *)p->search_entry)); + + g_strstrip(txt); + if (p->search_text && strcmp(p->search_text, txt) == 0 && !p->search_wrap) { + if (!gtk_html_engine_search_next(((EMFormatHTML *)efhd)->html)) + p->search_wrap = TRUE; + g_free(txt); + } else { + g_free(p->search_text); + p->search_text = txt; + if (!p->search_wrap) + efhd_update_search(efhd); + p->search_wrap = FALSE; + gtk_html_engine_search(((EMFormatHTML *)efhd)->html, txt, + gtk_toggle_button_get_active((GtkToggleButton *)p->search_case_check), + TRUE, FALSE); + } + } else { + g_free(p->search_text); + p->search_text = NULL; + gtk_widget_destroy((GtkWidget *)p->search_dialog); + p->search_dialog = NULL; + } +} + +static void +efhd_search_case_toggled(GtkWidget *w, EMFormatHTMLDisplay *efhd) +{ + struct _EMFormatHTMLDisplayPrivate *p = efhd->priv; + + g_free(p->search_text); + p->search_text = NULL; + efhd_search_response(w, GTK_RESPONSE_ACCEPT, efhd); +} + +static void +efhd_search_entry_activate(GtkWidget *w, EMFormatHTMLDisplay *efhd) +{ + efhd_search_response(w, GTK_RESPONSE_ACCEPT, efhd); +} + +/** + * em_format_html_display_search: + * @efhd: + * + * Run an interactive search dialogue. + **/ +void +em_format_html_display_search(EMFormatHTMLDisplay *efhd) +{ + struct _EMFormatHTMLDisplayPrivate *p = efhd->priv; + GladeXML *xml; + + if (p->search_dialog) { + gdk_window_raise(((GtkWidget *)p->search_dialog)->window); + return; + } + + xml = glade_xml_new (EVOLUTION_GLADEDIR "/mail-search.glade", "search_message_dialog", NULL); + if (xml == NULL) { + g_warning("Cannot open search dialog glade file"); + /* ?? */ + return; + } + + /* TODO: The original put the subject in the frame, but it had some + ugly arbitrary string-cutting code to make sure it fit. */ + + p->search_dialog = (GtkDialog *)glade_xml_get_widget(xml, "search_message_dialog"); + p->search_entry = glade_xml_get_widget(xml, "search_entry"); + p->search_matches_label = glade_xml_get_widget(xml, "search_matches_label"); + p->search_case_check = glade_xml_get_widget(xml, "search_case_check"); + p->search_wrap = FALSE; + + gtk_dialog_set_default_response((GtkDialog *)p->search_dialog, GTK_RESPONSE_ACCEPT); + efhd_update_matches(efhd); + + g_signal_connect(p->search_entry, "activate", G_CALLBACK(efhd_search_entry_activate), efhd); + g_signal_connect(p->search_case_check, "toggled", G_CALLBACK(efhd_search_case_toggled), efhd); + g_signal_connect(p->search_dialog, "response", G_CALLBACK(efhd_search_response), efhd); + e_dialog_set_transient_for((GtkWindow *)p->search_dialog, (GtkWidget *)efhd); + gtk_widget_show((GtkWidget *)p->search_dialog); +} + +void +em_format_html_display_cut (EMFormatHTMLDisplay *efhd) +{ + gtk_html_cut (((EMFormatHTML *) efhd)->html); +} + +void +em_format_html_display_copy (EMFormatHTMLDisplay *efhd) +{ + gtk_html_copy (((EMFormatHTML *) efhd)->html); +} + +void +em_format_html_display_paste (EMFormatHTMLDisplay *efhd) +{ + gtk_html_paste (((EMFormatHTML *) efhd)->html, FALSE); +} + +void +em_format_html_display_zoom_in (EMFormatHTMLDisplay *efhd) +{ + gtk_html_zoom_in (((EMFormatHTML *) efhd)->html); +} + +void +em_format_html_display_zoom_out (EMFormatHTMLDisplay *efhd) +{ + gtk_html_zoom_out (((EMFormatHTML *) efhd)->html); +} + +void +em_format_html_display_zoom_reset (EMFormatHTMLDisplay *efhd) +{ + gtk_html_zoom_reset (((EMFormatHTML *) efhd)->html); +} + +/* ********************************************************************** */ + +static void +efhd_iframe_created(GtkHTML *html, GtkHTML *iframe, EMFormatHTMLDisplay *efh) +{ + d(printf("Iframe created %p ... \n", iframe)); + + g_signal_connect(iframe, "button_press_event", G_CALLBACK (efhd_html_button_press_event), efh); + + return; +} + +static int +efhd_html_button_press_event (GtkWidget *widget, GdkEventButton *event, EMFormatHTMLDisplay *efhd) +{ + HTMLEngine *e; + HTMLPoint *point; + const char *url; + gboolean res = FALSE; + + if (event->button != 3) + return FALSE; + + e = ((GtkHTML *)widget)->engine; + point = html_engine_get_point_at(e, event->x, event->y, FALSE); + if (point == NULL) + return FALSE; + + d(printf("popup button pressed\n")); + + if ( (url = html_object_get_src(point->object)) != NULL + || (url = html_object_get_url(point->object, 0)) != NULL) { + EMFormatPURI *puri; + char *uri; + + uri = gtk_html_get_url_object_relative((GtkHTML *)widget, point->object, url); + puri = em_format_find_puri((EMFormat *)efhd, uri); + + d(printf("poup event, uri = '%s' part = '%p'\n", uri, puri?puri->part:NULL)); + + g_signal_emit((GtkObject *)efhd, efhd_signals[EFHD_POPUP_EVENT], 0, event, uri, puri?puri->part:NULL, &res); + g_free(uri); + } + + html_point_destroy(point); + + return res; +} + +static void +efhd_html_link_clicked (GtkHTML *html, const char *url, EMFormatHTMLDisplay *efhd) +{ + d(printf("link clicked event '%s'\n", url)); + g_signal_emit((GObject *)efhd, efhd_signals[EFHD_LINK_CLICKED], 0, url); +} + +static void +efhd_complete(EMFormat *emf) +{ + EMFormatHTMLDisplay *efhd = (EMFormatHTMLDisplay *)emf; + + if (efhd->priv->search_dialog) + efhd_update_matches(efhd); +} + +/* ********************************************************************** */ + +static void +efhd_signature_check(GtkWidget *w, EMFormatHTMLPObject *pobject) +{ + d(printf("insert signature check here ... redraw ? or what ?\n")); + /* blah, do the old way for now, force a complete re-draw */ + em_format_set_inline((EMFormat *)pobject->format, pobject->part, TRUE); + em_format_format_clone((EMFormat *)pobject->format, ((EMFormat *)pobject->format)->message, (EMFormat *)pobject->format); +} + +static gboolean +efhd_signature_button(EMFormatHTML *efh, GtkHTMLEmbedded *eb, EMFormatHTMLPObject *pobject) +{ + GtkWidget *icon, *button; + GdkPixbuf *pixbuf; + + pixbuf = gdk_pixbuf_new_from_file(EVOLUTION_ICONSDIR "/pgp-signature-nokey.png", NULL); + if (pixbuf == NULL) + return FALSE; + + /* wtf isn't this just scaled on disk? */ + icon = gtk_image_new_from_pixbuf(gdk_pixbuf_scale_simple(pixbuf, 24, 24, GDK_INTERP_BILINEAR)); + g_object_unref(pixbuf); + gtk_widget_show(icon); + + button = gtk_button_new(); + g_signal_connect(button, "clicked", G_CALLBACK (efhd_signature_check), pobject); + /*g_signal_connect (button, "key_press_event", G_CALLBACK (inline_button_press), part);*/ + + gtk_container_add((GtkContainer *)button, icon); + gtk_widget_show(button); + gtk_container_add((GtkContainer *)eb, button); + + return TRUE; +} + +static void +efhd_multipart_signed (EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + char *classid; + static int signedid; + CamelMultipartSigned *mps; + CamelMimePart *cpart; + + mps = (CamelMultipartSigned *)camel_medium_get_content_object((CamelMedium *)part); + if (!CAMEL_IS_MULTIPART_SIGNED(mps) + || (cpart = camel_multipart_get_part((CamelMultipart *)mps, CAMEL_MULTIPART_SIGNED_CONTENT)) == NULL) { + em_format_format_source(emf, stream, part); + return; + } + + em_format_part(emf, stream, cpart); + + if (em_format_is_inline(emf, part)) { + em_format_html_multipart_signed_sign(emf, stream, part); + } else { + classid = g_strdup_printf("multipart-signed:///icon/%d", signedid++); + + /* wtf is this so fugly? */ + camel_stream_printf(stream, + "<br><table cellspacing=0 cellpadding=0>" + "<tr><td><table width=10 cellspacing=0 cellpadding=0>" + "<tr><td></td></tr></table></td>" + "<td><object classid=\"%s\"></object></td>" + "<td><table width=3 cellspacing=0 cellpadding=0>" + "<tr><td></td></tr></table></td>" + "<td><font size=-1>%s</font></td></tr>" + "<tr><td height=10>" + "<table cellspacing=0 cellpadding=0><tr>" + "<td height=10><a name=\"glue\"></td></tr>" + "</table></td></tr></table>\n", + classid, + _("This message is digitally signed. Click the lock icon for more information.")); + + em_format_html_add_pobject((EMFormatHTML *)emf, classid, efhd_signature_button, part); + g_free(classid); + } +} + +/* ********************************************************************** */ + +static EMFormatHandler type_builtin_table[] = { + { "multipart/signed", (EMFormatFunc)efhd_multipart_signed }, +}; + +static void +efhd_builtin_init(EMFormatHTMLDisplayClass *efhc) +{ + int i; + + for (i=0;i<sizeof(type_builtin_table)/sizeof(type_builtin_table[0]);i++) + em_format_class_add_handler((EMFormatClass *)efhc, &type_builtin_table[i]); +} + +/* ********************************************************************** */ + +static void efhd_format_clone(EMFormat *emf, CamelMedium *part, EMFormat *src) +{ + ((EMFormatClass *)efhd_parent)->format_clone(emf, part, src); +} + +/* TODO: if these aren't going to do anything should remove */ +static void efhd_format_error(EMFormat *emf, CamelStream *stream, const char *txt) +{ + ((EMFormatClass *)efhd_parent)->format_error(emf, stream, txt); +} + +static void efhd_format_message(EMFormat *emf, CamelStream *stream, CamelMedium *part) +{ + ((EMFormatClass *)efhd_parent)->format_message(emf, stream, part); +} + +static void efhd_format_source(EMFormat *emf, CamelStream *stream, CamelMimePart *part) +{ + ((EMFormatClass *)efhd_parent)->format_source(emf, stream, part); +} + +/* ********************************************************************** */ + +/* if it hasn't been processed yet, format the attachment */ +static void +efhd_attachment_show(GtkWidget *w, struct _attach_puri *info) +{ + d(printf("show attachment button called\n")); + + info->shown = ~info->shown; + em_format_set_inline(info->puri.format, info->puri.part, info->shown); + /* FIXME: do this in an idle handler */ + em_format_format_clone(info->puri.format, info->puri.format->message, info->puri.format); +#if 0 + /* FIXME: track shown state in parent */ + + if (info->shown) { + d(printf("hiding\n")); + info->shown = FALSE; + if (info->frame) + gtk_widget_hide((GtkWidget *)info->frame); + gtk_widget_show(info->forward); + gtk_widget_hide(info->down); + } else { + d(printf("showing\n")); + info->shown = TRUE; + if (info->frame) + gtk_widget_show((GtkWidget *)info->frame); + gtk_widget_hide(info->forward); + gtk_widget_show(info->down); + + /* have we decoded it yet? */ + if (info->output) { + info->handle->handler(info->puri.format, info->output, info->puri.part, info->handle); + camel_stream_close(info->output); + camel_object_unref(info->output); + info->output = NULL; + } + } + + em_format_set_inline(info->puri.format, info->puri.part, info->shown); +#endif +} + +static EMPopupItem efhd_menu_items[] = { + { EM_POPUP_BAR, "05.display", }, + { EM_POPUP_ITEM, "05.display.00", N_("_View Inline"), G_CALLBACK(efhd_attachment_show) }, + { EM_POPUP_ITEM, "05.display.00", N_("_Hide"), G_CALLBACK(efhd_attachment_show) }, +}; + +static void +efhd_popup_place_widget(GtkMenu *menu, int *x, int *y, gboolean *push_in, gpointer user_data) +{ + GtkWidget *w = user_data; + + gdk_window_get_origin(gtk_widget_get_parent_window(w), x, y); + *x += w->allocation.x + w->allocation.width; + *y += w->allocation.y; +} + +static gboolean +efhd_attachment_popup(GtkWidget *w, GdkEventButton *event, struct _attach_puri *info) +{ + GtkMenu *menu; + GSList *menus = NULL; + EMPopup *emp; + EMPopupTarget *target; + EMPopupItem *item; + + d(printf("attachment popup, button %d\n", event->button)); + + if (event && event->button != 1 && event->button != 3) { + /* ?? gtk_propagate_event(GTK_WIDGET (user_data), (GdkEvent *)event);*/ + return FALSE; + } + + emp = em_popup_new("com.ximian.mail.formathtmldisplay.popup.part"); + target = em_popup_target_new_part(info->puri.part, info->handle?info->handle->mime_type:NULL); + target->widget = w; + + /* add our local menus */ + efhd_menu_items[0].activate_data = info; + menus = g_slist_prepend(menus, &efhd_menu_items[0]); + item = &efhd_menu_items[info->shown?2:1]; + item->activate_data = info; + menus = g_slist_prepend(menus, item); + + menu = em_popup_create_menu_once(emp, target, target->mask, target->mask); + if (event) + gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time); + else + gtk_menu_popup(menu, NULL, NULL, (GtkMenuPositionFunc)efhd_popup_place_widget, w, 0, gtk_get_current_event_time()); + + return TRUE; +} + +static gboolean +efhd_attachment_popup_menu(GtkWidget *w, struct _attach_puri *info) +{ + return efhd_attachment_popup(w, NULL, info); +} + +/* ********************************************************************** */ + +static void +efhd_drag_data_get(GtkWidget *w, GdkDragContext *drag, GtkSelectionData *data, guint info, guint time, EMFormatHTMLPObject *pobject) +{ + CamelMimePart *part = pobject->part; + char *uri, *path; + CamelStream *stream; + + switch (info) { + case 0: /* mime/type request */ + stream = camel_stream_mem_new(); + /* TODO: shoudl format_format_text run on the content-object? */ + /* TODO: should we just do format_content? */ + if (header_content_type_is(((CamelDataWrapper *)part)->mime_type, "text", "*")) + /* FIXME: this should be an em_utils method, it only needs a default charset param */ + em_format_format_text((EMFormat *)pobject->format, stream, (CamelDataWrapper *)part); + else { + CamelDataWrapper *dw = camel_medium_get_content_object((CamelMedium *)part); + + camel_data_wrapper_decode_to_stream(dw, stream); + } + + gtk_selection_data_set(data, data->target, 8, + ((CamelStreamMem *)stream)->buffer->data, + ((CamelStreamMem *)stream)->buffer->len); + camel_object_unref(stream); + break; + case 1: /* text-uri-list request */ + /* Kludge around Nautilus requesting the same data many times */ + uri = g_object_get_data((GObject *)w, "e-drag-uri"); + if (uri) { + gtk_selection_data_set(data, data->target, 8, uri, strlen(uri)); + return; + } + + path = em_utils_temp_save_part(w, part); + if (path == NULL) + return; + + uri = g_strdup_printf("file://%s", path); + g_free(path); + gtk_selection_data_set(data, data->target, 8, uri, strlen(uri)); + g_object_set_data_full((GObject *)w, "e-drag-uri", uri, g_free); + break; + default: + abort(); + } +} + +static void +efhd_drag_data_delete(GtkWidget *w, GdkDragContext *drag, EMFormatHTMLPObject *pobject) +{ + char *uri; + + uri = g_object_get_data((GObject *)w, "e-drag-uri"); + if (uri) { + /* NB: this doesn't kill the dnd directory */ + /* NB: is this ever called? */ + unlink(uri+7); + g_object_set_data((GObject *)w, "e-drag-uri", NULL); + } +} + +static void +efhd_write_icon_job(struct _EMFormatHTMLJob *job, int cancelled) +{ + EMFormatHTMLPObject *pobject; + CamelDataWrapper *dw; + + if (cancelled) + return; + + pobject = job->u.data; + dw = camel_medium_get_content_object((CamelMedium *)pobject->part); + camel_data_wrapper_decode_to_stream(dw, job->stream); + camel_stream_close(job->stream); +} + +/* attachment button callback */ +static gboolean +efhd_attachment_button(EMFormatHTML *efh, GtkHTMLEmbedded *eb, EMFormatHTMLPObject *pobject) +{ + struct _attach_puri *info; + GtkWidget *hbox, *w, *button, *mainbox; + char *simple_type; + GtkTargetEntry drag_types[] = { + { NULL, 0, 0 }, + { "text/uri-list", 0, 1 }, + }; + + /* FIXME: handle default shown case */ + d(printf("adding attachment button/content\n")); + + info = (struct _attach_puri *)em_format_find_puri((EMFormat *)efh, pobject->classid); + g_assert(info != NULL); + g_assert(info->forward == NULL); + + mainbox = gtk_hbox_new(FALSE, 0); + + button = gtk_button_new(); + + if (info->handle) + g_signal_connect(button, "clicked", G_CALLBACK(efhd_attachment_show), info); + else + GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS); + + hbox = gtk_hbox_new(FALSE, 2); + info->forward = gtk_image_new_from_stock(GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_BUTTON); + gtk_box_pack_start((GtkBox *)hbox, info->forward, TRUE, TRUE, 0); + if (info->handle) { + info->down = gtk_image_new_from_stock(GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_BUTTON); + gtk_box_pack_start((GtkBox *)hbox, info->down, TRUE, TRUE, 0); + } + + w = gtk_image_new(); + gtk_widget_set_size_request(w, 24, 24); + gtk_box_pack_start((GtkBox *)hbox, w, TRUE, TRUE, 0); + gtk_container_add((GtkContainer *)button, hbox); + gtk_box_pack_start((GtkBox *)mainbox, button, TRUE, TRUE, 0); + + /* FIXME: loses any snoop info */ + simple_type = header_content_type_simple(((CamelDataWrapper *)pobject->part)->mime_type); + camel_strdown(simple_type); + + /* cache? */ + /* FIXME: offline parts, just get icon */ + if (header_content_type_is(((CamelDataWrapper *)pobject->part)->mime_type, "image", "*")) { + EMFormatHTMLJob *job; + + job = em_format_html_job_new(efh, efhd_write_icon_job, pobject); + job->stream = (CamelStream *)em_icon_stream_new((GtkImage *)w); + em_format_html_job_queue(efh, job); + } else { + GdkPixbuf *pixbuf = e_icon_for_mime_type(simple_type, 24); + GdkPixbuf *mini = gdk_pixbuf_scale_simple(pixbuf, 24, 24, GDK_INTERP_BILINEAR); + + gtk_image_set_from_pixbuf((GtkImage *)w, mini); + g_object_unref(mini); + g_object_unref(pixbuf); + } + + drag_types[0].target = simple_type; + gtk_drag_source_set(button, GDK_BUTTON1_MASK, drag_types, sizeof(drag_types)/sizeof(drag_types[0]), GDK_ACTION_COPY); + g_signal_connect(button, "drag-data-get", G_CALLBACK(efhd_drag_data_get), pobject); + g_signal_connect (button, "drag-data-delete", G_CALLBACK(efhd_drag_data_delete), pobject); + g_free(simple_type); + + button = gtk_button_new(); + /*GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);*/ + gtk_container_add((GtkContainer *)button, gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_ETCHED_IN)); + g_signal_connect(button, "button_press_event", G_CALLBACK(efhd_attachment_popup), info); + g_signal_connect(button, "popup_menu", G_CALLBACK(efhd_attachment_popup_menu), info); + g_signal_connect(button, "clicked", G_CALLBACK(efhd_attachment_popup_menu), info); + gtk_box_pack_start((GtkBox *)mainbox, button, TRUE, TRUE, 0); + + gtk_widget_show_all(mainbox); + + if (info->shown) + gtk_widget_hide(info->forward); + else if (info->down) + gtk_widget_hide(info->down); + + gtk_container_add((GtkContainer *)eb, mainbox); + + return TRUE; +} + +/* not used currently */ +/* frame source callback */ +static void +efhd_attachment_frame(EMFormat *emf, CamelStream *stream, EMFormatPURI *puri) +{ + struct _attach_puri *info = (struct _attach_puri *)puri; + + if (info->shown) { + d(printf("writing to frame content, handler is '%s'\n", info->handle->mime_type)); + info->handle->handler(emf, stream, info->puri.part, info->handle); + camel_stream_close(stream); + } else { + /* FIXME: this is leaked if the object is closed without showing it + NB: need a virtual puri_free method? */ + info->output = stream; + camel_object_ref(stream); + } +} + +static gboolean +efhd_bonobo_object(EMFormatHTML *efh, GtkHTMLEmbedded *eb, EMFormatHTMLPObject *pobject) +{ + CamelDataWrapper *wrapper; + Bonobo_ServerInfo *component; + GtkWidget *embedded; + Bonobo_PersistStream persist; + CORBA_Environment ev; + CamelStreamMem *cstream; + BonoboStream *bstream; + BonoboControlFrame *control_frame; + Bonobo_PropertyBag prop_bag; + + component = gnome_vfs_mime_get_default_component(eb->type); + if (component == NULL) + return FALSE; + + embedded = bonobo_widget_new_control(component->iid, NULL); + CORBA_free(component); + if (embedded == NULL) + return FALSE; + + CORBA_exception_init(&ev); + + control_frame = bonobo_widget_get_control_frame((BonoboWidget *)embedded); + prop_bag = bonobo_control_frame_get_control_property_bag(control_frame, NULL); + if (prop_bag != CORBA_OBJECT_NIL) { + /* + * Now we can take care of business. Currently, the only control + * that needs something passed to it through a property bag is + * the iTip control, and it needs only the From email address, + * but perhaps in the future we can generalize this section of code + * to pass a bunch of useful things to all embedded controls. + */ + const CamelInternetAddress *from; + char *from_address; + + from = camel_mime_message_get_from((CamelMimeMessage *)((EMFormat *)efh)->message); + from_address = camel_address_encode((CamelAddress *)from); + bonobo_property_bag_client_set_value_string(prop_bag, "from_address", from_address, &ev); + g_free(from_address); + + Bonobo_Unknown_unref(prop_bag, &ev); + } + + persist = (Bonobo_PersistStream)Bonobo_Unknown_queryInterface(bonobo_widget_get_objref((BonoboWidget *)embedded), + "IDL:Bonobo/PersistStream:1.0", NULL); + if (persist == CORBA_OBJECT_NIL) { + gtk_object_sink((GtkObject *)embedded); + CORBA_exception_free(&ev); + return FALSE; + } + + /* Write the data to a CamelStreamMem... */ + cstream = (CamelStreamMem *)camel_stream_mem_new(); + wrapper = camel_medium_get_content_object((CamelMedium *)pobject->part); + camel_data_wrapper_decode_to_stream(wrapper, (CamelStream *)cstream); + + /* ...convert the CamelStreamMem to a BonoboStreamMem... */ + bstream = bonobo_stream_mem_create(cstream->buffer->data, cstream->buffer->len, TRUE, FALSE); + camel_object_unref(cstream); + + /* ...and hydrate the PersistStream from the BonoboStream. */ + Bonobo_PersistStream_load(persist, + bonobo_object_corba_objref(BONOBO_OBJECT (bstream)), + eb->type, &ev); + bonobo_object_unref(BONOBO_OBJECT (bstream)); + Bonobo_Unknown_unref(persist, &ev); + CORBA_Object_release(persist, &ev); + + if (ev._major != CORBA_NO_EXCEPTION) { + gtk_object_sink((GtkObject *)embedded); + CORBA_exception_free(&ev); + return FALSE; + } + CORBA_exception_free(&ev); + + gtk_widget_show(embedded); + gtk_container_add(GTK_CONTAINER (eb), embedded); + + return TRUE; +} + +static gboolean +efhd_check_server_prop(Bonobo_ServerInfo *component, const char *propname, const char *value) +{ + CORBA_sequence_CORBA_string stringv; + Bonobo_ActivationProperty *prop; + int i; + + prop = bonobo_server_info_prop_find(component, propname); + if (!prop || prop->v._d != Bonobo_ACTIVATION_P_STRINGV) + return FALSE; + + stringv = prop->v._u.value_stringv; + for (i = 0; i < stringv._length; i++) { + if (!g_ascii_strcasecmp(value, stringv._buffer[i])) + return TRUE; + } + + return FALSE; +} + +static gboolean +efhd_use_component(const char *mime_type) +{ + GList *components, *iter; + Bonobo_ServerInfo *component = NULL; + + /* should this cache it? */ + + if (g_ascii_strcasecmp(mime_type, "text/x-vcard") != 0 + && g_ascii_strcasecmp(mime_type, "text/calendar") != 0) { + const char **mime_types; + int i; + + mime_types = mail_config_get_allowable_mime_types(); + for (i = 0; mime_types[i]; i++) { + if (!g_ascii_strcasecmp(mime_types[i], mime_type)) + goto type_ok; + } + return FALSE; + } +type_ok: + components = gnome_vfs_mime_get_all_components (mime_type); + for (iter = components; iter; iter = iter->next) { + Bonobo_ServerInfo *comp = iter->data; + + comp = iter->data; + if (efhd_check_server_prop(comp, "repo_ids", "IDL:Bonobo/PersistStream:1.0") + && efhd_check_server_prop(comp, "bonobo:supported_mime_types", mime_type)) { + component = comp; + break; + } + } + gnome_vfs_mime_component_list_free (components); + + return component != NULL; +} + +static void +efhd_format_attachment(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const char *mime_type, const EMFormatHandler *handle) +{ + char *classid, *text, *html; + struct _attach_puri *info; + + classid = g_strdup_printf("attachment-%p", part); + info = (struct _attach_puri *)em_format_add_puri(emf, sizeof(*info), classid, part, efhd_attachment_frame); + em_format_html_add_pobject((EMFormatHTML *)emf, classid, efhd_attachment_button, part); + info->handle = handle; + info->shown = em_format_is_inline(emf, info->puri.part) && handle != NULL; + + camel_stream_write_string(stream, + "<table cellspacing=0 cellpadding=0><tr><td>" + "<table width=10 cellspacing=0 cellpadding=0>" + "<tr><td></td></tr></table></td>"); + + camel_stream_printf(stream, "<td><object classid=\"%s\"></object></td>", classid); + + camel_stream_write_string(stream, + "<td><table width=3 cellspacing=0 cellpadding=0>" + "<tr><td></td></tr></table></td><td><font size=-1>"); + + /* output some info about it */ + /* FIXME: should we look up mime_type from object again? */ + text = em_format_describe_part(part, mime_type); + html = camel_text_to_html(text, ((EMFormatHTML *)emf)->text_html_flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); + camel_stream_write_string(stream, html); + g_free(html); + g_free(text); + + camel_stream_write_string(stream, "</font></td></tr><tr></table>"); + + if (handle) { + if (info->shown) + handle->handler(emf, stream, part, handle); + /*camel_stream_printf(stream, "<iframe src=\"%s\" marginheight=0 marginwidth=0>%s</iframe>\n", classid, _("Attachment content could not be loaded"));*/ + } else if (efhd_use_component(mime_type)) { + static int partid; + + g_free(classid); /* messy */ + + classid = g_strdup_printf("bonobo-unknown:///em-formath-html-display/%p/%d", part, partid++); + em_format_html_add_pobject((EMFormatHTML *)emf, classid, efhd_bonobo_object, part); + camel_stream_printf(stream, "<object classid=\"%s\" type=\"%s\">\n", classid, mime_type); + } + + g_free(classid); +} diff --git a/mail/em-format-html-display.h b/mail/em-format-html-display.h new file mode 100644 index 0000000000..cf78642112 --- /dev/null +++ b/mail/em-format-html-display.h @@ -0,0 +1,63 @@ + +/* + Concrete class for formatting mails to displayed html +*/ + +#ifndef _EM_FORMAT_HTML_DISPLAY_H +#define _EM_FORMAT_HTML_DISPLAY_H + +#include "em-format-html.h" + +typedef struct _EMFormatHTMLDisplay EMFormatHTMLDisplay; +typedef struct _EMFormatHTMLDisplayClass EMFormatHTMLDisplayClass; + +struct _CamelMimePart; + +struct _EMFormatHTMLDisplay { + EMFormatHTML formathtml; + + struct _EMFormatHTMLDisplayPrivate *priv; + + struct _ESearchingTokenizer *search_tok; + + unsigned int animate:1; + unsigned int caret_mode:1; +}; + +#define EM_FORMAT_HTML_DISPLAY_SEARCH_PRIMARY (0) +#define EM_FORMAT_HTML_DISPLAY_SEARCH_SECONDARY (1) +#define EM_FORMAT_HTML_DISPLAY_SEARCH_ICASE (1<<8) + +struct _EMFormatHTMLDisplayClass { + EMFormatHTMLClass formathtml_class; + + /* a link clicked normally */ + void (*link_clicked)(EMFormatHTMLDisplay *efhd, const char *uri); + /* a part or a link button pressed event */ + int (*popup_event)(EMFormatHTMLDisplay *efhd, GdkEventButton *event, const char *uri, struct _CamelMimePart *part); +}; + +GType em_format_html_display_get_type(void); +EMFormatHTMLDisplay *em_format_html_display_new(void); + +void em_format_html_display_goto_anchor(EMFormatHTMLDisplay *efhd, const char *name); + +void em_format_html_display_set_animate(EMFormatHTMLDisplay *efhd, gboolean state); +void em_format_html_display_set_caret_mode(EMFormatHTMLDisplay *efhd, gboolean state); + +void em_format_html_display_set_search(EMFormatHTMLDisplay *efhd, int type, GSList *strings); +void em_format_html_display_search(EMFormatHTMLDisplay *efhd); + +void em_format_html_display_cut (EMFormatHTMLDisplay *efhd); +void em_format_html_display_copy (EMFormatHTMLDisplay *efhd); +void em_format_html_display_paste (EMFormatHTMLDisplay *efhd); + +void em_format_html_display_zoom_in (EMFormatHTMLDisplay *efhd); +void em_format_html_display_zoom_out (EMFormatHTMLDisplay *efhd); +void em_format_html_display_zoom_reset (EMFormatHTMLDisplay *efhd); + +/* experimental */ +struct _EPopupExtension; +void em_format_html_display_set_popup(EMFormatHTMLDisplay *, struct _EPopupExtension *); + +#endif /* !_EM_FORMAT_HTML_DISPLAY_H */ diff --git a/mail/em-format-html-print.c b/mail/em-format-html-print.c new file mode 100644 index 0000000000..d02e72cebd --- /dev/null +++ b/mail/em-format-html-print.c @@ -0,0 +1,186 @@ + +#include <libgnomeprint/gnome-print-job.h> +#include <libgnomeprintui/gnome-print-job-preview.h> + +#include <gtkhtml/gtkhtml.h> +#include <gtk/gtkwindow.h> + +#include <camel/camel-i18n.h> +#include "em-format-html-print.h" + +#include <string.h> + +static void efhp_builtin_init(EMFormatHTMLPrintClass *efhc); + +static EMFormatHTMLClass *efhp_parent; + +static void +efhp_init(GObject *o) +{ + EMFormatHTMLPrint *efhp = (EMFormatHTMLPrint *)o; + GtkWidget *html = (GtkWidget *)efhp->formathtml.html; + + /* ?? */ + gtk_widget_set_name(html, "EvolutionMailPrintHTMLWidget"); + + /* gtk widgets don't like to be realized outside top level widget + so we put new html widget into gtk window */ + efhp->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_container_add((GtkContainer *)efhp->window, html); + gtk_widget_realize(html); +} + +static void +efhp_finalise(GObject *o) +{ + EMFormatHTMLPrint *efhp = (EMFormatHTMLPrint *)o; + + gtk_widget_destroy(efhp->window); + if (efhp->config) + g_object_unref(efhp->config); + + ((GObjectClass *)efhp_parent)->finalize(o); +} + +static void +efhp_base_init(EMFormatHTMLPrintClass *efhpklass) +{ + efhp_builtin_init(efhpklass); +} + +static void +efhp_class_init(GObjectClass *klass) +{ + klass->finalize = efhp_finalise; +} + +GType +em_format_html_print_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMFormatHTMLPrintClass), + (GBaseInitFunc)efhp_base_init, NULL, + (GClassInitFunc)efhp_class_init, + NULL, NULL, + sizeof(EMFormatHTMLPrint), 0, + (GInstanceInitFunc)efhp_init + }; + efhp_parent = g_type_class_ref(em_format_html_get_type()); + type = g_type_register_static(em_format_html_get_type(), "EMFormatHTMLPrint", &info, 0); + } + + return type; +} + +EMFormatHTMLPrint *em_format_html_print_new(void) +{ + EMFormatHTMLPrint *efhp; + + efhp = g_object_new(em_format_html_print_get_type(), 0); + + return efhp; +} + +struct footer_info { + GnomeFont *local_font; + gint page_num, pages; +}; + +static void +efhp_footer_cb(GtkHTML *html, GnomePrintContext *print_context, double x, double y, double width, double height, void *data) +{ + struct footer_info *info = data; + + /* do we want anything nicer here, like who its from, etc? */ + if (info->local_font) { + char *text = g_strdup_printf (_("Page %d of %d"), info->page_num, info->pages); + /*gdouble tw = gnome_font_get_width_string (info->local_font, text);*/ + /* FIXME: work out how to measure this */ + gdouble tw = strlen(text) * 8; + + gnome_print_gsave(print_context); + gnome_print_newpath(print_context); + gnome_print_setrgbcolor(print_context, .0, .0, .0); + gnome_print_moveto(print_context, x + width - tw, y - gnome_font_get_ascender(info->local_font)); + gnome_print_setfont(print_context, info->local_font); + gnome_print_show(print_context, text); + gnome_print_grestore(print_context); + + g_free(text); + info->page_num++; + } +} + +/* perform preview, or print */ +/* returns GNOME_PRINT_OK on success */ +static void +emfhp_complete(EMFormatHTMLPrint *efhp, void *data) +{ + GnomePrintContext *print_context; + GnomePrintJob *print_job; + gdouble line = 0.0; + struct footer_info info; + int res = GNOME_PRINT_OK; + + print_job = gnome_print_job_new(efhp->config); + print_context = gnome_print_job_get_context(print_job); + + gtk_html_print_set_master(efhp->formathtml.html, print_job); + info.local_font = gnome_font_find_closest("Helvetica", 10.0); + if (info.local_font) { + line = gnome_font_get_ascender(info.local_font) - gnome_font_get_descender(info.local_font); + info.page_num = 1; + info.pages = gtk_html_print_get_pages_num(efhp->formathtml.html, print_context, 0.0, line); + gtk_html_print_with_header_footer(efhp->formathtml.html, print_context, 0.0, line, NULL, efhp_footer_cb, &info); + gnome_font_unref(info.local_font); + } else { + gtk_html_print(efhp->formathtml.html, print_context); + } + gtk_html_print_set_master(efhp->formathtml.html, NULL); + + gnome_print_job_close(print_job); + + if (efhp->preview) + gtk_widget_show(gnome_print_job_preview_new(print_job, _("Print Preview"))); + else + res = gnome_print_job_print(print_job); + + g_object_unref(print_job); + g_object_unref(efhp); +} + +int em_format_html_print_print(EMFormatHTMLPrint *efhp, struct _CamelMedium *msg, EMFormatHTML *source, struct _GnomePrintConfig *print_config, int preview) +{ + efhp->config = print_config; + if (print_config) + g_object_ref(print_config); + efhp->preview = preview; + + ((EMFormatHTML *)efhp)->load_http = source->load_http_now; + + g_signal_connect(efhp, "complete", G_CALLBACK(emfhp_complete), efhp); + + g_object_ref(efhp); + em_format_format_clone((EMFormat *)efhp, msg, (EMFormat *)source); + + return 0; /* damn async ... */ +} + +/* ********************************************************************** */ + +/* if only ... but i doubt this is possible with gnome print/gtkhtml */ +static EMFormatHandler type_builtin_table[] = { + /*{ "application/postscript", (EMFormatFunc)efhp_application_postscript },*/ +}; + +static void +efhp_builtin_init(EMFormatHTMLPrintClass *efhc) +{ + int i; + + for (i=0;i<sizeof(type_builtin_table)/sizeof(type_builtin_table[0]);i++) + em_format_class_add_handler((EMFormatClass *)efhc, &type_builtin_table[i]); +} diff --git a/mail/em-format-html-print.h b/mail/em-format-html-print.h new file mode 100644 index 0000000000..78e3139e0f --- /dev/null +++ b/mail/em-format-html-print.h @@ -0,0 +1,37 @@ + +/* + Concrete class for formatting mails to displayed html +*/ + +#ifndef _EM_FORMAT_HTML_PRINT_H +#define _EM_FORMAT_HTML_PRINT_H + +#include "em-format-html.h" + +struct _GnomePrintConfig; + +typedef struct _EMFormatHTMLPrint EMFormatHTMLPrint; +typedef struct _EMFormatHTMLPrintClass EMFormatHTMLPrintClass; + +struct _CamelMimePart; + +struct _EMFormatHTMLPrint { + EMFormatHTML formathtml; + + struct _GtkWidget *window; /* used to realise the gtkhtml in a toplevel, i dont know why */ + struct _GnomePrintConfig *config; + + int preview:1; +}; + +struct _EMFormatHTMLPrintClass { + EMFormatHTMLClass formathtml_class; +}; + +GType em_format_html_print_get_type(void); + +EMFormatHTMLPrint *em_format_html_print_new(void); + +int em_format_html_print_print(EMFormatHTMLPrint *efhp, struct _CamelMedium *msg, EMFormatHTML *source, struct _GnomePrintConfig *print_config, int preview); + +#endif /* ! _EM_FORMAT_HTML_PRINT_H */ diff --git a/mail/em-format-html-quote.c b/mail/em-format-html-quote.c new file mode 100644 index 0000000000..4333251186 --- /dev/null +++ b/mail/em-format-html-quote.c @@ -0,0 +1,277 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <camel/camel-stream.h> +#include <camel/camel-mime-filter-tohtml.h> +#include "em-format-html-quote.h" + +struct _EMFormatHTMLQuotePrivate { + char *credits; +}; + +static void efhq_format_clone (EMFormat *, CamelMedium *, EMFormat *); +static void efhq_format_error (EMFormat *emf, CamelStream *stream, const char *txt); +static void efhq_format_message (EMFormat *, CamelStream *, CamelMedium *); +static void efhq_format_source (EMFormat *, CamelStream *, CamelMimePart *); +static void efhq_format_attachment (EMFormat *, CamelStream *, CamelMimePart *, const char *, const EMFormatHandler *); + +static void efhq_builtin_init (EMFormatHTMLQuoteClass *efhc); + +static EMFormatHTMLClass *efhq_parent; + +static void +efhq_init (GObject *o) +{ + EMFormatHTMLQuote *efhq = (EMFormatHTMLQuote *) o; + + efhq->priv = g_malloc0 (sizeof (*efhq->priv)); + + /* we want to convert url's etc */ + ((EMFormatHTML *) efhq)->text_html_flags |= CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES; + /* we want simple header format */ + ((EMFormatHTML *) efhq)->simple_headers = TRUE; +} + +static void +efhq_finalise (GObject *o) +{ + EMFormatHTMLQuote *efhq = (EMFormatHTMLQuote *) o; + + g_free (efhq->priv->credits); + g_free (efhq->priv); + + ((GObjectClass *) efhq_parent)->finalize (o); +} + +static void +efhq_base_init(EMFormatHTMLQuoteClass *efhqklass) +{ + efhq_builtin_init(efhqklass); +} + +static void +efhq_class_init (GObjectClass *klass) +{ + ((EMFormatClass *) klass)->format_clone = efhq_format_clone; + ((EMFormatClass *) klass)->format_error = efhq_format_error; + ((EMFormatClass *) klass)->format_message = efhq_format_message; + ((EMFormatClass *) klass)->format_source = efhq_format_source; + ((EMFormatClass *) klass)->format_attachment = efhq_format_attachment; + + klass->finalize = efhq_finalise; +} + +GType +em_format_html_quote_get_type (void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof (EMFormatHTMLQuoteClass), + (GBaseInitFunc)efhq_base_init, NULL, + (GClassInitFunc)efhq_class_init, + NULL, NULL, + sizeof (EMFormatHTMLQuote), 0, + (GInstanceInitFunc) efhq_init + }; + + efhq_parent = g_type_class_ref (em_format_html_get_type ()); + type = g_type_register_static (em_format_html_get_type (), "EMFormatHTMLQuote", &info, 0); + } + + return type; +} + +EMFormatHTMLQuote * +em_format_html_quote_new (void) +{ + return (EMFormatHTMLQuote *) g_object_new (em_format_html_quote_get_type (), NULL); +} + +EMFormatHTMLQuote * +em_format_html_quote_new_with_credits (const char *credits) +{ + EMFormatHTMLQuote *emfq; + + emfq = (EMFormatHTMLQuote *) g_object_new (em_format_html_quote_get_type (), NULL); + emfq->priv->credits = g_strdup (credits); + + return emfq; +} + +static void +efhq_format_clone (EMFormat *emf, CamelMedium *part, EMFormat *src) +{ + ((EMFormatClass *) efhq_parent)->format_clone (emf, part, src); +} + +static void +efhq_format_error (EMFormat *emf, CamelStream *stream, const char *txt) +{ + /* FIXME: should we even bother writign error text for quoting? probably not... */ + ((EMFormatClass *) efhq_parent)->format_error (emf, stream, txt); +} + +static void +efhq_format_message (EMFormat *emf, CamelStream *stream, CamelMedium *part) +{ + EMFormatHTMLQuote *emfq = (EMFormatHTMLQuote *) emf; + + camel_stream_printf (stream, "%s<!--+GtkHTML:<DATA class=\"ClueFlow\" key=\"orig\" value=\"1\">-->\n" + "<font color=\"#%06x\">\n" + "<blockquote type=cite>\n", + emfq->priv->credits ? emfq->priv->credits : "", + emfq->formathtml.citation_colour); + + if (!((EMFormatHTML *)emf)->hide_headers) + em_format_html_format_headers((EMFormatHTML *)emf, stream, part); + + em_format_part(emf, stream, (CamelMimePart *)part); + + camel_stream_write_string (stream, "</blockquote></font><!--+GtkHTML:<DATA class=\"ClueFlow\" clear=\"orig\">-->"); +} + +static void +efhq_format_source (EMFormat *emf, CamelStream *stream, CamelMimePart *part) +{ + /* FIXME: should we just format_message? */ + ((EMFormatClass *) efhq_parent)->format_source (emf, stream, part); +} + +static void +efhq_format_attachment (EMFormat *emf, CamelStream *stream, CamelMimePart *part, const char *mime_type, const EMFormatHandler *handle) +{ + ; +} + +#include <camel/camel-medium.h> +#include <camel/camel-mime-part.h> +#include <camel/camel-multipart.h> +#include <camel/camel-url.h> + +static void +efhq_multipart_related(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelMultipart *mp = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)part); + CamelMimePart *body_part, *display_part = NULL; + CamelContentType *content_type; + const char *location, *start; + int i, nparts; + CamelURL *base_save = NULL; + + if (!CAMEL_IS_MULTIPART(mp)) { + em_format_format_source(emf, stream, part); + return; + } + + /* sigh, so much for oo code reuse ... */ + /* FIXME: put in a function */ + nparts = camel_multipart_get_number(mp); + content_type = camel_mime_part_get_content_type(part); + start = header_content_type_param(content_type, "start"); + if (start && strlen(start)>2) { + int len; + const char *cid; + + /* strip <>'s */ + len = strlen (start) - 2; + start++; + + for (i=0; i<nparts; i++) { + body_part = camel_multipart_get_part(mp, i); + cid = camel_mime_part_get_content_id(body_part); + + if (cid && !strncmp(cid, start, len) && strlen(cid) == len) { + display_part = body_part; + break; + } + } + } else { + display_part = camel_multipart_get_part(mp, 0); + } + + if (display_part == NULL) { + em_format_part_as(emf, stream, part, "multipart/mixed"); + return; + } + + /* stack of present location and pending uri's */ + location = camel_mime_part_get_content_location(part); + if (location) { + base_save = emf->base; + emf->base = camel_url_new(location, NULL); + } + em_format_push_level(emf); + + em_format_part(emf, stream, display_part); + em_format_pull_level(emf); + + if (location) { + camel_url_free(emf->base); + emf->base = base_save; + } +} + +static const char *type_remove_table[] = { + "image/gif", + "image/jpeg", + "image/png", + "image/x-png", + "image/tiff", + "image/x-bmp", + "image/bmp", + "image/x-cmu-raster", + "image/x-portable-anymap", + "image/x-portable-bitmap", + "image/x-portable-graymap", + "image/x-portable-pixmap", + "image/x-xpixmap", + "message/external-body", + "multipart/appledouble", + "multipart/signed", + + "image/jpg", + "image/pjpeg", +}; + +static EMFormatHandler type_builtin_table[] = { + { "multipart/related", (EMFormatFunc)efhq_multipart_related }, +}; + +static void +efhq_builtin_init (EMFormatHTMLQuoteClass *efhc) +{ + int i; + + for (i = 0; i < sizeof (type_remove_table) / sizeof (type_remove_table[0]); i++) + em_format_class_remove_handler ((EMFormatClass *) efhc, type_remove_table[i]); + + for (i=0;i<sizeof(type_builtin_table)/sizeof(type_builtin_table[0]);i++) + em_format_class_add_handler((EMFormatClass *)efhc, &type_builtin_table[i]); +} diff --git a/mail/em-format-html-quote.h b/mail/em-format-html-quote.h new file mode 100644 index 0000000000..1422b04093 --- /dev/null +++ b/mail/em-format-html-quote.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifndef _EM_FORMAT_HTML_QUOTE_H +#define _EM_FORMAT_HTML_QUOTE_H + +#include "em-format-html.h" + +typedef struct _EMFormatHTMLQuote EMFormatHTMLQuote; +typedef struct _EMFormatHTMLQuoteClass EMFormatHTMLQuoteClass; + +struct _EMFormatHTMLQuote { + EMFormatHTML formathtml; + + struct _EMFormatHTMLQuotePrivate *priv; +}; + +struct _EMFormatHTMLQuoteClass { + EMFormatHTMLClass formathtml_class; + +}; + +GType em_format_html_quote_get_type (void); + +EMFormatHTMLQuote *em_format_html_quote_new (void); + +EMFormatHTMLQuote *em_format_html_quote_new_with_credits (const char *cedits); + +#endif /* !_EM_FORMAT_HTML_QUOTE_H */ diff --git a/mail/em-format-html.c b/mail/em-format-html.c new file mode 100644 index 0000000000..9fa0210813 --- /dev/null +++ b/mail/em-format-html.c @@ -0,0 +1,1542 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <ctype.h> + +#include <gal/util/e-iconv.h> +#include <gal/util/e-util.h> /* for e_utf8_strftime, what about e_time_format_time? */ +#include "e-util/e-time-utils.h" + +#include <gtkhtml/gtkhtml.h> +#include <gtkhtml/gtkhtml-embedded.h> +#include <gtkhtml/gtkhtml-stream.h> +#include <gtkhtml/htmlengine.h> + +#include <gconf/gconf-client.h> + +#include <libgnomevfs/gnome-vfs-utils.h> +#include <libgnomevfs/gnome-vfs-mime-utils.h> +#include <libgnomevfs/gnome-vfs-mime-handlers.h> + +#include <camel/camel-mime-message.h> +#include <camel/camel-stream.h> +#include <camel/camel-stream-filter.h> +#include <camel/camel-mime-filter.h> +#include <camel/camel-mime-filter-tohtml.h> +#include <camel/camel-mime-filter-enriched.h> +#include <camel/camel-multipart.h> +#include <camel/camel-multipart-signed.h> +#include <camel/camel-gpg-context.h> +#include <camel/camel-stream-mem.h> +#include <camel/camel-url.h> +#include <camel/camel-stream-fs.h> +#include <camel/camel-string-utils.h> +#include <camel/camel-http-stream.h> +#include <camel/camel-data-cache.h> +#include <camel/camel-file-utils.h> + +#include <e-util/e-msgport.h> +#include "mail-mt.h" + +#include "em-format-html.h" +#include "em-html-stream.h" +#include "em-utils.h" + +#define d(x) + +#define EFH_TABLE_OPEN "<table>" + +struct _EMFormatHTMLPrivate { + struct _CamelMedium *last_part; /* not reffed, DO NOT dereference */ + volatile int format_id; /* format thread id */ + guint format_timeout_id; + struct _format_msg *format_timeout_msg; + + /* Table that re-maps text parts into a mutlipart/mixed */ + GHashTable *text_inline_parts; + + EDList pending_jobs; + GMutex *lock; +}; + +static void efh_url_requested(GtkHTML *html, const char *url, GtkHTMLStream *handle, EMFormatHTML *efh); +static gboolean efh_object_requested(GtkHTML *html, GtkHTMLEmbedded *eb, EMFormatHTML *efh); +static void efh_gtkhtml_destroy(GtkHTML *html, EMFormatHTML *efh); + +static void efh_format_clone(EMFormat *, CamelMedium *, EMFormat *); +static void efh_format_error(EMFormat *emf, CamelStream *stream, const char *txt); +static void efh_format_message(EMFormat *, CamelStream *, CamelMedium *); +static void efh_format_source(EMFormat *, CamelStream *, CamelMimePart *); +static void efh_format_attachment(EMFormat *, CamelStream *, CamelMimePart *, const char *, const EMFormatHandler *); +static gboolean efh_busy(EMFormat *); + +static void efh_builtin_init(EMFormatHTMLClass *efhc); + +static void efh_write_image(EMFormat *emf, CamelStream *stream, EMFormatPURI *puri); + +static EMFormatClass *efh_parent; +static CamelDataCache *emfh_http_cache; + +#define EMFH_HTTP_CACHE_PATH "http" + +static void +efh_init(GObject *o) +{ + EMFormatHTML *efh = (EMFormatHTML *)o; + GConfClient *gconf; + + efh->priv = g_malloc0(sizeof(*efh->priv)); + + e_dlist_init(&efh->pending_object_list); + e_dlist_init(&efh->priv->pending_jobs); + efh->priv->lock = g_mutex_new(); + efh->priv->format_id = -1; + efh->priv->text_inline_parts = g_hash_table_new(NULL, NULL); + + efh->html = (GtkHTML *)gtk_html_new(); + g_object_ref(efh->html); + gtk_object_sink((GtkObject *)efh->html); + + gtk_html_set_default_content_type(efh->html, "text/html; charset=utf-8"); + gtk_html_set_editable(efh->html, FALSE); + + g_signal_connect(efh->html, "destroy", G_CALLBACK(efh_gtkhtml_destroy), efh); + g_signal_connect(efh->html, "url_requested", G_CALLBACK(efh_url_requested), efh); + g_signal_connect(efh->html, "object_requested", G_CALLBACK(efh_object_requested), efh); + + efh->header_colour = 0xeeeeee; + efh->text_colour = 0; + efh->text_html_flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES + | CAMEL_MIME_FILTER_TOHTML_MARK_CITATION; + + /* TODO: should this be here? wont track changes ... */ + gconf = gconf_client_get_default(); + efh->xmailer_mask = gconf_client_get_int(gconf, "/apps/evolution/mail/display/xmailer_mask", NULL); + g_object_unref(gconf); +} + +static void +efh_gtkhtml_destroy(GtkHTML *html, EMFormatHTML *efh) +{ + if (efh->priv->format_timeout_id != 0) { + g_source_remove(efh->priv->format_timeout_id); + efh->priv->format_timeout_id = 0; + mail_msg_free(efh->priv->format_timeout_msg); + efh->priv->format_timeout_msg = NULL; + } + + /* This probably works ... */ + if (efh->priv->format_id != -1) + mail_msg_cancel(efh->priv->format_id); + + if (efh->html) { + g_object_unref(efh->html); + efh->html = NULL; + } +} + +static void +efh_free_inline_parts(void *key, void *data, void *user) +{ + camel_object_unref(data); +} + +static void +efh_finalise(GObject *o) +{ + EMFormatHTML *efh = (EMFormatHTML *)o; + + /* FIXME: check for leaked stuff */ + + em_format_html_clear_pobject(efh); + + efh_gtkhtml_destroy(efh->html, efh); + + g_hash_table_foreach(efh->priv->text_inline_parts, efh_free_inline_parts, NULL); + g_hash_table_destroy(efh->priv->text_inline_parts); + + g_free(efh->priv); + + ((GObjectClass *)efh_parent)->finalize(o); +} + +static void +efh_base_init(EMFormatHTMLClass *efhklass) +{ + efh_builtin_init(efhklass); +} + +static void +efh_class_init(GObjectClass *klass) +{ + ((EMFormatClass *)klass)->format_clone = efh_format_clone; + ((EMFormatClass *)klass)->format_error = efh_format_error; + ((EMFormatClass *)klass)->format_message = efh_format_message; + ((EMFormatClass *)klass)->format_source = efh_format_source; + ((EMFormatClass *)klass)->format_attachment = efh_format_attachment; + ((EMFormatClass *)klass)->busy = efh_busy; + + klass->finalize = efh_finalise; +} + +GType +em_format_html_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMFormatHTMLClass), + (GBaseInitFunc)efh_base_init, NULL, + (GClassInitFunc)efh_class_init, + NULL, NULL, + sizeof(EMFormatHTML), 0, + (GInstanceInitFunc)efh_init + }; + extern char *evolution_dir; + char *path; + + efh_parent = g_type_class_ref(em_format_get_type()); + type = g_type_register_static(em_format_get_type(), "EMFormatHTML", &info, 0); + + /* cache expiry - 2 hour access, 1 day max */ + path = alloca(strlen(evolution_dir)+16); + sprintf(path, "%s/cache", evolution_dir); + emfh_http_cache = camel_data_cache_new(path, 0, NULL); + camel_data_cache_set_expire_age(emfh_http_cache, 24*60*60); + camel_data_cache_set_expire_access(emfh_http_cache, 2*60*60); + } + + return type; +} + +EMFormatHTML *em_format_html_new(void) +{ + EMFormatHTML *efh; + + efh = g_object_new(em_format_html_get_type(), 0); + + return efh; +} + +/* force loading of http images */ +void em_format_html_load_http(EMFormatHTML *emfh) +{ + if (emfh->load_http) + return; + + /* This will remain set while we're still rendering the same message, then it wont be */ + emfh->load_http_now = TRUE; + d(printf("redrawing with images forced on\n")); + em_format_format_clone((EMFormat *)emfh, emfh->format.message, (EMFormat *)emfh); +} + +void +em_format_html_set_load_http(EMFormatHTML *emfh, int state) +{ + if (emfh->load_http ^ state) { + emfh->load_http = state; + em_format_format_clone((EMFormat *)emfh, emfh->format.message, (EMFormat *)emfh); + } +} + +void +em_format_html_set_mark_citations(EMFormatHTML *emfh, int state, guint32 citation_colour) +{ + if (emfh->mark_citations ^ state || emfh->citation_colour != citation_colour) { + emfh->mark_citations = state; + emfh->citation_colour = citation_colour; + em_format_format_clone((EMFormat *)emfh, emfh->format.message, (EMFormat *)emfh); + } +} + +CamelMimePart * +em_format_html_file_part(EMFormatHTML *efh, const char *mime_type, const char *path, const char *name) +{ + CamelMimePart *part; + CamelStream *stream; + CamelDataWrapper *dw; + char *filename; + + filename = g_build_filename(path, name, NULL); + stream = camel_stream_fs_new_with_name(filename, O_RDONLY, 0); + g_free(filename); + if (stream == NULL) + return NULL; + + part = camel_mime_part_new(); + dw = camel_data_wrapper_new(); + camel_data_wrapper_construct_from_stream(dw, stream); + camel_object_unref(stream); + if (mime_type) + camel_data_wrapper_set_mime_type(dw, mime_type); + part = camel_mime_part_new(); + camel_medium_set_content_object((CamelMedium *)part, dw); + camel_object_unref(dw); + camel_mime_part_set_filename(part, name); + + return part; +} + +/* all this api is a pain in the bum ... */ + +/* should it have a user-data field? */ +const char * +em_format_html_add_pobject(EMFormatHTML *efh, const char *classid, EMFormatHTMLPObjectFunc func, CamelMimePart *part) +{ + EMFormatHTMLPObject *pobj; + + pobj = g_malloc(sizeof(*pobj)); + if (classid) { + pobj->classid = g_strdup(classid); + } else { + static unsigned int uriid = 0; + + pobj->classid = g_strdup_printf("e-object:///%u", uriid++); + } + + pobj->format = efh; + pobj->func = func; + pobj->part = part; + + e_dlist_addtail(&efh->pending_object_list, (EDListNode *)pobj); + + return pobj->classid; +} + +EMFormatHTMLPObject * +em_format_html_find_pobject(EMFormatHTML *emf, const char *classid) +{ + EMFormatHTMLPObject *pw; + + pw = (EMFormatHTMLPObject *)emf->pending_object_list.head; + while (pw->next) { + if (!strcmp(pw->classid, classid)) + return pw; + pw = pw->next; + } + + return NULL; +} + +EMFormatHTMLPObject * +em_format_html_find_pobject_func(EMFormatHTML *emf, CamelMimePart *part, EMFormatHTMLPObjectFunc func) +{ + EMFormatHTMLPObject *pw; + + pw = (EMFormatHTMLPObject *)emf->pending_object_list.head; + while (pw->next) { + if (pw->func == func && pw->part == part) + return pw; + pw = pw->next; + } + + return NULL; +} + +void +em_format_html_remove_pobject(EMFormatHTML *emf, EMFormatHTMLPObject *pobject) +{ + e_dlist_remove((EDListNode *)pobject); + g_free(pobject->classid); + g_free(pobject); +} + +void +em_format_html_clear_pobject(EMFormatHTML *emf) +{ + d(printf("clearing pending objects\n")); + while (!e_dlist_empty(&emf->pending_object_list)) + em_format_html_remove_pobject(emf, (EMFormatHTMLPObject *)emf->pending_object_list.head); +} + +struct _EMFormatHTMLJob * +em_format_html_job_new(EMFormatHTML *emfh, void (*callback)(struct _EMFormatHTMLJob *job, int cancelled), void *data) +{ + struct _EMFormatHTMLJob *job = g_malloc0(sizeof(*job)); + + job->format = emfh; + job->puri_level = ((EMFormat *)emfh)->pending_uri_level; + job->callback = callback; + job->u.data = data; + if (((EMFormat *)emfh)->base) + job->base = camel_url_copy(((EMFormat *)emfh)->base); + + return job; +} + +void +em_format_html_job_queue(EMFormatHTML *emfh, struct _EMFormatHTMLJob *job) +{ + g_mutex_lock(emfh->priv->lock); + e_dlist_addtail(&emfh->priv->pending_jobs, (EDListNode *)job); + g_mutex_unlock(emfh->priv->lock); +} + +/* ********************************************************************** */ + +static void emfh_getpuri(struct _EMFormatHTMLJob *job, int cancelled) +{ + d(printf(" running getpuri task\n")); + if (!cancelled) + job->u.puri->func((EMFormat *)job->format, job->stream, job->u.puri); +} + +static void emfh_gethttp(struct _EMFormatHTMLJob *job, int cancelled) +{ + CamelStream *cistream = NULL, *costream = NULL, *instream = NULL; + CamelURL *url; + ssize_t n, total = 0; + char buffer[1500]; + + if (cancelled + || (url = camel_url_new(job->u.uri, NULL)) == NULL) + goto badurl; + + d(printf(" running load uri task: %s\n", job->u.uri)); + + if (emfh_http_cache) + instream = cistream = camel_data_cache_get(emfh_http_cache, EMFH_HTTP_CACHE_PATH, job->u.uri, NULL); + + if (instream == NULL) { + char *proxy; + + if (!job->format->load_http_now) { + /* TODO: Ideally we would put the http requests into another queue and only send them out + if the user selects 'load images', when they do. The problem is how to maintain this + state with multiple renderings, and how to adjust the thread dispatch/setup routine to handle it */ + /* FIXME: Need to handle 'load if sender in addressbook' case too */ + camel_url_free(url); + goto done; + } + + instream = camel_http_stream_new(CAMEL_HTTP_METHOD_GET, ((EMFormat *)job->format)->session, url); + proxy = em_utils_get_proxy_uri(); + camel_http_stream_set_proxy((CamelHttpStream *)instream, proxy); + g_free(proxy); + camel_operation_start(NULL, _("Retrieving `%s'"), job->u.uri); + } else + camel_operation_start_transient(NULL, _("Retrieving `%s'"), job->u.uri); + + camel_url_free(url); + + if (instream == NULL) + goto done; + + if (emfh_http_cache != NULL && cistream == NULL) + costream = camel_data_cache_add(emfh_http_cache, EMFH_HTTP_CACHE_PATH, job->u.uri, NULL); + + do { + /* FIXME: progress reporting in percentage, can we get the length always? do we care? */ + n = camel_stream_read(instream, buffer, 1500); + if (n > 0) { + camel_operation_progress_count(NULL, total); + total += n; + d(printf(" read %d bytes\n", n)); + if (costream && camel_stream_write(costream, buffer, n) == -1) { + camel_data_cache_remove(emfh_http_cache, EMFH_HTTP_CACHE_PATH, job->u.uri, NULL); + camel_object_unref(costream); + costream = NULL; + } + + camel_stream_write(job->stream, buffer, n); + } else if (n < 0 && costream) { + camel_data_cache_remove(emfh_http_cache, EMFH_HTTP_CACHE_PATH, job->u.uri, NULL); + camel_object_unref(costream); + costream = NULL; + } + } while (n>0); + + /* indicates success */ + if (n == 0) + camel_stream_close(job->stream); + + if (costream) + camel_object_unref(costream); + + camel_object_unref(instream); +done: + camel_operation_end(NULL); +badurl: + g_free(job->u.uri); +} + +/* ********************************************************************** */ + +static void +efh_url_requested(GtkHTML *html, const char *url, GtkHTMLStream *handle, EMFormatHTML *efh) +{ + EMFormatPURI *puri; + struct _EMFormatHTMLJob *job = NULL; + + d(printf("url requested, html = %p, url '%s'\n", html, url)); + + puri = em_format_find_visible_puri((EMFormat *)efh, url); + if (puri) { + puri->use_count++; + + d(printf(" adding puri job\n")); + job = em_format_html_job_new(efh, emfh_getpuri, puri); + } else if (g_ascii_strncasecmp(url, "http:", 5) == 0 || g_ascii_strncasecmp(url, "https:", 6) == 0) { + d(printf(" adding job, get %s\n", url)); + job = em_format_html_job_new(efh, emfh_gethttp, g_strdup(url)); + } else { + d(printf("HTML Includes reference to unknown uri '%s'\n", url)); + gtk_html_stream_close(handle, GTK_HTML_STREAM_ERROR); + } + + if (job) { + job->stream = em_html_stream_new(html, handle); + em_format_html_job_queue(efh, job); + } +} + +static gboolean +efh_object_requested(GtkHTML *html, GtkHTMLEmbedded *eb, EMFormatHTML *efh) +{ + EMFormatHTMLPObject *pobject; + int res = FALSE; + + if (eb->classid == NULL) + return FALSE; + + pobject = em_format_html_find_pobject(efh, eb->classid); + if (pobject) { + /* This stops recursion of the part */ + e_dlist_remove((EDListNode *)pobject); + res = pobject->func(efh, eb, pobject); + e_dlist_addhead(&efh->pending_object_list, (EDListNode *)pobject); + } else { + printf("HTML Includes reference to unknown object '%s'\n", eb->classid); + } + + return res; +} + +/* ********************************************************************** */ +#include "em-inline-filter.h" +#include <camel/camel-stream-null.h> + +static void +efh_text_plain(EMFormatHTML *efh, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info) +{ + CamelStreamFilter *filtered_stream; + CamelMimeFilter *html_filter; + CamelMultipart *mp; + CamelContentType *type; + const char *format; + guint32 rgb = 0x737373, flags; + int i, count; + + flags = efh->text_html_flags; + + /* Check for RFC 2646 flowed text. */ + type = camel_mime_part_get_content_type(part); + if (header_content_type_is(type, "text", "plain") + && (format = header_content_type_param(type, "format")) + && !g_ascii_strcasecmp(format, "flowed")) + flags |= CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED; + + /* This scans the text part for inline-encoded data, creates + a multipart of all the parts inside it. */ + + /* FIXME: We should discard this multipart if it only contains + the original text, but it makes this hash lookup more complex */ + + /* TODO: We could probably put this in the superclass, since + no knowledge of html is required - but this messes with + filters a bit. Perhaps the superclass should just deal with + html anyway and be done with it ... */ + + mp = g_hash_table_lookup(efh->priv->text_inline_parts, part); + if (mp == NULL) { + EMInlineFilter *inline_filter; + CamelStream *null; + + null = camel_stream_null_new(); + filtered_stream = camel_stream_filter_new_with_stream(null); + camel_object_unref(null); + inline_filter = em_inline_filter_new(camel_mime_part_get_encoding(part)); + camel_stream_filter_add(filtered_stream, (CamelMimeFilter *)inline_filter); + camel_data_wrapper_write_to_stream(camel_medium_get_content_object((CamelMedium *)part), (CamelStream *)filtered_stream); + camel_stream_close((CamelStream *)filtered_stream); + camel_object_unref(filtered_stream); + mp = em_inline_filter_get_multipart(inline_filter); + g_hash_table_insert(efh->priv->text_inline_parts, part, mp); + camel_object_unref(inline_filter); + } + + filtered_stream = camel_stream_filter_new_with_stream(stream); + html_filter = camel_mime_filter_tohtml_new(flags, rgb); + camel_stream_filter_add(filtered_stream, html_filter); + camel_object_unref(html_filter); + + /* We handle our made-up multipart here, so we don't recursively call ourselves */ + + count = camel_multipart_get_number(mp); + for (i=0;i<count;i++) { + CamelMimePart *newpart = camel_multipart_get_part(mp, i); + + type = camel_mime_part_get_content_type(newpart); + if (header_content_type_is(type, "text", "plain")) { + camel_stream_write_string(stream, "<table><tr><td><tt>\n"); + em_format_format_text((EMFormat *)efh, (CamelStream *)filtered_stream, camel_medium_get_content_object((CamelMedium *)newpart)); + camel_stream_flush((CamelStream *)filtered_stream); + camel_stream_write_string(stream, "</tt></td></tr></table>\n"); + } else { + em_format_part((EMFormat *)efh, stream, newpart); + } + } + + camel_object_unref(filtered_stream); +} + +static void +efh_text_enriched(EMFormatHTML *efh, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info) +{ + CamelStreamFilter *filtered_stream; + CamelMimeFilter *enriched; + CamelDataWrapper *dw; + guint32 flags = 0; + + dw = camel_medium_get_content_object((CamelMedium *)part); + + if (!strcmp(info->mime_type, "text/richtext")) { + flags = CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT; + camel_stream_write_string( stream, "\n<!-- text/richtext -->\n"); + } else { + camel_stream_write_string( stream, "\n<!-- text/enriched -->\n"); + } + + enriched = camel_mime_filter_enriched_new(flags); + filtered_stream = camel_stream_filter_new_with_stream (stream); + camel_stream_filter_add(filtered_stream, enriched); + camel_object_unref(enriched); + + camel_stream_write_string(stream, EFH_TABLE_OPEN "<tr><td><tt>\n"); + em_format_format_text((EMFormat *)efh, (CamelStream *)filtered_stream, dw); + + camel_stream_write_string(stream, "</tt></td></tr></table>\n"); + camel_object_unref(filtered_stream); +} + +static void +efh_write_text_html(EMFormat *emf, CamelStream *stream, EMFormatPURI *puri) +{ + em_format_format_text(emf, stream, camel_medium_get_content_object((CamelMedium *)puri->part)); +} + +static void +efh_text_html(EMFormatHTML *efh, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info) +{ + const char *location, *base; + EMFormatPURI *puri; + + camel_stream_write_string(stream, "\n<!-- text/html -->\n"); + + if ((base = camel_medium_get_header((CamelMedium *)part, "Content-Base"))) { + char *base_url; + size_t len; + + len = strlen(base); + if (*base == '"' && *(base + len - 1) == '"') { + len -= 2; + base_url = alloca(len + 1); + memcpy(base_url, base + 1, len); + base_url[len] = '\0'; + base = base_url; + } + + /* FIXME: set base needs to go on the gtkhtml stream? */ + gtk_html_set_base(efh->html, base); + } + + puri = em_format_add_puri((EMFormat *)efh, sizeof(EMFormatPURI), NULL, part, efh_write_text_html); + location = puri->uri?puri->uri:puri->cid; + d(printf("adding iframe, location %s\n", location)); + camel_stream_printf(stream, + "<iframe src=\"%s\" frameborder=0 scrolling=no>could not get %s</iframe>", + location, location); +} + +/* This is a lot of code for something useless ... */ +static void +efh_message_external(EMFormatHTML *efh, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info) +{ + CamelContentType *type; + const char *access_type; + char *url = NULL, *desc = NULL; + + /* needs to be cleaner */ + type = camel_mime_part_get_content_type(part); + access_type = header_content_type_param(type, "access-type"); + if (!access_type) { + camel_stream_printf(stream, _("Malformed external-body part.")); + return; + } + + if (!g_ascii_strcasecmp(access_type, "ftp") || + !g_ascii_strcasecmp(access_type, "anon-ftp")) { + const char *name, *site, *dir, *mode; + char *path; + char ftype[16]; + + name = header_content_type_param(type, "name"); + site = header_content_type_param(type, "site"); + dir = header_content_type_param(type, "directory"); + mode = header_content_type_param(type, "mode"); + if (name == NULL || site == NULL) + goto fail; + + /* Generate the path. */ + if (dir) + path = g_strdup_printf("/%s/%s", *dir=='/'?dir+1:dir, name); + else + path = g_strdup_printf("/%s", *name=='/'?name+1:name); + + if (mode && &mode) + sprintf(ftype, ";type=%c", *mode); + else + ftype[0] = 0; + + url = g_strdup_printf ("ftp://%s%s%s", site, path, ftype); + g_free (path); + desc = g_strdup_printf (_("Pointer to FTP site (%s)"), url); + } else if (!g_ascii_strcasecmp (access_type, "local-file")) { + const char *name, *site; + + name = header_content_type_param (type, "name"); + site = header_content_type_param (type, "site"); + if (name == NULL) + goto fail; + + url = g_strdup_printf ("file:///%s", *name == '/' ? name+1:name); + if (site) + desc = g_strdup_printf(_("Pointer to local file (%s) valid at site \"%s\""), name, site); + else + desc = g_strdup_printf(_("Pointer to local file (%s)"), name); + } else if (!g_ascii_strcasecmp (access_type, "URL")) { + const char *urlparam; + char *s, *d; + + /* RFC 2017 */ + + urlparam = header_content_type_param (type, "url"); + if (urlparam == NULL) + goto fail; + + /* For obscure MIMEy reasons, the URL may be split into words */ + url = g_strdup (urlparam); + s = d = url; + while (*s) { + /* FIXME: use camel_isspace */ + if (!isspace ((unsigned char)*s)) + *d++ = *s; + s++; + } + *d = 0; + desc = g_strdup_printf (_("Pointer to remote data (%s)"), url); + } else + goto fail; + + camel_stream_printf(stream, "<a href=\"%s\">%s</a>", url, desc); + g_free(url); + g_free(desc); + + return; + +fail: + camel_stream_printf(stream, _("Pointer to unknown external data (\"%s\" type)"), access_type); +} + +static void +emfh_write_related(EMFormat *emf, CamelStream *stream, EMFormatPURI *puri) +{ + em_format_format_content(emf, stream, puri->part); + camel_stream_close(stream); +} + +static void +emfh_multipart_related_check(struct _EMFormatHTMLJob *job, int cancelled) +{ + struct _EMFormatPURITree *ptree; + EMFormatPURI *puri, *purin; + + if (cancelled) + return; + + d(printf(" running multipart/related check task\n")); + + ptree = job->puri_level; + puri = (EMFormatPURI *)ptree->uri_list.head; + purin = puri->next; + while (purin) { + if (puri->use_count == 0) { + d(printf("part '%s' '%s' used '%d'\n", puri->uri?puri->uri:"", puri->cid, puri->use_count)); + if (puri->func == emfh_write_related) + em_format_part((EMFormat *)job->format, (CamelStream *)job->stream, puri->part); + /* else it was probably added by a previous format this loop */ + } + puri = purin; + purin = purin->next; + } +} + +/* RFC 2387 */ +static void +efh_multipart_related(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelMultipart *mp = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)part); + CamelMimePart *body_part, *display_part = NULL; + CamelContentType *content_type; + const char *location, *start; + int i, nparts; + CamelURL *base_save = NULL; + EMFormatPURI *puri; + struct _EMFormatHTMLJob *job; + + if (!CAMEL_IS_MULTIPART(mp)) { + em_format_format_source(emf, stream, part); + return; + } + + nparts = camel_multipart_get_number(mp); + content_type = camel_mime_part_get_content_type(part); + start = header_content_type_param(content_type, "start"); + if (start && strlen(start)>2) { + int len; + const char *cid; + + /* strip <>'s */ + len = strlen (start) - 2; + start++; + + for (i=0; i<nparts; i++) { + body_part = camel_multipart_get_part(mp, i); + cid = camel_mime_part_get_content_id(body_part); + + if (cid && !strncmp(cid, start, len) && strlen(cid) == len) { + display_part = body_part; + break; + } + } + } else { + display_part = camel_multipart_get_part(mp, 0); + } + + if (display_part == NULL) { + em_format_part_as(emf, stream, part, "multipart/mixed"); + return; + } + + /* stack of present location and pending uri's */ + location = camel_mime_part_get_content_location(part); + if (location) { + d(printf("setting content location %s\n", location)); + base_save = emf->base; + emf->base = camel_url_new(location, NULL); + } + em_format_push_level(emf); + + /* queue up the parts for possible inclusion */ + for (i = 0; i < nparts; i++) { + body_part = camel_multipart_get_part(mp, i); + if (body_part != display_part) { + puri = em_format_add_puri(emf, sizeof(EMFormatPURI), NULL, body_part, emfh_write_related); + d(printf(" part '%s' '%s' added\n", puri->uri?puri->uri:"", puri->cid)); + } + } + + em_format_part(emf, stream, display_part); + camel_stream_flush(stream); + + /* queue a job to check for un-referenced parts to add as attachments */ + job = em_format_html_job_new((EMFormatHTML *)emf, emfh_multipart_related_check, NULL); + job->stream = stream; + camel_object_ref(stream); + em_format_html_job_queue((EMFormatHTML *)emf, job); + + em_format_pull_level(emf); + + if (location) { + camel_url_free(emf->base); + emf->base = base_save; + } +} + +static const struct { + const char *icon; + const char *text; +} signed_table[2] = { + { "pgp-signature-bad.png", N_("This message is digitally signed but can not be proven to be authentic.") }, + { "pgp-signature-ok.png", N_("This message is digitally signed and has been found to be authentic.") } +}; + +void +em_format_html_multipart_signed_sign(EMFormat *emf, CamelStream *stream, CamelMimePart *part) +{ + CamelMimePart *spart; + CamelMultipartSigned *mps; + CamelCipherValidity *valid = NULL; + CamelException ex; + const char *message = NULL; + int good = 0; + CamelCipherContext *cipher; + char *classid; + EMFormatPURI *iconpuri; + CamelMimePart *iconpart; + static int iconid; + + mps = (CamelMultipartSigned *)camel_medium_get_content_object((CamelMedium *)part); + spart = camel_multipart_get_part((CamelMultipart *)mps, CAMEL_MULTIPART_SIGNED_SIGNATURE); + camel_exception_init(&ex); + if (spart == NULL) { + message = _("No signature present"); + } else if (emf->session == NULL) { + message = _("Session not initialised"); + } else if ((cipher = camel_gpg_context_new(emf->session)) == NULL) { + message = _("Could not create signature verfication context"); + } else { + valid = camel_multipart_signed_verify(mps, cipher, &ex); + camel_object_unref(cipher); + if (valid) { + good = camel_cipher_validity_get_valid(valid)?1:0; + message = camel_cipher_validity_get_description(valid); + } else { + message = camel_exception_get_description(&ex); + } + } + + classid = g_strdup_printf("multipart-signed:///em-format-html/%p/icon/%d", part, iconid++); + iconpart = em_format_html_file_part((EMFormatHTML *)emf, "image/png", EVOLUTION_ICONSDIR, signed_table[good].icon); + if (iconpart) { + iconpuri = em_format_add_puri(emf, sizeof(*iconpuri), classid, iconpart, efh_write_image); + camel_object_unref(iconpart); + } + + camel_stream_printf(stream, "<table><tr valign=top>" + "<td><img src=\"%s\"></td>" + "<td>%s<br><br>", + classid, + _(signed_table[good].text)); + g_free(classid); + + if (message) { + char *tmp = camel_text_to_html(message, ((EMFormatHTML *)emf)->text_html_flags, 0); + + camel_stream_printf(stream, "<font size=-1%s>%s</font>", good?"":" color=red", tmp); + g_free(tmp); + } + + camel_stream_write_string(stream, "</td></tr></table>"); + + camel_exception_clear(&ex); + camel_cipher_validity_free(valid); +} + +static void +efh_multipart_signed(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelMimePart *cpart; + CamelMultipartSigned *mps; + + mps = (CamelMultipartSigned *)camel_medium_get_content_object((CamelMedium *)part); + if (!CAMEL_IS_MULTIPART_SIGNED(mps) + || (cpart = camel_multipart_get_part((CamelMultipart *)mps, CAMEL_MULTIPART_SIGNED_CONTENT)) == NULL) { + em_format_format_error(emf, stream, _("Could not parse MIME message. Displaying as source.")); + em_format_format_source(emf, stream, part); + return; + } + + em_format_part(emf, stream, cpart); + em_format_html_multipart_signed_sign(emf, stream, part); +} + +static void +efh_write_image(EMFormat *emf, CamelStream *stream, EMFormatPURI *puri) +{ + CamelDataWrapper *dw = camel_medium_get_content_object((CamelMedium *)puri->part); + + d(printf("writing image '%s'\n", puri->uri?puri->uri:puri->cid)); + camel_data_wrapper_decode_to_stream(dw, stream); + camel_stream_close(stream); +} + +static void +efh_image(EMFormatHTML *efh, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info) +{ + EMFormatPURI *puri; + const char *location; + + puri = em_format_add_puri((EMFormat *)efh, sizeof(EMFormatPURI), NULL, part, efh_write_image); + location = puri->uri?puri->uri:puri->cid; + d(printf("adding image '%s'\n", location)); + camel_stream_printf(stream, "<img hspace=10 vspace=10 src=\"%s\">", location); +} + +static EMFormatHandler type_builtin_table[] = { + { "image/gif", (EMFormatFunc)efh_image }, + { "image/jpeg", (EMFormatFunc)efh_image }, + { "image/png", (EMFormatFunc)efh_image }, + { "image/x-png", (EMFormatFunc)efh_image }, + { "image/tiff", (EMFormatFunc)efh_image }, + { "image/x-bmp", (EMFormatFunc)efh_image }, + { "image/bmp", (EMFormatFunc)efh_image }, + { "image/svg", (EMFormatFunc)efh_image }, + { "image/x-cmu-raster", (EMFormatFunc)efh_image }, + { "image/x-ico", (EMFormatFunc)efh_image }, + { "image/x-portable-anymap", (EMFormatFunc)efh_image }, + { "image/x-portable-bitmap", (EMFormatFunc)efh_image }, + { "image/x-portable-graymap", (EMFormatFunc)efh_image }, + { "image/x-portable-pixmap", (EMFormatFunc)efh_image }, + { "image/x-xpixmap", (EMFormatFunc)efh_image }, + { "text/enriched", (EMFormatFunc)efh_text_enriched }, + { "text/plain", (EMFormatFunc)efh_text_plain }, + { "text/html", (EMFormatFunc)efh_text_html }, + { "text/richtext", (EMFormatFunc)efh_text_enriched }, + /*{ "text/*", (EMFormatFunc)efh_text_plain },*/ + { "message/external-body", (EMFormatFunc)efh_message_external }, + { "multipart/signed", (EMFormatFunc)efh_multipart_signed }, + { "multipart/related", (EMFormatFunc)efh_multipart_related }, + + /* This is where one adds those busted, non-registered types, + that some idiot mailer writers out there decide to pull out + of their proverbials at random. */ + + { "image/jpg", (EMFormatFunc)efh_image }, + { "image/pjpeg", (EMFormatFunc)efh_image }, +}; + +static void +efh_builtin_init(EMFormatHTMLClass *efhc) +{ + int i; + + for (i=0;i<sizeof(type_builtin_table)/sizeof(type_builtin_table[0]);i++) + em_format_class_add_handler((EMFormatClass *)efhc, &type_builtin_table[i]); +} + +/* ********************************************************************** */ + +/* Sigh, this is so we have a cancellable, async rendering thread */ +struct _format_msg { + struct _mail_msg msg; + + EMFormatHTML *format; + EMFormat *format_source; + EMHTMLStream *estream; + CamelMedium *message; +}; + +static char *efh_format_desc(struct _mail_msg *mm, int done) +{ + return g_strdup(_("Formatting message")); +} + +static void efh_format_do(struct _mail_msg *mm) +{ + struct _format_msg *m = (struct _format_msg *)mm; + struct _EMFormatHTMLJob *job; + struct _EMFormatPURITree *puri_level; + int cancelled = FALSE; + CamelURL *base; + + if (m->format->html == NULL) + return; + + camel_stream_printf((CamelStream *)m->estream, + "<!doctype html public \"-//W3C//DTD HTML 4.0 TRANSITIONAL//EN\">\n<html>\n" + "<head>\n<meta name=\"generator\" content=\"Evolution Mail Component\">\n</head>\n" + "<body text=\"#%06x\"\n", + m->format->text_colour & 0xffffff); + + /* <insert top-header stuff here> */ + + if (((EMFormat *)m->format)->mode == EM_FORMAT_SOURCE) + em_format_format_source((EMFormat *)m->format, (CamelStream *)m->estream, (CamelMimePart *)m->message); + else + em_format_format_message((EMFormat *)m->format, (CamelStream *)m->estream, m->message); + camel_stream_flush((CamelStream *)m->estream); + + puri_level = ((EMFormat *)m->format)->pending_uri_level; + base = ((EMFormat *)m->format)->base; + + /* now dispatch any added tasks ... */ + g_mutex_lock(m->format->priv->lock); + while ((job = (struct _EMFormatHTMLJob *)e_dlist_remhead(&m->format->priv->pending_jobs))) { + g_mutex_unlock(m->format->priv->lock); + + /* This is an implicit check to see if the gtkhtml has been destroyed */ + if (!cancelled) + cancelled = m->format->html == NULL; + + /* Now do an explicit check for user cancellation */ + if (!cancelled) + cancelled = camel_operation_cancel_check(NULL); + + /* call jobs even if cancelled, so they can clean up resources */ + ((EMFormat *)m->format)->pending_uri_level = job->puri_level; + if (job->base) + ((EMFormat *)m->format)->base = job->base; + job->callback(job, cancelled); + ((EMFormat *)m->format)->base = base; + + /* clean up the job */ + camel_object_unref(job->stream); + if (job->base) + camel_url_free(job->base); + g_free(job); + + /* incase anything got added above, force it through */ + camel_stream_flush((CamelStream *)m->estream); + + g_mutex_lock(m->format->priv->lock); + } + g_mutex_unlock(m->format->priv->lock); + d(printf("out of jobs, done\n")); + + camel_stream_write_string((CamelStream *)m->estream, "</body>\n</html>\n"); + + camel_stream_close((CamelStream *)m->estream); + camel_object_unref(m->estream); + m->estream = NULL; + + ((EMFormat *)m->format)->pending_uri_level = puri_level; +} + +static void efh_format_done(struct _mail_msg *mm) +{ + struct _format_msg *m = (struct _format_msg *)mm; + + d(printf("formatting finished\n")); + + m->format->priv->format_id = -1; + g_signal_emit_by_name(m->format, "complete"); +} + +static void efh_format_free(struct _mail_msg *mm) +{ + struct _format_msg *m = (struct _format_msg *)mm; + + d(printf("formatter freed\n")); + g_object_unref(m->format); + if (m->estream) { + camel_stream_close((CamelStream *)m->estream); + camel_object_unref(m->estream); + } + if (m->message) + camel_object_unref(m->message); + if (m->format_source) + g_object_unref(m->format_source); +} + +static struct _mail_msg_op efh_format_op = { + efh_format_desc, + efh_format_do, + efh_format_done, + efh_format_free, +}; + +static gboolean +efh_format_timeout(struct _format_msg *m) +{ + GtkHTMLStream *hstream; + EMFormatHTML *efh = m->format; + struct _EMFormatHTMLPrivate *p = efh->priv; + + if (m->format->html == NULL) { + mail_msg_free(m); + return FALSE; + } + + d(printf("timeout called ...\n")); + if (p->format_id != -1) { + d(printf(" still waiting for cancellation to take effect, waiting ...\n")); + return TRUE; + } + + g_assert(e_dlist_empty(&p->pending_jobs)); + + d(printf(" ready to go, firing off format thread\n")); + + /* call super-class to kick it off */ + efh_parent->format_clone((EMFormat *)efh, m->message, m->format_source); + em_format_html_clear_pobject(m->format); + + if (m->message == NULL) { + hstream = gtk_html_begin(efh->html); + gtk_html_stream_close(hstream, GTK_HTML_STREAM_OK); + mail_msg_free(m); + } else { + hstream = gtk_html_begin(efh->html); + m->estream = (EMHTMLStream *)em_html_stream_new(efh->html, hstream); + + if (p->last_part == m->message) { + /* HACK: so we redraw in the same spot */ + /* FIXME: It doesn't work! */ + efh->html->engine->newPage = FALSE; + } else { + /* clear cache of inline-scanned text parts */ + g_hash_table_foreach(p->text_inline_parts, efh_free_inline_parts, NULL); + g_hash_table_destroy(p->text_inline_parts); + p->text_inline_parts = g_hash_table_new(NULL, NULL); + + p->last_part = m->message; + /* FIXME: Need to handle 'load if sender in addressbook' case too */ + efh->load_http_now = efh->load_http; + } + + efh->priv->format_id = m->msg.seq; + e_thread_put(mail_thread_new, (EMsg *)m); + } + + efh->priv->format_timeout_id = 0; + efh->priv->format_timeout_msg = NULL; + + return FALSE; +} + +static void efh_format_clone(EMFormat *emf, CamelMedium *part, EMFormat *emfsource) +{ + EMFormatHTML *efh = (EMFormatHTML *)emf; + struct _format_msg *m; + + /* How to sub-class ? Might need to adjust api ... */ + + if (efh->html == NULL) + return; + + d(printf("efh_format called\n")); + if (efh->priv->format_timeout_id != 0) { + d(printf(" timeout for last still active, removing ...\n")); + g_source_remove(efh->priv->format_timeout_id); + efh->priv->format_timeout_id = 0; + mail_msg_free(efh->priv->format_timeout_msg); + efh->priv->format_timeout_msg = NULL; + } + + m = mail_msg_new(&efh_format_op, NULL, sizeof(*m)); + m->format = (EMFormatHTML *)emf; + g_object_ref(emf); + m->format_source = emfsource; + if (emfsource) + g_object_ref(emfsource); + m->message = part; + if (part) + camel_object_ref(part); + + if (efh->priv->format_id == -1) { + d(printf(" idle, forcing format\n")); + efh_format_timeout(m); + } else { + d(printf(" still busy, cancelling and queuing wait\n")); + /* cancel and poll for completion */ + mail_msg_cancel(efh->priv->format_id); + efh->priv->format_timeout_msg = m; + efh->priv->format_timeout_id = g_timeout_add(100, (GSourceFunc)efh_format_timeout, m); + } +} + +static void efh_format_error(EMFormat *emf, CamelStream *stream, const char *txt) +{ + char *html; + + html = camel_text_to_html (txt, CAMEL_MIME_FILTER_TOHTML_CONVERT_NL|CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); + camel_stream_printf(stream, "<em><font color=\"red\">%s</font></em><br>", html); + g_free(html); +} + +static void +efh_format_text_header(EMFormat *emf, CamelStream *stream, const char *label, const char *value, guint32 flags) +{ + char *mhtml = NULL; + const char *fmt, *html; + + if (value == NULL) + return; + + while (*value == ' ') + value++; + + if (flags & EM_FORMAT_HTML_HEADER_HTML) + html = value; + else + html = mhtml = camel_text_to_html(value, ((EMFormatHTML *)emf)->text_html_flags, 0); + + if (((EMFormatHTML *)emf)->simple_headers) { + fmt = "<b>%s</b>: %s<br>"; + } else { + if (flags & EM_FORMAT_HTML_HEADER_NOCOLUMNS) { + if (flags & EM_FORMAT_HEADER_BOLD) + fmt = "<tr><td><b>%s:</b> %s</td></tr>"; + else + fmt = "<tr><td>%s: %s</td></tr>"; + } else { + if (flags & EM_FORMAT_HEADER_BOLD) + fmt = "<tr><th align=\"right\" valign=\"top\">%s:<b> </b></th><td>%s</td></tr>"; + else + fmt = "<tr><td align=\"right\" valign=\"top\">%s:<b> </b></td><td>%s</td></tr>"; + } + } + + camel_stream_printf(stream, fmt, label, html); + g_free(mhtml); +} + +static void +efh_format_address(EMFormat *emf, CamelStream *stream, const CamelInternetAddress *cia, const char *name, guint32 flags) +{ + char *text; + + if (cia == NULL || !camel_internet_address_get(cia, 0, NULL, NULL)) + return; + + text = camel_address_format((CamelAddress *)cia); + efh_format_text_header(emf, stream, name, text, flags | EM_FORMAT_HEADER_BOLD); + g_free(text); +} + +static void +efh_format_header(EMFormat *emf, CamelStream *stream, CamelMedium *part, const char *namein, guint32 flags, const char *charset) +{ +#define msg ((CamelMimeMessage *)part) +#define efh ((EMFormatHTML *)emf) + char *name; + + name = alloca(strlen(namein)+1); + strcpy(name, namein); + camel_strdown(name); + + if (!strcmp(name, "from")) + efh_format_address(emf, stream, camel_mime_message_get_from(msg), _("From"), flags); + else if (!strcmp(name, "reply-to")) + efh_format_address(emf, stream, camel_mime_message_get_reply_to(msg), _("Reply-To"), flags); + else if (!strcmp(name, "to")) + efh_format_address(emf, stream, camel_mime_message_get_recipients(msg, CAMEL_RECIPIENT_TYPE_TO), _("To"), flags); + else if (!strcmp(name, "cc")) + efh_format_address(emf, stream, camel_mime_message_get_recipients(msg, CAMEL_RECIPIENT_TYPE_CC), _("Cc"), flags); + else if (!strcmp(name, "bcc")) + efh_format_address(emf, stream, camel_mime_message_get_recipients(msg, CAMEL_RECIPIENT_TYPE_BCC), _("Bcc"), flags); + else { + const char *txt, *label; + char *value = NULL; + + if (!strcmp(name, "subject")) { + txt = camel_mime_message_get_subject(msg); + label = _("Subject"); + flags |= EM_FORMAT_HEADER_BOLD; + } else if (!strcmp(name, "x-evolution-mailer")) { /* pseudo-header */ + txt = camel_medium_get_header(part, "x-mailer"); + if (txt == NULL) + txt = camel_medium_get_header(part, "user-agent"); + if (txt == NULL + || ((efh->xmailer_mask & EM_FORMAT_HTML_XMAILER_OTHER) == 0 + && ((efh->xmailer_mask & EM_FORMAT_HTML_XMAILER_EVOLUTION) == 0 + || strstr(txt, "Evolution") == NULL))) + return; + + label = _("Mailer"); + flags |= EM_FORMAT_HEADER_BOLD; + } else if (!strcmp(name, "date")) { + int msg_offset, local_tz; + time_t msg_date; + struct tm local; + const char *date; + + date = camel_medium_get_header(part, "date"); + if (date == NULL) + return; + + /* Show the local timezone equivalent in brackets if the sender is remote */ + msg_date = header_decode_date(date, &msg_offset); + e_localtime_with_offset(msg_date, &local, &local_tz); + + /* Convert message offset to minutes (e.g. -0400 --> -240) */ + msg_offset = ((msg_offset / 100) * 60) + (msg_offset % 100); + /* Turn into offset from localtime, not UTC */ + msg_offset -= local_tz / 60; + + if (msg_offset) { + char buf[32], *html; + + msg_offset += (local.tm_hour * 60) + local.tm_min; + if (msg_offset >= (24 * 60) || msg_offset < 0) { + /* translators: strftime format for local time equivalent in Date header display, with day */ + e_utf8_strftime(buf, sizeof(buf), _("<I> (%a, %R %Z)</I>"), &local); + } else { + /* translators: strftime format for local time equivalent in Date header display, without day */ + e_utf8_strftime(buf, sizeof(buf), _("<I> (%R %Z)</I>"), &local); + } + + html = camel_text_to_html(date, ((EMFormatHTML *)emf)->text_html_flags, 0); + txt = value = g_strdup_printf("%s %s", html, buf); + g_free(html); + flags |= EM_FORMAT_HTML_HEADER_HTML; + } else { + txt = date; + } + + label = _("Date"); + flags |= EM_FORMAT_HEADER_BOLD; + } else { + txt = camel_medium_get_header(part, name); + value = header_decode_string(txt, charset); + txt = value; + label = namein; + } + + efh_format_text_header(emf, stream, label, txt, flags); + g_free(value); + } +#undef msg +#undef efh +} + +void +em_format_html_format_headers(EMFormatHTML *efh, CamelStream *stream, CamelMedium *part) +{ + EMFormatHeader *h; + const char *charset; + CamelContentType *ct; +#define emf ((EMFormat *)efh) + + ct = camel_mime_part_get_content_type((CamelMimePart *)part); + charset = header_content_type_param(ct, "charset"); + charset = e_iconv_charset_name(charset); + + if (!efh->simple_headers) + camel_stream_printf(stream, + "<table width=\"100%%\" cellpadding=5 cellspacing=0>" + "<tr><td>" + "<table width=\"100%%\" cellpaddding=1 cellspacing=0 bgcolor=\"#000000\">" + "<tr><td>" + "<table width=\"100%%\"cellpadding=0 cellspacing=0 bgcolor=\"#%06x\">" + "<tr><td>" + "<table><font color=\"#%06x\"", + efh->header_colour & 0xffffff, + efh->text_colour & 0xffffff); + + /* dump selected headers */ + h = (EMFormatHeader *)emf->header_list.head; + if (h->next == NULL || emf->mode == EM_FORMAT_ALLHEADERS) { + struct _header_raw *header; + + header = ((CamelMimePart *)part)->headers; + while (header) { + efh_format_header(emf, stream, part, header->name, EM_FORMAT_HTML_HEADER_NOCOLUMNS, charset); + header = header->next; + } + } else { + while (h->next) { + efh_format_header(emf, stream, part, h->name, h->flags, charset); + h = h->next; + } + } + + if (!efh->simple_headers) + camel_stream_printf(stream, + "</font></table>" + "</td></tr></table>" + "</td></tr></table>" + "</td></tr></table>"); +#undef emf +} + +static void efh_format_message(EMFormat *emf, CamelStream *stream, CamelMedium *part) +{ +#define efh ((EMFormatHTML *)emf) + + if (!efh->hide_headers) + em_format_html_format_headers(efh, stream, part); + + if (emf->message != part) + camel_stream_printf(stream, "<blockquote>"); + + em_format_part(emf, stream, (CamelMimePart *)part); + + if (emf->message != part) + camel_stream_printf(stream, "</blockquote>"); +#undef efh +} + +static void efh_format_source(EMFormat *emf, CamelStream *stream, CamelMimePart *part) +{ + CamelStreamFilter *filtered_stream; + CamelMimeFilter *html_filter; + CamelDataWrapper *dw = (CamelDataWrapper *)part; + + filtered_stream = camel_stream_filter_new_with_stream ((CamelStream *) stream); + html_filter = camel_mime_filter_tohtml_new (CAMEL_MIME_FILTER_TOHTML_CONVERT_NL + | CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES + | CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT, 0); + camel_stream_filter_add(filtered_stream, html_filter); + camel_object_unref(html_filter); + + camel_stream_write_string((CamelStream *)stream, EFH_TABLE_OPEN "<tr><td><tt>"); + em_format_format_text(emf, (CamelStream *)filtered_stream, dw); + camel_object_unref(filtered_stream); + + camel_stream_write_string(stream, "</tt></td></tr></table>"); +} + +static void +efh_format_attachment(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const char *mime_type, const EMFormatHandler *handle) +{ + char *text, *html; + + /* we display all inlined attachments only */ + + /* this could probably be cleaned up ... */ + camel_stream_write_string(stream, + "<table cellspacing=0 cellpadding=0><tr><td>" + "<table width=10 cellspacing=0 cellpadding=0>" + "<tr><td></td></tr></table></td>" + "<td><table width=3 cellspacing=0 cellpadding=0>" + "<tr><td></td></tr></table></td><td><font size=-1>"); + + /* output some info about it */ + text = em_format_describe_part(part, mime_type); + html = camel_text_to_html(text, ((EMFormatHTML *)emf)->text_html_flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); + camel_stream_write_string(stream, html); + g_free(html); + g_free(text); + + camel_stream_write_string(stream, "</font></td></tr><tr></table>"); + + if (handle && em_format_is_inline(emf, part)) + handle->handler(emf, stream, part, handle); +} + +static gboolean +efh_busy(EMFormat *emf) +{ + return (((EMFormatHTML *)emf)->priv->format_id != -1); +} diff --git a/mail/em-format-html.h b/mail/em-format-html.h new file mode 100644 index 0000000000..c9b4705a96 --- /dev/null +++ b/mail/em-format-html.h @@ -0,0 +1,154 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +/* + Concrete class for formatting mails to html +*/ + +#ifndef _EM_FORMAT_HTML_H +#define _EM_FORMAT_HTML_H + +#include "em-format.h" + +typedef struct _EMFormatHTML EMFormatHTML; +typedef struct _EMFormatHTMLClass EMFormatHTMLClass; + +#if 0 +struct _EMFormatHTMLHandler { + EFrormatHandler base; +}; +#endif + +struct _GtkHTMLEmbedded; +struct _CamelMimePart; +struct _CamelMedium; +struct _CamelStream; + +/* A HTMLJob will be executed in another thread, in sequence, + It's job is to write to its stream, close it if successful, + then exit */ + +typedef struct _EMFormatHTMLJob EMFormatHTMLJob; + +struct _EMFormatHTMLJob { + struct _EMFormatHTMLJob *next, *prev; + + EMFormatHTML *format; + struct _CamelStream *stream; + + /* We need to track the state of the visibility tree at + the point this uri was generated */ + struct _EMFormatPURITree *puri_level; + struct _CamelURL *base; + + void (*callback)(struct _EMFormatHTMLJob *job, int cancelled); + union { + char *uri; + struct _CamelMedium *msg; + EMFormatPURI *puri; + struct _EMFormatPURITree *puri_level; + void *data; + } u; +}; + +/* Pending object (classid: url) */ +typedef struct _EMFormatHTMLPObject EMFormatHTMLPObject; + +typedef gboolean (*EMFormatHTMLPObjectFunc)(EMFormatHTML *md, struct _GtkHTMLEmbedded *eb, EMFormatHTMLPObject *pobject); + +struct _EMFormatHTMLPObject { + struct _EMFormatHTMLPObject *next, *prev; + + struct _EMFormatHTML *format; + + char *classid; + + EMFormatHTMLPObjectFunc func; + struct _CamelMimePart *part; +}; + +#define EM_FORMAT_HTML_HEADER_NOCOLUMNS (EM_FORMAT_HEADER_LAST) +#define EM_FORMAT_HTML_HEADER_HTML (EM_FORMAT_HEADER_LAST<<1) /* header already in html format */ +#define EM_FORMAT_HTML_HEADER_LAST (EM_FORMAT_HEADER_LAST<<8) + +/* xmailer_mask bits */ +#define EM_FORMAT_HTML_XMAILER_EVOLUTION (1<<0) +#define EM_FORMAT_HTML_XMAILER_OTHER (1<<1) +#define EM_FORMAT_HTML_XMAILER_RUPERT (1<<2) + +struct _EMFormatHTML { + EMFormat format; + + struct _EMFormatHTMLPrivate *priv; + + struct _GtkHTML *html; + + EDList pending_object_list; + + GSList *headers; + + guint32 text_html_flags; /* default flags for text to html conversion */ + guint32 header_colour; /* header box colour */ + guint32 text_colour; + guint32 citation_colour; + unsigned int xmailer_mask:4; + unsigned int load_http:1; + unsigned int load_http_now:1; + unsigned int mark_citations:1; + unsigned int simple_headers:1; /* simple header format, no box/table */ + unsigned int hide_headers:1; /* no headers at all */ +}; + +struct _EMFormatHTMLClass { + EMFormatClass format_class; +}; + +GType em_format_html_get_type(void); +EMFormatHTML *em_format_html_new(void); + +void em_format_html_load_http(EMFormatHTML *emf); + +void em_format_html_set_load_http(EMFormatHTML *emf, int state); +void em_format_html_set_mark_citations(EMFormatHTML *emf, int state, guint32 citation_colour); + +/* output headers */ +void em_format_html_format_headers(EMFormatHTML *efh, struct _CamelStream *stream, struct _CamelMedium *part); + +/* retrieves a pseudo-part icon wrapper for a file */ +struct _CamelMimePart *em_format_html_file_part(EMFormatHTML *efh, const char *mime_type, const char *path, const char *name); + +/* for implementers */ +const char *em_format_html_add_pobject(EMFormatHTML *efh, const char *classid, EMFormatHTMLPObjectFunc func, struct _CamelMimePart *part); +EMFormatHTMLPObject * em_format_html_find_pobject(EMFormatHTML *emf, const char *classid); +EMFormatHTMLPObject *em_format_html_find_pobject_func(EMFormatHTML *emf, struct _CamelMimePart *part, EMFormatHTMLPObjectFunc func); +void em_format_html_remove_pobject(EMFormatHTML *emf, EMFormatHTMLPObject *pobject); +void em_format_html_clear_pobject(EMFormatHTML *emf); + +EMFormatHTMLJob *em_format_html_job_new(EMFormatHTML *emfh, void (*callback)(struct _EMFormatHTMLJob *job, int cancelled), void *data) +; +void em_format_html_job_queue(EMFormatHTML *emfh, struct _EMFormatHTMLJob *job); + +/* outputs a signature test */ +void em_format_html_multipart_signed_sign(EMFormat *emf, struct _CamelStream *stream, struct _CamelMimePart *part); + +#endif /* ! EM_FORMAT_HTML_H */ diff --git a/mail/em-format-quote.c b/mail/em-format-quote.c new file mode 100644 index 0000000000..113d854fc5 --- /dev/null +++ b/mail/em-format-quote.c @@ -0,0 +1,289 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <camel/camel-stream.h> +#include <camel/camel-stream-filter.h> +#include <camel/camel-mime-filter-tohtml.h> +#include <camel/camel-mime-filter-enriched.h> +#include "em-format-quote.h" + +struct _EMFormatQuotePrivate { + int dummy; +}; + +static void emfq_format_clone(EMFormat *, CamelMedium *, EMFormat *); +static void emfq_format_error(EMFormat *emf, CamelStream *stream, const char *txt); +static void emfq_format_message(EMFormat *, CamelStream *, CamelMedium *); +static void emfq_format_source(EMFormat *, CamelStream *, CamelMimePart *); +static void emfq_format_attachment(EMFormat *, CamelStream *, CamelMimePart *, const char *, const EMFormatHandler *); + +static void emfq_builtin_init(EMFormatQuoteClass *efhc); + +static EMFormatClass *emfq_parent; + +static void +emfq_init(GObject *o) +{ + EMFormatQuote *emfq =(EMFormatQuote *) o; + + emfq->priv = g_malloc0(sizeof(*emfq->priv)); + + /* we want to convert url's etc */ + emfq->text_html_flags = CAMEL_MIME_FILTER_TOHTML_PRE | CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS + | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES; +} + +static void +emfq_finalise(GObject *o) +{ + EMFormatQuote *emfq =(EMFormatQuote *) o; + + if (emfq->stream) + camel_object_unref(emfq->stream); + g_free(emfq->credits); + g_free(emfq->priv); + + ((GObjectClass *) emfq_parent)->finalize(o); +} + +static void +emfq_base_init(EMFormatQuoteClass *emfqklass) +{ + emfq_builtin_init(emfqklass); +} + +static void +emfq_class_init(GObjectClass *klass) +{ + ((EMFormatClass *) klass)->format_clone = emfq_format_clone; + ((EMFormatClass *) klass)->format_error = emfq_format_error; + ((EMFormatClass *) klass)->format_message = emfq_format_message; + ((EMFormatClass *) klass)->format_source = emfq_format_source; + ((EMFormatClass *) klass)->format_attachment = emfq_format_attachment; + + klass->finalize = emfq_finalise; +} + +GType +em_format_quote_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMFormatQuoteClass), + (GBaseInitFunc)emfq_base_init, NULL, + (GClassInitFunc)emfq_class_init, + NULL, NULL, + sizeof(EMFormatQuote), 0, + (GInstanceInitFunc) emfq_init + }; + + emfq_parent = g_type_class_ref(em_format_get_type()); + type = g_type_register_static(em_format_get_type(), "EMFormatQuote", &info, 0); + } + + return type; +} + +EMFormatQuote * +em_format_quote_new(const char *credits, CamelStream *stream, guint32 flags) +{ + EMFormatQuote *emfq; + + emfq = (EMFormatQuote *)g_object_new(em_format_quote_get_type(), NULL); + + emfq->credits = g_strdup(credits); + emfq->stream = stream; + camel_object_ref(stream); + emfq->flags = flags; + + return emfq; +} + +static void +emfq_format_clone(EMFormat *emf, CamelMedium *part, EMFormat *src) +{ +#define emfq ((EMFormatQuote *)emf) + + ((EMFormatClass *)emfq_parent)->format_clone(emf, part, src); + + camel_stream_reset(emfq->stream); + em_format_format_message(emf, emfq->stream, part); + camel_stream_flush(emfq->stream); + + g_signal_emit_by_name(emf, "complete"); +#undef emfq +} + +static void +emfq_format_error(EMFormat *emf, CamelStream *stream, const char *txt) +{ + /* FIXME: should we even bother writign error text for quoting? probably not... */ +} + +static void +emfq_format_message(EMFormat *emf, CamelStream *stream, CamelMedium *part) +{ + EMFormatQuote *emfq =(EMFormatQuote *) emf; + + if (emfq->credits) + camel_stream_printf(stream, "%s", emfq->credits); + + + if (emfq->flags & EM_FORMAT_QUOTE_CITE) + camel_stream_printf(stream, "<!--+GtkHTML:<DATA class=\"ClueFlow\" key=\"orig\" value=\"1\">-->\n" + "<blockquote type=cite>\n" + "<font color=\"#%06x\">\n", + emfq->citation_colour & 0xffffff); + + if (emfq->flags & EM_FORMAT_QUOTE_HEADERS) { + camel_stream_printf(stream, "<b>To: </b> Header goes here<br>"); + } + + em_format_part(emf, stream, (CamelMimePart *)part); + + if (emfq->flags & EM_FORMAT_QUOTE_CITE) + camel_stream_write_string(stream, "</blockquote></font><!--+GtkHTML:<DATA class=\"ClueFlow\" clear=\"orig\">-->"); +} + +static void +emfq_format_source(EMFormat *emf, CamelStream *stream, CamelMimePart *part) +{ + CamelStreamFilter *filtered_stream; + CamelMimeFilter *html_filter; + CamelDataWrapper *dw = (CamelDataWrapper *)part; + + filtered_stream = camel_stream_filter_new_with_stream ((CamelStream *) stream); + html_filter = camel_mime_filter_tohtml_new (CAMEL_MIME_FILTER_TOHTML_CONVERT_NL + | CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES + | CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT, 0); + camel_stream_filter_add(filtered_stream, html_filter); + camel_object_unref(html_filter); + + em_format_format_text(emf, (CamelStream *)filtered_stream, dw); + camel_object_unref(filtered_stream); +} + +static void +emfq_format_attachment(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const char *mime_type, const EMFormatHandler *handle) +{ + ; +} + +#include <camel/camel-medium.h> +#include <camel/camel-mime-part.h> +#include <camel/camel-multipart.h> +#include <camel/camel-url.h> + +static void +emfq_text_plain(EMFormatQuote *emfq, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info) +{ + CamelStreamFilter *filtered_stream; + CamelMimeFilter *html_filter; + CamelContentType *type; + const char *format; + guint32 rgb = 0x737373, flags; + + flags = emfq->text_html_flags; + + /* Check for RFC 2646 flowed text. */ + type = camel_mime_part_get_content_type(part); + if (header_content_type_is(type, "text", "plain") + && (format = header_content_type_param(type, "format")) + && !g_ascii_strcasecmp(format, "flowed")) + flags |= CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED; + + html_filter = camel_mime_filter_tohtml_new(flags, rgb); + filtered_stream = camel_stream_filter_new_with_stream(stream); + camel_stream_filter_add(filtered_stream, html_filter); + camel_object_unref(html_filter); + + em_format_format_text((EMFormat *)emfq, (CamelStream *)filtered_stream, camel_medium_get_content_object((CamelMedium *)part)); + camel_stream_flush((CamelStream *)filtered_stream); + camel_object_unref(filtered_stream); +} + +static void +emfq_text_enriched(EMFormatQuote *emfq, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info) +{ + CamelStreamFilter *filtered_stream; + CamelMimeFilter *enriched; + CamelDataWrapper *dw; + guint32 flags = 0; + + dw = camel_medium_get_content_object((CamelMedium *)part); + + if (!strcmp(info->mime_type, "text/richtext")) { + flags = CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT; + camel_stream_write_string( stream, "\n<!-- text/richtext -->\n"); + } else { + camel_stream_write_string( stream, "\n<!-- text/enriched -->\n"); + } + + enriched = camel_mime_filter_enriched_new(flags); + filtered_stream = camel_stream_filter_new_with_stream (stream); + camel_stream_filter_add(filtered_stream, enriched); + camel_object_unref(enriched); + + camel_stream_write_string(stream, "<br><hr><br>"); + em_format_format_text((EMFormat *)emfq, (CamelStream *)filtered_stream, dw); + camel_object_unref(filtered_stream); +} + +static void +emfq_text_html(EMFormat *emf, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info) +{ + camel_stream_write_string(stream, "\n<!-- text/html -->\n"); + em_format_format_text(emf, stream, camel_medium_get_content_object((CamelMedium *)part)); +} + +static const char *type_remove_table[] = { + "message/external-body", + "multipart/appledouble", +}; + +static EMFormatHandler type_builtin_table[] = { + { "text/plain",(EMFormatFunc)emfq_text_plain }, + { "text/enriched",(EMFormatFunc)emfq_text_enriched }, + { "text/richtext",(EMFormatFunc)emfq_text_enriched }, + { "text/html",(EMFormatFunc)emfq_text_html }, +/* { "multipart/related",(EMFormatFunc)emfq_multipart_related },*/ +}; + +static void +emfq_builtin_init(EMFormatQuoteClass *efhc) +{ + int i; + + for (i = 0; i < sizeof(type_remove_table) / sizeof(type_remove_table[0]); i++) + em_format_class_remove_handler((EMFormatClass *) efhc, type_remove_table[i]); + + for (i=0;i<sizeof(type_builtin_table)/sizeof(type_builtin_table[0]);i++) + em_format_class_add_handler((EMFormatClass *)efhc, &type_builtin_table[i]); +} diff --git a/mail/em-format-quote.h b/mail/em-format-quote.h new file mode 100644 index 0000000000..218937b935 --- /dev/null +++ b/mail/em-format-quote.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifndef _EM_FORMAT_QUOTE_H +#define _EM_FORMAT_QUOTE_H + +#include "em-format.h" + +typedef struct _EMFormatQuote EMFormatQuote; +typedef struct _EMFormatQuoteClass EMFormatQuoteClass; + +#define EM_FORMAT_QUOTE_CITE (1<<0) +#define EM_FORMAT_QUOTE_HEADERS (1<<1) + +struct _EMFormatQuote { + EMFormat format; + + struct _EMFormatQuotePrivate *priv; + + char *credits; + struct _CamelStream *stream; + guint32 flags; + + guint32 text_html_flags; + guint32 citation_colour; +}; + +struct _EMFormatQuoteClass { + EMFormatClass format_class; +}; + +GType em_format_quote_get_type(void); + +EMFormatQuote *em_format_quote_new(const char *, struct _CamelStream *, guint32 flags); + +#endif /* !_EM_FORMAT_QUOTE_H */ diff --git a/mail/em-format.c b/mail/em-format.c new file mode 100644 index 0000000000..d239266806 --- /dev/null +++ b/mail/em-format.c @@ -0,0 +1,1228 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include <libgnomevfs/gnome-vfs-mime.h> +#include <libgnomevfs/gnome-vfs-mime-utils.h> + +#include <gconf/gconf-client.h> + +#include <e-util/e-msgport.h> +#include <camel/camel-url.h> +#include <camel/camel-stream.h> +#include <camel/camel-stream-mem.h> +#include <camel/camel-multipart.h> +#include <camel/camel-multipart-encrypted.h> +#include <camel/camel-multipart-signed.h> +#include <camel/camel-medium.h> +#include <camel/camel-mime-message.h> +#include <camel/camel-gpg-context.h> +#include <camel/camel-string-utils.h> +#include <camel/camel-stream-filter.h> +#include <camel/camel-stream-null.h> +#include <camel/camel-mime-filter-charset.h> +#include <camel/camel-mime-filter-windows.h> + +#include "em-format.h" + +#define d(x) + +static void emf_builtin_init(EMFormatClass *); +static const char *emf_snoop_part(CamelMimePart *part); + +static void emf_format_clone(EMFormat *emf, CamelMedium *msg, EMFormat *emfsource); +static gboolean emf_busy(EMFormat *emf); + +enum { + EMF_COMPLETE, + EMF_LAST_SIGNAL, +}; + +static guint emf_signals[EMF_LAST_SIGNAL]; +static GObjectClass *emf_parent; + +static void +emf_init(GObject *o) +{ + EMFormat *emf = (EMFormat *)o; + + emf->inline_table = g_hash_table_new(NULL, NULL); + e_dlist_init(&emf->header_list); + em_format_default_headers(emf); +} + +static void +emf_finalise(GObject *o) +{ + EMFormat *emf = (EMFormat *)o; + + if (emf->session) + camel_object_unref(emf->session); + + if (emf->inline_table) + g_hash_table_destroy(emf->inline_table); + + em_format_clear_headers(emf); + g_free(emf->charset); + + /* FIXME: check pending jobs */ + + ((GObjectClass *)emf_parent)->finalize(o); +} + +static void +emf_base_init(EMFormatClass *emfklass) +{ + emfklass->type_handlers = g_hash_table_new(g_str_hash, g_str_equal); + emf_builtin_init(emfklass); +} + +static void +emf_class_init(GObjectClass *klass) +{ + ((EMFormatClass *)klass)->type_handlers = g_hash_table_new(g_str_hash, g_str_equal); + emf_builtin_init((EMFormatClass *)klass); + + klass->finalize = emf_finalise; + ((EMFormatClass *)klass)->format_clone = emf_format_clone; + ((EMFormatClass *)klass)->busy = emf_busy; + + emf_signals[EMF_COMPLETE] = + g_signal_new("complete", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EMFormatClass, complete), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +GType +em_format_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMFormatClass), + (GBaseInitFunc)emf_base_init, NULL, + (GClassInitFunc)emf_class_init, + NULL, NULL, + sizeof(EMFormat), 0, + (GInstanceInitFunc)emf_init + }; + emf_parent = g_type_class_ref(G_TYPE_OBJECT); + type = g_type_register_static(G_TYPE_OBJECT, "EMFormat", &info, 0); + } + + return type; +} + +/** + * em_format_class_add_handler: + * @emfc: EMFormatClass + * @info: Callback information. + * + * Add a mime type handler to this class. This is only used by implementing + * classes. + * + * When a mime type described by @info is encountered, the callback will + * be invoked. Note that @info may be extended by sub-classes if + * they require additional context information. + * + * Use a mime type of "foo/ *" to insert a fallback handler for type "foo". + **/ +void +em_format_class_add_handler(EMFormatClass *emfc, EMFormatHandler *info) +{ + g_hash_table_insert(emfc->type_handlers, info->mime_type, info); + /* FIXME: do we care? This is really gui stuff */ + /* + if (info->applications == NULL) + info->applications = gnome_vfs_mime_get_short_list_applications(info->mime_type);*/ +} + + +/** + * em_format_class_remove_handler: + * @emfc: EMFormatClass + * @mime_type: mime-type of handler to remove + * + * Remove a mime type handler from this class. This is only used by + * implementing classes. + **/ +void +em_format_class_remove_handler (EMFormatClass *emfc, const char *mime_type) +{ + g_hash_table_remove (emfc->type_handlers, mime_type); +} + + +/** + * em_format_find_handler: + * @emf: + * @mime_type: + * + * Find a format handler by @mime_type. + * + * Return value: NULL if no handler is available. + **/ +const EMFormatHandler * +em_format_find_handler(EMFormat *emf, const char *mime_type) +{ + EMFormatClass *emfc = (EMFormatClass *)G_OBJECT_GET_CLASS(emf); + + return g_hash_table_lookup(emfc->type_handlers, mime_type); +} + +/** + * em_format_fallback_handler: + * @emf: + * @mime_type: + * + * Try to find a format handler based on the major type of the @mime_type. + * + * The subtype is replaced with "*" and a lookup performed. + * + * Return value: + **/ +const EMFormatHandler * +em_format_fallback_handler(EMFormat *emf, const char *mime_type) +{ + char *mime, *s; + + s = strchr(mime_type, '/'); + if (s == NULL) + mime = (char *)mime_type; + else { + size_t len = (s-mime_type)+1; + + mime = alloca(len+2); + strncpy(mime, mime_type, len); + strcpy(mime+len, "*"); + } + + return em_format_find_handler(emf, mime); +} + +/** + * em_format_add_puri: + * @emf: + * @size: + * @cid: Override the autogenerated content id. + * @part: + * @func: + * + * Add a pending-uri handler. When formatting parts that reference + * other parts, a pending-uri (PURI) can be used to track the reference. + * + * @size is used to allocate the structure, so that it can be directly + * subclassed by implementors. + * + * @cid can be used to override the key used to retreive the PURI, if NULL, + * then the content-location and the content-id of the @part are stored + * as lookup keys for the part. + * + * FIXME: This may need a free callback. + * + * Return value: A new PURI, with a referenced copy of @part, and the cid + * always set. The uri will be set if one is available. Clashes + * are resolved by forgetting the old PURI in the global index. + **/ +EMFormatPURI * +em_format_add_puri(EMFormat *emf, size_t size, const char *cid, CamelMimePart *part, EMFormatPURIFunc func) +{ + EMFormatPURI *puri; + const char *tmp; + static unsigned int uriid; + + g_assert(size >= sizeof(*puri)); + puri = g_malloc0(size); + + puri->format = emf; + puri->func = func; + puri->use_count = 0; + puri->cid = g_strdup(cid); + + if (part) { + camel_object_ref(part); + puri->part = part; + } + + if (part != NULL && cid == NULL) { + tmp = camel_mime_part_get_content_id(part); + if (tmp) + puri->cid = g_strdup_printf("cid:%s", tmp); + else + puri->cid = g_strdup_printf("em-no-cid-%u", uriid++); + + d(printf("built cid '%s'\n", puri->cid)); + + /* not quite same as old behaviour, it also put in the relative uri and a fallback for no parent uri */ + tmp = camel_mime_part_get_content_location(part); + puri->uri = NULL; + if (tmp == NULL) { + if (emf->base) + puri->uri = camel_url_to_string(emf->base, 0); + } else { + if (strchr(tmp, ':') == NULL && emf->base != NULL) { + CamelURL *uri; + + uri = camel_url_new_with_base(emf->base, tmp); + puri->uri = camel_url_to_string(uri, 0); + camel_url_free(uri); + } else { + puri->uri = g_strdup(tmp); + } + } + } + + g_assert(puri->cid != NULL); + g_assert(emf->pending_uri_level != NULL); + g_assert(emf->pending_uri_table != NULL); + + e_dlist_addtail(&emf->pending_uri_level->uri_list, (EDListNode *)puri); + + if (puri->uri) + g_hash_table_insert(emf->pending_uri_table, puri->uri, puri); + g_hash_table_insert(emf->pending_uri_table, puri->cid, puri); + + return puri; +} + +/** + * em_format_push_level: + * @emf: + * + * This is used to build a heirarchy of visible PURI objects based on + * the structure of the message. Used by multipart/alternative formatter. + * + * FIXME: This could probably also take a uri so it can automaticall update + * the base location. + **/ +void +em_format_push_level(EMFormat *emf) +{ + struct _EMFormatPURITree *purilist; + + d(printf("em_format_push_level\n")); + purilist = g_malloc0(sizeof(*purilist)); + e_dlist_init(&purilist->children); + e_dlist_init(&purilist->uri_list); + purilist->parent = emf->pending_uri_level; + if (emf->pending_uri_tree == NULL) { + emf->pending_uri_tree = purilist; + } else { + e_dlist_addtail(&emf->pending_uri_level->children, (EDListNode *)purilist); + } + emf->pending_uri_level = purilist; +} + +/** + * em_format_pull_level: + * @emf: + * + * Drop a level of visibility back to the parent. Note that + * no PURI values are actually freed. + **/ +void +em_format_pull_level(EMFormat *emf) +{ + d(printf("em_format_pull_level\n")); + emf->pending_uri_level = emf->pending_uri_level->parent; +} + +/** + * em_format_find_visible_puri: + * @emf: + * @uri: + * + * Search for a PURI based on the visibility defined by :push_level() + * and :pull_level(). + * + * Return value: + **/ +EMFormatPURI * +em_format_find_visible_puri(EMFormat *emf, const char *uri) +{ + EMFormatPURI *pw; + struct _EMFormatPURITree *ptree; + + d(printf("checking for visible uri '%s'\n", uri)); + + ptree = emf->pending_uri_level; + while (ptree) { + pw = (EMFormatPURI *)ptree->uri_list.head; + while (pw->next) { + d(printf(" pw->uri = '%s' pw->cid = '%s\n", pw->uri?pw->uri:"", pw->cid)); + if ((pw->uri && !strcmp(pw->uri, uri)) || !strcmp(pw->cid, uri)) + return pw; + pw = pw->next; + } + ptree = ptree->parent; + } + + return NULL; +} + +/** + * em_format_find_puri: + * @emf: + * @uri: + * + * Search for a PURI based on a uri. Both the content-id + * and content-location are checked. + * + * Return value: + **/ +EMFormatPURI * +em_format_find_puri(EMFormat *emf, const char *uri) +{ + return g_hash_table_lookup(emf->pending_uri_table, uri); +} + +static void +emf_clear_puri_node(struct _EMFormatPURITree *node) +{ + { + EMFormatPURI *pw, *pn; + + /* clear puri's at this level */ + pw = (EMFormatPURI *)node->uri_list.head; + pn = pw->next; + while (pn) { + g_free(pw->uri); + g_free(pw->cid); + if (pw->part) + camel_object_unref(pw->part); + g_free(pw); + pw = pn; + pn = pn->next; + } + } + + { + struct _EMFormatPURITree *cw, *cn; + + /* clear child nodes */ + cw = (struct _EMFormatPURITree *)node->children.head; + cn = cw->next; + while (cn) { + emf_clear_puri_node(cw); + cw = cn; + cn = cn->next; + } + } + + g_free(node); +} + +/** + * em_format_clear_puri_tree: + * @emf: + * + * For use by implementors to clear out the message structure + * data. + **/ +void +em_format_clear_puri_tree(EMFormat *emf) +{ + d(printf("clearing pending uri's\n")); + + if (emf->pending_uri_table) { + g_hash_table_destroy(emf->pending_uri_table); + emf_clear_puri_node(emf->pending_uri_tree); + emf->pending_uri_level = NULL; + emf->pending_uri_tree = NULL; + } + emf->pending_uri_table = g_hash_table_new(g_str_hash, g_str_equal); + em_format_push_level(emf); +} + +/* use mime_type == NULL to force showing as application/octet-stream */ +void +em_format_part_as(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const char *mime_type) +{ + const EMFormatHandler *handle = NULL; + + if (mime_type != NULL) { + if (g_ascii_strcasecmp(mime_type, "application/octet-stream") == 0) + mime_type = emf_snoop_part(part); + + handle = em_format_find_handler(emf, mime_type); + if (handle == NULL) + handle = em_format_fallback_handler(emf, mime_type); + + if (handle != NULL + && !em_format_is_attachment(emf, part)) { + d(printf("running handler for type '%s'\n", mime_type)); + handle->handler(emf, stream, part, handle); + return; + } + d(printf("this type is an attachment? '%s'\n", mime_type)); + } else { + mime_type = "application/octet-stream"; + } + + ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->format_attachment(emf, stream, part, mime_type, handle); +} + +void +em_format_part(EMFormat *emf, CamelStream *stream, CamelMimePart *part) +{ + char *mime_type; + CamelDataWrapper *dw; + + dw = camel_medium_get_content_object((CamelMedium *)part); + mime_type = camel_data_wrapper_get_mime_type(dw); + camel_strdown(mime_type); + em_format_part_as(emf, stream, part, mime_type); + g_free(mime_type); +} + +static void +emf_clone_inlines(void *key, void *val, void *data) +{ + g_hash_table_insert(((EMFormat *)data)->inline_table, key, val); +} + +static void +emf_format_clone(EMFormat *emf, CamelMedium *msg, EMFormat *emfsource) +{ + em_format_clear_puri_tree(emf); + + if (emf != emfsource) { + g_hash_table_destroy(emf->inline_table); + emf->inline_table = g_hash_table_new(NULL, NULL); + if (emfsource) { + /* We clone the current state here */ + g_hash_table_foreach(emfsource->inline_table, emf_clone_inlines, emf); + emf->mode = emfsource->mode; + g_free(emf->charset); + emf->charset = g_strdup(emfsource->charset); + /* FIXME: clone headers shown */ + } + } + + if (msg != emf->message) { + if (emf->message) + camel_object_unref(emf->message); + if (msg) + camel_object_ref(msg); + emf->message = msg; + } +} + +static gboolean +emf_busy(EMFormat *emf) +{ + return FALSE; +} + +/** + * em_format_format_clone: + * @emf: Mail formatter. + * @msg: Mail message. + * @emfsource: Used as a basis for user-altered layout, e.g. inline viewed + * attachments. + * + * Format a message @msg. If @emfsource is non NULL, then the status of + * inlined expansion and so forth is copied direction from @emfsource. + * + * By passing the same value for @emf and @emfsource, you can perform + * a display refresh, or it can be used to generate an identical layout, + * e.g. to print what the user has shown inline. + **/ +/* e_format_format_clone is a macro */ + +/** + * em_format_set_session: + * @emf: + * @s: + * + * Set the CamelSession to be used for signature verification and decryption + * purposes. If this is not set, then signatures cannot be verified or + * encrypted messages viewed. + **/ +void +em_format_set_session(EMFormat *emf, struct _CamelSession *s) +{ + if (s) + camel_object_ref(s); + if (emf->session) + camel_object_unref(emf->session); + emf->session = s; +} + +/** + * em_format_set_mode: + * @emf: + * @type: + * + * Set display mode, EM_FORMAT_SOURCE, EM_FORMAT_ALLHEADERS, or + * EM_FORMAT_NORMAL. + **/ +void +em_format_set_mode(EMFormat *emf, em_format_mode_t type) +{ + if (emf->mode == type) + return; + + emf->mode = type; + + /* force redraw if type changed afterwards */ + if (emf->message) + em_format_format_clone(emf, emf->message, emf); +} + +/** + * em_format_set_charset: + * @emf: + * @charset: + * + * set override charset on formatter. message will be redisplayed if + * required. + **/ +void +em_format_set_charset(EMFormat *emf, const char *charset) +{ + if ((emf->charset && charset && g_ascii_strcasecmp(emf->charset, charset) == 0) + || (emf->charset == NULL && charset == NULL) + || (emf->charset == charset)) + return; + + g_free(emf->charset); + emf->charset = g_strdup(charset); + + if (emf->message) + em_format_format_clone(emf, emf->message, emf); +} + +/** + * em_format_clear_headers: + * @emf: + * + * Clear the list of headers to be displayed. This will force all headers to + * be shown. + **/ +void +em_format_clear_headers(EMFormat *emf) +{ + EMFormatHeader *eh; + + while ((eh = (EMFormatHeader *)e_dlist_remhead(&emf->header_list))) + g_free(eh); +} + +static const struct { + const char *name; + guint32 flags; +} default_headers[] = { + { N_("From"), EM_FORMAT_HEADER_BOLD }, + { N_("Reply-To"), EM_FORMAT_HEADER_BOLD }, + { N_("To"), EM_FORMAT_HEADER_BOLD }, + { N_("Cc"), EM_FORMAT_HEADER_BOLD }, + { N_("Bcc"), EM_FORMAT_HEADER_BOLD }, + { N_("Subject"), EM_FORMAT_HEADER_BOLD }, + { N_("Date"), EM_FORMAT_HEADER_BOLD }, + { "x-evolution-mailer", 0 }, /* DO NOT translate */ +}; + +/** + * em_format_default_headers: + * @emf: + * + * Set the headers to show to the default list. + * + * From, Reply-To, To, Cc, Bcc, Subject and Date. + **/ +void +em_format_default_headers(EMFormat *emf) +{ + int i; + + em_format_clear_headers(emf); + for (i=0;i<sizeof(default_headers)/sizeof(default_headers[0]);i++) + em_format_add_header(emf, default_headers[i].name, default_headers[i].flags); +} + +/** + * em_format_add_header: + * @emf: + * @name: The name of the header, as it will appear during output. + * @flags: EM_FORMAT_HEAD_* defines to control display attributes. + * + * Add a specific header to show. If any headers are set, they will + * be displayed in the order set by this function. Certain known + * headers included in this list will be shown using special + * formatting routines. + **/ +void em_format_add_header(EMFormat *emf, const char *name, guint32 flags) +{ + EMFormatHeader *h; + + h = g_malloc(sizeof(*h) + strlen(name)); + h->flags = flags; + strcpy(h->name, name); + e_dlist_addtail(&emf->header_list, (EDListNode *)h); +} + +/** + * em_format_is_attachment: + * @emf: + * @part: Part to check. + * + * Returns true if the part is an attachment. + * + * A part is not considered an attachment if it is a + * multipart, or a text part with no filename. It is used + * to determine if an attachment header should be displayed for + * the part. + * + * Content-Disposition is not checked. + * + * Return value: TRUE/FALSE + **/ +int em_format_is_attachment(EMFormat *emf, CamelMimePart *part) +{ + /*CamelContentType *ct = camel_mime_part_get_content_type(part);*/ + CamelDataWrapper *dw = camel_medium_get_content_object((CamelMedium *)part); + + /*printf("checking is attachment %s/%s\n", ct->type, ct->subtype);*/ + return !(/*header_content_type_is(ct, "message", "*") + ||*/ header_content_type_is(dw->mime_type, "multipart", "*") + || (header_content_type_is(dw->mime_type, "text", "*") + && camel_mime_part_get_filename(part) == NULL)); + +} + +/** + * em_format_is_inline: + * @emf: + * @part: + * + * Returns true if the part should be displayed inline. Any part with + * a Content-Disposition of inline, or any message type is displayed + * inline. + * + * ::set_inline() called on the same part will override any calculated + * value. + * + * Return value: + **/ +int em_format_is_inline(EMFormat *emf, CamelMimePart *part) +{ + void *dummy, *override; + const char *tmp; + CamelContentType *ct; + + if (g_hash_table_lookup_extended(emf->inline_table, part, &dummy, &override)) + return GPOINTER_TO_INT(override); + + tmp = camel_mime_part_get_disposition(part); + if (tmp) + return g_ascii_strcasecmp(tmp, "inline") == 0; + + /* messages are always inline? */ + ct = camel_mime_part_get_content_type(part); + + return header_content_type_is(ct, "message", "*"); +} + +/** + * em_format_set_inline: + * @emf: + * @part: + * @state: + * + * Force the attachment @part to be expanded or hidden explictly to match + * @state. This is used only to record the change for a redraw or + * cloned layout render and does not force a redraw. + **/ +void em_format_set_inline(EMFormat *emf, CamelMimePart *part, int state) +{ + g_hash_table_insert(emf->inline_table, part, GINT_TO_POINTER(state)); +} + +/* should this be virtual? */ +void +em_format_format_content(EMFormat *emf, CamelStream *stream, CamelMimePart *part) +{ + CamelDataWrapper *dw = camel_medium_get_content_object((CamelMedium *)part); + + if (header_content_type_is(dw->mime_type, "text", "*")) + em_format_format_text(emf, stream, dw); + else + camel_data_wrapper_decode_to_stream(dw, stream); +} + +/** + * em_format_format_content: + * @emf: + * @stream: Where to write the converted text + * @part: Part whose container is to be formatted + * + * Decode/output a part's content to @stream. + **/ +void +em_format_format_text(EMFormat *emf, CamelStream *stream, CamelDataWrapper *dw) +{ + CamelStreamFilter *filter_stream; + CamelMimeFilterCharset *filter; + const char *charset; + char *fallback_charset = NULL; + + if (emf->charset) { + charset = emf->charset; + } else if (dw->mime_type + && (charset = header_content_type_param(dw->mime_type, "charset")) + && g_ascii_strncasecmp(charset, "iso-8859-", 9) == 0) { + CamelMimeFilterWindows *windows; + CamelStream *null; + + /* Since a few Windows mailers like to claim they sent + * out iso-8859-# encoded text when they really sent + * out windows-cp125#, do some simple sanity checking + * before we move on... */ + + null = camel_stream_null_new(); + filter_stream = camel_stream_filter_new_with_stream(null); + camel_object_unref(null); + + windows = (CamelMimeFilterWindows *)camel_mime_filter_windows_new(charset); + camel_stream_filter_add(filter_stream, (CamelMimeFilter *)windows); + + camel_data_wrapper_decode_to_stream(dw, (CamelStream *)filter_stream); + camel_stream_flush((CamelStream *)filter_stream); + camel_object_unref(filter_stream); + + charset = fallback_charset = g_strdup(camel_mime_filter_windows_real_charset(windows)); + camel_object_unref(windows); + } else if (charset == NULL) { + /* FIXME: remove gconf query every time */ + GConfClient *gconf = gconf_client_get_default(); + + charset = fallback_charset = gconf_client_get_string(gconf, "/apps/evolution/mail/format/charset", NULL); + g_object_unref(gconf); + } + + filter_stream = camel_stream_filter_new_with_stream(stream); + + if ((filter = camel_mime_filter_charset_new_convert(charset, "UTF-8"))) { + camel_stream_filter_add(filter_stream, (CamelMimeFilter *) filter); + camel_object_unref(filter); + } + + g_free(fallback_charset); + + camel_data_wrapper_decode_to_stream(dw, (CamelStream *)filter_stream); + camel_stream_flush((CamelStream *)filter_stream); + camel_object_unref(filter_stream); +} + +/** + * em_format_describe_part: + * @part: + * @mimetype: + * + * Generate a simple textual description of a part, @mime_type represents the + * the content. + * + * Return value: + **/ +char * +em_format_describe_part(CamelMimePart *part, const char *mime_type) +{ + GString *stext; + const char *text; + char *out; + + stext = g_string_new(""); + text = gnome_vfs_mime_get_description(mime_type); + g_string_append_printf(stext, _("%s attachment"), text?text:mime_type); + if ((text = camel_mime_part_get_filename (part))) + g_string_append_printf(stext, " (%s)", text); + if ((text = camel_mime_part_get_description(part))) + g_string_append_printf(stext, ", \"%s\"", text); + + out = stext->str; + g_string_free(stext, FALSE); + + return out; +} + +/* ********************************************************************** */ + +/* originally from mail-identify.c */ +static const char * +emf_snoop_part(CamelMimePart *part) +{ + const char *filename, *name_type = NULL, *magic_type = NULL; + CamelDataWrapper *dw; + + filename = camel_mime_part_get_filename (part); + if (filename) { + /* GNOME-VFS will misidentify TNEF attachments as MPEG */ + if (!strcmp (filename, "winmail.dat")) + return "application/vnd.ms-tnef"; + + name_type = gnome_vfs_mime_type_from_name(filename); + } + + dw = camel_medium_get_content_object((CamelMedium *)part); + if (!camel_data_wrapper_is_offline(dw)) { + CamelStreamMem *mem = (CamelStreamMem *)camel_stream_mem_new(); + + if (camel_data_wrapper_decode_to_stream(dw, (CamelStream *)mem) > 0) + magic_type = gnome_vfs_get_mime_type_for_data(mem->buffer->data, mem->buffer->len); + camel_object_unref(mem); + } + + d(printf("snooped part, magic_type '%s' name_type '%s'\n", magic_type, name_type)); + + /* If GNOME-VFS doesn't recognize the data by magic, but it + * contains English words, it will call it text/plain. If the + * filename-based check came up with something different, use + * that instead and if it returns "application/octet-stream" + * try to do better with the filename check. + */ + + if (magic_type) { + if (name_type + && (!strcmp(magic_type, "text/plain") + || !strcmp(magic_type, "application/octet-stream"))) + return name_type; + else + return magic_type; + } else + return name_type; + + /* We used to load parts to check their type, we dont anymore, + see bug #11778 for some discussion */ +} + +/* RFC 1740 */ +static void +emf_multipart_appledouble(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelMultipart *mp = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)part); + + if (!CAMEL_IS_MULTIPART(mp)) { + em_format_format_source(emf, stream, part); + return; + } + + /* try the data fork for something useful, doubtful but who knows */ + em_format_part(emf, stream, camel_multipart_get_part(mp, 1)); +} + +/* RFC ??? */ +static void +emf_multipart_mixed(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelMultipart *mp = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)part); + int i, nparts; + + if (!CAMEL_IS_MULTIPART(mp)) { + em_format_format_source(emf, stream, part); + return; + } + + nparts = camel_multipart_get_number(mp); + for (i = 0; i < nparts; i++) { + /* FIXME: separate part markers ... + if (i != 0) + camel_stream_printf(stream, "<hr>\n");*/ + + part = camel_multipart_get_part(mp, i); + em_format_part(emf, stream, part); + } +} + +/* RFC 1740 */ +static void +emf_multipart_alternative(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelMultipart *mp = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)part); + int i, nparts; + CamelMimePart *best = NULL; + + if (!CAMEL_IS_MULTIPART(mp)) { + em_format_format_source(emf, stream, part); + return; + } + + /* as pre rfc, find the last part we know how to display */ + nparts = camel_multipart_get_number(mp); + for (i = 0; i < nparts; i++) { + CamelMimePart *part = camel_multipart_get_part(mp, i); + CamelContentType *type = camel_mime_part_get_content_type (part); + char *mime_type = header_content_type_simple (type); + + camel_strdown (mime_type); + + /*if (want_plain && !strcmp (mime_type, "text/plain")) + return part;*/ + + if (em_format_find_handler(emf, mime_type) + || (best == NULL && em_format_fallback_handler(emf, mime_type))) + best = part; + + g_free(mime_type); + } + + if (best) + em_format_part(emf, stream, best); + else + emf_multipart_mixed(emf, stream, part, info); +} + +static void +emf_multipart_encrypted(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelMultipartEncrypted *mpe; + CamelMimePart *mime_part; + CamelCipherContext *cipher; + CamelException ex; + const char *protocol; + + /* Currently we only handle RFC2015-style PGP encryption. */ + protocol = header_content_type_param (((CamelDataWrapper *) part)->mime_type, "protocol"); + if (!protocol || strcmp (protocol, "application/pgp-encrypted") != 0) + return emf_multipart_mixed(emf, stream, part, info); + + mpe = (CamelMultipartEncrypted *)camel_medium_get_content_object((CamelMedium *)part); + + if (!CAMEL_IS_MULTIPART_ENCRYPTED(mpe)) { + em_format_format_source(emf, stream, part); + return; + } + + camel_exception_init (&ex); + cipher = camel_gpg_context_new(emf->session); + mime_part = camel_multipart_encrypted_decrypt(mpe, cipher, &ex); + camel_object_unref(cipher); + + if (camel_exception_is_set(&ex)) { + /* FIXME: error handler */ + em_format_format_error(emf, stream, camel_exception_get_description(&ex)); + camel_exception_clear(&ex); + return; + } + + em_format_part(emf, stream, mime_part); + camel_object_unref(mime_part); +} + +static void +emf_write_related(EMFormat *emf, CamelStream *stream, EMFormatPURI *puri) +{ + em_format_format_content(emf, stream, puri->part); + camel_stream_close(stream); +} + +/* RFC 2387 */ +static void +emf_multipart_related(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelMultipart *mp = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)part); + CamelMimePart *body_part, *display_part = NULL; + CamelContentType *content_type; + const char *location, *start; + int i, nparts; + CamelURL *base_save = NULL; + struct _EMFormatPURITree *ptree; + EMFormatPURI *puri, *purin; + + if (!CAMEL_IS_MULTIPART(mp)) { + em_format_format_source(emf, stream, part); + return; + } + + /* FIXME: put this stuff in a shared function */ + nparts = camel_multipart_get_number(mp); + content_type = camel_mime_part_get_content_type(part); + start = header_content_type_param(content_type, "start"); + if (start && strlen(start)>2) { + int len; + const char *cid; + + /* strip <>'s */ + len = strlen (start) - 2; + start++; + + for (i=0; i<nparts; i++) { + body_part = camel_multipart_get_part(mp, i); + cid = camel_mime_part_get_content_id(body_part); + + if (cid && !strncmp(cid, start, len) && strlen(cid) == len) { + display_part = body_part; + break; + } + } + } else { + display_part = camel_multipart_get_part(mp, 0); + } + + if (display_part == NULL) { + emf_multipart_mixed(emf, stream, part, info); + return; + } + + /* stack of present location and pending uri's */ + location = camel_mime_part_get_content_location(part); + if (location) { + d(printf("setting content location %s\n", location)); + base_save = emf->base; + emf->base = camel_url_new(location, NULL); + } + em_format_push_level(emf); + + /* queue up the parts for possible inclusion */ + for (i = 0; i < nparts; i++) { + body_part = camel_multipart_get_part(mp, i); + if (body_part != display_part) { + puri = em_format_add_puri(emf, sizeof(EMFormatPURI), NULL, body_part, emf_write_related); + d(printf(" part '%s' '%s' added\n", puri->uri?puri->uri:"", puri->cid)); + } + } + + em_format_part(emf, stream, display_part); + camel_stream_flush(stream); + + ptree = emf->pending_uri_level; + puri = (EMFormatPURI *)ptree->uri_list.head; + purin = puri->next; + while (purin) { + if (purin->use_count == 0) { + d(printf("part '%s' '%s' used '%d'\n", purin->uri?purin->uri:"", purin->cid, purin->use_count)); + if (purin->func == emf_write_related) + em_format_part(emf, stream, puri->part); + else + printf("unreferenced uri generated by format code: %s\n", purin->uri?purin->uri:purin->cid); + } + puri = purin; + purin = purin->next; + } + em_format_pull_level(emf); + + if (location) { + camel_url_free(emf->base); + emf->base = base_save; + } +} + +/* this is only a fallback implementation, implementations should override */ +static void +emf_multipart_signed(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelMimePart *cpart, *spart; + CamelMultipartSigned *mps; + CamelCipherValidity *valid = NULL; + CamelException ex; + const char *message = NULL; + gboolean good = FALSE; + CamelCipherContext *cipher; + + mps = (CamelMultipartSigned *)camel_medium_get_content_object((CamelMedium *)part); + if (!CAMEL_IS_MULTIPART_SIGNED(mps) + || (cpart = camel_multipart_get_part((CamelMultipart *)mps, CAMEL_MULTIPART_SIGNED_CONTENT)) == NULL) { + em_format_format_source(emf, stream, part); + return; + } + + em_format_part(emf, stream, cpart); + + spart = camel_multipart_get_part((CamelMultipart *)mps, CAMEL_MULTIPART_SIGNED_SIGNATURE); + camel_exception_init(&ex); + if (spart == NULL) { + message = _("No signature present"); + } else if (emf->session == NULL) { + message = _("Session not initialised"); + } else if ((cipher = camel_gpg_context_new(emf->session)) == NULL) { + message = _("Could not create signature verfication context"); + } else { + valid = camel_multipart_signed_verify(mps, cipher, &ex); + camel_object_unref(cipher); + if (valid) { + good = camel_cipher_validity_get_valid(valid); + message = camel_cipher_validity_get_description(valid); + } else { + message = camel_exception_get_description(&ex); + } + } + + if (good) + em_format_format_error(emf, stream, _("This message is digitally signed and has been found to be authentic.")); + else + em_format_format_error(emf, stream, _("This message is digitally signed but can not be proven to be authentic.")); + + if (message) + em_format_format_error(emf, stream, message); + + camel_exception_clear(&ex); + camel_cipher_validity_free(valid); +} + +/* this is only a fallback, any implementer should implement */ +static void +emf_message_rfc822(EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info) +{ + CamelDataWrapper *dw = camel_medium_get_content_object((CamelMedium *)part); + + if (!CAMEL_IS_MIME_MESSAGE(dw)) { + em_format_format_source(emf, stream, part); + return; + } + + em_format_format_message(emf, stream, (CamelMedium *)dw); +} + +static EMFormatHandler type_builtin_table[] = { + { "multipart/alternative", emf_multipart_alternative }, + { "multipart/appledouble", emf_multipart_appledouble }, + { "multipart/encrypted", emf_multipart_encrypted }, + { "multipart/mixed", emf_multipart_mixed }, + { "multipart/signed", emf_multipart_signed }, + { "multipart/related", emf_multipart_related }, + { "multipart/*", emf_multipart_mixed }, + { "message/rfc822", emf_message_rfc822 }, + { "message/news", emf_message_rfc822 }, + { "message/*", emf_message_rfc822 }, +}; + +static void +emf_builtin_init(EMFormatClass *klass) +{ + int i; + + for (i=0;i<sizeof(type_builtin_table)/sizeof(type_builtin_table[0]);i++) + g_hash_table_insert(klass->type_handlers, type_builtin_table[i].mime_type, &type_builtin_table[i]); +} diff --git a/mail/em-format.h b/mail/em-format.h new file mode 100644 index 0000000000..98812ea377 --- /dev/null +++ b/mail/em-format.h @@ -0,0 +1,200 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + +/* + Abstract class for formatting mime messages +*/ + +#ifndef _EM_FORMAT_H +#define _EM_FORMAT_H + +#include <glib-object.h> +#include "e-util/e-msgport.h" + +struct _CamelStream; +struct _CamelMimePart; +struct _CamelMedium; +struct _CamelSession; +struct _CamelURL; +struct _CamelDataWrapper; + +typedef struct _EMFormat EMFormat; +typedef struct _EMFormatClass EMFormatClass; + +typedef struct _EMFormatHandler EMFormatHandler; +typedef struct _EMFormatHeader EMFormatHeader; + +typedef void (*EMFormatFunc) (EMFormat *md, struct _CamelStream *stream, struct _CamelMimePart *part, const EMFormatHandler *info); + +typedef enum _em_format_mode_t { + EM_FORMAT_NORMAL, + EM_FORMAT_ALLHEADERS, + EM_FORMAT_SOURCE, +} em_format_mode_t; + +/* can be subclassed/extended ... */ +struct _EMFormatHandler { + char *mime_type; + EMFormatFunc handler; + GList *applications; /* gnome vfs short-list of applications, do we care? */ +}; + +typedef struct _EMFormatPURI EMFormatPURI; +typedef void (*EMFormatPURIFunc)(EMFormat *md, struct _CamelStream *stream, EMFormatPURI *puri); + +struct _EMFormatPURI { + struct _EMFormatPURI *next, *prev; + + struct _EMFormat *format; + + char *uri; /* will be the location of the part, may be empty */ + char *cid; /* will always be set, a fake one created if needed */ + + EMFormatPURIFunc func; + struct _CamelMimePart *part; + + unsigned int use_count; /* used by multipart/related to see if it was accessed */ +}; + +/* used to stack pending uri's for visibility (multipart/related) */ +struct _EMFormatPURITree { + struct _EMFormatPURITree *next, *prev, *parent; + + EDList uri_list; + EDList children; +}; + +struct _EMFormatHeader { + struct _EMFormatHeader *next, *prev; + + guint32 flags; /* E_FORMAT_HEADER_* */ + char name[1]; +}; + +#define EM_FORMAT_HEADER_BOLD (1<<0) +#define EM_FORMAT_HEADER_LAST (1<<4) /* reserve 4 slots */ + +struct _EMFormat { + GObject parent; + + struct _CamelMedium *message; /* the current message */ + + EDList header_list; /* if empty, then all */ + + struct _CamelSession *session; /* session, used for authentication when required */ + struct _CamelURL *base; /* current location (base url) */ + + /* for forcing inlining */ + GHashTable *inline_table; + + /* global lookup table for message */ + GHashTable *pending_uri_table; + + /* visibility tree, also stores every puri permanently */ + struct _EMFormatPURITree *pending_uri_tree; + /* current level to search from */ + struct _EMFormatPURITree *pending_uri_level; + + em_format_mode_t mode; /* source/headers/etc */ + char *charset; /* charset override */ +}; + +struct _EMFormatClass { + GObjectClass parent_class; + + GHashTable *type_handlers; + + /* start formatting a message */ + void (*format_clone)(EMFormat *, struct _CamelMedium *, EMFormat *); + /* some internel error/inconsistency */ + void (*format_error)(EMFormat *, struct _CamelStream *, const char *msg); + + /* use for external structured parts */ + void (*format_attachment)(EMFormat *, struct _CamelStream *, struct _CamelMimePart *, const char *mime_type, const struct _EMFormatHandler *info); + /* for any message parts */ + void (*format_message)(EMFormat *, struct _CamelStream *, struct _CamelMedium *); + /* use for unparsable content */ + void (*format_source)(EMFormat *, struct _CamelStream *, struct _CamelMimePart *); + + /* returns true if the formatter is still busy with pending stuff */ + gboolean (*busy)(EMFormat *); + + /* signals */ + /* complete, alternative to polling busy, for asynchronous work */ + void (*complete)(EMFormat *); +}; + +/* helper entry point */ +void em_format_set_session(EMFormat *emf, struct _CamelSession *s); + +void em_format_set_mode(EMFormat *emf, em_format_mode_t type); +void em_format_set_charset(EMFormat *emf, const char *charset); + +void em_format_clear_headers(EMFormat *emf); /* also indicates to show all headers */ +void em_format_default_headers(EMFormat *emf); +void em_format_add_header(EMFormat *emf, const char *name, guint32 flags); + +/* FIXME: Need a 'clone' api to copy details about the current view (inlines etc) + Or maybe it should live with sub-classes? */ + +int em_format_is_attachment(EMFormat *emf, struct _CamelMimePart *part); +int em_format_is_inline(EMFormat *emf, struct _CamelMimePart *part); +/* FIXME: not sure about this api */ +void em_format_set_inline(EMFormat *emf, struct _CamelMimePart *part, int state); +char *em_format_describe_part(struct _CamelMimePart *part, const char *mimetype); + +/* for implementers */ +GType em_format_get_type(void); + +void em_format_class_add_handler(EMFormatClass *emfc, EMFormatHandler *info); +void em_format_class_remove_handler (EMFormatClass *emfc, const char *mime_type); +const EMFormatHandler *em_format_find_handler(EMFormat *emf, const char *mime_type); +const EMFormatHandler *em_format_fallback_handler(EMFormat *emf, const char *mime_type); + +/* puri is short for pending uri ... really */ +EMFormatPURI *em_format_add_puri(EMFormat *emf, size_t size, const char *uri, struct _CamelMimePart *part, EMFormatPURIFunc func); +EMFormatPURI *em_format_find_visible_puri(EMFormat *emf, const char *uri); +EMFormatPURI *em_format_find_puri(EMFormat *emf, const char *uri); +void em_format_clear_puri_tree(EMFormat *emf); +void em_format_push_level(EMFormat *emf); +void em_format_pull_level(EMFormat *emf); + +/* clones inline state/view and format, or use to redraw */ +#define em_format_format_clone(emf, msg, src) ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->format_clone((emf), (msg), (src)) +/* formats a new message */ +#define em_format_format(emf, msg) ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->format_clone((emf), (msg), NULL) +#define em_format_format_error(emf, stream, txt) ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->format_error((emf), (stream), (txt)) +#define em_format_format_attachment(emf, stream, msg, type, info) ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->format_attachment((emf), (stream), (msg), (type), (info)) +#define em_format_format_message(emf, stream, msg) ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->format_message((emf), (stream), (msg)) +#define em_format_format_source(emf, stream, msg) ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->format_source((emf), (stream), (msg)) + +#define em_format_busy(emf) ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->busy((emf)) + +/* raw content only */ +void em_format_format_content(EMFormat *emf, struct _CamelStream *stream, struct _CamelMimePart *part); +/* raw content text parts - should this just be checked/done by above? */ +void em_format_format_text(EMFormat *emf, struct _CamelStream *stream, struct _CamelDataWrapper *part); + +void em_format_part_as(EMFormat *emf, struct _CamelStream *stream, struct _CamelMimePart *part, const char *mime_type); +void em_format_part(EMFormat *emf, struct _CamelStream *stream, struct _CamelMimePart *part); + +#endif /* ! _EM_FORMAT_H */ diff --git a/mail/em-html-stream.c b/mail/em-html-stream.c new file mode 100644 index 0000000000..e50d6caee0 --- /dev/null +++ b/mail/em-html-stream.c @@ -0,0 +1,168 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <gtkhtml/gtkhtml.h> +#include <gtkhtml/gtkhtml-stream.h> +#include <gtk/gtkmain.h> +#include "em-html-stream.h" + +#define d(x) + +static void em_html_stream_class_init (EMHTMLStreamClass *klass); +static void em_html_stream_init (CamelObject *object); +static void em_html_stream_finalize (CamelObject *object); + +static ssize_t emhs_sync_write(CamelStream *stream, const char *buffer, size_t n); +static int emhs_sync_close(CamelStream *stream); +static int emhs_sync_flush(CamelStream *stream); + +static EMSyncStreamClass *parent_class = NULL; + +CamelType +em_html_stream_get_type (void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + parent_class = (EMSyncStreamClass *)em_sync_stream_get_type(); + type = camel_type_register (em_sync_stream_get_type(), + "EMHTMLStream", + sizeof (EMHTMLStream), + sizeof (EMHTMLStreamClass), + (CamelObjectClassInitFunc) em_html_stream_class_init, + NULL, + (CamelObjectInitFunc) em_html_stream_init, + (CamelObjectFinalizeFunc) em_html_stream_finalize); + } + + return type; +} + +static void +em_html_stream_class_init (EMHTMLStreamClass *klass) +{ + ((EMSyncStreamClass *)klass)->sync_write = emhs_sync_write; + ((EMSyncStreamClass *)klass)->sync_flush = emhs_sync_flush; + ((EMSyncStreamClass *)klass)->sync_close = emhs_sync_close; +} + +static void +em_html_stream_init (CamelObject *object) +{ + /*EMHTMLStream *emhs = (EMHTMLStream *)object;*/ +} + +static void +emhs_cleanup(EMHTMLStream *emhs) +{ + emhs->html_stream = NULL; + emhs->sync.cancel = TRUE; + g_signal_handler_disconnect(emhs->html, emhs->destroy_id); + g_object_unref(emhs->html); + emhs->html = NULL; +} + +static void +em_html_stream_finalize (CamelObject *object) +{ + EMHTMLStream *emhs = (EMHTMLStream *)object; + + d(printf("%p: finalising stream\n", object)); + if (emhs->html_stream) { + d(printf("%p: html stream still open - error\n", object)); + /* set 'in finalise' flag */ + camel_stream_close((CamelStream *)emhs); + } +} + +static ssize_t +emhs_sync_write(CamelStream *stream, const char *buffer, size_t n) +{ + EMHTMLStream *emhs = EM_HTML_STREAM (stream); + + if (emhs->html_stream == NULL) + return -1; + + gtk_html_stream_write(emhs->html_stream, buffer, n); + + return (ssize_t) n; +} + +static int +emhs_sync_flush(CamelStream *stream) +{ + EMHTMLStream *emhs = (EMHTMLStream *)stream; + + if (emhs->html_stream == NULL) + return -1; + + /* FIXME: flush html stream via gtkhtml_stream_flush which doens't exist yet ... */ + while (gtk_events_pending ()) + gtk_main_iteration (); + + return 0; +} + +static int +emhs_sync_close(CamelStream *stream) +{ + EMHTMLStream *emhs = (EMHTMLStream *)stream; + + if (emhs->html_stream == NULL) + return -1; + + gtk_html_stream_close(emhs->html_stream, GTK_HTML_STREAM_OK); + emhs_cleanup(emhs); + + return 0; +} + +static void +emhs_gtkhtml_destroy(struct _GtkHTML *html, EMHTMLStream *emhs) +{ + d(printf("%p: emhs gtkhtml destroy\n", emhs)); + emhs_cleanup(emhs); +} + +/* TODO: Could pass NULL for html_stream, and do a gtk_html_begin + on first data -> less flashing */ +CamelStream * +em_html_stream_new(struct _GtkHTML *html, struct _GtkHTMLStream *html_stream) +{ + EMHTMLStream *new; + + new = EM_HTML_STREAM (camel_object_new (EM_HTML_STREAM_TYPE)); + new->html_stream = html_stream; + new->html = html; + g_object_ref(html); + new->destroy_id = g_signal_connect(html, "destroy", G_CALLBACK(emhs_gtkhtml_destroy), new); + + em_sync_stream_set_buffer_size(&new->sync, 4096); + + return (CamelStream *)new; +} diff --git a/mail/mail-display-stream.h b/mail/em-html-stream.h index 943398c49a..dc4b59cde8 100644 --- a/mail/mail-display-stream.h +++ b/mail/em-html-stream.h @@ -20,43 +20,45 @@ * */ - -#ifndef MAIL_DISPLAY_STREAM_H -#define MAIL_DISPLAY_STREAM_H +#ifndef EM_HTML_STREAM_H +#define EM_HTML_STREAM_H #ifdef __cplusplus extern "C" { #pragma } #endif /* __cplusplus */ -#include <camel/camel-stream.h> -#include <gtkhtml/gtkhtml.h> +#define EM_HTML_STREAM_TYPE (em_html_stream_get_type ()) +#define EM_HTML_STREAM(obj) (CAMEL_CHECK_CAST((obj), EM_HTML_STREAM_TYPE, EMHTMLStream)) +#define EM_HTML_STREAM_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), EM_HTML_STREAM_TYPE, EMHTMLStreamClass)) +#define EM_IS_HTML_STREAM(o) (CAMEL_CHECK_TYPE((o), EM_HTML_STREAM_TYPE)) -#define MAIL_DISPLAY_STREAM_TYPE (mail_display_stream_get_type ()) -#define MAIL_DISPLAY_STREAM(obj) (CAMEL_CHECK_CAST((obj), MAIL_DISPLAY_STREAM_TYPE, MailDisplayStream)) -#define MAIL_DISPLAY_STREAM_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), MAIL_DISPLAY_STREAM_TYPE, MailDisplayStreamClass)) -#define MAIL_IS_DISPLAY_STREAM(o) (CAMEL_CHECK_TYPE((o), MAIL_DISPLAY_STREAM_TYPE)) +struct _GtkHTML; +struct _GtkHTMLStream; -typedef struct _MailDisplayStream { - CamelStream parent_stream; - - GtkHTML *html; - GtkHTMLStream *html_stream; -} MailDisplayStream; +#include "em-sync-stream.h" + +typedef struct _EMHTMLStream { + EMSyncStream sync; + + guint destroy_id; + struct _GtkHTML *html; + struct _GtkHTMLStream *html_stream; +} EMHTMLStream; typedef struct { - CamelStreamClass parent_class; + EMSyncStreamClass parent_class; -} MailDisplayStreamClass; +} EMHTMLStreamClass; -CamelType mail_display_stream_get_type (void); +CamelType em_html_stream_get_type (void); -/* Note: stream does not ref these objects! */ -CamelStream *mail_display_stream_new (GtkHTML *html, GtkHTMLStream *html_stream); +/* the html_stream is closed when we are finalised (with an error), or closed (ok) */ +CamelStream *em_html_stream_new(struct _GtkHTML *html, struct _GtkHTMLStream *html_stream); #ifdef __cplusplus } #endif /* __cplusplus */ -#endif /* MAIL_DISPLAY_STREAM_H */ +#endif /* EM_HTML_STREAM_H */ diff --git a/mail/em-icon-stream.c b/mail/em-icon-stream.c new file mode 100644 index 0000000000..9ba67cf69a --- /dev/null +++ b/mail/em-icon-stream.c @@ -0,0 +1,201 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gdk-pixbuf/gdk-pixbuf-loader.h> +#include <gtk/gtkimage.h> +#include "em-icon-stream.h" + +#define d(x) + +static void em_icon_stream_class_init (EMIconStreamClass *klass); +static void em_icon_stream_init (CamelObject *object); +static void em_icon_stream_finalize (CamelObject *object); + +static ssize_t emis_sync_write(CamelStream *stream, const char *buffer, size_t n); +static int emis_sync_close(CamelStream *stream); +static int emis_sync_flush(CamelStream *stream); + +static EMSyncStreamClass *parent_class = NULL; + +CamelType +em_icon_stream_get_type (void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + parent_class = (EMSyncStreamClass *)em_sync_stream_get_type(); + type = camel_type_register (em_sync_stream_get_type(), + "EMIconStream", + sizeof (EMIconStream), + sizeof (EMIconStreamClass), + (CamelObjectClassInitFunc) em_icon_stream_class_init, + NULL, + (CamelObjectInitFunc) em_icon_stream_init, + (CamelObjectFinalizeFunc) em_icon_stream_finalize); + } + + return type; +} + +static void +em_icon_stream_class_init (EMIconStreamClass *klass) +{ + ((EMSyncStreamClass *)klass)->sync_write = emis_sync_write; + ((EMSyncStreamClass *)klass)->sync_flush = emis_sync_flush; + ((EMSyncStreamClass *)klass)->sync_close = emis_sync_close; +} + +static void +em_icon_stream_init (CamelObject *object) +{ + EMIconStream *emis = (EMIconStream *)object; + + emis->width = 24; + emis->height = 24; +} + +static void +emis_cleanup(EMIconStream *emis) +{ + if (emis->loader) { + gdk_pixbuf_loader_close(emis->loader, NULL); + g_object_unref(emis->loader); + emis->loader = NULL; + } + + if (emis->destroy_id) { + g_signal_handler_disconnect(emis->image, emis->destroy_id); + emis->destroy_id = 0; + } + + emis->image = NULL; + emis->sync.cancel = TRUE; +} + +static void +em_icon_stream_finalize(CamelObject *object) +{ + EMIconStream *emis = (EMIconStream *)object; + + emis_cleanup(emis); +} + +static ssize_t +emis_sync_write(CamelStream *stream, const char *buffer, size_t n) +{ + EMIconStream *emis = EM_ICON_STREAM (stream); + + if (emis->loader == NULL) + return -1; + + if (!gdk_pixbuf_loader_write(emis->loader, buffer, n, NULL)) { + emis_cleanup(emis); + return -1; + } + + return (ssize_t) n; +} + +static int +emis_sync_flush(CamelStream *stream) +{ + return 0; +} + +static int +emis_sync_close(CamelStream *stream) +{ + EMIconStream *emis = (EMIconStream *)stream; + int width, height, ratio; + GdkPixbuf *pixbuf, *mini; + + if (emis->loader == NULL) + return -1; + + gdk_pixbuf_loader_close(emis->loader, NULL); + + pixbuf = gdk_pixbuf_loader_get_pixbuf(emis->loader); + if (pixbuf == NULL) { + printf("couldn't get pixbuf from loader\n"); + emis_cleanup(emis); + return -1; + } + + width = gdk_pixbuf_get_width(pixbuf); + height = gdk_pixbuf_get_height(pixbuf); + + if (width != emis->width || height != emis->height) { + if (width >= height) { + if (width > emis->width) { + ratio = width / emis->width; + width = emis->width; + height /= ratio; + } + } else { + if (height > emis->height) { + ratio = height / emis->height; + height = emis->height; + width /= ratio; + } + } + + mini = gdk_pixbuf_scale_simple(pixbuf, width, height, GDK_INTERP_BILINEAR); + gtk_image_set_from_pixbuf(emis->image, mini); + g_object_unref(mini); + } else { + gtk_image_set_from_pixbuf(emis->image, pixbuf); + } + + g_object_unref(emis->loader); + emis->loader = NULL; + + g_signal_handler_disconnect(emis->image, emis->destroy_id); + emis->destroy_id = 0; + + return 0; +} + +static void +emis_image_destroy(struct _GtkImage *image, EMIconStream *emis) +{ + emis_cleanup(emis); +} + +CamelStream * +em_icon_stream_new(GtkImage *image) +{ + EMIconStream *new; + + new = EM_ICON_STREAM(camel_object_new(EM_ICON_STREAM_TYPE)); + new->image = image; + new->destroy_id = g_signal_connect(image, "destroy", G_CALLBACK(emis_image_destroy), new); + new->loader = gdk_pixbuf_loader_new(); + + return (CamelStream *)new; +} diff --git a/mail/em-icon-stream.h b/mail/em-icon-stream.h new file mode 100644 index 0000000000..3776732578 --- /dev/null +++ b/mail/em-icon-stream.h @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * 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. + * + */ + +#ifndef EM_ICON_STREAM_H +#define EM_ICON_STREAM_H + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#define EM_ICON_STREAM_TYPE (em_icon_stream_get_type ()) +#define EM_ICON_STREAM(obj) (CAMEL_CHECK_CAST((obj), EM_ICON_STREAM_TYPE, EMIconStream)) +#define EM_ICON_STREAM_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), EM_ICON_STREAM_TYPE, EMIconStreamClass)) +#define EM_IS_ICON_STREAM(o) (CAMEL_CHECK_TYPE((o), EM_ICON_STREAM_TYPE)) + +struct _GtkHTML; +struct _GtkIconStream; + +#include "em-sync-stream.h" + +typedef struct _EMIconStream { + EMSyncStream sync; + + unsigned int width, height; + guint destroy_id; + struct _GdkPixbufLoader *loader; + struct _GtkImage *image; +} EMIconStream; + +typedef struct { + EMSyncStreamClass parent_class; +} EMIconStreamClass; + +CamelType em_icon_stream_get_type (void); + +CamelStream *em_icon_stream_new(GtkImage *image); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* EM_ICON_STREAM_H */ diff --git a/mail/em-inline-filter.c b/mail/em-inline-filter.c new file mode 100644 index 0000000000..efaa09baf5 --- /dev/null +++ b/mail/em-inline-filter.c @@ -0,0 +1,335 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * + * + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "em-inline-filter.h" +#include <camel/camel-mime-part.h> +#include <camel/camel-multipart.h> +#include <camel/camel-stream-mem.h> + +#define d(x) + +static void em_inline_filter_class_init (EMInlineFilterClass *klass); +static void em_inline_filter_init (CamelObject *object); +static void em_inline_filter_finalize (CamelObject *object); + +static void emif_filter(CamelMimeFilter *f, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace); +static void emif_complete(CamelMimeFilter *f, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace); +static void emif_reset(CamelMimeFilter *f); + +static CamelMimeFilterClass *parent_class = NULL; + +CamelType +em_inline_filter_get_type (void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + parent_class = (CamelMimeFilterClass *)camel_mime_filter_get_type(); + + type = camel_type_register(camel_mime_filter_get_type(), + "EMInlineFilter", + sizeof (EMInlineFilter), + sizeof (EMInlineFilterClass), + (CamelObjectClassInitFunc) em_inline_filter_class_init, + NULL, + (CamelObjectInitFunc) em_inline_filter_init, + (CamelObjectFinalizeFunc) em_inline_filter_finalize); + } + + return type; +} + +static void +em_inline_filter_class_init (EMInlineFilterClass *klass) +{ + ((CamelMimeFilterClass *)klass)->filter = emif_filter; + ((CamelMimeFilterClass *)klass)->complete = emif_complete; + ((CamelMimeFilterClass *)klass)->reset = emif_reset; +} + +static void +em_inline_filter_init (CamelObject *object) +{ + EMInlineFilter *emif = (EMInlineFilter *)object; + + emif->data = g_byte_array_new(); +} + + +static void +em_inline_filter_finalize (CamelObject *object) +{ + EMInlineFilter *emif = (EMInlineFilter *)object; + + emif_reset((CamelMimeFilter *)emif); + g_byte_array_free(emif->data, TRUE); + g_free(emif->filename); +} + +enum { + EMIF_PLAIN, + EMIF_UUENC, + EMIF_BINHEX, + EMIF_POSTSCRIPT, + EMIF_PGPSIGNED, +}; +const struct { + const char *name; + CamelMimePartEncodingType type; +} emif_types[] = { + { "text/plain", CAMEL_MIME_PART_ENCODING_DEFAULT, }, + { "application/octet-stream", CAMEL_MIME_PART_ENCODING_UUENCODE, }, + { "application/mac-binhex40", CAMEL_MIME_PART_ENCODING_7BIT, }, + { "application/postscript", CAMEL_MIME_PART_ENCODING_7BIT, }, + { "text/plain", CAMEL_MIME_PART_ENCODING_7BIT, }, +}; + +static void +emif_add_part(EMInlineFilter *emif, const char *data, int len) +{ + CamelMimePartEncodingType type; + CamelStream *mem; + CamelDataWrapper *dw; + CamelMimePart *part; + + if (emif->state == EMIF_PLAIN) + type = emif->base_encoding; + else + type = emif_types[emif->state].type; + + g_byte_array_append(emif->data, data, len); + mem = camel_stream_mem_new_with_byte_array(emif->data); + emif->data = g_byte_array_new(); + + dw = camel_data_wrapper_new(); + camel_data_wrapper_construct_from_stream(dw, mem); + camel_object_unref(mem); + camel_data_wrapper_set_mime_type(dw, emif_types[emif->state].name); + dw->encoding = type; + + part = camel_mime_part_new(); + camel_medium_set_content_object((CamelMedium *)part, dw); + camel_mime_part_set_encoding(part, type); + camel_object_unref(dw); + + if (emif->filename) { + camel_mime_part_set_filename(part, emif->filename); + g_free(emif->filename); + emif->filename = NULL; + } + + emif->parts = g_slist_append(emif->parts, part); +} + +static int +emif_scan(CamelMimeFilter *f, char *in, size_t len, int final) +{ + EMInlineFilter *emif = (EMInlineFilter *)f; + char *inptr = in, *inend = in+len; + char *data_start = in; + char *start = in; + + while (inptr < inend) { + start = inptr; + + while (inptr < inend && *inptr != '\n') + inptr++; + + if (inptr == inend) { + if (!final) { + camel_mime_filter_backup(f, start, inend-start); + inend = start; + } + break; + } + + *inptr++ = 0; + + switch(emif->state) { + case EMIF_PLAIN: + /* This could use some funky plugin shit, but this'll do for now */ + if (strncmp(start, "begin ", 6) == 0 + && start[6] >= '0' && start[6] <= '7') { + int i = 7; + + while (start[i] >='0' && start[i] <='7') + i++; + + inptr[-1] = '\n'; + + if (start[i++] != ' ') + break; + + emif_add_part(emif, data_start, start-data_start); + emif->filename = g_strndup(start+i, inptr-start-i-1); + data_start = start; + emif->state = EMIF_UUENC; + } else if (strncmp(start, "(This file must be converted with BinHex 4.0)", 45) == 0) { + inptr[-1] = '\n'; + emif_add_part(emif, data_start, start-data_start); + data_start = start; + emif->state = EMIF_BINHEX; + } else if (strncmp(start, "%!PS-Adobe-", 11) == 0) { + inptr[-1] = '\n'; + emif_add_part(emif, data_start, start-data_start); + data_start = start; + emif->state = EMIF_POSTSCRIPT; +#if 0 +/* This should be hooked in once someone can work out how to handle it. + Maybe we need a multipart_gpg_inline_signed or some crap, if it + can't be converted to a real multipart/signed */ + } else if (strncmp(start, "-----BEGIN PGP SIGNED MESSAGE-----", 34) == 0) { + inptr[-1] = '\n'; + emif_add_part(emif, data_start, start-data_start); + data_start = start; + emif->state = EMIF_PGPSIGNED; +#endif + } + break; + case EMIF_UUENC: + if (strcmp(start, "end") == 0) { + inptr[-1] = '\n'; + emif_add_part(emif, data_start, inptr-data_start); + data_start = inptr; + emif->state = EMIF_PLAIN; + } + break; + case EMIF_BINHEX: + if (inptr > (start+1) && inptr[-2] == ':') { + inptr[-1] = '\n'; + emif_add_part(emif, data_start, inptr-data_start); + data_start = inptr; + emif->state = EMIF_PLAIN; + } + break; + case EMIF_POSTSCRIPT: + if (strcmp(start, "%%EOF") == 0) { + inptr[-1] = '\n'; + emif_add_part(emif, data_start, inptr-data_start); + data_start = inptr; + emif->state = EMIF_PLAIN; + } + break; + case EMIF_PGPSIGNED: + /* This is currently a noop - it just turns it into a text part */ + if (strcmp(start, "-----END PGP SIGNATURE-----") == 0) { + inptr[-1] = '\n'; + emif_add_part(emif, data_start, inptr-data_start); + data_start = inptr; + emif->state = EMIF_PLAIN; + } + break; + } + + inptr[-1] = '\n'; + } + + if (final) { + emif_add_part(emif, data_start, inend-data_start); + } else { + g_byte_array_append(emif->data, data_start, inend-data_start); + } + + return 0; +} + +static void +emif_filter(CamelMimeFilter *f, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace) +{ + emif_scan(f, in, len, FALSE); + + *out = in; + *outlen = len; + *outprespace = prespace; +} + +static void +emif_complete(CamelMimeFilter *f, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace) +{ + emif_scan(f, in, len, TRUE); + + *out = in; + *outlen = len; + *outprespace = prespace; +} + +static void +emif_reset(CamelMimeFilter *f) +{ + EMInlineFilter *emif = (EMInlineFilter *)f; + GSList *l; + + l = emif->parts; + while (l) { + GSList *n = l->next; + + camel_object_unref(l->data); + g_slist_free_1(l); + + l = n; + } + emif->parts = NULL; + g_byte_array_set_size(emif->data, 0); +} + +/** + * em_inline_filter_new: + * @base_encoding: The base transfer-encoding of the + * raw data being processed. + * + * Create a filter which will scan a (text) stream for + * embedded parts. You can then retrieve the contents + * as a CamelMultipart object. + * + * Return value: + **/ +EMInlineFilter * +em_inline_filter_new(CamelMimePartEncodingType base_encoding) +{ + EMInlineFilter *emif; + + emif = (EMInlineFilter *)camel_object_new(em_inline_filter_get_type()); + emif->base_encoding = base_encoding; + + return emif; +} + +CamelMultipart * +em_inline_filter_get_multipart(EMInlineFilter *emif) +{ + GSList *l = emif->parts; + CamelMultipart *mp; + + mp = camel_multipart_new(); + while (l) { + camel_multipart_add_part(mp, l->data); + l = l->next; + } + + return mp; +} diff --git a/mail/em-inline-filter.h b/mail/em-inline-filter.h new file mode 100644 index 0000000000..8efc8454a5 --- /dev/null +++ b/mail/em-inline-filter.h @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * 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. + * + */ + +#ifndef EM_INLINE_FILTER_H +#define EM_INLINE_FILTER_H + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#define EM_INLINE_FILTER_TYPE (em_inline_filter_get_type ()) +#define EM_INLINE_FILTER(obj) (CAMEL_CHECK_CAST((obj), EM_INLINE_FILTER_TYPE, EMInlineFilter)) +#define EM_INLINE_FILTER_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), EM_INLINE_FILTER_TYPE, EMInlineFilterClass)) +#define EM_IS_INLINE_FILTER(o) (CAMEL_CHECK_TYPE((o), EM_INLINE_FILTER_TYPE)) + +#include <camel/camel-mime-filter.h> +#include <camel/camel-mime-utils.h> + +typedef struct _EMInlineFilter { + CamelMimeFilter filter; + + int state; + + CamelMimePartEncodingType base_encoding; + GByteArray *data; + char *filename; + GSList *parts; +} EMInlineFilter; + +typedef struct _EMInlineFilterClass { + CamelMimeFilterClass filter_class; +} EMInlineFilterClass; + +CamelType em_inline_filter_get_type(void); +EMInlineFilter *em_inline_filter_new(CamelMimePartEncodingType base_encoding); +struct _CamelMultipart *em_inline_filter_get_multipart(EMInlineFilter *emif); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* EM_INLINE_FILTER_H */ diff --git a/mail/em-marshal.list b/mail/em-marshal.list new file mode 100644 index 0000000000..910bfb1b3d --- /dev/null +++ b/mail/em-marshal.list @@ -0,0 +1 @@ +BOOLEAN:BOXED,POINTER,POINTER diff --git a/mail/em-message-browser.c b/mail/em-message-browser.c new file mode 100644 index 0000000000..503ebef9e5 --- /dev/null +++ b/mail/em-message-browser.c @@ -0,0 +1,171 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkbutton.h> + +#include <camel/camel-folder.h> + +#include <bonobo/bonobo-main.h> +#include <bonobo/bonobo-object.h> +#include <bonobo/bonobo-window.h> +#include <bonobo/bonobo-generic-factory.h> +#include <bonobo/bonobo-ui-component.h> +#include <bonobo/bonobo-ui-util.h> + +#include "em-format-html-display.h" +#include "em-message-browser.h" + +#include "evolution-shell-component-utils.h" /* Pixmap stuff, sigh */ + +struct _EMMessageBrowserPrivate { + GtkWidget *preview; /* container for message display */ +}; + +static void emmb_set_message(EMFolderView *emfv, const char *uid); +static void emmb_activate(EMFolderView *emfv, BonoboUIComponent *uic, int state); + +static EMFolderViewClass *emmb_parent; + +static void +emmb_init(GObject *o) +{ + EMMessageBrowser *emmb = (EMMessageBrowser *)o; + struct _EMMessageBrowserPrivate *p; + + p = emmb->priv = g_malloc0(sizeof(struct _EMMessageBrowserPrivate)); + + ((EMFolderView *)emmb)->preview_active = TRUE; + + g_slist_free(emmb->view.ui_files); + emmb->view.ui_files = g_slist_append(NULL, EVOLUTION_UIDIR "/evolution-mail-message.xml"); + emmb->view.ui_files = g_slist_append(emmb->view.ui_files, EVOLUTION_UIDIR "/evolution-mail-messagedisplay.xml"); + + /* currently: just use a scrolledwindow for preview widget */ + p->preview = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy((GtkScrolledWindow *)p->preview, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type((GtkScrolledWindow *)p->preview, GTK_SHADOW_IN); + gtk_widget_show(p->preview); + + gtk_container_add((GtkContainer *)p->preview, (GtkWidget *)emmb->view.preview->formathtml.html); + gtk_widget_show((GtkWidget *)emmb->view.preview->formathtml.html); + + gtk_widget_show(p->preview); + + gtk_box_pack_start_defaults((GtkBox *)emmb, p->preview); +} + +static void +emmb_finalise(GObject *o) +{ + EMMessageBrowser *emmb = (EMMessageBrowser *)o; + + g_free(emmb->priv); + ((GObjectClass *)emmb_parent)->finalize(o); +} + +static void +emmb_class_init(GObjectClass *klass) +{ + klass->finalize = emmb_finalise; + ((EMFolderViewClass *)klass)->set_message = emmb_set_message; + ((EMFolderViewClass *)klass)->activate = emmb_activate; +} + +GType +em_message_browser_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMMessageBrowserClass), + NULL, NULL, + (GClassInitFunc)emmb_class_init, + NULL, NULL, + sizeof(EMMessageBrowser), 0, + (GInstanceInitFunc)emmb_init + }; + emmb_parent = g_type_class_ref(em_folder_view_get_type()); + type = g_type_register_static(em_folder_view_get_type(), "EMMessageBrowser", &info, 0); + } + + return type; +} + +GtkWidget *em_message_browser_new(void) +{ + EMMessageBrowser *emmb = g_object_new(em_message_browser_get_type(), 0); + + return (GtkWidget *)emmb; +} + +GtkWidget *em_message_browser_window_new(void) +{ + EMMessageBrowser *emmb; + BonoboUIContainer *uicont; + BonoboUIComponent *uic; + + emmb = (EMMessageBrowser *)em_message_browser_new(); + gtk_widget_show((GtkWidget *)emmb); + /* FIXME: title set elsewhere? */ + emmb->window = g_object_new(bonobo_window_get_type(), "title", "Ximian Evolution", NULL); + bonobo_window_set_contents((BonoboWindow *)emmb->window, (GtkWidget *)emmb); + + uicont = bonobo_window_get_ui_container((BonoboWindow *)emmb->window); + uic = bonobo_ui_component_new_default(); + bonobo_ui_component_set_container(uic, BONOBO_OBJREF(uicont), NULL); + + em_folder_view_activate((EMFolderView *)emmb, uic, TRUE); + + /* FIXME: keep track of size changes for next instantation */ + gtk_window_set_default_size((GtkWindow *)emmb->window, 600, 400); + + /* cleanup? */ + + return (GtkWidget *)emmb; +} + +/* ********************************************************************** */ + +static void +emmb_set_message(EMFolderView *emfv, const char *uid) +{ + emmb_parent->set_message(emfv, uid); + + /* Well we don't know if it got displayed (yet) ... but whatever ... */ + camel_folder_set_message_flags(emfv->folder, uid, CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN); +} + +static void +emmb_activate(EMFolderView *emfv, BonoboUIComponent *uic, int state) +{ + emmb_parent->activate(emfv, uic, state); + + if (state) + bonobo_ui_component_set_prop(uic, "/commands/EditPaste", "sensitive", "0", NULL); +} diff --git a/mail/em-message-browser.h b/mail/em-message-browser.h new file mode 100644 index 0000000000..c36a87ee0b --- /dev/null +++ b/mail/em-message-browser.h @@ -0,0 +1,30 @@ + +#ifndef _EM_MESSAGE_BROWSER_H +#define _EM_MESSAGE_BROWSER_H + +#include "em-folder-view.h" + +typedef struct _EMMessageBrowser EMMessageBrowser; +typedef struct _EMMessageBrowserClass EMMessageBrowserClass; + +struct _EMMessageBrowser { + EMFolderView view; + + /* container, if setup */ + struct _GtkWidget *window; + + struct _EMMessageBrowserPrivate *priv; +}; + +struct _EMMessageBrowserClass { + EMFolderViewClass parent_class; +}; + +GType em_message_browser_get_type(void); + +GtkWidget *em_message_browser_new(void); + +/* also sets up a bonobo container window w/ docks and so on */ +GtkWidget *em_message_browser_window_new(void); + +#endif /* ! _EM_MESSAGE_BROWSER_H */ diff --git a/mail/em-popup.c b/mail/em-popup.c new file mode 100644 index 0000000000..d2b2ca1e6e --- /dev/null +++ b/mail/em-popup.c @@ -0,0 +1,823 @@ + +#include <string.h> +#include <stdlib.h> + +#include <gtk/gtkmenu.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtkimagemenuitem.h> +#include <gtk/gtkcheckmenuitem.h> +#include <gtk/gtkradiomenuitem.h> +#include <gtk/gtkseparatormenuitem.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkimage.h> + +#include <libgnome/gnome-url.h> + +#include <glib.h> + +#include "em-popup.h" +#include "e-util/e-msgport.h" +#include "em-utils.h" + +#include <camel/camel-folder.h> +#include <camel/camel-mime-message.h> +#include <camel/camel-string-utils.h> + +static void emp_standard_menu_factory(EMPopup *emp, EMPopupTarget *target, void *data); + +struct _EMPopupFactory { + struct _EMPopupFactory *next, *prev; + + char *menuid; + EMPopupFactoryFunc factory; + void *factory_data; +}; + +struct _menu_node { + struct _menu_node *next, *prev; + + GSList *menu; + GDestroyNotify freefunc; +}; + +struct _EMPopupPrivate { + EDList menus; +}; + +static EDList emp_factories = E_DLIST_INITIALISER(emp_factories); + +static GObjectClass *emp_parent; + +static void +emp_init(GObject *o) +{ + EMPopup *emp = (EMPopup *)o; + struct _EMPopupPrivate *p; + + p = emp->priv = g_malloc0(sizeof(struct _EMPopupPrivate)); + + e_dlist_init(&p->menus); +} + +static void +emp_finalise(GObject *o) +{ + EMPopup *emp = (EMPopup *)o; + struct _EMPopupPrivate *p = emp->priv; + struct _menu_node *mnode, *nnode; + + g_free(emp->menuid); + + mnode = (struct _menu_node *)p->menus.head; + nnode = mnode->next; + while (nnode) { + if (mnode->freefunc) + mnode->freefunc(mnode->menu); + + g_free(mnode); + mnode = nnode; + nnode = nnode->next; + } + + g_free(p); + + ((GObjectClass *)emp_parent)->finalize(o); +} + +static void +emp_class_init(GObjectClass *klass) +{ + klass->finalize = emp_finalise; +} + +GType +em_popup_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMPopupClass), + NULL, NULL, + (GClassInitFunc)emp_class_init, + NULL, NULL, + sizeof(EMPopup), 0, + (GInstanceInitFunc)emp_init + }; + emp_parent = g_type_class_ref(G_TYPE_OBJECT); + type = g_type_register_static(G_TYPE_OBJECT, "EMPopup", &info, 0); + + /* FIXME: this should probably sit somewhere in global setup */ + em_popup_static_add_factory(NULL, (EMPopupFactoryFunc)emp_standard_menu_factory, NULL); + } + + return type; +} + +EMPopup *em_popup_new(const char *menuid) +{ + EMPopup *emp = g_object_new(em_popup_get_type(), 0); + + emp->menuid = g_strdup(menuid); + + return emp; +} + +/** + * em_popup_add_items: + * @emp: + * @items: + * @freefunc: + * + * Add new EMPopupItems to the menu's. Any with the same path + * will override previously defined menu items, at menu building + * time. + **/ +void +em_popup_add_items(EMPopup *emp, GSList *items, GDestroyNotify freefunc) +{ + struct _menu_node *node; + + node = g_malloc(sizeof(*node)); + node->menu = items; + node->freefunc = freefunc; + e_dlist_addtail(&emp->priv->menus, (EDListNode *)node); +} + +/** + * em_popup_add_static_items: + * @emp: + * @target: Target of this menu. + * + * Will load up any matching menu items from an installed + * popup factory. If the menuid of @emp is NULL, then this + * has no effect. + * + **/ +void +em_popup_add_static_items(EMPopup *emp, EMPopupTarget *target) +{ + struct _EMPopupFactory *f; + + if (emp->menuid == NULL || target == NULL) + return; + + /* setup the menu itself */ + f = (struct _EMPopupFactory *)emp_factories.head; + while (f->next) { + if (f->menuid == NULL + || !strcmp(f->menuid, emp->menuid)) { + f->factory(emp, target, f->factory_data); + } + f = f->next; + } +} + +static int +emp_cmp(const void *ap, const void *bp) +{ + struct _EMPopupItem *a = *((void **)ap); + struct _EMPopupItem *b = *((void **)bp); + + return strcmp(a->path, b->path); +} + +/** + * em_popup_create: + * @menuitems: + * @hide_mask: used to hide menu items, not sure of it's utility, + * since you could just 'not add them' in the first place. Saves + * copying logic anyway. + * @disable_mask: used to disable menu items. + * + * TEMPORARY code to create a menu from a list of items. + * + * The menu items are merged based on their path element, and + * built into a menu tree. + * + * Return value: + **/ +GtkMenu * +em_popup_create_menu(EMPopup *emp, guint32 hide_mask, guint32 disable_mask) +{ + struct _EMPopupPrivate *p = emp->priv; + struct _menu_node *mnode, *nnode; + GPtrArray *items = g_ptr_array_new(); + GSList *l; + GString *ppath = g_string_new(""); + GtkMenu *topmenu; + GHashTable *menu_hash = g_hash_table_new(g_str_hash, g_str_equal), + *group_hash = g_hash_table_new(g_str_hash, g_str_equal); + /*char *domain = NULL;*/ + int i; + + /* FIXME: need to override old ones with new names */ + mnode = (struct _menu_node *)p->menus.head; + nnode = mnode->next; + while (nnode) { + for (l=mnode->menu; l; l = l->next) + g_ptr_array_add(items, l->data); + mnode = nnode; + nnode = nnode->next; + } + + qsort(items->pdata, items->len, sizeof(items->pdata[0]), emp_cmp); + + topmenu = (GtkMenu *)gtk_menu_new(); + for (i=0;i<items->len;i++) { + GtkWidget *label; + struct _EMPopupItem *item = items->pdata[i]; + GtkMenu *thismenu; + GtkMenuItem *menuitem; + char *tmp; + + /* for bar's, the mask is exclusive or */ + if (item->mask) { + if ((item->type & EM_POPUP_TYPE_MASK) == EM_POPUP_BAR) { + if ((item->mask & hide_mask) == item->mask) + continue; + } else if (item->mask & hide_mask) + continue; + } + + g_string_truncate(ppath, 0); + tmp = strrchr(item->path, '/'); + if (tmp) { + g_string_append_len(ppath, item->path, tmp-item->path); + thismenu = g_hash_table_lookup(menu_hash, ppath->str); + g_assert(thismenu != NULL); + } else { + thismenu = topmenu; + } + + switch (item->type & EM_POPUP_TYPE_MASK) { + case EM_POPUP_ITEM: + if (item->image) { + char *path; + GtkWidget *image; + + path = g_build_filename(EVOLUTION_IMAGES, (char *)item->image, NULL); + image = gtk_image_new_from_file(path); + g_free(path); + + gtk_widget_show(image); + menuitem = (GtkMenuItem *)gtk_image_menu_item_new(); + gtk_image_menu_item_set_image((GtkImageMenuItem *)menuitem, image); + } else { + menuitem = (GtkMenuItem *)gtk_menu_item_new(); + } + break; + case EM_POPUP_TOGGLE: + menuitem = (GtkMenuItem *)gtk_check_menu_item_new(); + gtk_check_menu_item_set_active((GtkCheckMenuItem *)menuitem, item->type & EM_POPUP_ACTIVE); + break; + case EM_POPUP_RADIO: + menuitem = (GtkMenuItem *)gtk_radio_menu_item_new(g_hash_table_lookup(group_hash, ppath->str)); + g_hash_table_insert(group_hash, ppath->str, gtk_radio_menu_item_get_group((GtkRadioMenuItem *)menuitem)); + gtk_check_menu_item_set_active((GtkCheckMenuItem *)menuitem, item->type & EM_POPUP_ACTIVE); + break; + case EM_POPUP_IMAGE: + menuitem = (GtkMenuItem *)gtk_image_menu_item_new(); + gtk_image_menu_item_set_image((GtkImageMenuItem *)menuitem, item->image); + break; + case EM_POPUP_SUBMENU: { + GtkMenu *submenu = (GtkMenu *)gtk_menu_new(); + + g_hash_table_insert(menu_hash, item->path, submenu); + menuitem = (GtkMenuItem *)gtk_menu_item_new(); + gtk_menu_item_set_submenu(menuitem, (GtkWidget *)submenu); + break; } + case EM_POPUP_BAR: + /* TODO: double-bar, end-bar stuff? */ + menuitem = (GtkMenuItem *)gtk_separator_menu_item_new(); + break; + default: + continue; + } + + if (item->label) { + label = gtk_label_new_with_mnemonic(item->label); + gtk_misc_set_alignment((GtkMisc *)label, 0.0, 0.5); + gtk_widget_show(label); + gtk_container_add((GtkContainer *)menuitem, label); + } + + if (item->activate) + g_signal_connect(menuitem, "activate", item->activate, item->activate_data); + + gtk_menu_shell_append((GtkMenuShell *)thismenu, (GtkWidget *)menuitem); + + if (item->mask & disable_mask) + gtk_widget_set_sensitive((GtkWidget *)menuitem, FALSE); + + gtk_widget_show((GtkWidget *)menuitem); + } + + g_string_free(ppath, TRUE); + g_ptr_array_free(items, TRUE); + g_hash_table_destroy(menu_hash); + g_hash_table_destroy(group_hash); + + return topmenu; +} + +static void +emp_popup_done(GtkWidget *w, EMPopup *emp) +{ + gtk_widget_destroy(w); + g_object_unref(emp); +} + +/** + * em_popup_create_menu_once: + * @emp: EMPopup, once the menu is shown, this cannot be + * considered a valid pointer. + * @target: If set, the target of the selection. Static menu + * items will be added. The target will be freed once complete. + * @hide_mask: + * @disable_mask: + * + * Like popup_create_menu, but automatically sets up the menu + * so that it is destroyed once a selection takes place, and + * the EMPopup is unreffed. + * + * Return value: A menu, to popup. + **/ +GtkMenu * +em_popup_create_menu_once(EMPopup *emp, EMPopupTarget *target, guint32 hide_mask, guint32 disable_mask) +{ + GtkMenu *menu; + + if (target) + em_popup_add_static_items(emp, target); + + menu = em_popup_create_menu(emp, hide_mask, disable_mask); + + g_signal_connect_swapped(menu, "selection_done", G_CALLBACK(em_popup_target_free), target); + g_signal_connect(menu, "selection_done", G_CALLBACK(emp_popup_done), emp); + + return menu; +} + +/* ********************************************************************** */ + +/** + * em_popup_static_add_factory: + * @menuid: + * @func: + * @data: + * + * Add a popup factory which will be called to add_items() any + * extra menu's if wants to do the current PopupTarget. + * + * TODO: Make the menuid a pattern? + * + * Return value: A handle to the factory. + **/ +EMPopupFactory * +em_popup_static_add_factory(const char *menuid, EMPopupFactoryFunc func, void *data) +{ + struct _EMPopupFactory *f = g_malloc0(sizeof(*f)); + + f->menuid = g_strdup(menuid); + f->factory = func; + f->factory_data = data; + e_dlist_addtail(&emp_factories, (EDListNode *)f); + + return f; +} + +/** + * em_popup_static_remove_factory: + * @f: + * + * Remove a popup factory. + **/ +void +em_popup_static_remove_factory(EMPopupFactory *f) +{ + e_dlist_remove((EDListNode *)f); + g_free(f->menuid); + g_free(f); +} + +/** + * em_popup_target_new_select: + * @folder: The selection will ref this for the life of it. + * @folder_uri: + * @uids: The selection will free this when done with it. + * + * Create a new selection popup target. + * + * Return value: + **/ +EMPopupTarget * +em_popup_target_new_select(struct _CamelFolder *folder, const char *folder_uri, GPtrArray *uids) +{ + EMPopupTarget *t = g_malloc0(sizeof(*t)); + guint32 mask = ~0; + int i; + const char *tmp; + + t->type = EM_POPUP_TARGET_SELECT; + t->data.select.uids = uids; + t->data.select.folder = folder; + camel_object_ref(folder); + t->data.select.folder_uri = g_strdup(folder_uri); + + if (em_utils_folder_is_sent(folder, folder_uri)) + mask &= ~EM_POPUP_SELECT_RESEND; + + if (!(em_utils_folder_is_drafts(folder, folder_uri) + || em_utils_folder_is_outbox(folder, folder_uri))) + mask &= ~EM_POPUP_SELECT_ADD_SENDER; + + if (uids->len == 1) + mask &= ~EM_POPUP_SELECT_ONE; + + if (uids->len >= 1) + mask &= ~EM_POPUP_SELECT_MANY; + + for (i = 0; i < uids->len; i++) { + CamelMessageInfo *info = camel_folder_get_message_info(folder, uids->pdata[i]); + + if (info == NULL) + continue; + + if (info->flags & CAMEL_MESSAGE_SEEN) + mask &= ~EM_POPUP_SELECT_MARK_UNREAD; + else + mask &= ~EM_POPUP_SELECT_MARK_READ; + + if (info->flags & CAMEL_MESSAGE_DELETED) + mask &= ~EM_POPUP_SELECT_UNDELETE; + else + mask &= ~EM_POPUP_SELECT_DELETE; + + if (info->flags & CAMEL_MESSAGE_FLAGGED) + mask &= ~EM_POPUP_SELECT_MARK_UNIMPORTANT; + else + mask &= ~EM_POPUP_SELECT_MARK_IMPORTANT; + + tmp = camel_tag_get (&info->user_tags, "follow-up"); + if (tmp && *tmp) { + mask &= ~EM_POPUP_SELECT_FLAG_CLEAR; + tmp = camel_tag_get(&info->user_tags, "completed-on"); + if (tmp == NULL || *tmp == 0) + mask &= ~EM_POPUP_SELECT_FLAG_COMPLETED; + } else + mask &= ~EM_POPUP_SELECT_FLAG_FOLLOWUP; + + if (i == 0 && uids->len == 1 + && (tmp = camel_message_info_mlist(info)) + && tmp[0] != 0) + mask &= ~EM_POPUP_SELECT_MAILING_LIST; + + camel_folder_free_message_info(folder, info); + } + + t->mask = mask; + + return t; +} + +EMPopupTarget * +em_popup_target_new_uri(const char *uri) +{ + EMPopupTarget *t = g_malloc0(sizeof(*t)); + guint32 mask = ~0; + + t->type = EM_POPUP_TARGET_URI; + t->data.uri = g_strdup(uri); + + if (g_ascii_strncasecmp(uri, "http:", 5) == 0 + || g_ascii_strncasecmp(uri, "https:", 6) == 0) + mask &= ~EM_POPUP_URI_HTTP; + if (g_ascii_strncasecmp(uri, "mailto:", 7) == 0) + mask &= ~EM_POPUP_URI_MAILTO; + else + mask &= ~EM_POPUP_URI_NOT_MAILTO; + + t->mask = mask; + + return t; +} + +EMPopupTarget * +em_popup_target_new_part(struct _CamelMimePart *part, const char *mime_type) +{ + EMPopupTarget *t = g_malloc0(sizeof(*t)); + guint32 mask = ~0; + + t->type = EM_POPUP_TARGET_PART; + t->data.part.part = part; + camel_object_ref(part); + if (mime_type) + t->data.part.mime_type = g_strdup(mime_type); + else + t->data.part.mime_type = camel_data_wrapper_get_mime_type((CamelDataWrapper *)part); + + camel_strdown(t->data.part.mime_type); + + if (CAMEL_IS_MIME_MESSAGE(camel_medium_get_content_object((CamelMedium *)part))) + mask &= ~EM_POPUP_PART_MESSAGE; + + if (strncmp(t->data.part.mime_type, "image/", 6) == 0) + mask &= ~EM_POPUP_PART_IMAGE; + + t->mask = mask; + + return t; +} + +void +em_popup_target_free(EMPopupTarget *t) +{ + switch (t->type) { + case EM_POPUP_TARGET_SELECT: + camel_object_unref(t->data.select.folder); + g_free(t->data.select.folder_uri); + if (t->data.select.uids) + em_utils_uids_free(t->data.select.uids); + break; + case EM_POPUP_TARGET_URI: + g_free(t->data.uri); + break; + case EM_POPUP_TARGET_PART: + camel_object_unref(t->data.part.part); + g_free(t->data.part.mime_type); + break; + } + + g_free(t); +} + +/* ********************************************************************** */ + +#if 0 +/* TODO: flesh these out where possible */ +static void +emp_popup_open(GtkWidget *w, EMFolderView *emfv) +{ + em_folder_view_open_selected(emfv); +} + +static void +emp_popup_resend(GtkWidget *w, EMPopupTarget *t) +{ + if (!em_utils_check_user_can_send_mail(t->widget)) + return; + + em_utils_edit_messages(t->widget, t->data.select.folder, em_utils_uids_copy(t->data.select.uids)); +} + +static void +emp_popup_saveas(GtkWidget *w, EMPopupTarget *t) +{ + em_utils_save_messages(t->widget, t->data.select.folder, em_utils_uids_copy(t->data.select.uids)); +} + +static EMPopupItem emp_standard_select_popups[] = { + /*{ EM_POPUP_ITEM, "00.select.00", N_("_Open"), G_CALLBACK(emp_popup_open), NULL, NULL, 0 },*/ + { EM_POPUP_ITEM, "00.select.01", N_("_Edit as New Message..."), G_CALLBACK(emp_popup_resend), NULL, NULL, EM_POPUP_SELECT_RESEND }, + { EM_POPUP_ITEM, "00.select.02", N_("_Save As..."), G_CALLBACK(emp_popup_saveas), NULL, "save-as-16.png", 0 }, +}; +#endif + +/* ********************************************************************** */ + +static void +emp_part_popup_saveas(GtkWidget *w, EMPopupTarget *t) +{ + em_utils_save_part(w, _("Save As..."), t->data.part.part); +} + +static void +emp_part_popup_set_background(GtkWidget *w, EMPopupTarget *t) +{ + /* set as background ... */ + printf("UNIMPLEMENTED: set background, but it would be cool, no?\n"); +} + +static void +emp_part_popup_reply_sender(GtkWidget *w, EMPopupTarget *t) +{ + em_utils_reply_to_message(t->widget, + (CamelMimeMessage *)camel_medium_get_content_object((CamelMedium *)t->data.part.part), + REPLY_MODE_SENDER); +} + +static void +emp_part_popup_reply_list(GtkWidget *w, EMPopupTarget *t) +{ + em_utils_reply_to_message(t->widget, + (CamelMimeMessage *)camel_medium_get_content_object((CamelMedium *)t->data.part.part), + REPLY_MODE_LIST); +} + +static void +emp_part_popup_reply_all(GtkWidget *w, EMPopupTarget *t) +{ + em_utils_reply_to_message(t->widget, + (CamelMimeMessage *)camel_medium_get_content_object((CamelMedium *)t->data.part.part), + REPLY_MODE_ALL); +} + +static void +emp_part_popup_forward(GtkWidget *w, EMPopupTarget *t) +{ + em_utils_forward_message(t->widget, + (CamelMimeMessage *)camel_medium_get_content_object((CamelMedium *)t->data.part.part)); +} + +static EMPopupItem emp_standard_object_popups[] = { + { EM_POPUP_ITEM, "00.part.00", N_("_Save As..."), G_CALLBACK(emp_part_popup_saveas), NULL, "save-as-16.png", 0 }, + { EM_POPUP_ITEM, "00.part.10", N_("Set as _Background"), G_CALLBACK(emp_part_popup_set_background), NULL, NULL, EM_POPUP_PART_IMAGE }, + { EM_POPUP_BAR, "10.part", NULL, NULL, NULL, NULL, EM_POPUP_PART_MESSAGE }, + { EM_POPUP_ITEM, "10.part.00", N_("_Reply to sender"), G_CALLBACK(emp_part_popup_reply_sender), NULL, "reply.xpm" , EM_POPUP_PART_MESSAGE }, + { EM_POPUP_ITEM, "10.part.01", N_("Reply to _List"), G_CALLBACK(emp_part_popup_reply_list), NULL, NULL, EM_POPUP_PART_MESSAGE}, + { EM_POPUP_ITEM, "10.part.03", N_("Reply to _All"), G_CALLBACK(emp_part_popup_reply_all), NULL, "reply_to_all.xpm", EM_POPUP_PART_MESSAGE}, + { EM_POPUP_BAR, "20.part", NULL, NULL, NULL, NULL, EM_POPUP_PART_MESSAGE }, + { EM_POPUP_ITEM, "20.part.00", N_("_Forward"), G_CALLBACK(emp_part_popup_forward), NULL, "forward.xpm", EM_POPUP_PART_MESSAGE }, + +}; + +static const EMPopupItem emp_standard_part_apps_bar = { EM_POPUP_BAR, "99.object" }; + +/* ********************************************************************** */ + +static void +emp_uri_popup_link_open(GtkWidget *w, EMPopupTarget *t) +{ + GError *err = NULL; + + gnome_url_show(t->data.uri, &err); + if (err) { + g_warning("gnome_url_show: %s", err->message); + g_error_free(err); + } +} + +static void +emp_uri_popup_link_copy(GtkWidget *w, EMPopupTarget *t) +{ +#if 0 + g_free(p->selection_uri); + p->selection_uri = g_strdup(t->data.uri); + + 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()); +#endif +} + +static void +emp_uri_popup_address_send(GtkWidget *w, EMPopupTarget *t) +{ + em_utils_compose_new_message_with_mailto(t->widget, t->data.uri); +} + +static void +emp_uri_popup_address_add(GtkWidget *w, EMPopupTarget *t) +{ + printf("UNIMPLEMENTED: Add address '%s'\n", t->data.uri); +} + +static EMPopupItem emp_standard_uri_popups[] = { + { EM_POPUP_ITEM, "00.uri.00", N_("_Open Link in Browser"), G_CALLBACK(emp_uri_popup_link_open), NULL, NULL, EM_POPUP_URI_NOT_MAILTO }, + { EM_POPUP_ITEM, "00.uri.01", N_("_Copy Link Location"), G_CALLBACK(emp_uri_popup_link_copy), NULL, NULL, EM_POPUP_URI_NOT_MAILTO }, + { EM_POPUP_ITEM, "00.uri.10", N_("Se_nd message to..."), G_CALLBACK(emp_uri_popup_address_send), NULL, NULL, EM_POPUP_URI_MAILTO }, + { EM_POPUP_ITEM, "00.uri.20", N_("_Add to Addressbook"), G_CALLBACK(emp_uri_popup_address_add), NULL, NULL, EM_POPUP_URI_MAILTO }, +}; + +/* ********************************************************************** */ + +#define LEN(x) (sizeof(x)/sizeof(x[0])) + +#include <libgnomevfs/gnome-vfs-mime-handlers.h> + +struct _open_in_item { + EMPopupItem item; + EMPopupTarget *target; + GnomeVFSMimeApplication *app; +}; + +static void +emp_apps_open_in(GtkWidget *w, struct _open_in_item *item) +{ + char *path; + + path = em_utils_temp_save_part(item->target->widget, item->target->data.part.part); + if (path) { + char *command; + int douri = (item->app->expects_uris == GNOME_VFS_MIME_APPLICATION_ARGUMENT_TYPE_URIS); + + command = g_strdup_printf(douri?"%s file://%s &":"%s %s &", item->app->command, path); + + /* FIXME: Do not use system here */ + system(command); + g_free(command); + g_free(path); + } +} + +static void +emp_apps_popup_free(GSList *free_list) +{ + while (free_list) { + GSList *n = free_list->next; + struct _open_in_item *item = free_list->data; + + g_free(item->item.path); + g_free(item->item.label); + g_free(item); + g_slist_free_1(free_list); + + free_list = n; + } +} + +static void +emp_standard_menu_factory(EMPopup *emp, EMPopupTarget *target, void *data) +{ + int i, len; + EMPopupItem *items; + GSList *menus = NULL; + + switch (target->type) { + case EM_POPUP_TARGET_SELECT: + return; +#if 0 + items = emp_standard_select_popups; + len = LEN(emp_standard_select_popups); + break; +#endif + case EM_POPUP_TARGET_URI: + items = emp_standard_uri_popups; + len = LEN(emp_standard_uri_popups); + break; + case EM_POPUP_TARGET_PART: { + GList *apps = gnome_vfs_mime_get_short_list_applications(target->data.part.mime_type); + + /* FIXME: use the snoop_part stuff from em-format.c */ + if (apps == NULL && strcmp(target->data.part.mime_type, "application/octet-stream") == 0) { + const char *filename = camel_mime_part_get_filename(target->data.part.part), *name_type; + + if (filename) { + /* GNOME-VFS will misidentify TNEF attachments as MPEG */ + if (!strcmp (filename, "winmail.dat")) + name_type = "application/vnd.ms-tnef"; + else + name_type = gnome_vfs_mime_type_from_name(filename); + if (name_type) + apps = gnome_vfs_mime_get_short_list_applications(name_type); + } + } + + if (apps) { + GString *label = g_string_new(""); + GSList *open_menus = NULL; + GList *l; + + menus = g_slist_prepend(menus, (void *)&emp_standard_part_apps_bar); + + for (l=apps;l;l=l->next) { + GnomeVFSMimeApplication *app = l->data; + struct _open_in_item *item; + + if (app->requires_terminal) + continue; + + item = g_malloc0(sizeof(*item)); + item->item.type = EM_POPUP_ITEM; + item->item.path = g_strdup_printf("99.object.%02d", i); + item->item.label = g_strdup_printf(_("Open in %s..."), app->name); + item->item.activate = G_CALLBACK(emp_apps_open_in); + item->item.activate_data = item; + item->target = target; + item->app = app; + + open_menus = g_slist_prepend(open_menus, item); + } + + if (open_menus) + em_popup_add_items(emp, open_menus, (GDestroyNotify)emp_apps_popup_free); + + g_string_free(label, TRUE); + g_list_free(apps); + } + + items = emp_standard_object_popups; + len = LEN(emp_standard_object_popups); + break; } + } + + for (i=0;i<len;i++) { + if ((items[i].mask & target->mask) == 0) { + items[i].activate_data = target; + menus = g_slist_prepend(menus, &items[i]); + } + } + + if (menus) + em_popup_add_items(emp, menus, (GDestroyNotify)g_slist_free); +} diff --git a/mail/em-popup.h b/mail/em-popup.h new file mode 100644 index 0000000000..877b28fc28 --- /dev/null +++ b/mail/em-popup.h @@ -0,0 +1,158 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Authors: Michel Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __EM_POPUP_H__ +#define __EM_POPUP_H__ + +#include <glib-object.h> + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +/* NB: This is TEMPORARY, to be replaced by EggMenu, if it does what we need? */ + +typedef struct _EMPopup EMPopup; +typedef struct _EMPopupClass EMPopupClass; + +typedef struct _EMPopupItem EMPopupItem; +typedef struct _EMPopupFactory EMPopupFactory; /* anonymous type */ +typedef struct _EMPopupTarget EMPopupTarget; + +typedef void (*EMPopupFactoryFunc)(EMPopup *emp, EMPopupTarget *target, void *data); + +/* Menu item descriptions */ +enum _em_popup_t { + EM_POPUP_ITEM = 0, + EM_POPUP_TOGGLE, + EM_POPUP_RADIO, + EM_POPUP_IMAGE, + EM_POPUP_SUBMENU, + EM_POPUP_BAR, + EM_POPUP_TYPE_MASK = 0xffff, + EM_POPUP_ACTIVE = 0x10000, +}; + +struct _EMPopupItem { + enum _em_popup_t type; + char *path; /* absolute path! must sort ascii-lexographically into the right spot */ + char *label; + GCallback activate; + void *activate_data; + void *image; /* char* for item type, GtkWidget * for image type */ + guint32 mask; +}; + +/* Current target description */ +/* Types of popup tagets */ +enum _em_popup_target_t { + EM_POPUP_TARGET_SELECT, + EM_POPUP_TARGET_URI, + EM_POPUP_TARGET_PART, +}; + +/* Flags that describe a TARGET_SELECT */ +enum { + EM_POPUP_SELECT_ONE = 1<<1, + EM_POPUP_SELECT_MANY = 1<<2, + EM_POPUP_SELECT_MARK_READ = 1<<3, + EM_POPUP_SELECT_MARK_UNREAD = 1<<4, + EM_POPUP_SELECT_DELETE = 1<<5, + EM_POPUP_SELECT_UNDELETE = 1<<6, + EM_POPUP_SELECT_MAILING_LIST = 1<<7, + EM_POPUP_SELECT_RESEND = 1<<8, + EM_POPUP_SELECT_MARK_IMPORTANT = 1<<9, + EM_POPUP_SELECT_MARK_UNIMPORTANT = 1<<10, + EM_POPUP_SELECT_FLAG_FOLLOWUP = 1<<11, + EM_POPUP_SELECT_FLAG_COMPLETED = 1<<12, + EM_POPUP_SELECT_FLAG_CLEAR = 1<<13, + EM_POPUP_SELECT_ADD_SENDER = 1<<14, + EM_POPUP_SELECT_LAST = 1<<16 /* reserve 2 slots */ +}; + +/* Flags that describe a TARGET_URI */ +enum { + EM_POPUP_URI_HTTP = 1<<0, + EM_POPUP_URI_MAILTO = 1<<1, + EM_POPUP_URI_NOT_MAILTO = 1<<2, +}; + +/* Flags that describe TARGET_PART */ +enum { + EM_POPUP_PART_MESSAGE = 1<<0, + EM_POPUP_PART_IMAGE = 1<<1, +}; + +struct _EMPopupTarget { + enum _em_popup_target_t type; + guint32 mask; /* depends on type, see above */ + struct _GtkWidget *widget; /* used if you need a parent toplevel, if available */ + union { + char *uri; + struct { + struct _CamelFolder *folder; + char *folder_uri; + GPtrArray *uids; + } select; + struct { + char *mime_type; + struct _CamelMimePart *part; + } part; + } data; +}; + +/* The object */ +struct _EMPopup { + GObject object; + + struct _EMPopupPrivate *priv; + + char *menuid; +}; + +struct _EMPopupClass { + GObjectClass object_class; +}; + +GType em_popup_get_type(void); + +/* Static class methods */ +EMPopupFactory *em_popup_static_add_factory(const char *menuid, EMPopupFactoryFunc func, void *data); +void em_popup_static_remove_factory(EMPopupFactory *f); + +EMPopup *em_popup_new(const char *menuid); +void em_popup_add_items(EMPopup *, GSList *items, GDestroyNotify freefunc); +void em_popup_add_static_items(EMPopup *emp, EMPopupTarget *target); +struct _GtkMenu *em_popup_create_menu(EMPopup *, guint32 hide_mask, guint32 disable_mask); +struct _GtkMenu *em_popup_create_menu_once(EMPopup *emp, EMPopupTarget *, guint32 hide_mask, guint32 disable_mask); + +EMPopupTarget *em_popup_target_new_uri(const char *uri); +EMPopupTarget *em_popup_target_new_select(struct _CamelFolder *folder, const char *folder_uri, GPtrArray *uids); +EMPopupTarget *em_popup_target_new_part(struct _CamelMimePart *part, const char *mime_type); +void em_popup_target_free(EMPopupTarget *target); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __EM_POPUP_H__ */ diff --git a/mail/em-subscribe-editor.c b/mail/em-subscribe-editor.c new file mode 100644 index 0000000000..e00b79ce8c --- /dev/null +++ b/mail/em-subscribe-editor.c @@ -0,0 +1,849 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * em-subscribe-editor.c * + * + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <pthread.h> + +/*#include "evolution-shell-component-utils.h" + #include "mail.h"*/ +#include "mail-tools.h" +#include "mail-ops.h" +#include "mail-mt.h" +/*#include "mail-folder-cache.h"*/ +#include "camel/camel-exception.h" +#include "camel/camel-store.h" +#include "camel/camel-session.h" +#include "e-util/e-account-list.h" + +#include "em-subscribe-editor.h" + +#include "mail-config.h" + +#include <glade/glade.h> + +#include <gtk/gtkdialog.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreestore.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertoggle.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkoptionmenu.h> +#include <gtk/gtkprogressbar.h> +#include <gtk/gtkmenuitem.h> + +#define d(x) + +typedef struct _EMSubscribeEditor EMSubscribeEditor; +struct _EMSubscribeEditor { + EDList stores; + + int busy; + guint busy_id; + + struct _EMSubscribe *current; /* the current one, if any */ + + GtkDialog *dialog; + GtkWidget *vbox; /* where new stores are added */ + GtkWidget *optionmenu; + GtkWidget *none_selected; /* 'please select a xxx' message */ + GtkWidget *none_selected_item; + GtkWidget *subscribe_button; + GtkWidget *unsubscribe_button; + GtkWidget *progress; +}; + +typedef struct _EMSubscribe EMSubscribe; +struct _EMSubscribe { + struct _EMSubscribe *next; + struct _EMSubscribe *prev; + + int ref_count; + int cancel; + + struct _EMSubscribeEditor *editor; /* parent object*/ + + char *store_uri; + int store_id; /* looking up a store */ + + CamelStore *store; + GHashTable *folders; + + GtkWidget *widget; /* widget to show for this store */ + GtkTreeView *tree; /* tree, if we have it */ + + /* list of all returns from get_folder_info, accessed by other structures */ + GSList *info_list; + + /* pending LISTs, EMSubscribeNode's */ + int pending_id; + EDList pending; + + /* queue of pending UN/SUBSCRIBEs, EMsg's */ + int subscribe_id; + EDList subscribe; + + /* working variables at runtime */ + int selected_count; + int selected_subscribed_count; + gboolean subscribed_state:1; /* for setting the selection*/ +}; + +typedef struct _EMSubscribeNode EMSubscribeNode; +struct _EMSubscribeNode { + struct _EMSubscribeNode *next; + struct _EMSubscribeNode *prev; + + CamelFolderInfo *info; + GtkTreePath *path; +}; + +static void sub_editor_busy(EMSubscribeEditor *se, int dir); +static int sub_queue_fill_level(EMSubscribe *sub, EMSubscribeNode *node); + +static void +sub_node_free(char *key, EMSubscribeNode *node, EMSubscribe *sub) +{ + d(printf("sub node free '%s'\n", node->info?node->info->full_name:"<unknown>")); + if (node->path) + gtk_tree_path_free(node->path); + g_free(node); +} + +static void +sub_ref(EMSubscribe *sub) +{ + sub->ref_count++; +} + +static void +sub_unref(EMSubscribe *sub) +{ + GSList *l; + + sub->ref_count--; + if (sub->ref_count == 0) { + d(printf("subscribe object finalised\n")); + /* we dont have to delete the "subscribe" task list, as it must be empty, + otherwise we wouldn't be unreffed (intentional circular reference) */ + if (sub->folders) { + g_hash_table_foreach(sub->folders, (GHFunc)sub_node_free, sub); + g_hash_table_destroy(sub->folders); + } + l = sub->info_list; + while (l) { + GSList *n = l->next; + + camel_store_free_folder_info(sub->store, (CamelFolderInfo *)l->data); + g_slist_free_1(l); + l = n; + } + if (sub->store) + camel_object_unref(sub->store); + g_free(sub->store_uri); + g_free(sub); + } +} + +/* ** Subscribe folder operation **************************************** */ + +struct _zsubscribe_msg { + struct _mail_msg msg; + + EMSubscribe *sub; + EMSubscribeNode *node; + int subscribe; + char *path; +}; + +static void +sub_folder_subscribe (struct _mail_msg *mm) +{ + struct _zsubscribe_msg *m = (struct _zsubscribe_msg *) mm; + + if (m->subscribe) + camel_store_subscribe_folder (m->sub->store, m->node->info->full_name, &mm->ex); + else + camel_store_unsubscribe_folder (m->sub->store, m->node->info->full_name, &mm->ex); +} + +static void +sub_folder_subscribed (struct _mail_msg *mm) +{ + struct _zsubscribe_msg *m = (struct _zsubscribe_msg *) mm; + GtkTreeIter iter; + GtkTreeModel *model; + EMSubscribeNode *node; + gboolean subscribed, issub; + + m->sub->subscribe_id = -1; + if (m->sub->cancel) + return; + + if (!camel_exception_is_set(&mm->ex)) { + if (m->subscribe) + m->node->info->flags |= CAMEL_FOLDER_SUBSCRIBED; + else + m->node->info->flags &= ~CAMEL_FOLDER_SUBSCRIBED; + } + + /* make sure the tree view matches the correct state */ + model = gtk_tree_view_get_model(m->sub->tree); + if (gtk_tree_model_get_iter_from_string(model, &iter, m->path)) { + issub = (m->node->info->flags & CAMEL_FOLDER_SUBSCRIBED) != 0; + gtk_tree_model_get(model, &iter, 0, &subscribed, 2, &node, -1); + if (node == m->node) + gtk_tree_store_set((GtkTreeStore *)model, &iter, 0, issub, -1); + else + d(printf("node mismatch, or subscribe state changed failed\n")); + } + + /* queue any further ones */ + m = (struct _zsubscribe_msg *)e_dlist_remhead(&m->sub->subscribe); + if (m) { + m->sub->subscribe_id = m->msg.seq; + e_thread_put (mail_thread_new, (EMsg *)m); + } +} + +static void +sub_folder_free (struct _mail_msg *mm) +{ + struct _zsubscribe_msg *m = (struct _zsubscribe_msg *) mm; + + g_free(m->path); + sub_unref(m->sub); +} + +static struct _mail_msg_op sub_subscribe_folder_op = { + NULL, /*subscribe_folder_desc,*/ + sub_folder_subscribe, + sub_folder_subscribed, + sub_folder_free, +}; + +/* spath is tree path in string form */ +static int +sub_subscribe_folder (EMSubscribe *sub, EMSubscribeNode *node, int state, const char *spath) +{ + struct _zsubscribe_msg *m; + int id; + + m = mail_msg_new (&sub_subscribe_folder_op, NULL, sizeof(*m)); + m->sub = sub; + sub_ref(sub); + m->node = node; + m->subscribe = state; + m->path = g_strdup(spath); + + id = m->msg.seq; + if (sub->subscribe_id == -1) { + sub->subscribe_id = id; + d(printf("running subscribe folder '%s'\n", spath)); + e_thread_put (mail_thread_new, (EMsg *)m); + } else { + d(printf("queueing subscribe folder '%s'\n", spath)); + e_dlist_addtail(&sub->subscribe, (EDListNode *)m); + } + + return id; +} + +/* ********************************************************************** */ +static void +sub_fill_level(EMSubscribe *sub, CamelFolderInfo *info, GtkTreeIter *parent, int pending) +{ + CamelFolderInfo *fi; + GtkTreeStore *treestore; + GtkTreeIter iter; + EMSubscribeNode *node; + + treestore = (GtkTreeStore *)gtk_tree_view_get_model(sub->tree); + + /* first, fill a level up */ + fi = info; + while (fi) { + if (g_hash_table_lookup(sub->folders, fi->full_name) == NULL) { + gboolean state; + + gtk_tree_store_append(treestore, &iter, parent); + node = g_malloc0(sizeof(*node)); + node->info = fi; + /* FIXME: CAMEL_FOLDER_SUBSCRIBED not implemented properly in imap */ + state = camel_store_folder_subscribed(sub->store, fi->full_name); + /* state = (fi->flags & CAMEL_FOLDER_SUBSCRIBED) != 0; */ + gtk_tree_store_set(treestore, &iter, 0, state, 1, fi->name, 2, node, -1); + if ((fi->flags & CAMEL_FOLDER_NOINFERIORS) == 0) { + node->path = gtk_tree_model_get_path((GtkTreeModel *)treestore, &iter); + if (node->path) { + /* save time, if we have any children alread, dont re-scan */ + if (fi->child) { + d(printf("scanning child '%s'\n", fi->child->full_name)); + sub_fill_level(sub, fi->child, &iter, FALSE); + } else { + if (pending) + e_dlist_addtail(&sub->pending, (EDListNode *)node); + } + } + } + g_hash_table_insert(sub->folders, fi->full_name, node); + } + fi = fi->sibling; + } +} + +/* async query of folderinfo */ + +struct _emse_folderinfo_msg { + struct _mail_msg msg; + + EMSubscribe *sub; + EMSubscribeNode *node; + CamelFolderInfo *info; +}; + +static void +sub_folderinfo_get (struct _mail_msg *mm) +{ + struct _emse_folderinfo_msg *m = (struct _emse_folderinfo_msg *) mm; + + camel_operation_register(mm->cancel); + m->info = camel_store_get_folder_info (m->sub->store, m->node?m->node->info->full_name:"", CAMEL_STORE_FOLDER_INFO_FAST, &mm->ex); + camel_operation_unregister(mm->cancel); +} + +static void +sub_folderinfo_got(struct _mail_msg *mm) +{ + struct _emse_folderinfo_msg *m = (struct _emse_folderinfo_msg *) mm; + EMSubscribeNode *node; + + m->sub->pending_id = -1; + if (m->sub->cancel) + return; + + if (camel_exception_is_set (&mm->ex)) { + g_warning ("Error getting folder info from store: %s", + camel_exception_get_description (&mm->ex)); + } + + if (m->info) { + if (m->node) { + GtkTreeIter iter; + + gtk_tree_model_get_iter(gtk_tree_view_get_model(m->sub->tree), &iter, m->node->path); + sub_fill_level(m->sub, m->info, &iter, FALSE); + } else { + sub_fill_level(m->sub, m->info, NULL, TRUE); + } + } + + /* check for more to do */ + node = (EMSubscribeNode *)e_dlist_remhead(&m->sub->pending); + if (node) + sub_queue_fill_level(m->sub, node); +} + +static void +sub_folderinfo_free(struct _mail_msg *mm) +{ + struct _emse_folderinfo_msg *m = (struct _emse_folderinfo_msg *) mm; + + if (m->info) + m->sub->info_list = g_slist_prepend(m->sub->info_list, m->info); + + if (!m->sub->cancel) + sub_editor_busy(m->sub->editor, -1); + + sub_unref(m->sub); +} + +static struct _mail_msg_op sub_folderinfo_op = { + NULL, /*sub_folderinfo_desc, we do our own progress reporting/cancellation */ + sub_folderinfo_get, + sub_folderinfo_got, + sub_folderinfo_free, +}; + +static int +sub_queue_fill_level(EMSubscribe *sub, EMSubscribeNode *node) +{ + struct _emse_folderinfo_msg *m; + int id; + + d(printf("Starting get folderinfo of '%s'\n", node?node->info->full_name:"<root>")); + + m = mail_msg_new (&sub_folderinfo_op, NULL, sizeof(*m)); + sub_ref(sub); + m->sub = sub; + m->node = node; + + sub->pending_id = m->msg.seq; + + sub_editor_busy(sub->editor, 1); + + e_thread_put (mail_thread_new, (EMsg *)m); + return id; +} + +/* ********************************************************************** */ + +/* (un) subscribes the current selection */ +static void sub_do_subscribe(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, void *data) +{ + EMSubscribe *sub = data; + EMSubscribeNode *node; + gboolean subscribed; + + gtk_tree_model_get(model, iter, 0, &subscribed, 2, &node, -1); + if (sub->subscribed_state ^ subscribed) { + char *spath; + + spath = gtk_tree_path_to_string(path); + gtk_tree_store_set((GtkTreeStore *)model, iter, 0, subscribed, -1); + sub_subscribe_folder(sub, node, sub->subscribed_state, spath); + g_free(spath); + } +} + +static void +sub_subscribe(EMSubscribe *sub, gboolean subscribed) +{ + GtkTreeSelection *selection; + + if (sub->tree == NULL) + return; + + sub->subscribed_state = subscribed; + selection = gtk_tree_view_get_selection (sub->tree); + gtk_tree_selection_selected_foreach(selection, sub_do_subscribe, sub); +} + +static void +sub_subscribe_toggled(GtkCellRendererToggle *render, const char *spath, EMSubscribe *sub) +{ + GtkTreeIter iter; + GtkTreeModel *model = gtk_tree_view_get_model(sub->tree); + EMSubscribeNode *node; + gboolean subscribed; + + d(printf("subscribe toggled?\n")); + + if (gtk_tree_model_get_iter_from_string(model, &iter, spath)) { + gtk_tree_model_get(model, &iter, 0, &subscribed, 2, &node, -1); + subscribed = !subscribed; + d(printf("new state is %s\n", subscribed?"subscribed":"not subscribed")); + gtk_tree_store_set((GtkTreeStore *)model, &iter, 0, subscribed, -1); + sub_subscribe_folder(sub, node, subscribed, spath); + } +} + +static void sub_do_changed(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, void *data) +{ + EMSubscribe *sub = data; + EMSubscribeNode *node; + gboolean subscribed; + + gtk_tree_model_get(model, iter, 0, &subscribed, 2, &node, -1); + + if (subscribed) + sub->selected_subscribed_count++; + sub->selected_count++; +} + +static void +sub_selection_changed(GtkTreeSelection *selection, EMSubscribe *sub) +{ + int dosub = TRUE, dounsub = TRUE; + + sub->selected_count = 0; + sub->selected_subscribed_count = 0; + gtk_tree_selection_selected_foreach(selection, sub_do_changed, sub); + + if (sub->selected_count == 0) { + dosub = FALSE; + dounsub = FALSE; + } else if (sub->selected_subscribed_count == sub->selected_count) + dosub = FALSE; + else if (sub->selected_subscribed_count == 0) + dounsub = FALSE; + + gtk_widget_set_sensitive(sub->editor->subscribe_button, dosub); + gtk_widget_set_sensitive(sub->editor->unsubscribe_button, dounsub); +} + +static void +sub_row_expanded(GtkTreeView *tree, GtkTreeIter *iter, GtkTreePath *path, EMSubscribe *sub) +{ + EMSubscribeNode *node; + GtkTreeIter child; + GtkTreeModel *model = (GtkTreeModel *)gtk_tree_view_get_model(tree); + EDList list; + + gtk_tree_model_get(model, iter, 2, &node, -1); + if (node->path == NULL) { + d(printf("path '%s' already processed\n", node->info->full_name)); + return; + } + gtk_tree_path_free(node->path); + node->path = NULL; + + e_dlist_init(&list); + + /* add all children nodes to pending, and fire off a pending */ + /* we add them to the head of the pending list, to make it more interactive */ + gtk_tree_model_iter_children(model, &child, iter); + do { + gtk_tree_model_get(model, &child, 2, &node, -1); + if (node->path) + e_dlist_addtail(&list, (EDListNode *)node); + } while (gtk_tree_model_iter_next(model, &child)); + + while ( (node = (EMSubscribeNode *)e_dlist_remtail(&list)) ) + e_dlist_addhead(&sub->pending, (EDListNode *)node); + + if (sub->pending_id == -1 + && (node = (EMSubscribeNode *)e_dlist_remtail(&sub->pending))) + sub_queue_fill_level(sub, node); +} + +static void +sub_destroy(GtkWidget *w, EMSubscribe *sub) +{ + struct _zsubscribe_msg *m; + + d(printf("subscribe closed\n")); + sub->cancel = TRUE; + + if (sub->pending_id != -1) + mail_msg_cancel(sub->pending_id); + + if (sub->subscribe_id != -1) + mail_msg_cancel(sub->subscribe_id); + + while ( (m = (struct _zsubscribe_msg *)e_dlist_remhead(&sub->subscribe)) ) + mail_msg_free(m); + + sub_unref(sub); +} + +static EMSubscribe * +subscribe_new(EMSubscribeEditor *se, const char *uri) +{ + EMSubscribe *sub; + + sub = g_malloc0(sizeof(*sub)); + sub->store_uri = g_strdup(uri); + sub->editor = se; + sub->ref_count = 1; + sub->pending_id = -1; + e_dlist_init(&sub->pending); + sub->subscribe_id = -1; + e_dlist_init(&sub->subscribe); + sub->store_id = -1; + + return sub; +} + +static void +subscribe_set_store(EMSubscribe *sub, CamelStore *store) +{ + if (store == NULL || !camel_store_supports_subscriptions(store)) { + GtkWidget *w = gtk_label_new(_("This store does not support subscriptions, or the are not enabled.")); + + gtk_label_set_line_wrap((GtkLabel *)w, TRUE); + sub->widget = gtk_viewport_new(NULL, NULL); + gtk_viewport_set_shadow_type((GtkViewport *)sub->widget, GTK_SHADOW_IN); + gtk_container_add((GtkContainer *)sub->widget, w); + gtk_widget_show(w); + gtk_widget_show(sub->widget); + } else { + GtkTreeSelection *selection; + GtkCellRenderer *renderer; + GtkTreeStore *model; + + sub->store = store; + camel_object_ref(store); + sub->folders = g_hash_table_new(g_str_hash, g_str_equal); + + model = gtk_tree_store_new (3, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_POINTER); + sub->tree = (GtkTreeView *) gtk_tree_view_new_with_model ((GtkTreeModel *) model); + gtk_widget_show ((GtkWidget *)sub->tree); + + sub->widget = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sub->widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sub->widget), GTK_SHADOW_IN); + gtk_container_add((GtkContainer *)sub->widget, (GtkWidget *)sub->tree); + gtk_widget_show(sub->widget); + + renderer = gtk_cell_renderer_toggle_new (); + g_object_set(renderer, "activatable", TRUE, NULL); + gtk_tree_view_insert_column_with_attributes (sub->tree, -1, _("Subscribed"), renderer, "active", 0, NULL); + g_signal_connect(renderer, "toggled", G_CALLBACK(sub_subscribe_toggled), sub); + + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_insert_column_with_attributes (sub->tree, -1, _("Folder"), renderer, "text", 1, NULL); + gtk_tree_view_set_expander_column(sub->tree, gtk_tree_view_get_column(sub->tree, 1)); + + selection = gtk_tree_view_get_selection (sub->tree); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + gtk_tree_view_set_headers_visible (sub->tree, FALSE); + + g_signal_connect(sub->tree, "row-expanded", G_CALLBACK(sub_row_expanded), sub); + g_signal_connect(sub->tree, "destroy", G_CALLBACK(sub_destroy), sub); + + sub_selection_changed(selection, sub); + g_signal_connect(selection, "changed", G_CALLBACK(sub_selection_changed), sub); + + sub_queue_fill_level(sub, NULL); + } + + gtk_box_pack_start((GtkBox *)sub->editor->vbox, sub->widget, TRUE, TRUE, 0); +} + +static void +sub_editor_destroy(GtkWidget *w, EMSubscribeEditor *se) +{ + /* need to clean out pending store opens */ + d(printf("editor destroyed, freeing editor\n")); + if (se->busy_id) + g_source_remove(se->busy_id); + + g_free(se); +} + +static void +sub_editor_close(GtkWidget *w, EMSubscribeEditor *se) +{ + gtk_widget_destroy((GtkWidget *)se->dialog); +} + +static void +sub_editor_refresh(GtkWidget *w, EMSubscribeEditor *se) +{ + EMSubscribe *sub = se->current; + GSList *l; + + d(printf("sub editor refresh?\n")); + if (sub == NULL || sub->store == NULL) + return; + + /* drop any currently pending */ + if (sub->pending_id != -1) + mail_msg_cancel(sub->pending_id); + + gtk_tree_store_clear((GtkTreeStore *)gtk_tree_view_get_model(sub->tree)); + + e_dlist_init(&sub->pending); + if (sub->folders) { + g_hash_table_foreach(sub->folders, (GHFunc)sub_node_free, sub); + g_hash_table_destroy(sub->folders); + } + sub->folders = g_hash_table_new(g_str_hash, g_str_equal); + + l = sub->info_list; + sub->info_list = NULL; + while (l) { + GSList *n = l->next; + + camel_store_free_folder_info(sub->store, (CamelFolderInfo *)l->data); + g_slist_free_1(l); + l = n; + } + + sub_queue_fill_level(sub, NULL); +} + +static void +sub_editor_subscribe(GtkWidget *w, EMSubscribeEditor *se) +{ + d(printf("subscribe clicked, current = %p\n", se->current)); + + if (se->current) + sub_subscribe(se->current, TRUE); +} + +static void +sub_editor_unsubscribe(GtkWidget *w, EMSubscribeEditor *se) +{ + d(printf("unsubscribe clicked\n")); + + if (se->current) + sub_subscribe(se->current, FALSE); +} + +static void +sub_editor_got_store(char *uri, CamelStore *store, void *data) +{ + struct _EMSubscribe *sub = data; + + if (!sub->cancel) + subscribe_set_store(sub, store); + sub_unref(sub); +} + +static void +sub_editor_menu_changed(GtkWidget *w, EMSubscribeEditor *se) +{ + int i, n; + struct _EMSubscribe *sub; + + d(printf("menu changed\n")); + + i = 1; + n = gtk_option_menu_get_history((GtkOptionMenu *)se->optionmenu); + if (n == 0) + gtk_widget_show(se->none_selected); + else { + gtk_widget_hide(se->none_selected); + gtk_widget_hide(se->none_selected_item); + } + + se->current = NULL; + sub = (struct _EMSubscribe *)se->stores.head; + while (sub->next) { + if (i == n) { + se->current = sub; + if (sub->widget) { + gtk_widget_show(sub->widget); + } else if (sub->store_id == -1) { + sub_ref(sub); + sub->store_id = mail_get_store(sub->store_uri, NULL, sub_editor_got_store, sub); + } + } else { + if (sub->widget) + gtk_widget_hide(sub->widget); + } + i++; + sub = sub->next; + } +} + +static gboolean sub_editor_timeout(EMSubscribeEditor *se) +{ + gtk_progress_bar_pulse((GtkProgressBar *)se->progress); + + return TRUE; +} + +static void sub_editor_busy(EMSubscribeEditor *se, int dir) +{ + int was; + + was = se->busy != 0; + se->busy += dir; + if (was && !se->busy) { + g_source_remove(se->busy_id); + se->busy_id = 0; + gtk_widget_hide(se->progress); + } else if (!was && se->busy) { + se->busy_id = g_timeout_add(1000/5, (GSourceFunc)sub_editor_timeout, se); + gtk_widget_show(se->progress); + } +} + +GtkDialog *em_subscribe_editor_new(void) +{ + EMSubscribeEditor *se; + EAccountList *accounts; + EIterator *iter; + GladeXML *xml; + GtkWidget *menu, *w; + + se = g_malloc0(sizeof(*se)); + e_dlist_init(&se->stores); + + xml = glade_xml_new (EVOLUTION_GLADEDIR "/subscribe-dialog.glade", "subscribe_dialog", NULL); + if (xml == NULL) { + /* ?? */ + return NULL; + } + se->dialog = (GtkDialog *)glade_xml_get_widget (xml, "subscribe_dialog"); + g_signal_connect(se->dialog, "destroy", G_CALLBACK(sub_editor_destroy), se); + + se->vbox = glade_xml_get_widget(xml, "tree_box"); + + se->subscribe_button = glade_xml_get_widget (xml, "subscribe_button"); + g_signal_connect(se->subscribe_button, "clicked", G_CALLBACK(sub_editor_subscribe), se); + se->unsubscribe_button = glade_xml_get_widget (xml, "unsubscribe_button"); + g_signal_connect(se->unsubscribe_button, "clicked", G_CALLBACK(sub_editor_unsubscribe), se); + + /* FIXME: This is just to get the shadow, is there a better way? */ + w = gtk_label_new(_("Please select a server.")); + se->none_selected = gtk_viewport_new(NULL, NULL); + gtk_viewport_set_shadow_type((GtkViewport *)se->none_selected, GTK_SHADOW_IN); + gtk_container_add((GtkContainer *)se->none_selected, w); + gtk_widget_show(w); + + gtk_box_pack_start((GtkBox *)se->vbox, se->none_selected, TRUE, TRUE, 0); + gtk_widget_show(se->none_selected); + + se->progress = glade_xml_get_widget(xml, "progress_bar"); + gtk_widget_hide(se->progress); + + w = glade_xml_get_widget(xml, "close_button"); + g_signal_connect(w, "clicked", G_CALLBACK(sub_editor_close), se); + + w = glade_xml_get_widget(xml, "refresh_button"); + g_signal_connect(w, "clicked", G_CALLBACK(sub_editor_refresh), se); + + /* setup stores menu */ + se->optionmenu = glade_xml_get_widget(xml, "store_menu"); + menu = gtk_menu_new(); + se->none_selected_item = w = gtk_menu_item_new_with_label(_("No server has been selected")); + gtk_widget_show(w); + gtk_menu_shell_append ((GtkMenuShell *)menu, w); + + accounts = mail_config_get_accounts (); + for (iter = e_list_get_iterator ((EList *) accounts); + e_iterator_is_valid (iter); + e_iterator_next (iter)) { + EAccount *account = (EAccount *) e_iterator_get (iter); + + /* setup url table, and store table? */ + if (account->enabled && account->source->url) { + d(printf("adding account '%s'\n", account->name)); + w = gtk_menu_item_new_with_label(account->name); + gtk_menu_shell_append ((GtkMenuShell *)menu, w); + gtk_widget_show(w); + e_dlist_addtail(&se->stores, (EDListNode *)subscribe_new(se, account->source->url)); + } else { + d(printf("not adding account '%s'\n", account->name)); + } + } + g_object_unref(iter); + + gtk_option_menu_set_menu((GtkOptionMenu *)se->optionmenu, menu); + g_signal_connect(se->optionmenu, "changed", G_CALLBACK(sub_editor_menu_changed), se); + + gtk_window_set_default_size((GtkWindow *)se->dialog, 350, 400); + + return se->dialog; +} diff --git a/mail/em-subscribe-editor.h b/mail/em-subscribe-editor.h new file mode 100644 index 0000000000..ca02f14bee --- /dev/null +++ b/mail/em-subscribe-editor.h @@ -0,0 +1,9 @@ + +#ifndef _EM_SUBSCRIBE_EDITOR_H +#define _EM_SUBSCRIBE_EDITOR_H + +#include <gtk/gtkdialog.h> + +GtkDialog *em_subscribe_editor_new(void); + +#endif /* ! _EM_SUBSCRIBE_EDITOR_H */ diff --git a/mail/em-sync-stream.c b/mail/em-sync-stream.c new file mode 100644 index 0000000000..df5659253e --- /dev/null +++ b/mail/em-sync-stream.c @@ -0,0 +1,286 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * Michael Zucchi <notzed@ximian.com> + * + * Copyright 2001 Ximian, Inc. (www.ximian.com) + * + * 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. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdio.h> +#include <camel/camel-stream.h> +#include <camel/camel-object.h> +#include <gtk/gtkmain.h> +#include "em-sync-stream.h" + +#include "mail-mt.h" + +/*#define LOG_STREAM*/ + +#define d(x) + +#define EMSS_CLASS(x) ((EMSyncStreamClass *)(((CamelObject *)(x))->klass)) + +struct _EMSyncStreamPrivate { + /* FIXME: use a single data port/gui channel for all instances */ + /* TODO: possibly just use one of the mail-mt ports ... */ + struct _EMsgPort *data_port, *reply_port; + struct _GIOChannel *gui_channel; + guint gui_watch; + + char *buf_data; + int buf_used; + int buf_size; +}; + +/* Should probably expose messages to outside world ... so subclasses can extend */ +enum _write_msg_t { + EMSS_WRITE, + EMSS_FLUSH, + EMSS_CLOSE, +}; + +struct _write_msg { + EMsg msg; + + enum _write_msg_t op; + + const char *data; + size_t n; +}; + +static void em_sync_stream_class_init (EMSyncStreamClass *klass); +static void em_sync_stream_init (CamelObject *object); +static void em_sync_stream_finalize (CamelObject *object); + +static ssize_t stream_write(CamelStream *stream, const char *buffer, size_t n); +static int stream_close(CamelStream *stream); +static int stream_flush(CamelStream *stream); + +static CamelStreamClass *parent_class = NULL; + +CamelType +em_sync_stream_get_type (void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + type = camel_type_register (CAMEL_STREAM_TYPE, + "EMSyncStream", + sizeof (EMSyncStream), + sizeof (EMSyncStreamClass), + (CamelObjectClassInitFunc) em_sync_stream_class_init, + NULL, + (CamelObjectInitFunc) em_sync_stream_init, + (CamelObjectFinalizeFunc) em_sync_stream_finalize); + } + + return type; +} + +static void +em_sync_stream_class_init (EMSyncStreamClass *klass) +{ + CamelStreamClass *stream_class = CAMEL_STREAM_CLASS (klass); + + parent_class = (CamelStreamClass *) CAMEL_STREAM_TYPE; + + /* virtual method overload */ + stream_class->write = stream_write; + stream_class->flush = stream_flush; + stream_class->close = stream_close; +} + +static gboolean +emcs_gui_received(GIOChannel *source, GIOCondition cond, void *data) +{ + EMSyncStream *emss = data; + struct _EMSyncStreamPrivate *p = emss->priv; + struct _write_msg *msg; + + d(printf("%p: gui sync op job waiting\n", emss)); + + msg = (struct _write_msg *)e_msgport_get(p->data_port); + /* Should never happen ... */ + if (msg == NULL) + return TRUE; + + d(printf("%p: running sync op %d\n", emss, msg->op)); + + /* force out any pending data before doing anything else */ + if (p->buf_used > 0) { + EMSS_CLASS(emss)->sync_write((CamelStream *)emss, p->buf_data, p->buf_used); + p->buf_used = 0; + } + + /* FIXME: need to handle return values */ + + switch (msg->op) { + case EMSS_WRITE: + EMSS_CLASS(emss)->sync_write((CamelStream *)emss, msg->data, msg->n); + break; + case EMSS_FLUSH: + EMSS_CLASS(emss)->sync_flush((CamelStream *)emss); + break; + case EMSS_CLOSE: + EMSS_CLASS(emss)->sync_close((CamelStream *)emss); + break; + } + + e_msgport_reply((EMsg *)msg); + d(printf("%p: gui sync op jobs done\n", emss)); + + return TRUE; +} + +static void +em_sync_stream_init (CamelObject *object) +{ + EMSyncStream *emss = (EMSyncStream *)object; + struct _EMSyncStreamPrivate *p; + + p = emss->priv = g_malloc0(sizeof(*p)); + + p->data_port = e_msgport_new(); + p->reply_port = e_msgport_new(); + + p->gui_channel = g_io_channel_unix_new(e_msgport_fd(p->data_port)); + p->gui_watch = g_io_add_watch(p->gui_channel, G_IO_IN, emcs_gui_received, emss); + + d(printf("%p: new emss\n", emss)); +} + +static void +sync_op(EMSyncStream *emss, enum _write_msg_t op, const char *data, size_t n) +{ + struct _EMSyncStreamPrivate *p = emss->priv; + struct _write_msg msg; + + d(printf("%p: launching sync op %d\n", emss, op)); + + /* we do everything synchronous, we should never have any locks, and + this prevents overflow from banked up data */ + + msg.msg.reply_port = p->reply_port; + msg.op = op; + msg.data = data; + msg.n = n; + + e_msgport_put(p->data_port, &msg.msg); + e_msgport_wait(p->reply_port); + + g_assert(e_msgport_get(msg.msg.reply_port) == &msg.msg); + d(printf("%p: returned sync op %d\n", emss, op)); +} + +static void +em_sync_stream_finalize (CamelObject *object) +{ + EMSyncStream *emss = (EMSyncStream *)object; + struct _EMSyncStreamPrivate *p = emss->priv; + + /* TODO: is this stuff safe to do in another thread? */ + g_source_remove(p->gui_watch); + g_io_channel_unref(p->gui_channel); + + e_msgport_destroy(p->data_port); + e_msgport_destroy(p->reply_port); + + p->data_port = NULL; + p->reply_port = NULL; + + g_free(p->buf_data); + g_free(p); +} + +static ssize_t +stream_write (CamelStream *stream, const char *buffer, size_t n) +{ + EMSyncStream *emss = EM_SYNC_STREAM (stream); + struct _EMSyncStreamPrivate *p = emss->priv; + + if (emss->cancel) + return -1; + + if (pthread_self() == mail_gui_thread) + EMSS_CLASS(emss)->sync_write(stream, buffer, n); + else if (p->buf_size > 0) { + size_t left = p->buf_size-p->buf_used; + + if (n >= left) { + sync_op(emss, EMSS_WRITE, buffer, n); + } else { + memcpy(p->buf_data + p->buf_used, buffer, n); + p->buf_used += n; + } + } else { + sync_op(emss, EMSS_WRITE, buffer, n); + } + + return (ssize_t) n; +} + +static int +stream_flush(CamelStream *stream) +{ + EMSyncStream *emss = (EMSyncStream *)stream; + + if (emss->cancel) + return -1; + + if (pthread_self() == mail_gui_thread) + return ((EMSyncStreamClass *)(((CamelObject *)emss)->klass))->sync_flush(stream); + else + sync_op(emss, EMSS_FLUSH, NULL, 0); + + return 0; +} + +static int +stream_close(CamelStream *stream) +{ + EMSyncStream *emss = (EMSyncStream *)stream; + + if (emss->cancel) + return -1; + + d(printf("%p: closing stream\n", stream)); + + if (pthread_self() == mail_gui_thread) + return ((EMSyncStreamClass *)(((CamelObject *)emss)->klass))->sync_close(stream); + else + sync_op(emss, EMSS_CLOSE, NULL, 0); + + return 0; +} + +void +em_sync_stream_set_buffer_size(EMSyncStream *emss, size_t size) +{ + struct _EMSyncStreamPrivate *p = emss->priv; + + g_free(p->buf_data); + p->buf_data = g_malloc(size); + p->buf_size = size; + p->buf_used = 0; +} diff --git a/mail/em-sync-stream.h b/mail/em-sync-stream.h new file mode 100644 index 0000000000..abccae3311 --- /dev/null +++ b/mail/em-sync-stream.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * 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. + * + */ + +/* +EMSyncStream - Abstract class. +A synchronous stream, that can be written from any thread, but whose +requests are always handled in the main gui thread in the correct order. +*/ + +#ifndef EM_SYNC_STREAM_H +#define EM_SYNC_STREAM_H + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#define EM_SYNC_STREAM_TYPE (em_sync_stream_get_type ()) +#define EM_SYNC_STREAM(obj) (CAMEL_CHECK_CAST((obj), EM_SYNC_STREAM_TYPE, EMSyncStream)) +#define EM_SYNC_STREAM_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), EM_SYNC_STREAM_TYPE, EMSyncStreamClass)) +#define EM_IS_SYNC_STREAM(o) (CAMEL_CHECK_TYPE((o), EM_SYNC_STREAM_TYPE)) + +#include <glib.h> +#include <camel/camel-stream.h> + +typedef struct _EMSyncStream { + CamelStream parent_stream; + + struct _EMSyncStreamPrivate *priv; + + int cancel; +} EMSyncStream; + +typedef struct { + CamelStreamClass parent_class; + + ssize_t (*sync_write) (CamelStream *stream, const char *buffer, size_t n); + int (*sync_close) (CamelStream *stream); + int (*sync_flush) (CamelStream *stream); + +} EMSyncStreamClass; + +CamelType em_sync_stream_get_type (void); +void em_sync_stream_set_buffer_size(EMSyncStream *, size_t size); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* EM_SYNC_STREAM_H */ diff --git a/mail/em-utils.c b/mail/em-utils.c new file mode 100644 index 0000000000..07de855e0f --- /dev/null +++ b/mail/em-utils.c @@ -0,0 +1,2327 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <time.h> + +#include <camel/camel-stream-fs.h> +#include <camel/camel-url-scanner.h> + +#include <filter/filter-editor.h> + +#include "mail-mt.h" +#include "mail-ops.h" +#include "mail-tools.h" +#include "mail-config.h" +#include "mail-config-druid.h" +#include "message-tag-followup.h" + +#include <e-util/e-mktemp.h> +#include <e-util/e-dialog-utils.h> + +#include "em-utils.h" +#include "em-composer-utils.h" +#include "em-format-quote.h" + +static EAccount *guess_account (CamelMimeMessage *message); + +/** + * em_utils_prompt_user: + * @parent: parent window + * @def: default response + * @again: continue prompting the user in the future + * @fmt: prompt format + * @Varargs: varargs + * + * Convenience function to query the user with a Yes/No dialog and a + * "Don't show this dialog again" checkbox. If the user checks that + * checkbox, then @again is set to %FALSE, otherwise it is set to + * %TRUE. + * + * Returns %TRUE if the user clicks Yes or %FALSE otherwise. + **/ +gboolean +em_utils_prompt_user (GtkWindow *parent, int def, gboolean *again, const char *fmt, ...) +{ + GtkWidget *mbox, *check = NULL; + va_list ap; + int button; + char *str; + + va_start (ap, fmt); + str = g_strdup_vprintf (fmt, ap); + va_end (ap); + mbox = gtk_message_dialog_new (parent, GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, + "%s", str); + g_free (str); + gtk_dialog_set_default_response ((GtkDialog *) mbox, def); + if (again) { + check = gtk_check_button_new_with_label (_("Don't show this message again.")); + gtk_box_pack_start ((GtkBox *)((GtkDialog *) mbox)->vbox, check, TRUE, TRUE, 10); + gtk_widget_show (check); + } + + button = gtk_dialog_run ((GtkDialog *) mbox); + if (again) + *again = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check)); + gtk_widget_destroy (mbox); + + return button == GTK_RESPONSE_YES; +} + + +/** + * em_utils_uids_copy: + * @uids: array of uids + * + * Duplicates the array of uids held by @uids into a new + * GPtrArray. Use em_utils_uids_free() to free the resultant uid + * array. + * + * Returns a duplicate copy of @uids. + **/ +GPtrArray * +em_utils_uids_copy (GPtrArray *uids) +{ + GPtrArray *copy; + int i; + + copy = g_ptr_array_new (); + g_ptr_array_set_size (copy, uids->len); + + for (i = 0; i < uids->len; i++) + copy->pdata[i] = g_strdup (uids->pdata[i]); + + return copy; +} + +/** + * em_utils_uids_free: + * @uids: array of uids + * + * Frees the array of uids pointed to by @uids back to the system. + **/ +void +em_utils_uids_free (GPtrArray *uids) +{ + int i; + + for (i = 0; i < uids->len; i++) + g_free (uids->pdata[i]); + + g_ptr_array_free (uids, TRUE); +} + +static void +druid_destroy_cb (gpointer user_data, GObject *deadbeef) +{ + gtk_main_quit (); +} + +/** + * em_utils_configure_account: + * @parent: parent window for the druid to be a child of. + * + * Displays a druid allowing the user to configure an account. If + * @parent is non-NULL, then the druid will be created as a child + * window of @parent's toplevel window. + * + * Returns %TRUE if an account has been configured or %FALSE + * otherwise. + **/ +gboolean +em_utils_configure_account (GtkWidget *parent) +{ + MailConfigDruid *druid; + + druid = mail_config_druid_new (); + + if (parent != NULL) + e_dialog_set_transient_for ((GtkWindow *) druid, parent); + + g_object_weak_ref ((GObject *) druid, (GWeakNotify) druid_destroy_cb, NULL); + gtk_widget_show ((GtkWidget *) druid); + gtk_grab_add ((GtkWidget *) druid); + gtk_main (); + + return mail_config_is_configured (); +} + +/** + * em_utils_check_user_can_send_mail: + * @parent: parent window for the druid to be a child of. + * + * If no accounts have been configured, the user will be given a + * chance to configure an account. In the case that no accounts are + * configured, a druid will be created. If @parent is non-NULL, then + * the druid will be created as a child window of @parent's toplevel + * window. + * + * Returns %TRUE if the user has an account configured (to send mail) + * or %FALSE otherwise. + **/ +gboolean +em_utils_check_user_can_send_mail (GtkWidget *parent) +{ + EAccount *account; + + if (!mail_config_is_configured ()) { + if (!em_utils_configure_account (parent)) + return FALSE; + } + + if (!(account = mail_config_get_default_account ())) + return FALSE; + + /* Check for a transport */ + if (!account->transport->url) + return FALSE; + + return TRUE; +} + +/* Editing Filters/vFolders... */ + +static GtkWidget *filter_editor = NULL; + +static void +filter_editor_response (GtkWidget *dialog, int button, gpointer user_data) +{ + extern char *evolution_dir; + FilterContext *fc; + + if (button == GTK_RESPONSE_ACCEPT) { + char *user; + + fc = g_object_get_data ((GObject *) dialog, "context"); + user = g_strdup_printf ("%s/filters.xml", evolution_dir); + rule_context_save ((RuleContext *) fc, user); + g_free (user); + } + + gtk_widget_destroy (dialog); + + filter_editor = NULL; +} + +static const char *filter_source_names[] = { + "incoming", + "outgoing", + NULL, +}; + +/** + * em_utils_edit_filters: + * @parent: parent window + * + * Opens or raises the filters editor dialog so that the user may edit + * his/her filters. If @parent is non-NULL, then the dialog will be + * created as a child window of @parent's toplevel window. + **/ +void +em_utils_edit_filters (GtkWidget *parent) +{ + extern char *evolution_dir; + char *user, *system; + FilterContext *fc; + + if (filter_editor) { + gdk_window_raise (GTK_WIDGET (filter_editor)->window); + return; + } + + fc = filter_context_new (); + user = g_strdup_printf ("%s/filters.xml", evolution_dir); + system = EVOLUTION_PRIVDATADIR "/filtertypes.xml"; + rule_context_load ((RuleContext *) fc, system, user); + g_free (user); + + if (((RuleContext *) fc)->error) { + e_notice (parent, GTK_MESSAGE_ERROR, + _("Error loading filter information:\n%s"), + ((RuleContext *) fc)->error); + return; + } + + filter_editor = (GtkWidget *) filter_editor_new (fc, filter_source_names); + if (parent != NULL) + e_dialog_set_transient_for ((GtkWindow *) filter_editor, parent); + + gtk_window_set_title (GTK_WINDOW (filter_editor), _("Filters")); + g_object_set_data_full ((GObject *) filter_editor, "context", fc, (GtkDestroyNotify) g_object_unref); + g_signal_connect (filter_editor, "response", G_CALLBACK (filter_editor_response), NULL); + gtk_widget_show (GTK_WIDGET (filter_editor)); +} + +/* Composing messages... */ + +static EMsgComposer * +create_new_composer (GtkWidget *parent) +{ + EMsgComposer *composer; + + composer = e_msg_composer_new (); + + if (parent != NULL) + e_dialog_set_transient_for ((GtkWindow *) composer, parent); + + em_composer_utils_setup_default_callbacks (composer); + + return composer; +} + +/** + * em_utils_compose_new_message: + * @parent: parent window + * + * Opens a new composer window as a child window of @parent's toplevel + * window. + **/ +void +em_utils_compose_new_message (GtkWidget *parent) +{ + GtkWidget *composer; + + composer = (GtkWidget *) create_new_composer (parent); + + gtk_widget_show (composer); +} + +/** + * em_utils_compose_new_message_with_mailto: + * @parent: parent window + * @url: mailto url + * + * Opens a new composer window as a child window of @parent's toplevel + * window. If @url is non-NULL, the composer fields will be filled in + * according to the values in the mailto url. + **/ +void +em_utils_compose_new_message_with_mailto (GtkWidget *parent, const char *url) +{ + EMsgComposer *composer; + + if (url != NULL) + composer = e_msg_composer_new_from_url (url); + else + composer = e_msg_composer_new (); + + if (parent != NULL) + e_dialog_set_transient_for ((GtkWindow *) composer, parent); + + em_composer_utils_setup_default_callbacks (composer); + + gtk_widget_show ((GtkWidget *) composer); +} + +/** + * em_utils_post_to_url: + * @parent: parent window + * @url: mailto url + * + * Opens a new composer window as a child window of @parent's toplevel + * window. If @url is non-NULL, the composer will default to posting + * mail to the folder specified by @url. + **/ +void +em_utils_post_to_url (GtkWidget *parent, const char *url) +{ + EMsgComposer *composer; + + composer = e_msg_composer_new_post (); + + if (parent != NULL) + e_dialog_set_transient_for ((GtkWindow *) composer, parent); + + if (url != NULL) + e_msg_composer_hdrs_set_post_to ((EMsgComposerHdrs *) ((EMsgComposer *) composer)->hdrs, url); + + em_composer_utils_setup_default_callbacks (composer); + + e_msg_composer_unset_changed (composer); + e_msg_composer_drop_editor_undo (composer); + + gtk_widget_show ((GtkWidget *) composer); +} + +/* Editing messages... */ + +static void +edit_message (GtkWidget *parent, CamelMimeMessage *message, CamelFolder *drafts, const char *uid) +{ + EMsgComposer *composer; + + composer = e_msg_composer_new_with_message (message); + em_composer_utils_setup_callbacks (composer, NULL, NULL, 0, 0, drafts, uid); + e_msg_composer_unset_changed (composer); + e_msg_composer_drop_editor_undo (composer); + + gtk_widget_show (GTK_WIDGET (composer)); +} + +/** + * em_utils_edit_message: + * @parent: parent window + * @message: message to edit + * + * Opens a composer filled in with the headers/mime-parts/etc of + * @message. + **/ +void +em_utils_edit_message (GtkWidget *parent, CamelMimeMessage *message) +{ + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + edit_message (parent, message, NULL, NULL); +} + +static void +edit_messages (CamelFolder *folder, GPtrArray *uids, GPtrArray *msgs, void *user_data) +{ + int i; + + if (msgs == NULL) + return; + + for (i = 0; i < msgs->len; i++) { + camel_medium_remove_header (CAMEL_MEDIUM (msgs->pdata[i]), "X-Mailer"); + + edit_message ((GtkWidget *) user_data, msgs->pdata[i], folder, uids->pdata[i]); + } +} + +/** + * em_utils_edit_messages: + * @parent: parent window + * @folder: folder containing messages to edit + * @uids: uids of messages to edit + * + * Opens a composer for each message to be edited. + **/ +void +em_utils_edit_messages (GtkWidget *parent, CamelFolder *folder, GPtrArray *uids) +{ + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + g_return_if_fail (uids != NULL); + + mail_get_messages (folder, uids, edit_messages, parent); +} + +/* Forwarding messages... */ + +static void +forward_attached (CamelFolder *folder, GPtrArray *messages, CamelMimePart *part, char *subject, void *user_data) +{ + EMsgComposer *composer; + + if (part == NULL) + return; + + composer = create_new_composer ((GtkWidget *) user_data); + e_msg_composer_set_headers (composer, NULL, NULL, NULL, NULL, subject); + e_msg_composer_attach (composer, part); + + e_msg_composer_unset_changed (composer); + e_msg_composer_drop_editor_undo (composer); + + gtk_widget_show (GTK_WIDGET (composer)); +} + +/** + * em_utils_forward_attached: + * @parent: parent window + * @folder: folder containing messages to forward + * @uids: uids of messages to forward + * + * If there is more than a single message in @uids, a multipart/digest + * will be constructed and attached to a new composer window preset + * with the appropriate header defaults for forwarding the first + * message in the list. If only one message is to be forwarded, it is + * forwarded as a simple message/rfc822 attachment. + **/ +void +em_utils_forward_attached (GtkWidget *parent, CamelFolder *folder, GPtrArray *uids) +{ + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + g_return_if_fail (uids != NULL); + + mail_build_attachment (folder, uids, forward_attached, parent); +} + +static void +forward_non_attached (GtkWidget *parent, GPtrArray *messages, int style) +{ + CamelMimeMessage *message; + CamelDataWrapper *wrapper; + EMsgComposer *composer; + char *subject, *text; + int i; + guint32 flags; + + if (messages->len == 0) + return; + + flags = EM_FORMAT_QUOTE_HEADERS; + if (style == MAIL_CONFIG_FORWARD_QUOTED) + flags |= EM_FORMAT_QUOTE_CITE; + + for (i = 0; i < messages->len; i++) { + message = messages->pdata[i]; + subject = mail_tool_generate_forward_subject (message); + + text = em_utils_message_to_html(message, _("-------- Forwarded Message --------"), flags); + + if (text) { + composer = create_new_composer (parent); + e_msg_composer_set_headers (composer, NULL, NULL, NULL, NULL, subject); + e_msg_composer_set_body_text (composer, text); + + wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (message)); + if (CAMEL_IS_MULTIPART (wrapper)) + e_msg_composer_add_message_attachments (composer, message, FALSE); + + e_msg_composer_unset_changed (composer); + e_msg_composer_drop_editor_undo (composer); + + gtk_widget_show (GTK_WIDGET (composer)); + + g_free (text); + } + + g_free (subject); + } +} + +static void +forward_inline (CamelFolder *folder, GPtrArray *uids, GPtrArray *messages, void *user_data) +{ + forward_non_attached ((GtkWidget *) user_data, messages, MAIL_CONFIG_FORWARD_INLINE); +} + +/** + * em_utils_forward_inline: + * @parent: parent window + * @folder: folder containing messages to forward + * @uids: uids of messages to forward + * + * Forwards each message in the 'inline' form, each in its own composer window. + **/ +void +em_utils_forward_inline (GtkWidget *parent, CamelFolder *folder, GPtrArray *uids) +{ + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + g_return_if_fail (uids != NULL); + + mail_get_messages (folder, uids, forward_inline, parent); +} + +static void +forward_quoted (CamelFolder *folder, GPtrArray *uids, GPtrArray *messages, void *user_data) +{ + forward_non_attached ((GtkWidget *) user_data, messages, MAIL_CONFIG_FORWARD_QUOTED); +} + +/** + * em_utils_forward_quoted: + * @parent: parent window + * @folder: folder containing messages to forward + * @uids: uids of messages to forward + * + * Forwards each message in the 'quoted' form (each line starting with + * a "> "), each in its own composer window. + **/ +void +em_utils_forward_quoted (GtkWidget *parent, CamelFolder *folder, GPtrArray *uids) +{ + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + g_return_if_fail (uids != NULL); + + mail_get_messages (folder, uids, forward_quoted, parent); +} + +/** + * em_utils_forward_message: + * @parent: parent window + * @message: message to be forwarded + * + * Forwards a message in the user's configured default style. + **/ +void +em_utils_forward_message (GtkWidget *parent, CamelMimeMessage *message) +{ + GPtrArray *messages; + CamelMimePart *part; + GConfClient *gconf; + char *subject; + int mode; + + messages = g_ptr_array_new (); + g_ptr_array_add (messages, message); + + gconf = mail_config_get_gconf_client (); + mode = gconf_client_get_int (gconf, "/apps/evolution/mail/format/forward_style", NULL); + + switch (mode) { + case MAIL_CONFIG_FORWARD_ATTACHED: + default: + part = mail_tool_make_message_attachment (message); + + subject = mail_tool_generate_forward_subject (message); + + forward_attached (NULL, messages, part, subject, parent); + camel_object_unref (part); + g_free (subject); + break; + case MAIL_CONFIG_FORWARD_INLINE: + forward_non_attached(parent, messages, MAIL_CONFIG_FORWARD_INLINE); + break; + case MAIL_CONFIG_FORWARD_QUOTED: + forward_non_attached(parent, messages, MAIL_CONFIG_FORWARD_QUOTED); + break; + } + + g_ptr_array_free (messages, TRUE); +} + +/** + * em_utils_forward_messages: + * @parent: parent window + * @folder: folder containing messages to forward + * @uids: uids of messages to forward + * + * Forwards a group of messages in the user's configured default + * style. + **/ +void +em_utils_forward_messages (GtkWidget *parent, CamelFolder *folder, GPtrArray *uids) +{ + GConfClient *gconf; + int mode; + + gconf = mail_config_get_gconf_client (); + mode = gconf_client_get_int (gconf, "/apps/evolution/mail/format/forward_style", NULL); + + switch (mode) { + case MAIL_CONFIG_FORWARD_ATTACHED: + default: + em_utils_forward_attached (parent, folder, uids); + break; + case MAIL_CONFIG_FORWARD_INLINE: + em_utils_forward_inline (parent, folder, uids); + break; + case MAIL_CONFIG_FORWARD_QUOTED: + em_utils_forward_quoted (parent, folder, uids); + break; + } +} + +/* Redirecting messages... */ + +static EMsgComposer * +redirect_get_composer (GtkWidget *parent, CamelMimeMessage *message) +{ + EMsgComposer *composer; + EAccount *account; + + /* QMail will refuse to send a message if it finds one of + it's Delivered-To headers in the message, so remove all + Delivered-To headers. Fixes bug #23635. */ + while (camel_medium_get_header (CAMEL_MEDIUM (message), "Delivered-To")) + camel_medium_remove_header (CAMEL_MEDIUM (message), "Delivered-To"); + + account = guess_account (message); + + composer = e_msg_composer_new_redirect (message, account ? account->name : NULL); + + if (parent != NULL) + e_dialog_set_transient_for ((GtkWindow *) composer, parent); + + em_composer_utils_setup_default_callbacks (composer); + + return composer; +} + +/** + * em_utils_redirect_message: + * @parent: parent window + * @message: message to redirect + * + * Opens a composer to redirect @message (Note: only headers will be + * editable). Adds Resent-From/Resent-To/etc headers. + **/ +void +em_utils_redirect_message (GtkWidget *parent, CamelMimeMessage *message) +{ + EMsgComposer *composer; + CamelDataWrapper *wrapper; + + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + composer = redirect_get_composer (parent, message); + + wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (message)); + if (CAMEL_IS_MULTIPART (wrapper)) + e_msg_composer_add_message_attachments (composer, message, FALSE); + + gtk_widget_show (GTK_WIDGET (composer)); + e_msg_composer_unset_changed (composer); + e_msg_composer_drop_editor_undo (composer); +} + +static void +redirect_msg (CamelFolder *folder, const char *uid, CamelMimeMessage *message, void *user_data) +{ + if (message == NULL) + return; + + em_utils_redirect_message ((GtkWidget *) user_data, message); +} + +/** + * em_utils_redirect_message_by_uid: + * @parent: parent window + * @folder: folder containing message to be redirected + * @uid: uid of message to be redirected + * + * Opens a composer to redirect the message (Note: only headers will + * be editable). Adds Resent-From/Resent-To/etc headers. + **/ +void +em_utils_redirect_message_by_uid (GtkWidget *parent, CamelFolder *folder, const char *uid) +{ + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + g_return_if_fail (uid != NULL); + + mail_get_message (folder, uid, redirect_msg, parent, mail_thread_new); +} + +/* Replying to messages... */ + +static GHashTable * +generate_account_hash (void) +{ + GHashTable *account_hash; + EAccount *account, *def; + EAccountList *accounts; + EIterator *iter; + + accounts = mail_config_get_accounts (); + account_hash = g_hash_table_new (camel_strcase_hash, camel_strcase_equal); + + /* add the default account to the hash first */ + if ((def = mail_config_get_default_account ())) { + if (def->id->address) + g_hash_table_insert (account_hash, (char *) def->id->address, (void *) def); + } + + iter = e_list_get_iterator ((EList *) accounts); + while (e_iterator_is_valid (iter)) { + account = (EAccount *) e_iterator_get (iter); + + if (account->id->address) { + EAccount *acnt; + + /* Accounts with identical email addresses that are enabled + * take precedence over the accounts that aren't. If all + * accounts with matching email addresses are disabled, then + * the first one in the list takes precedence. The default + * account always takes precedence no matter what. + */ + acnt = g_hash_table_lookup (account_hash, account->id->address); + if (acnt && acnt != def && !acnt->enabled && account->enabled) { + g_hash_table_remove (account_hash, acnt->id->address); + acnt = NULL; + } + + if (!acnt) + g_hash_table_insert (account_hash, (char *) account->id->address, (void *) account); + } + + e_iterator_next (iter); + } + + g_object_unref (iter); + + return account_hash; +} + +static EDestination ** +em_utils_camel_address_to_destination (CamelInternetAddress *iaddr) +{ + EDestination *dest, **destv; + int n, i, j; + + if (iaddr == NULL) + return NULL; + + if ((n = camel_address_length ((CamelAddress *) iaddr)) == 0) + return NULL; + + destv = g_malloc (sizeof (EDestination *) * (n + 1)); + for (i = 0, j = 0; i < n; i++) { + const char *name, *addr; + + if (camel_internet_address_get (iaddr, i, &name, &addr)) { + dest = e_destination_new (); + e_destination_set_name (dest, name); + e_destination_set_email (dest, addr); + + destv[j++] = dest; + } + } + + if (j == 0) { + g_free (destv); + return NULL; + } + + destv[j] = NULL; + + return destv; +} + +static EMsgComposer * +reply_get_composer (GtkWidget *parent, CamelMimeMessage *message, EAccount *account, + CamelInternetAddress *to, CamelInternetAddress *cc) +{ + const char *message_id, *references; + EDestination **tov, **ccv; + EMsgComposer *composer; + char *subject; + + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); + g_return_val_if_fail (to == NULL || CAMEL_IS_INTERNET_ADDRESS (to), NULL); + g_return_val_if_fail (cc == NULL || CAMEL_IS_INTERNET_ADDRESS (cc), NULL); + + composer = e_msg_composer_new (); + + if (parent != NULL) + e_dialog_set_transient_for ((GtkWindow *) composer, parent); + + /* construct the tov/ccv */ + tov = em_utils_camel_address_to_destination (to); + ccv = em_utils_camel_address_to_destination (cc); + + /* Set the subject of the new message. */ + if ((subject = (char *) camel_mime_message_get_subject (message))) { + if (strncasecmp (subject, "Re: ", 4) != 0) + subject = g_strdup_printf ("Re: %s", subject); + else + subject = g_strdup (subject); + } else { + subject = g_strdup (""); + } + + e_msg_composer_set_headers (composer, account ? account->name : NULL, tov, ccv, NULL, subject); + + g_free (subject); + + /* Add In-Reply-To and References. */ + message_id = camel_medium_get_header (CAMEL_MEDIUM (message), "Message-Id"); + references = camel_medium_get_header (CAMEL_MEDIUM (message), "References"); + if (message_id) { + char *reply_refs; + + e_msg_composer_add_header (composer, "In-Reply-To", message_id); + + if (references) + reply_refs = g_strdup_printf ("%s %s", references, message_id); + else + reply_refs = g_strdup (message_id); + + e_msg_composer_add_header (composer, "References", reply_refs); + g_free (reply_refs); + } else if (references) { + e_msg_composer_add_header (composer, "References", references); + } + + e_msg_composer_drop_editor_undo (composer); + + return composer; +} + +static EAccount * +guess_account (CamelMimeMessage *message) +{ + const CamelInternetAddress *to, *cc; + GHashTable *account_hash; + EAccount *account = NULL; + const char *addr; + int i; + + to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO); + cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC); + + if (to == NULL && cc == NULL) + return NULL; + + account_hash = generate_account_hash (); + + if (to) { + for (i = 0; camel_internet_address_get (to, i, NULL, &addr); i++) { + account = g_hash_table_lookup (account_hash, addr); + if (account) + goto found; + } + } + + if (cc) { + for (i = 0; camel_internet_address_get (cc, i, NULL, &addr); i++) { + account = g_hash_table_lookup (account_hash, addr); + if (account) + goto found; + } + } + + found: + + g_hash_table_destroy (account_hash); + + return account; +} + +static void +get_reply_sender (CamelMimeMessage *message, CamelInternetAddress **to) +{ + const CamelInternetAddress *reply_to; + const char *name, *addr; + int i; + + reply_to = camel_mime_message_get_reply_to (message); + if (!reply_to) + reply_to = camel_mime_message_get_from (message); + + if (reply_to) { + *to = camel_internet_address_new (); + + for (i = 0; camel_internet_address_get (reply_to, i, &name, &addr); i++) + camel_internet_address_add (*to, name, addr); + } +} + +static gboolean +get_reply_list (CamelMimeMessage *message, CamelInternetAddress **to) +{ + const char *header, *p; + char *addr; + + /* Examples: + * + * List-Post: <mailto:list@host.com> + * List-Post: <mailto:moderator@host.com?subject=list%20posting> + * List-Post: NO (posting not allowed on this list) + */ + if (!(header = camel_medium_get_header ((CamelMedium *) message, "List-Post"))) + return FALSE; + + while (*header == ' ' || *header == '\t') + header++; + + /* check for NO */ + if (!strncasecmp (header, "NO", 2)) + return FALSE; + + /* Search for the first mailto angle-bracket enclosed URL. + * (See rfc2369, Section 2, paragraph 3 for details) */ + if (!(header = camel_strstrcase (header, "<mailto:"))) + return FALSE; + + header += 8; + + p = header; + while (*p && !strchr ("?>", *p)) + p++; + + addr = g_strndup (header, p - header); + + *to = camel_internet_address_new (); + camel_internet_address_add (*to, NULL, addr); + + g_free (addr); + + return TRUE; +} + +static void +concat_unique_addrs (CamelInternetAddress *dest, const CamelInternetAddress *src, GHashTable *rcpt_hash) +{ + const char *name, *addr; + int i; + + for (i = 0; camel_internet_address_get (src, i, &name, &addr); i++) { + if (!g_hash_table_lookup (rcpt_hash, addr)) { + camel_internet_address_add (dest, name, addr); + g_hash_table_insert (rcpt_hash, (char *) addr, GINT_TO_POINTER (1)); + } + } +} + +static void +get_reply_all (CamelMimeMessage *message, CamelInternetAddress **to, CamelInternetAddress **cc) +{ + const CamelInternetAddress *reply_to, *to_addrs, *cc_addrs; + const char *name, *addr; + GHashTable *rcpt_hash; + int i; + + rcpt_hash = generate_account_hash (); + + reply_to = camel_mime_message_get_reply_to (message); + if (!reply_to) + reply_to = camel_mime_message_get_from (message); + + to_addrs = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO); + cc_addrs = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC); + + *to = camel_internet_address_new (); + *cc = camel_internet_address_new (); + + if (reply_to) { + for (i = 0; camel_internet_address_get (reply_to, i, &name, &addr); i++) { + /* ignore references to the Reply-To address in the To and Cc lists */ + if (addr && !g_hash_table_lookup (rcpt_hash, addr)) { + /* In the case that we are doing a Reply-To-All, we do not want + to include the user's email address because replying to oneself + is kinda silly. */ + + camel_internet_address_add (*to, name, addr); + g_hash_table_insert (rcpt_hash, (char *) addr, GINT_TO_POINTER (1)); + } + } + } + + concat_unique_addrs (*cc, to_addrs, rcpt_hash); + concat_unique_addrs (*cc, cc_addrs, rcpt_hash); + + /* promote the first Cc: address to To: if To: is empty */ + if (camel_address_length ((CamelAddress *) *to) == 0 && camel_address_length ((CamelAddress *) *cc) > 0) { + camel_internet_address_get (*cc, 0, &name, &addr); + camel_internet_address_add (*to, name, addr); + camel_address_remove ((CamelAddress *) *cc, 0); + } + + g_hash_table_destroy (rcpt_hash); +} + +static void +composer_set_body (EMsgComposer *composer, CamelMimeMessage *message) +{ + const CamelInternetAddress *sender; + char *text, *credits, format[256]; + const char *name, *addr; + CamelMimePart *part; + GConfClient *gconf; + time_t date; + int date_offset; + + gconf = mail_config_get_gconf_client (); + + switch (gconf_client_get_int (gconf, "/apps/evolution/mail/format/reply_style", NULL)) { + case MAIL_CONFIG_REPLY_DO_NOT_QUOTE: + /* do nothing */ + break; + case MAIL_CONFIG_REPLY_ATTACH: + /* attach the original message as an attachment */ + part = mail_tool_make_message_attachment (message); + e_msg_composer_attach (composer, part); + camel_object_unref (part); + break; + case MAIL_CONFIG_REPLY_QUOTED: + default: + /* do what any sane user would want when replying... */ + sender = camel_mime_message_get_from (message); + if (sender != NULL && camel_address_length (CAMEL_ADDRESS (sender)) > 0) { + camel_internet_address_get (sender, 0, &name, &addr); + } else { + name = _("an unknown sender"); + } + + date = camel_mime_message_get_date(message, &date_offset); + /* Convert to UTC */ + date += (date_offset / 100) * 60 * 60; + date += (date_offset % 100) * 60; + + /* translators: attribution string used when quoting messages, + it must contain a single single %%+05d followed by a single '%%s' */ + e_utf8_strftime(format, sizeof(format), _("On %a, %Y-%m-%d at %H:%M %%+05d, %%s wrote:"), gmtime(&date)); + credits = g_strdup_printf(format, date_offset, name && *name ? name : addr); + text = em_utils_message_to_html(message, credits, EM_FORMAT_QUOTE_CITE); + g_free (credits); + e_msg_composer_set_body_text(composer, text); + g_free (text); + break; + } + + e_msg_composer_drop_editor_undo (composer); +} + +/** + * em_utils_reply_to_message: + * @parent: parent window + * @message: message to reply to + * @mode: reply mode + * + * Creates a new composer ready to reply to @message. + **/ +void +em_utils_reply_to_message (GtkWidget *parent, CamelMimeMessage *message, int mode) +{ + CamelInternetAddress *to = NULL, *cc = NULL; + EMsgComposer *composer; + EAccount *account; + + account = guess_account (message); + + switch (mode) { + case REPLY_MODE_SENDER: + get_reply_sender (message, &to); + break; + case REPLY_MODE_LIST: + if (get_reply_list (message, &to)) + break; + case REPLY_MODE_ALL: + get_reply_all (message, &to, &cc); + break; + } + + composer = reply_get_composer (parent, message, account, to, cc); + e_msg_composer_add_message_attachments (composer, message, TRUE); + + if (to != NULL) + camel_object_unref (to); + + if (cc != NULL) + camel_object_unref (cc); + + composer_set_body (composer, message); + + em_composer_utils_setup_default_callbacks (composer); + + gtk_widget_show (GTK_WIDGET (composer)); + e_msg_composer_unset_changed (composer); +} + +struct rtm_t { + GtkWidget *parent; + int mode; +}; + +static void +reply_to_message (CamelFolder *folder, const char *uid, CamelMimeMessage *message, void *user_data) +{ + CamelInternetAddress *to = NULL, *cc = NULL; + struct rtm_t *rtm = user_data; + EMsgComposer *composer; + EAccount *account; + guint32 flags; + + account = guess_account (message); + flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN; + + switch (rtm->mode) { + case REPLY_MODE_SENDER: + get_reply_sender (message, &to); + break; + case REPLY_MODE_LIST: + flags |= CAMEL_MESSAGE_ANSWERED_ALL; + if (get_reply_list (message, &to)) + break; + case REPLY_MODE_ALL: + flags |= CAMEL_MESSAGE_ANSWERED_ALL; + get_reply_all (message, &to, &cc); + break; + } + + composer = reply_get_composer (rtm->parent, message, account, to, cc); + e_msg_composer_add_message_attachments (composer, message, TRUE); + + if (to != NULL) + camel_object_unref (to); + + if (cc != NULL) + camel_object_unref (cc); + + composer_set_body (composer, message); + + em_composer_utils_setup_callbacks (composer, folder, uid, flags, flags, NULL, NULL); + + gtk_widget_show (GTK_WIDGET (composer)); + e_msg_composer_unset_changed (composer); +} + +/** + * em_utils_reply_to_message_by_uid: + * @parent: parent window + * @folder: folder containing message to reply to + * @uid: message uid + * @mode: reply mode + * + * Creates a new composer ready to reply to the message referenced by + * @folder and @uid. + **/ +void +em_utils_reply_to_message_by_uid (GtkWidget *parent, CamelFolder *folder, const char *uid, int mode) +{ + struct rtm_t *rtm; + + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + g_return_if_fail (uid != NULL); + + rtm = g_malloc (sizeof (struct rtm_t)); + rtm->parent = parent; + rtm->mode = mode; + + mail_get_message (folder, uid, reply_to_message, rtm, mail_thread_new); +} + +/* Posting replies... */ + +static void +post_reply_to_message (CamelFolder *folder, const char *uid, CamelMimeMessage *message, void *user_data) +{ + /* FIXME: would be nice if this shared more code with reply_get_composer() */ + const char *message_id, *references; + CamelInternetAddress *to = NULL; + GtkWidget *parent = user_data; + EDestination **tov = NULL; + EMsgComposer *composer; + char *subject, *url; + EAccount *account; + guint32 flags; + + account = guess_account (message); + flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN; + + get_reply_sender (message, &to); + + composer = e_msg_composer_new_post (); + + if (parent != NULL) + e_dialog_set_transient_for ((GtkWindow *) composer, parent); + + /* construct the tov/ccv */ + tov = em_utils_camel_address_to_destination (to); + + /* Set the subject of the new message. */ + if ((subject = (char *) camel_mime_message_get_subject (message))) { + if (strncasecmp (subject, "Re: ", 4) != 0) + subject = g_strdup_printf ("Re: %s", subject); + else + subject = g_strdup (subject); + } else { + subject = g_strdup (""); + } + + e_msg_composer_set_headers (composer, account ? account->name : NULL, tov, NULL, NULL, subject); + + g_free (subject); + + url = mail_tools_folder_to_url (folder); + e_msg_composer_hdrs_set_post_to ((EMsgComposerHdrs *) composer->hdrs, url); + g_free (url); + + /* Add In-Reply-To and References. */ + message_id = camel_medium_get_header (CAMEL_MEDIUM (message), "Message-Id"); + references = camel_medium_get_header (CAMEL_MEDIUM (message), "References"); + if (message_id) { + char *reply_refs; + + e_msg_composer_add_header (composer, "In-Reply-To", message_id); + + if (references) + reply_refs = g_strdup_printf ("%s %s", references, message_id); + else + reply_refs = g_strdup (message_id); + + e_msg_composer_add_header (composer, "References", reply_refs); + g_free (reply_refs); + } else if (references) { + e_msg_composer_add_header (composer, "References", references); + } + + e_msg_composer_drop_editor_undo (composer); + + e_msg_composer_add_message_attachments (composer, message, TRUE); + + if (to != NULL) + camel_object_unref (to); + + composer_set_body (composer, message); + + em_composer_utils_setup_callbacks (composer, folder, uid, flags, flags, NULL, NULL); + + gtk_widget_show (GTK_WIDGET (composer)); + e_msg_composer_unset_changed (composer); +} + +/** + * em_utils_post_reply_to_message_by_uid: + * @parent: parent window + * @folder: folder containing message to reply to + * @uid: message uid + * @mode: reply mode + * + * Creates a new composer (post mode) ready to reply to the message + * referenced by @folder and @uid. + **/ +void +em_utils_post_reply_to_message_by_uid (GtkWidget *parent, CamelFolder *folder, const char *uid) +{ + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + g_return_if_fail (uid != NULL); + + mail_get_message (folder, uid, post_reply_to_message, parent, mail_thread_new); +} + +/* Saving messages... */ + +static GtkFileSelection * +emu_get_save_filesel(GtkWidget *parent, const char *title, const char *name) +{ + GtkFileSelection *filesel; + char *gdir, *mname = NULL, *filename; + const char *realname, *dir; + GConfClient *gconf; + + filesel = (GtkFileSelection *)gtk_file_selection_new(title); + if (parent) + e_dialog_set_transient_for((GtkWindow *)filesel, parent); + + gconf = gconf_client_get_default(); + dir = gdir = gconf_client_get_string(gconf, "/apps/evolution/mail/save_dir", NULL); + g_object_unref(gconf); + if (dir == NULL) + dir = g_get_home_dir(); + + if (name && name[0]) { + realname = mname = g_strdup(name); + e_filename_make_safe(mname); + } else { + realname = "/"; + } + + filename = g_build_filename(dir, realname, NULL); + gtk_file_selection_set_filename(filesel, filename); + g_free(filename); + g_free(mname); + g_free (gdir); + + return filesel; +} + +static void +emu_update_save_path(const char *filename) +{ + char *dir = g_path_get_dirname(filename); + GConfClient *gconf = gconf_client_get_default(); + + gconf_client_set_string(gconf, "/apps/evolution/mail/save_dir", dir, NULL); + g_object_unref(gconf); + g_free(dir); +} + +static gboolean +emu_can_save(GtkWindow *parent, const char *path) +{ + struct stat st; + + if (path[0] == 0) + return FALSE; + + /* make sure we can actually save to it... */ + if (stat (path, &st) != -1 && !S_ISREG (st.st_mode)) + return FALSE; + + if (access (path, F_OK) == 0) { + if (access (path, W_OK) != 0) { + e_notice (parent, GTK_MESSAGE_ERROR, + _("Cannot save to `%s'\n %s"), path, g_strerror (errno)); + return FALSE; + } + + return em_utils_prompt_user (parent, GTK_RESPONSE_NO, NULL, + _("`%s' already exists.\nOverwrite it?"), path); + } + + return TRUE; +} + +static void +emu_save_part_response(GtkFileSelection *filesel, int response, CamelMimePart *part) +{ + if (response == GTK_RESPONSE_OK) { + const char *path = gtk_file_selection_get_filename(filesel); + + if (!emu_can_save((GtkWindow *)filesel, path)) + return; + + emu_update_save_path(path); + /* FIXME: popup error if it fails? */ + mail_save_part(part, path, NULL, NULL); + } + + gtk_widget_destroy((GtkWidget *)filesel); + camel_object_unref(part); +} + +/** + * em_utils_save_part: + * @parent: parent window + * @prompt: prompt string + * @part: part to save + * + * Saves a mime part to disk (prompting the user for filename). + **/ +void +em_utils_save_part(GtkWidget *parent, const char *prompt, CamelMimePart *part) +{ + const char *name; + GtkFileSelection *filesel; + + name = camel_mime_part_get_filename(part); + if (name == NULL) { + if (CAMEL_IS_MIME_MESSAGE(part)) { + name = camel_mime_message_get_subject((CamelMimeMessage *)part); + if (name == NULL) + name = _("message"); + } else { + name = _("attachment"); + } + } + + filesel = emu_get_save_filesel(parent, prompt, name); + camel_object_ref(part); + g_signal_connect(filesel, "response", G_CALLBACK(emu_save_part_response), part); + gtk_widget_show((GtkWidget *)filesel); +} + + +struct _save_messages_data { + CamelFolder *folder; + GPtrArray *uids; +}; + +static void +emu_save_messages_response(GtkFileSelection *filesel, int response, struct _save_messages_data *data) +{ + if (response == GTK_RESPONSE_OK) { + const char *path = gtk_file_selection_get_filename(filesel); + + if (!emu_can_save((GtkWindow *)filesel, path)) + return; + + emu_update_save_path(path); + mail_save_messages(data->folder, data->uids, path, NULL, NULL); + data->uids = NULL; + } + + camel_object_unref(data->folder); + if (data->uids) + em_utils_uids_free(data->uids); + g_free(data); + gtk_widget_destroy((GtkWidget *)filesel); +} + +/** + * em_utils_save_messages: + * @parent: parent window + * @folder: folder containing messages to save + * @uids: uids of messages to save + * + * Saves a group of messages to disk in mbox format (prompting the + * user for filename). + **/ +void +em_utils_save_messages (GtkWidget *parent, CamelFolder *folder, GPtrArray *uids) +{ + struct _save_messages_data *data; + GtkFileSelection *filesel; + + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + g_return_if_fail (uids != NULL); + + filesel = emu_get_save_filesel(parent, _("Save Message..."), NULL); + camel_object_ref(folder); + + data = g_malloc(sizeof(struct _save_messages_data)); + data->folder = folder; + data->uids = uids; + + g_signal_connect(filesel, "response", G_CALLBACK(emu_save_messages_response), data); + gtk_widget_show((GtkWidget *)filesel); +} + +/* Flag-for-Followup... */ + +/* tag-editor callback data */ +struct ted_t { + MessageTagEditor *editor; + CamelFolder *folder; + GPtrArray *uids; +}; + +static void +ted_free (struct ted_t *ted) +{ + camel_object_unref (ted->folder); + em_utils_uids_free (ted->uids); + g_free (ted); +} + +static void +tag_editor_response (GtkWidget *dialog, int button, struct ted_t *ted) +{ + CamelFolder *folder; + CamelTag *tags, *t; + GPtrArray *uids; + int i; + + if (button == GTK_RESPONSE_OK && (tags = message_tag_editor_get_tag_list (ted->editor))) { + folder = ted->folder; + uids = ted->uids; + + camel_folder_freeze (folder); + for (i = 0; i < uids->len; i++) { + for (t = tags; t; t = t->next) + camel_folder_set_message_user_tag (folder, uids->pdata[i], t->name, t->value); + } + + camel_folder_thaw (folder); + camel_tag_list_free (&tags); + } + + gtk_widget_destroy (dialog); +} + +/** + * em_utils_flag_for_followup: + * @parent: parent window + * @folder: folder containing messages to flag + * @uids: uids of messages to flag + * + * Open the Flag-for-Followup editor for the messages specified by + * @folder and @uids. + **/ +void +em_utils_flag_for_followup (GtkWidget *parent, CamelFolder *folder, GPtrArray *uids) +{ + GtkWidget *editor; + struct ted_t *ted; + int i; + + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + g_return_if_fail (uids != NULL); + + editor = (GtkWidget *) message_tag_followup_new (); + + if (parent != NULL) + e_dialog_set_transient_for ((GtkWindow *) editor, parent); + + camel_object_ref (folder); + + ted = g_new (struct ted_t, 1); + ted->editor = MESSAGE_TAG_EDITOR (editor); + ted->folder = folder; + ted->uids = uids; + + for (i = 0; i < uids->len; i++) { + CamelMessageInfo *info; + + info = camel_folder_get_message_info (folder, uids->pdata[i]); + message_tag_followup_append_message (MESSAGE_TAG_FOLLOWUP (editor), + camel_message_info_from (info), + camel_message_info_subject (info)); + } + + /* special-case... */ + if (uids->len == 1) { + CamelMessageInfo *info; + + info = camel_folder_get_message_info (folder, uids->pdata[0]); + if (info) { + if (info->user_tags) + message_tag_editor_set_tag_list (MESSAGE_TAG_EDITOR (editor), info->user_tags); + camel_folder_free_message_info (folder, info); + } + } + + g_signal_connect (editor, "response", G_CALLBACK (tag_editor_response), ted); + g_object_weak_ref ((GObject *) editor, (GWeakNotify) ted_free, ted); + + gtk_widget_show (editor); +} + +/** + * em_utils_flag_for_followup_clear: + * @parent: parent window + * @folder: folder containing messages to unflag + * @uids: uids of messages to unflag + * + * Clears the Flag-for-Followup flag on the messages referenced by + * @folder and @uids. + **/ +void +em_utils_flag_for_followup_clear (GtkWidget *parent, CamelFolder *folder, GPtrArray *uids) +{ + int i; + + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + g_return_if_fail (uids != NULL); + + camel_folder_freeze (folder); + for (i = 0; i < uids->len; i++) { + camel_folder_set_message_user_tag (folder, uids->pdata[i], "follow-up", ""); + camel_folder_set_message_user_tag (folder, uids->pdata[i], "due-by", ""); + camel_folder_set_message_user_tag (folder, uids->pdata[i], "completed-on", ""); + } + camel_folder_thaw (folder); + + em_utils_uids_free (uids); +} + +/** + * em_utils_flag_for_followup_completed: + * @parent: parent window + * @folder: folder containing messages to 'complete' + * @uids: uids of messages to 'complete' + * + * Sets the completed state (and date/time) for each message + * referenced by @folder and @uids that is marked for + * Flag-for-Followup. + **/ +void +em_utils_flag_for_followup_completed (GtkWidget *parent, CamelFolder *folder, GPtrArray *uids) +{ + char *now; + int i; + + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + g_return_if_fail (uids != NULL); + + now = header_format_date (time (NULL), 0); + + camel_folder_freeze (folder); + for (i = 0; i < uids->len; i++) { + const char *tag; + + tag = camel_folder_get_message_user_tag (folder, uids->pdata[i], "follow-up"); + if (tag == NULL || *tag == '\0') + continue; + + camel_folder_set_message_user_tag (folder, uids->pdata[i], "completed-on", now); + } + camel_folder_thaw (folder); + + g_free (now); + + em_utils_uids_free (uids); +} + +#include "camel/camel-stream-mem.h" +#include "camel/camel-stream-filter.h" +#include "camel/camel-mime-filter-from.h" + +/* This kind of sucks, because for various reasons most callers need to run synchronously + in the gui thread, however this could take a long, blocking time, to run */ +static int +em_utils_write_messages_to_stream(CamelFolder *folder, GPtrArray *uids, CamelStream *stream) +{ + CamelStreamFilter *filtered_stream; + CamelMimeFilterFrom *from_filter; + int i, res = 0; + + from_filter = camel_mime_filter_from_new(); + filtered_stream = camel_stream_filter_new_with_stream(stream); + camel_stream_filter_add(filtered_stream, (CamelMimeFilter *)from_filter); + camel_object_unref(from_filter); + + for (i=0; i<uids->len; i++) { + CamelMimeMessage *message; + char *from; + + message = camel_folder_get_message(folder, uids->pdata[i], NULL); + if (message == NULL) { + res = -1; + break; + } + + /* we need to flush after each stream write since we are writing to the same stream */ + from = camel_mime_message_build_mbox_from(message); + + if (camel_stream_write_string(stream, from) == -1 + || camel_stream_flush(stream) == -1 + || camel_data_wrapper_write_to_stream((CamelDataWrapper *)message, (CamelStream *)filtered_stream) == -1 + || camel_stream_flush((CamelStream *)filtered_stream) == -1) + res = -1; + + g_free(from); + camel_object_unref(message); + + if (res == -1) + break; + } + + camel_object_unref(filtered_stream); + + return res; +} + +/* This kind of sucks, because for various reasons most callers need to run synchronously + in the gui thread, however this could take a long, blocking time, to run */ +static int +em_utils_read_messages_from_stream(CamelFolder *folder, CamelStream *stream) +{ + CamelException *ex = camel_exception_new(); + CamelMimeParser *mp = camel_mime_parser_new(); + int res = -1; + + camel_mime_parser_scan_from(mp, TRUE); + camel_mime_parser_init_with_stream(mp, stream); + camel_object_unref(stream); + + while (camel_mime_parser_step(mp, 0, 0) == HSCAN_FROM) { + CamelMimeMessage *msg; + + /* NB: de-from filter, once written */ + msg = camel_mime_message_new(); + if (camel_mime_part_construct_from_parser((CamelMimePart *)msg, mp) == -1) { + camel_object_unref(msg); + break; + } + + camel_folder_append_message(folder, msg, NULL, NULL, ex); + camel_object_unref(msg); + + if (camel_exception_is_set (ex)) + break; + + camel_mime_parser_step(mp, 0, 0); + } + + camel_object_unref(mp); + if (!camel_exception_is_set(ex)) + res = 0; + camel_exception_free(ex); + + return res; +} + +/** + * em_utils_selection_set_mailbox: + * @data: selection data + * @folder: folder containign messages to copy into the selection + * @uids: uids of the messages to copy into the selection + * + * Creates a mailbox-format selection. + * Warning: Could be BIG! + * Warning: This could block the ui for an extended period. + **/ +void +em_utils_selection_set_mailbox(GtkSelectionData *data, CamelFolder *folder, GPtrArray *uids) +{ + CamelStream *stream; + + stream = camel_stream_mem_new(); + if (em_utils_write_messages_to_stream(folder, uids, stream) == 0) + gtk_selection_data_set(data, data->target, 8, + ((CamelStreamMem *)stream)->buffer->data, + ((CamelStreamMem *)stream)->buffer->len); + + camel_object_unref(stream); +} + +/** + * em_utils_selection_get_mailbox: + * @data: selection data + * @folder: + * + * Receive a mailbox selection/dnd + * Warning: Could be BIG! + * Warning: This could block the ui for an extended period. + * FIXME: Exceptions? + **/ +void +em_utils_selection_get_mailbox(GtkSelectionData *data, CamelFolder *folder) +{ + CamelStream *stream; + + if (data->data == NULL || data->length == -1) + return; + + /* TODO: a stream mem with read-only access to existing data? */ + /* NB: Although copying would let us run this async ... which it should */ + stream = camel_stream_mem_new_with_buffer(data->data, data->length); + em_utils_read_messages_from_stream(folder, stream); + camel_object_unref(stream); +} + +/** + * em_utils_selection_set_uidlist: + * @data: selection data + * @uri: + * @uids: + * + * Sets a "x-evolution-message" format selection data. + * + * FIXME: be nice if this could take a folder argument rather than uri + **/ +void +em_utils_selection_set_uidlist(GtkSelectionData *data, const char *uri, GPtrArray *uids) +{ + GByteArray *array = g_byte_array_new(); + int i; + + /* format: "uri\0uid1\0uid2\0uid3\0...\0uidn\0" */ + /* NB: original form missed trailing \0 */ + + g_byte_array_append(array, uri, strlen(uri)+1); + + for (i=0; i<uids->len; i++) + g_byte_array_append(array, uids->pdata[i], strlen(uids->pdata[i])+1); + + gtk_selection_data_set(data, data->target, 8, array->data, array->len); + g_byte_array_free(array, TRUE); +} + +/** + * em_utils_selection_get_uidlist: + * @data: selection data + * @urip: Pointer to uri string, to be free'd by caller + * @uidsp: Pointer to an array of uid's. + * + * Convert an x-evolution-message type to a uri and a uid list. + * + * Return value: The number of uid's found. If 0, then @urip and + * @uidsp will be empty. + **/ +int +em_utils_selection_get_uidlist(GtkSelectionData *data, char **urip, GPtrArray **uidsp) +{ + /* format: "uri\0uid1\0uid2\0uid3\0...\0uidn" */ + char *inptr, *inend; + GPtrArray *uids; + int res; + + *urip = NULL; + *uidsp = NULL; + + if (data == NULL || data->data == NULL || data->length == -1) + return 0; + + uids = g_ptr_array_new(); + + inptr = data->data; + inend = data->data + data->length; + while (inptr < inend) { + char *start = inptr; + + while (inptr < inend && *inptr) + inptr++; + + if (start > (char *)data->data) + g_ptr_array_add(uids, g_strndup(start, inptr-start)); + + inptr++; + } + + if (uids->len == 0) { + g_ptr_array_free(uids, TRUE); + res = 0; + } else { + *urip = g_strdup(data->data); + *uidsp = uids; + res = uids->len; + } + + return res; +} + +/** + * em_utils_selection_set_urilist: + * @data: + * @folder: + * @uids: + * + * Set the selection data @data to a uri which points to a file, which is + * a berkely mailbox format mailbox. The file is automatically cleaned + * up when the application quits. + **/ +void +em_utils_selection_set_urilist(GtkSelectionData *data, CamelFolder *folder, GPtrArray *uids) +{ + const char *tmpdir; + CamelStream *fstream; + char *uri; + int fd; + + tmpdir = e_mkdtemp("drag-n-drop-XXXXXX"); + if (tmpdir == NULL) + return; + + /* FIXME: this used to save a single message with the subject + as the filename but it was unsafe, and makes this messier, + the pain */ + + uri = alloca(strlen(tmpdir)+16); + sprintf(uri, "file:///%s/mbox", tmpdir); + + fd = open(uri + 7, O_WRONLY | O_CREAT | O_EXCL, 0666); + if (fd == -1) + return; + + fstream = camel_stream_fs_new_with_fd(fd); + if (fstream) { + if (em_utils_write_messages_to_stream(folder, uids, fstream) == 0) + gtk_selection_data_set(data, data->target, 8, uri, strlen(uri)); + + camel_object_unref(fstream); + } +} + +static void +emu_save_part_done(CamelMimePart *part, char *name, int done, void *data) +{ + ((int *)data)[0] = done; +} + +/** + * em_utils_temp_save_part: + * @parent: + * @part: + * + * Save a part's content to a temporary file, and return the + * filename. + * + * Return value: NULL if anything failed. + **/ +char * +em_utils_temp_save_part(GtkWidget *parent, CamelMimePart *part) +{ + const char *tmpdir, *filename; + char *path, *mfilename = NULL; + int done; + + tmpdir = e_mkdtemp("evolution-tmp-XXXXXX"); + if (tmpdir == NULL) { + e_notice(parent, GTK_MESSAGE_ERROR, + _("Could not create temporary directory: %s"), + g_strerror (errno)); + + return NULL; + } + + filename = camel_mime_part_get_filename (part); + if (filename == NULL) { + /* This is the default filename used for temporary file creation */ + filename = _("Unknown"); + } else { + mfilename = g_strdup(filename); + e_filename_make_safe(mfilename); + filename = mfilename; + } + + path = g_build_filename(tmpdir, filename, NULL); + g_free(mfilename); + + /* FIXME: This doesn't handle default charsets */ + mail_msg_wait(mail_save_part(part, path, emu_save_part_done, &done)); + + if (!done) { + /* mail_save_part should popup an error box automagically */ + g_free(path); + path = NULL; + } + + return path; +} + +extern CamelFolder *drafts_folder, *sent_folder, *outbox_folder; + +/** + * em_utils_folder_is_drafts: + * @folder: folder + * @uri: uri for this folder, if known + * + * Decides if @folder is a Drafts folder. + * + * Returns %TRUE if this is a Drafts folder or %FALSE otherwise. + **/ +gboolean +em_utils_folder_is_drafts(CamelFolder *folder, const char *uri) +{ + EAccountList *accounts; + EAccount *account; + EIterator *iter; + int is = FALSE; + + if (folder == drafts_folder) + return TRUE; + + if (uri == NULL) + return FALSE; + + accounts = mail_config_get_accounts(); + iter = e_list_get_iterator((EList *)accounts); + while (e_iterator_is_valid(iter)) { + account = (EAccount *)e_iterator_get(iter); + if (account->drafts_folder_uri && + camel_store_uri_cmp(folder->parent_store, account->drafts_folder_uri, uri)) { + is = TRUE; + break; + } + + e_iterator_next(iter); + } + + g_object_unref(iter); + + return is; +} + +/** + * em_utils_folder_is_sent: + * @folder: folder + * @uri: uri for this folder, if known + * + * Decides if @folder is a Sent folder + * + * Returns %TRUE if this is a Sent folder or %FALSE otherwise. + **/ +gboolean +em_utils_folder_is_sent(CamelFolder *folder, const char *uri) +{ + EAccountList *accounts; + EAccount *account; + EIterator *iter; + int is = FALSE; + + if (folder == sent_folder) + return TRUE; + + if (uri == NULL) + return FALSE; + + accounts = mail_config_get_accounts(); + iter = e_list_get_iterator((EList *)accounts); + while (e_iterator_is_valid(iter)) { + account = (EAccount *)e_iterator_get(iter); + if (account->sent_folder_uri && + camel_store_uri_cmp(folder->parent_store, account->sent_folder_uri, uri)) { + is = TRUE; + break; + } + + e_iterator_next(iter); + } + + g_object_unref(iter); + + return is; +} + +/** + * em_utils_folder_is_outbox: + * @folder: folder + * @uri: uri for this folder, if known + * + * Decides if @folder is an Outbox folder + * + * Returns %TRUE if this is an Outbox folder or %FALSE otherwise. + **/ +gboolean +em_utils_folder_is_outbox(CamelFolder *folder, const char *uri) +{ + /* <Highlander>There can be only one.</Highlander> */ + return folder == outbox_folder; +} + +/** + * em_utils_adjustment_page: + * @adj: + * @down: + * + * Move an adjustment up/down forward/back one page. + **/ +void +em_utils_adjustment_page(GtkAdjustment *adj, gboolean down) +{ + float page_size = adj->page_size - adj->step_increment; + + if (down) { + if (adj->value < adj->upper - adj->page_size - page_size) + adj->value += page_size; + else if (adj->upper >= adj->page_size) + adj->value = adj->upper - adj->page_size; + else + adj->value = adj->lower; + } else { + if (adj->value > adj->lower + page_size) + adj->value -= page_size; + else + adj->value = adj->lower; + } + + gtk_adjustment_value_changed(adj); +} + +/* ********************************************************************** */ +static char *emu_proxy_uri; + +static void +emu_set_proxy(GConfClient *client) +{ + char *server; + int port; + + if (!gconf_client_get_bool(client, "/system/http_proxy/use_http_proxy", NULL)) { + g_free(emu_proxy_uri); + emu_proxy_uri = NULL; + + return; + } + + /* TODO: Should lock ... */ + + server = gconf_client_get_string(client, "/system/http_proxy/host", NULL); + port = gconf_client_get_int(client, "/system/http_proxy/port", NULL); + + if (server && server[0]) { + g_free(emu_proxy_uri); + + if (gconf_client_get_bool(client, "/system/http_proxy/use_authentication", NULL)) { + char *user = gconf_client_get_string(client, "/system/http_proxy/authentication_user", NULL); + char *pass = gconf_client_get_string(client, "/system/http_proxy/authentication_password", NULL); + + emu_proxy_uri = g_strdup_printf("http://%s:%s@%s:%d", user, pass, server, port); + g_free(user); + g_free(pass); + } else { + emu_proxy_uri = g_strdup_printf("http://%s:%d", server, port); + } + } + + g_free(server); +} + +static void +emu_proxy_changed(GConfClient *client, guint32 cnxn_id, GConfEntry *entry, gpointer user_data) +{ + emu_set_proxy(client); +} + +/** + * em_utils_get_proxy_uri: + * + * Get the system proxy uri. + * + * Return value: Must be freed when finished with. + **/ +char * +em_utils_get_proxy_uri(void) +{ + static int init; + + if (!init) { + GConfClient *client = gconf_client_get_default(); + + gconf_client_add_dir(client, "/system/http_proxy", GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); + gconf_client_notify_add(client, "/system/http_proxy", emu_proxy_changed, NULL, NULL, NULL); + emu_set_proxy(client); + g_object_unref(client); + init = TRUE; + } + + return g_strdup(emu_proxy_uri); +} + +/** + * em_utils_part_to_html: + * @part: + * + * Converts a mime part's contents into html text. If @credits is given, + * then it will be used as an attribution string, and the + * content will be cited. Otherwise no citation or attribution + * will be performed. + * + * Return Value: The part in displayable html format. + **/ +char * +em_utils_part_to_html(CamelMimePart *part) +{ + EMFormatQuote *emfq; + CamelStreamMem *mem; + GByteArray *buf; + char *text; + + buf = g_byte_array_new (); + mem = (CamelStreamMem *) camel_stream_mem_new (); + camel_stream_mem_set_byte_array (mem, buf); + + emfq = em_format_quote_new(NULL, (CamelStream *)mem, 0); + em_format_part((EMFormat *) emfq, (CamelStream *) mem, part); + g_object_unref (emfq); + + camel_stream_write ((CamelStream *) mem, "", 1); + camel_object_unref (mem); + + text = buf->data; + g_byte_array_free (buf, FALSE); + + return text; +} + +/** + * em_utils_message_to_html: + * @message: + * @credits: + * @flags: EMFormatQuote flags + * + * Convert a message to html, quoting if the @credits attribution + * string is given. + * + * Return value: The html version. + **/ +char * +em_utils_message_to_html(CamelMimeMessage *message, const char *credits, guint32 flags) +{ + EMFormatQuote *emfq; + CamelStreamMem *mem; + GByteArray *buf; + char *text; + + buf = g_byte_array_new (); + mem = (CamelStreamMem *) camel_stream_mem_new (); + camel_stream_mem_set_byte_array (mem, buf); + + emfq = em_format_quote_new(credits, (CamelStream *)mem, flags); + em_format_format((EMFormat *)emfq, (CamelMedium *)message); + g_object_unref (emfq); + + camel_stream_write ((CamelStream *) mem, "", 1); + camel_object_unref (mem); + + text = buf->data; + g_byte_array_free (buf, FALSE); + + return text; +} + +/* ********************************************************************** */ + +static gboolean +emu_confirm_expunge (GtkWidget *parent) +{ + gboolean res, show_again; + GConfClient *gconf; + + gconf = mail_config_get_gconf_client (); + + if (!gconf_client_get_bool (gconf, "/apps/evolution/mail/prompts/expunge", NULL)) + return TRUE; + + /* FIXME: we need to get the parent GtkWindow from @parent... */ + + res = em_utils_prompt_user (NULL, GTK_RESPONSE_NO, &show_again, + _("This operation will permanently erase all messages marked as\n" + "deleted. If you continue, you will not be able to recover these messages.\n" + "\nReally erase these messages?")); + + gconf_client_set_bool (gconf, "/apps/evolution/mail/prompts/expunge", show_again, NULL); + + return res; +} + +/** + * em_utils_expunge_folder: + * @parent: parent window + * @folder: folder to expunge + * + * Expunges @folder. + **/ +void +em_utils_expunge_folder (GtkWidget *parent, CamelFolder *folder) +{ + if (!emu_confirm_expunge(parent)) + return; + + mail_expunge_folder(folder, NULL, NULL); +} + +/** + * em_utils_empty_trash: + * @parent: parent window + * + * Empties all Trash folders. + **/ +void +em_utils_empty_trash (GtkWidget *parent) +{ + extern CamelSession *session; + CamelProvider *provider; + EAccountList *accounts; + EAccount *account; + EIterator *iter; + CamelException ex; + + if (!emu_confirm_expunge (parent)) + return; + + camel_exception_init (&ex); + + /* expunge all remote stores */ + accounts = mail_config_get_accounts (); + iter = e_list_get_iterator ((EList *) accounts); + while (e_iterator_is_valid (iter)) { + account = (EAccount *) e_iterator_get (iter); + + /* make sure this is a valid source */ + if (account->enabled && account->source->url) { + provider = camel_session_get_provider (session, account->source->url, &ex); + if (provider) { + /* make sure this store is a remote store */ + if (provider->flags & CAMEL_PROVIDER_IS_STORAGE && + provider->flags & CAMEL_PROVIDER_IS_REMOTE) { + mail_empty_trash (account, NULL, NULL); + } + } + + /* clear the exception for the next round */ + camel_exception_clear (&ex); + } + + e_iterator_next (iter); + } + + g_object_unref (iter); + + /* Now empty the local trash folder */ + mail_empty_trash (NULL, NULL, NULL); +} diff --git a/mail/em-utils.h b/mail/em-utils.h new file mode 100644 index 0000000000..0114c86ae2 --- /dev/null +++ b/mail/em-utils.h @@ -0,0 +1,124 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifndef __EM_UTILS_H__ +#define __EM_UTILS_H__ + +#include <glib.h> + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +struct _GtkWidget; +struct _GtkWindow; +struct _CamelFolder; +struct _CamelStream; +struct _CamelMimeMessage; +struct _GtkSelectionData; +struct _GtkAdjustment; +struct _EMsgComposer; + +gboolean em_utils_prompt_user (struct _GtkWindow *parent, int def, gboolean *again, const char *fmt, ...); + +GPtrArray *em_utils_uids_copy (GPtrArray *uids); +void em_utils_uids_free (GPtrArray *uids); + +gboolean em_utils_configure_account (struct _GtkWidget *parent); +gboolean em_utils_check_user_can_send_mail (struct _GtkWidget *parent); + +void em_utils_edit_filters (struct _GtkWidget *parent); +void em_utils_edit_vfolders (struct _GtkWidget *parent); + +void em_utils_composer_send_cb(struct _EMsgComposer *composer, gpointer user_data); +void em_utils_composer_save_draft_cb(struct _EMsgComposer *composer, int quit, gpointer user_data); + +void em_utils_compose_new_message (struct _GtkWidget *parent); + +/* FIXME: mailto? url? should make up its mind what its called. imho use 'uri' */ +void em_utils_compose_new_message_with_mailto (struct _GtkWidget *parent, const char *url); +void em_utils_post_to_url (struct _GtkWidget *parent, const char *url); + +void em_utils_edit_message (struct _GtkWidget *parent, struct _CamelMimeMessage *message); +void em_utils_edit_messages (struct _GtkWidget *parent, struct _CamelFolder *folder, GPtrArray *uids); + +void em_utils_forward_attached (struct _GtkWidget *parent, struct _CamelFolder *folder, GPtrArray *uids); +void em_utils_forward_inline (struct _GtkWidget *parent, struct _CamelFolder *folder, GPtrArray *uids); +void em_utils_forward_quoted (struct _GtkWidget *parent, struct _CamelFolder *folder, GPtrArray *uids); + +void em_utils_forward_message(struct _GtkWidget *parent, struct _CamelMimeMessage *msg); +void em_utils_forward_messages(struct _GtkWidget *parent, struct _CamelFolder *folder, GPtrArray *uids); + +void em_utils_redirect_message (struct _GtkWidget *parent, struct _CamelMimeMessage *message); +void em_utils_redirect_message_by_uid (struct _GtkWidget *parent, struct _CamelFolder *folder, const char *uid); + +enum { + REPLY_MODE_SENDER, + REPLY_MODE_ALL, + REPLY_MODE_LIST +}; + +void em_utils_reply_to_message (struct _GtkWidget *parent, struct _CamelMimeMessage *message, int mode); +void em_utils_reply_to_message_by_uid (struct _GtkWidget *parent, struct _CamelFolder *folder, const char *uid, int mode); + +void em_utils_post_reply_to_message_by_uid (struct _GtkWidget *parent, struct _CamelFolder *folder, const char *uid); + +void em_utils_save_part(struct _GtkWidget *parent, const char *prompt, struct _CamelMimePart *part); +void em_utils_save_messages (struct _GtkWidget *parent, struct _CamelFolder *folder, GPtrArray *uids); + +void em_utils_flag_for_followup (struct _GtkWidget *parent, struct _CamelFolder *folder, GPtrArray *uids); +void em_utils_flag_for_followup_clear (struct _GtkWidget *parent, struct _CamelFolder *folder, GPtrArray *uids); +void em_utils_flag_for_followup_completed (struct _GtkWidget *parent, struct _CamelFolder *folder, GPtrArray *uids); + +/* This stuff that follows probably doesn't belong here, then again, the stuff above probably belongs elsewhere */ + +void em_utils_selection_set_mailbox(struct _GtkSelectionData *data, struct _CamelFolder *folder, GPtrArray *uids); +void em_utils_selection_get_mailbox(struct _GtkSelectionData *data, struct _CamelFolder *folder); +/* FIXME: be nice if these also worked on struct _CamelFolder's, no easy way to get uri from folder yet tho */ +void em_utils_selection_set_uidlist(struct _GtkSelectionData *data, const char *uri, GPtrArray *uids); +int em_utils_selection_get_uidlist(struct _GtkSelectionData *data, char **uri, GPtrArray **uidsp); +void em_utils_selection_set_urilist(struct _GtkSelectionData *data, struct _CamelFolder *folder, GPtrArray *uids); + +char *em_utils_temp_save_part(struct _GtkWidget *parent, struct _CamelMimePart *part); + +gboolean em_utils_folder_is_drafts(struct _CamelFolder *folder, const char *uri); +gboolean em_utils_folder_is_sent(struct _CamelFolder *folder, const char *uri); +gboolean em_utils_folder_is_outbox(struct _CamelFolder *folder, const char *uri); + +void em_utils_adjustment_page(struct _GtkAdjustment *adj, gboolean down); + +char *em_utils_get_proxy_uri(void); + +/* FIXME: should this have an override charset? */ +char *em_utils_part_to_html(struct _CamelMimePart *part); +char *em_utils_message_to_html(struct _CamelMimeMessage *msg, const char *credits, guint32 flags); + +void em_utils_expunge_folder (struct _GtkWidget *parent, struct _CamelFolder *folder); +void em_utils_empty_trash (struct _GtkWidget *parent); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __EM_UTILS_H__ */ diff --git a/mail/evolution-mail.schemas b/mail/evolution-mail.schemas index 35ec615733..a5a761d8b3 100644 --- a/mail/evolution-mail.schemas +++ b/mail/evolution-mail.schemas @@ -177,21 +177,7 @@ <key>/schemas/apps/evolution/mail/display/caret_mode</key> <applyto>/apps/evolution/mail/display/caret_mode</applyto> <owner>evolution-mail</owner> - <type>boolean</type> - <default>false</default> - <locale name="C"> - <short>Enable/disable caret mode</short> - <long> - Enable caret mode, so that you can see a cursor when reading mail. - </long> - </locale> - </schema> - - <schema> - <key>/schemas/apps/evolution/mail/display/caret_mode</key> - <applyto>/apps/evolution/mail/display/caret_mode</applyto> - <owner>evolution-mail</owner> - <type>boolean</type> + <type>bool</type> <default>false</default> <locale name="C"> <short>Enable/disable caret mode</short> diff --git a/mail/folder-browser-factory.c b/mail/folder-browser-factory.c index 35e1aeb2ab..f0157bac9a 100644 --- a/mail/folder-browser-factory.c +++ b/mail/folder-browser-factory.c @@ -44,16 +44,16 @@ #include "folder-browser-factory.h" -#include "folder-browser.h" -#include "folder-browser-ui.h" #include "mail.h" -#include "mail-callbacks.h" #include "shell/Evolution.h" #include "mail-config.h" #include "mail-ops.h" #include "mail-session.h" #include "mail-folder-cache.h" +#include "em-folder-browser.h" +#include "em-format.h" + #include "evolution-shell-component-utils.h" /* The FolderBrowser BonoboControls we have. */ @@ -85,79 +85,30 @@ fb_get_svi (BonoboControl *control) } static void -control_activate (BonoboControl *control, - BonoboUIComponent *uic, - FolderBrowser *fb) -{ - GtkWidget *folder_browser; - Bonobo_UIContainer container; - GNOME_Evolution_ShellView svi; - - container = bonobo_control_get_remote_ui_container (control, NULL); - bonobo_ui_component_set_container (uic, container, NULL); - bonobo_object_release_unref (container, NULL); - - g_assert (container == bonobo_ui_component_get_container (uic)); - g_return_if_fail (container != CORBA_OBJECT_NIL); - - folder_browser = bonobo_control_get_widget (control); - folder_browser_set_ui_component (FOLDER_BROWSER (folder_browser), uic); - - /*bonobo_ui_component_freeze (uic, NULL);*/ - - folder_browser_ui_add_global (fb); - folder_browser_ui_add_list (fb); - folder_browser_ui_add_message (fb); - - /*bonobo_ui_component_thaw (uic, NULL);*/ - - svi = fb_get_svi (control); - folder_browser_set_shell_view (fb, svi); - bonobo_object_release_unref (svi, NULL); - - folder_browser_reload (fb); - - e_search_bar_set_ui_component (E_SEARCH_BAR (fb->search), uic); -} - -static void -control_deactivate (BonoboControl *control, - BonoboUIComponent *uic, - FolderBrowser *fb) -{ - /*bonobo_ui_component_freeze (uic, NULL);*/ - - folder_browser_ui_rm_list (fb); - folder_browser_ui_rm_all (fb); - - /*bonobo_ui_component_thaw (uic, NULL);*/ - - if (fb->folder) - mail_sync_folder (fb->folder, NULL, NULL); - - if (fb->message_list) - message_list_save_state (fb->message_list); - - folder_browser_set_ui_component (fb, NULL); - folder_browser_set_shell_view (fb, CORBA_OBJECT_NIL); - - e_search_bar_set_ui_component (E_SEARCH_BAR (fb->search), NULL); -} - -static void control_activate_cb (BonoboControl *control, gboolean activate, gpointer user_data) { BonoboUIComponent *uic; - uic = bonobo_control_get_ui_component (control); + uic = bonobo_control_get_ui_component(control); g_assert (uic != NULL); - if (activate) - control_activate (control, uic, user_data); - else - control_deactivate (control, uic, user_data); + if (activate) { + Bonobo_UIContainer container; + + container = bonobo_control_get_remote_ui_container(control, NULL); + bonobo_ui_component_set_container(uic, container, NULL); + bonobo_object_release_unref(container, NULL); + + g_assert(container == bonobo_ui_component_get_container(uic)); + g_return_if_fail(container != CORBA_OBJECT_NIL); + + em_folder_view_activate(user_data, uic, activate); + } else { + em_folder_view_activate(user_data, uic, activate); + bonobo_ui_component_unset_container(uic, NULL); + } } static void @@ -172,14 +123,18 @@ folder_browser_factory_new_control (const char *uri, { BonoboControl *control; GtkWidget *fb; - + +#if 0 if (!(fb = folder_browser_new (shell, uri))) return NULL; FOLDER_BROWSER (fb)->pref_master = TRUE; /* save UI settings changed in this FB */ - +#endif + fb = em_folder_browser_new(); gtk_widget_show (fb); - + em_folder_view_set_folder_uri((EMFolderView *)fb, uri); + em_format_set_session((EMFormat *)((EMFolderView *)fb)->preview, session); + control = bonobo_control_new (fb); if (control == NULL) { @@ -207,14 +162,16 @@ folder_browser_factory_get_control_list (void) return control_list; } -FolderBrowser * +struct _EMFolderBrowser * folder_browser_factory_get_browser(const char *uri) { EList *controls; EIterator *it; BonoboControl *control; - FolderBrowser *fb = NULL; - + EMFolderBrowser *fb = NULL; + + return NULL; + if (control_list == NULL) return NULL; @@ -223,8 +180,8 @@ folder_browser_factory_get_browser(const char *uri) it = e_list_get_iterator (controls); while (e_iterator_is_valid (it)) { control = BONOBO_CONTROL (e_iterator_get (it)); - fb = FOLDER_BROWSER (bonobo_control_get_widget (control)); - if (fb->uri && strcmp (fb->uri, uri) == 0) + fb = (EMFolderBrowser *)bonobo_control_get_widget(control); + if (((EMFolderView *)fb)->folder_uri && strcmp (((EMFolderView *)fb)->folder_uri, uri) == 0) break; fb = NULL; diff --git a/mail/folder-browser-factory.h b/mail/folder-browser-factory.h index 84ebd7a97b..2c858c9c56 100644 --- a/mail/folder-browser-factory.h +++ b/mail/folder-browser-factory.h @@ -14,12 +14,11 @@ #include <bonobo/bonobo-control.h> #include "Evolution.h" #include "e-util/e-list.h" -#include "folder-browser.h" BonoboControl *folder_browser_factory_new_control (const char *uri, const GNOME_Evolution_Shell shell); EList *folder_browser_factory_get_control_list (void); -FolderBrowser *folder_browser_factory_get_browser(const char *uri); +struct _EMFolderBrowser *folder_browser_factory_get_browser(const char *uri); #endif /* _FOLDER_BROWSER_FACTORY_H */ diff --git a/mail/folder-browser-ui.c b/mail/folder-browser-ui.c deleted file mode 100644 index 9ccdb2db79..0000000000 --- a/mail/folder-browser-ui.c +++ /dev/null @@ -1,816 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Peter Williams <peterw@ximian.com> - * Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright 2002 Ximian, Inc. (www.ximian.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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 Street #330, Boston, MA 02111-1307, USA. - * - */ - - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <string.h> - -#include <gconf/gconf.h> -#include <gconf/gconf-client.h> - -#include <libgnome/gnome-util.h> /* gnome_util_prepend_user_home */ - -#include <bonobo/bonobo-exception.h> -#include <bonobo/bonobo-ui-component.h> -#include <bonobo/bonobo-ui-util.h> - -#include "widgets/misc/e-charset-picker.h" -#include "widgets/menus/gal-view-menus.h" /* GalView stuff */ -#include <gal/menus/gal-view-factory-etable.h> -#include <gal/menus/gal-view-etable.h> - -#include "e-util/e-meta.h" - -#include "mail-config.h" -#include "mail-callbacks.h" /* almost all the verbs */ -#include "mail-session.h" /* mail_session_forget_passwords */ - -#include "folder-browser-ui.h" - -#include "evolution-shell-component-utils.h" /* Pixmap stuff */ - - -/* - * Add with 'folder_browser' - */ - -static BonoboUIVerb message_verbs [] = { - BONOBO_UI_UNSAFE_VERB ("MailNext", next_msg), - BONOBO_UI_UNSAFE_VERB ("MailNextFlagged", next_flagged_msg), - BONOBO_UI_UNSAFE_VERB ("MailNextUnread", next_unread_msg), - BONOBO_UI_UNSAFE_VERB ("MailNextThread", next_thread), - BONOBO_UI_UNSAFE_VERB ("MailPrevious", previous_msg), - BONOBO_UI_UNSAFE_VERB ("MailPreviousFlagged", previous_flagged_msg), - BONOBO_UI_UNSAFE_VERB ("MailPreviousUnread", previous_unread_msg), - BONOBO_UI_UNSAFE_VERB ("AddSenderToAddressbook", add_sender_to_addrbook), - BONOBO_UI_UNSAFE_VERB ("MessageApplyFilters", apply_filters), - BONOBO_UI_UNSAFE_VERB ("MessageCopy", copy_msg), - BONOBO_UI_UNSAFE_VERB ("MessageDelete", delete_msg), - BONOBO_UI_UNSAFE_VERB ("MessageForward", forward), - BONOBO_UI_UNSAFE_VERB ("MessageForwardAttached", forward_attached), - BONOBO_UI_UNSAFE_VERB ("MessageForwardInline", forward_inline), - BONOBO_UI_UNSAFE_VERB ("MessageForwardQuoted", forward_quoted), - BONOBO_UI_UNSAFE_VERB ("MessageRedirect", redirect), - BONOBO_UI_UNSAFE_VERB ("MessageMarkAsRead", mark_as_seen), - BONOBO_UI_UNSAFE_VERB ("MessageMarkAsUnRead", mark_as_unseen), - BONOBO_UI_UNSAFE_VERB ("MessageMarkAsImportant", mark_as_important), - BONOBO_UI_UNSAFE_VERB ("MessageMarkAsUnimportant", mark_as_unimportant), - BONOBO_UI_UNSAFE_VERB ("MessageFollowUpFlag", flag_for_followup), - BONOBO_UI_UNSAFE_VERB ("MessageMove", move_msg), - BONOBO_UI_UNSAFE_VERB ("MessageOpen", open_message), - BONOBO_UI_UNSAFE_VERB ("MessagePostReply", post_reply), - BONOBO_UI_UNSAFE_VERB ("MessageReplyAll", reply_to_all), - BONOBO_UI_UNSAFE_VERB ("MessageReplyList", reply_to_list), - BONOBO_UI_UNSAFE_VERB ("MessageReplySender", reply_to_sender), - BONOBO_UI_UNSAFE_VERB ("MessageResend", resend_msg), - BONOBO_UI_UNSAFE_VERB ("MessageSaveAs", save_msg), - BONOBO_UI_UNSAFE_VERB ("MessageSearch", search_msg), - BONOBO_UI_UNSAFE_VERB ("MessageUndelete", undelete_msg), - BONOBO_UI_UNSAFE_VERB ("PrintMessage", print_msg), - BONOBO_UI_UNSAFE_VERB ("TextZoomIn", zoom_in), - BONOBO_UI_UNSAFE_VERB ("TextZoomOut", zoom_out), - BONOBO_UI_UNSAFE_VERB ("TextZoomReset", zoom_reset), - BONOBO_UI_UNSAFE_VERB ("PrintPreviewMessage", print_preview_msg), - BONOBO_UI_UNSAFE_VERB ("ToolsFilterMailingList", filter_mlist), - BONOBO_UI_UNSAFE_VERB ("ToolsFilterRecipient", filter_recipient), - BONOBO_UI_UNSAFE_VERB ("ToolsFilterSender", filter_sender), - BONOBO_UI_UNSAFE_VERB ("ToolsFilterSubject", filter_subject), - BONOBO_UI_UNSAFE_VERB ("ToolsVFolderMailingList", vfolder_mlist), - BONOBO_UI_UNSAFE_VERB ("ToolsVFolderRecipient", vfolder_recipient), - BONOBO_UI_UNSAFE_VERB ("ToolsVFolderSender", vfolder_sender), - BONOBO_UI_UNSAFE_VERB ("ToolsVFolderSubject", vfolder_subject), - BONOBO_UI_UNSAFE_VERB ("ViewLoadImages", load_images), - /* ViewHeaders stuff is a radio */ - /* CaretMode is a toggle */ - - BONOBO_UI_VERB_END -}; - -static BonoboUIVerb list_verbs [] = { - BONOBO_UI_UNSAFE_VERB ("EditCut", folder_browser_cut), - BONOBO_UI_UNSAFE_VERB ("EditCopy", folder_browser_copy), - BONOBO_UI_UNSAFE_VERB ("EditPaste", folder_browser_paste), - BONOBO_UI_UNSAFE_VERB ("EditInvertSelection", invert_selection), - BONOBO_UI_UNSAFE_VERB ("EditSelectAll", select_all), - BONOBO_UI_UNSAFE_VERB ("EditSelectThread", select_thread), - BONOBO_UI_UNSAFE_VERB ("ChangeFolderProperties", configure_folder), - BONOBO_UI_UNSAFE_VERB ("FolderExpunge", expunge_folder), - /* HideDeleted is a toggle */ - BONOBO_UI_UNSAFE_VERB ("MessageMarkAllAsRead", mark_all_as_seen), - BONOBO_UI_UNSAFE_VERB ("ViewHideRead", hide_read), - BONOBO_UI_UNSAFE_VERB ("ViewHideSelected", hide_selected), - BONOBO_UI_UNSAFE_VERB ("ViewShowAll", hide_none), - /* ViewThreaded is a toggle */ - - BONOBO_UI_VERB_END -}; - -static BonoboUIVerb global_verbs [] = { - BONOBO_UI_UNSAFE_VERB ("EmptyTrash", empty_trash), - BONOBO_UI_UNSAFE_VERB ("ForgetPasswords", mail_session_forget_passwords), - BONOBO_UI_UNSAFE_VERB ("MailCompose", compose_msg), - BONOBO_UI_UNSAFE_VERB ("MailPost", post_message), - BONOBO_UI_UNSAFE_VERB ("MailStop", stop_threads), - BONOBO_UI_UNSAFE_VERB ("ToolsFilters", filter_edit), - BONOBO_UI_UNSAFE_VERB ("ToolsSubscriptions", manage_subscriptions), - BONOBO_UI_UNSAFE_VERB ("ToolsVFolders", vfolder_edit_vfolders), - /* ViewPreview is a toggle */ - - BONOBO_UI_VERB_END -}; - -static EPixmap message_pixcache [] = { - E_PIXMAP ("/commands/PrintMessage", "print.xpm"), - E_PIXMAP ("/commands/PrintPreviewMessage", "print-preview.xpm"), - E_PIXMAP ("/commands/MessageDelete", "evolution-trash-mini.png"), - E_PIXMAP ("/commands/MessageUndelete", "undelete_message-16.png"), - E_PIXMAP ("/commands/MessageCopy", "copy_16_message.xpm"), - E_PIXMAP ("/commands/MessageMove", "move_message.xpm"), - E_PIXMAP ("/commands/MessageReplyAll", "reply_to_all.xpm"), - E_PIXMAP ("/commands/MessageReplySender", "reply.xpm"), - E_PIXMAP ("/commands/MessageForward", "forward.xpm"), - E_PIXMAP ("/commands/MessageApplyFilters", "apply-filters-16.xpm"), - E_PIXMAP ("/commands/MessageSearch", "search-16.png"), - E_PIXMAP ("/commands/MessageSaveAs", "save-as-16.png"), - E_PIXMAP ("/commands/MessageMarkAsRead", "mail-read.xpm"), - E_PIXMAP ("/commands/MessageMarkAsUnRead", "mail-new.xpm"), - E_PIXMAP ("/commands/MessageMarkAsImportant", "priority-high.xpm"), - E_PIXMAP ("/commands/MessageFollowUpFlag", "flag-for-followup-16.png"), - - E_PIXMAP ("/Toolbar/MailMessageToolbar/MessageReplySender", "buttons/reply.png"), - E_PIXMAP ("/Toolbar/MailMessageToolbar/MessageReplyAll", "buttons/reply-to-all.png"), - E_PIXMAP ("/Toolbar/MailMessageToolbar/MessageForward", "buttons/forward.png"), - E_PIXMAP ("/Toolbar/MailMessageToolbar/PrintMessage", "buttons/print.png"), - E_PIXMAP ("/Toolbar/MailMessageToolbar/MessageMove", "buttons/move-message.png"), - E_PIXMAP ("/Toolbar/MailMessageToolbar/MessageCopy", "buttons/copy-message.png"), - E_PIXMAP ("/Toolbar/MailMessageToolbar/MessageDelete", "buttons/delete-message.png"), - - E_PIXMAP ("/Toolbar/MailNextButtons/MailNext", "buttons/next-message.png"), - E_PIXMAP ("/Toolbar/MailNextButtons/MailPrevious", "buttons/previous-message.png"), - - E_PIXMAP_END -}; - -static EPixmap list_pixcache [] = { - E_PIXMAP ("/commands/ChangeFolderProperties", "configure_16_folder.xpm"), - E_PIXMAP ("/commands/ViewHideRead", "hide_read_messages.xpm"), - E_PIXMAP ("/commands/ViewHideSelected", "hide_selected_messages.xpm"), - E_PIXMAP ("/commands/ViewShowAll", "show_all_messages.xpm"), - - E_PIXMAP ("/commands/EditCut", "16_cut.png"), - E_PIXMAP ("/commands/EditCopy", "16_copy.png"), - E_PIXMAP ("/commands/EditPaste", "16_paste.png"), - - E_PIXMAP_END -}; - -static EPixmap global_pixcache [] = { - E_PIXMAP ("/commands/MailCompose", "new-message.xpm"), - - E_PIXMAP_END -}; - -enum { - IS_DRAFTS_FOLDER = (1 << 0), - IS_OUTBOX_FOLDER = (1 << 1), - IS_SENT_FOLDER = (1 << 2), - - IS_OUTGOING_FOLDER = (IS_DRAFTS_FOLDER | IS_OUTBOX_FOLDER | IS_SENT_FOLDER), - IS_INCOMING_FOLDER = (1 << 3), - - IS_ANY_FOLDER = (IS_OUTGOING_FOLDER | IS_INCOMING_FOLDER), - - SELECTION_NONE = (1 << 4), - SELECTION_SINGLE = (1 << 5), - SELECTION_MULTIPLE = (1 << 6), - - SELECTION_ANYTHING = (SELECTION_SINGLE | SELECTION_MULTIPLE), - - IS_THREADED = (1 << 7), - NOT_THREADED = (1<<8), - ANY_THREADED = (IS_THREADED|NOT_THREADED), - - HAS_UNDELETED = (1 << 9), - HAS_DELETED = (1 << 10), - HAS_UNREAD = (1 << 11), - HAS_READ = (1 << 12), - HAS_UNIMPORTANT = (1 << 13), - HAS_IMPORTANT = (1 << 14) -}; - -#define HAS_FLAGS (HAS_UNDELETED | HAS_DELETED | \ - HAS_UNREAD | HAS_READ | \ - HAS_UNIMPORTANT | HAS_IMPORTANT) - -#define IS_1MESSAGE (IS_ANY_FOLDER | SELECTION_SINGLE | ANY_THREADED | HAS_FLAGS) -#define IS_0MESSAGE (IS_ANY_FOLDER | SELECTION_ANYTHING | SELECTION_NONE | ANY_THREADED | HAS_FLAGS) -#define IS_NMESSAGE (IS_ANY_FOLDER | SELECTION_ANYTHING | ANY_THREADED | HAS_FLAGS) - -struct _UINode { - const char *name; - guint32 enable_mask; -}; - -struct _UINode default_ui_nodes[] = { - { "ViewLoadImages", IS_1MESSAGE }, - { "ViewFullHeaders", IS_0MESSAGE }, - { "ViewNormal", IS_0MESSAGE }, - { "ViewSource", IS_0MESSAGE }, - { "CaretMode", IS_0MESSAGE }, - - { "AddSenderToAddressbook", IS_INCOMING_FOLDER | SELECTION_SINGLE | ANY_THREADED | HAS_FLAGS }, - - { "MessageResend", IS_SENT_FOLDER | SELECTION_SINGLE | ANY_THREADED | HAS_FLAGS }, - - /* actions that work on exactly 1 message */ - { "MessagePostReply", IS_1MESSAGE }, - { "MessageReplyAll", IS_1MESSAGE }, - { "MessageReplyList", IS_1MESSAGE }, - { "MessageReplySender", IS_1MESSAGE }, - { "MessageForwardInline", IS_1MESSAGE }, - { "MessageForwardQuoted", IS_1MESSAGE }, - { "MessageRedirect", IS_1MESSAGE }, - { "MessageSearch", IS_1MESSAGE }, - - { "PrintMessage", IS_1MESSAGE }, - { "PrintPreviewMessage", IS_1MESSAGE }, - - { "ToolsFilterMailingList", IS_1MESSAGE }, - { "ToolsFilterRecipient", IS_1MESSAGE }, - { "ToolsFilterSender", IS_1MESSAGE }, - { "ToolsFilterSubject", IS_1MESSAGE }, - - { "ToolsVFolderMailingList", IS_1MESSAGE }, - { "ToolsVFolderRecipient", IS_1MESSAGE }, - { "ToolsVFolderSender", IS_1MESSAGE }, - { "ToolsVFolderSubject", IS_1MESSAGE }, - - /* actions that work on >= 1 message */ - { "MessageApplyFilters", IS_NMESSAGE }, - { "MessageCopy", IS_NMESSAGE }, - { "MessageMove", IS_NMESSAGE }, - { "MessageDelete", IS_NMESSAGE }, - { "MessageUndelete", IS_NMESSAGE & ~HAS_DELETED }, - { "MessageMarkAsRead", IS_NMESSAGE & ~HAS_UNREAD }, - { "MessageMarkAsUnRead", IS_NMESSAGE & ~HAS_READ }, - { "MessageMarkAsImportant", IS_NMESSAGE & ~HAS_UNIMPORTANT }, - { "MessageMarkAsUnimportant", IS_NMESSAGE & ~HAS_IMPORTANT }, - { "MessageFollowUpFlag", IS_NMESSAGE }, - { "MessageOpen", IS_NMESSAGE }, - { "MessageSaveAs", IS_NMESSAGE }, - { "MessageForward", IS_NMESSAGE }, - { "MessageForwardAttached", IS_NMESSAGE }, - - { "EditCut", IS_NMESSAGE }, - { "EditCopy", IS_NMESSAGE }, - { "EditPaste", IS_0MESSAGE }, - { "EditSelectThread", IS_ANY_FOLDER | SELECTION_ANYTHING | IS_THREADED | HAS_FLAGS}, - - { "ViewHideSelected", IS_NMESSAGE }, - - /* FIXME: should these be single-selection? */ - { "MailNext", IS_NMESSAGE }, - { "MailNextFlagged", IS_NMESSAGE }, - { "MailNextUnread", IS_NMESSAGE }, - { "MailNextThread", IS_NMESSAGE }, - { "MailPrevious", IS_NMESSAGE }, - { "MailPreviousFlagged", IS_NMESSAGE }, - { "MailPreviousUnread", IS_NMESSAGE }, -}; - -static int num_default_ui_nodes = sizeof (default_ui_nodes) / sizeof (default_ui_nodes[0]); - - -static void -ui_add (FolderBrowser *fb, const char *name, BonoboUIVerb verb[], EPixmap pixcache[]) -{ - BonoboUIComponent *uic = fb->uicomp; - char *file; - - bonobo_ui_component_add_verb_list_with_data (uic, verb, fb); - - /*bonobo_ui_component_freeze (uic, NULL);*/ - - file = g_strconcat (EVOLUTION_UIDIR "/evolution-mail-", name, ".xml", NULL); - bonobo_ui_util_set_ui (uic, PREFIX, file, "evolution-mail", NULL); - g_free (file); - - e_pixmaps_update (uic, pixcache); - - /*bonobo_ui_component_thaw (uic, NULL);*/ -} - -/* more complex stuff */ - -static void -display_view (GalViewInstance *instance, GalView *view, gpointer data) -{ - FolderBrowser *fb = data; - - if (GAL_IS_VIEW_ETABLE (view)) { - gal_view_etable_attach_tree (GAL_VIEW_ETABLE (view), fb->message_list->tree); - } -} - -void -folder_browser_ui_setup_view_menus (FolderBrowser *fb) -{ - static GalViewCollection *collection = NULL; - char *id; - gboolean outgoing; - - if (fb->uicomp == NULL || fb->folder == NULL) - return; - - g_assert (fb->view_instance == NULL); - g_assert (fb->view_menus == NULL); - - outgoing = folder_browser_is_drafts (fb) || - folder_browser_is_sent (fb) || - folder_browser_is_outbox (fb); - - if (collection == NULL) { - ETableSpecification *spec; - char *local_dir; - GalViewFactory *factory; - - collection = gal_view_collection_new (); - - gal_view_collection_set_title (collection, _("Mail")); - - local_dir = gnome_util_prepend_user_home ("/evolution/views/mail/"); - gal_view_collection_set_storage_directories (collection, - EVOLUTION_GALVIEWSDIR "/mail/", - local_dir); - g_free (local_dir); - - spec = e_table_specification_new (); - e_table_specification_load_from_file (spec, EVOLUTION_ETSPECDIR "/message-list.etspec"); - - factory = gal_view_factory_etable_new (spec); - g_object_unref (spec); - gal_view_collection_add_factory (collection, factory); - g_object_unref (factory); - - gal_view_collection_load (collection); - } - - id = mail_config_folder_to_safe_url (fb->folder); - fb->view_instance = gal_view_instance_new (collection, id); - g_free (id); - - if (outgoing) - gal_view_instance_set_default_view (fb->view_instance, "As_Sent_Folder"); - - if (!gal_view_instance_exists (fb->view_instance)) { - char *path; - struct stat st; - - gal_view_instance_load (fb->view_instance); - - path = mail_config_folder_to_cachename (fb->folder, "et-header-"); - if (path && stat (path, &st) == 0 && st.st_size > 0 && S_ISREG (st.st_mode)) { - ETableSpecification *spec; - ETableState *state; - GalView *view; - - spec = e_table_specification_new(); - e_table_specification_load_from_file (spec, EVOLUTION_ETSPECDIR "/message-list.etspec"); - view = gal_view_etable_new (spec, ""); - g_object_unref (spec); - - state = e_table_state_new (); - e_table_state_load_from_file (state, path); - gal_view_etable_set_state (GAL_VIEW_ETABLE (view), state); - g_object_unref (state); - - gal_view_instance_set_custom_view (fb->view_instance, view); - g_object_unref (view); - } - g_free (path); - } - - fb->view_menus = gal_view_menus_new (fb->view_instance); - gal_view_menus_apply (fb->view_menus, fb->uicomp, NULL); - - /* Due to CORBA reentrancy, the view could be gone now. */ - if (fb->view_instance == NULL) - return; - - g_signal_connect (fb->view_instance, "display_view", G_CALLBACK (display_view), fb); - - display_view (fb->view_instance, gal_view_instance_get_current_view (fb->view_instance), fb); -} - -/* Gets rid of the view instance and view menus objects */ -void -folder_browser_ui_discard_view_menus (FolderBrowser *fb) -{ - g_assert (fb->view_instance != NULL); - g_assert (fb->view_menus != NULL); - - g_object_unref (fb->view_instance); - fb->view_instance = NULL; - - g_object_unref (fb->view_menus); - fb->view_menus = NULL; -} - -void -folder_browser_ui_message_list_focus (FolderBrowser *fb) -{ - g_assert (fb->uicomp != NULL); - - bonobo_ui_component_set_prop (fb->uicomp, "/commands/EditInvertSelection", - "sensitive", "1", NULL); -/* bonobo_ui_component_set_prop (fb->uicomp, "/commands/EditSelectThread", - "sensitive", "1", NULL);*/ -} - -void -folder_browser_ui_message_list_unfocus (FolderBrowser *fb) -{ - g_assert (fb->uicomp != NULL); - - bonobo_ui_component_set_prop (fb->uicomp, "/commands/EditInvertSelection", - "sensitive", "0", NULL); - /*bonobo_ui_component_set_prop (fb->uicomp, "/commands/EditSelectThread", - "sensitive", "0", NULL);*/ -} - -static void -folder_browser_setup_property_menu (FolderBrowser *fb, BonoboUIComponent *uic) -{ - char *name, *base = NULL; - CamelURL *url; - - url = camel_url_new (fb->uri, NULL); - if (url) - base = g_path_get_basename(url->fragment?url->fragment:url->path); - - if (base && base[0] != '\0') - name = g_strdup_printf (_("Properties for \"%s\""), base); - else - name = g_strdup (_("Properties")); - - bonobo_ui_component_set_prop ( - uic, "/menu/File/Folder/ComponentPlaceholder/ChangeFolderProperties", - "label", name, NULL); - g_free (name); - g_free(base); - - if (url) - camel_url_free (url); - - fbui_sensitise_item (fb, "ChangeFolderProperties", - (strncmp (fb->uri, "vfolder:", 8) == 0 || strncmp (fb->uri, "file:", 5) == 0)); -} - -/* Must be in the same order as MailConfigDisplayStyle */ -/* used in folder-browser.c as well (therefore not static) */ -char *message_display_styles[] = { - "/commands/ViewNormal", - "/commands/ViewFullHeaders", - "/commands/ViewSource" -}; - -/* public */ - -void -folder_browser_ui_add_message (FolderBrowser *fb) -{ - BonoboUIComponent *uic = fb->uicomp; - FolderBrowserSelectionState prev_state; - GConfClient *gconf; - int style; - gboolean caret_mode; - - gconf = mail_config_get_gconf_client (); - - if (fb->sensitise_state) { - g_hash_table_destroy(fb->sensitise_state); - fb->sensitise_state = NULL; - } - - ui_add (fb, "message", message_verbs, message_pixcache); - - caret_mode = gconf_client_get_bool (gconf, "/apps/evolution/mail/display/caret_mode", NULL); - bonobo_ui_component_set_prop(uic, "/commands/CaretMode", "state", caret_mode?"1":"0", NULL); - bonobo_ui_component_add_listener (uic, "CaretMode", folder_browser_toggle_caret_mode, fb); - - /* Display Style */ - style = gconf_client_get_int (gconf, "/apps/evolution/mail/display/message_style", NULL); - style = style >= 0 && style < MAIL_CONFIG_DISPLAY_MAX ? style : 0; - bonobo_ui_component_set_prop (uic, message_display_styles[style], "state", "1", NULL); - bonobo_ui_component_add_listener (uic, "ViewNormal", folder_browser_set_message_display_style, fb); - bonobo_ui_component_add_listener (uic, "ViewFullHeaders", folder_browser_set_message_display_style, fb); - bonobo_ui_component_add_listener (uic, "ViewSource", folder_browser_set_message_display_style, fb); - if (fb->mail_display->display_style != style) { - fb->mail_display->display_style = style; - mail_display_redisplay (fb->mail_display, TRUE); - } - - /* Resend Message */ - if (fb->folder && !folder_browser_is_sent (fb)) - fbui_sensitise_item (fb, "MessageResend", FALSE); - - /* sensitivity of message-specific commands */ - prev_state = fb->selection_state; - fb->selection_state = FB_SELSTATE_UNDEFINED; - folder_browser_ui_set_selection_state (fb, prev_state); - - /* Charset picker */ - e_charset_picker_bonobo_ui_populate (uic, "/menu/View", FB_DEFAULT_CHARSET, - folder_browser_charset_changed, fb); -} - -void -folder_browser_ui_add_list (FolderBrowser *fb) -{ - BonoboUIComponent *uic = fb->uicomp; - GConfClient *gconf; - int state; - - gconf = mail_config_get_gconf_client (); - - if (fb->sensitise_state) { - g_hash_table_destroy (fb->sensitise_state); - fb->sensitise_state = NULL; - } - - ui_add (fb, "list", list_verbs, list_pixcache); - - /* Hide Deleted */ - state = !gconf_client_get_bool (gconf, "/apps/evolution/mail/display/show_deleted", NULL); - bonobo_ui_component_set_prop (uic, "/commands/HideDeleted", "state", state ? "1" : "0", NULL); - bonobo_ui_component_add_listener (uic, "HideDeleted", folder_browser_toggle_hide_deleted, fb); - if (!(fb->folder && (fb->folder->folder_flags & CAMEL_FOLDER_IS_TRASH))) - message_list_set_hidedeleted (fb->message_list, state); - else - fbui_sensitise_item (fb, "HideDeleted", FALSE); - - /* Threaded toggle */ - state = gconf_client_get_bool (gconf, "/apps/evolution/mail/display/thread_list", NULL); - if (fb->meta) - state = e_meta_get_bool(fb->meta, "thread_list", state); - - bonobo_ui_component_set_prop (uic, "/commands/ViewThreaded", "state", state ? "1" : "0", NULL); - bonobo_ui_component_add_listener (uic, "ViewThreaded", folder_browser_toggle_threads, fb); - message_list_set_threaded (fb->message_list, state); - state = fb->selection_state; - fb->selection_state = FB_SELSTATE_UNDEFINED; - folder_browser_ui_set_selection_state (fb, state); - - /* Property menu */ - folder_browser_setup_property_menu (fb, fb->uicomp); - - /* View menu */ - if (fb->view_instance == NULL) - folder_browser_ui_setup_view_menus (fb); -} - -void -folder_browser_ui_rm_list (FolderBrowser *fb) -{ - /* View menu */ - if (fb->view_instance != NULL) - folder_browser_ui_discard_view_menus (fb); -} - -void -folder_browser_ui_add_global (FolderBrowser *fb) -{ - BonoboUIComponent *uic = fb->uicomp; - gboolean show_preview; - GConfClient *gconf; - int paned_size; - - gconf = mail_config_get_gconf_client (); - - if (fb->sensitise_state) { - g_hash_table_destroy (fb->sensitise_state); - fb->sensitise_state = NULL; - } - - ui_add (fb, "global", global_verbs, global_pixcache); - - /* (Pre)view pane size (do this first because it affects the - preview settings - see folder_browser_set_message_preview() - internals for details) */ - paned_size = gconf_client_get_int (gconf, "/apps/evolution/mail/display/paned_size", NULL); - g_signal_handler_block (fb->vpaned, fb->paned_resize_id); - gtk_paned_set_position (GTK_PANED (fb->vpaned), paned_size); - g_signal_handler_unblock (fb->vpaned, fb->paned_resize_id); - - /* (Pre)view toggle */ - show_preview = gconf_client_get_bool (gconf, "/apps/evolution/mail/display/show_preview", NULL); - if (fb->meta) - show_preview = e_meta_get_bool(fb->meta, "show_preview", show_preview); - bonobo_ui_component_set_prop (uic, "/commands/ViewPreview", "state", show_preview ? "1" : "0", NULL); - folder_browser_set_message_preview (fb, show_preview); - - /* listen for user-changes */ - bonobo_ui_component_add_listener (uic, "ViewPreview", folder_browser_toggle_preview, fb); - - /* Stop button */ - /* TODO: Go through cache, but we can't becaus eof mail-mt.c:set_stop at the moment */ - bonobo_ui_component_set_prop (uic, "/commands/MailStop", "sensitive", "0", NULL); -} - -void -folder_browser_ui_rm_all (FolderBrowser *fb) -{ - BonoboUIComponent *uic = fb->uicomp; - - if (bonobo_ui_component_get_container (uic) != NULL) { - bonobo_ui_component_rm (uic, "/", NULL); - bonobo_ui_component_unset_container (uic, NULL); - } - - if (fb->sensitise_state) { - g_hash_table_destroy (fb->sensitise_state); - fb->sensitise_state = NULL; - } -} - -void -fbui_sensitise_item (FolderBrowser *fb, const char *item, int state) -{ - char *name, *key; - gpointer val_ptr; - int val; - - /* If this whole caching idea doesn't work, remove it here */ - if (fb->sensitise_state == NULL) - fb->sensitise_state = g_hash_table_new (g_str_hash, g_str_equal); - - if (g_hash_table_lookup_extended (fb->sensitise_state, item, (void **)&key, &val_ptr)) { - val = GPOINTER_TO_INT(val_ptr); - if (val == state) - return; - } - - if (fb->uicomp) { - name = g_alloca (strlen (item) + strlen ("/commands/") + 1); - sprintf (name, "/commands/%s", item); - bonobo_ui_component_set_prop (fb->uicomp, name, "sensitive", state ? "1" : "0", NULL); - g_hash_table_insert (fb->sensitise_state, (char *) item, GINT_TO_POINTER(state)); - } -} - -static void -fbui_sensitize_items (FolderBrowser *fb, guint32 enable_mask) -{ - gboolean enable; - int i; - - for (i = 0; i < num_default_ui_nodes; i++) { - enable = (default_ui_nodes[i].enable_mask & enable_mask) == enable_mask; - fbui_sensitise_item (fb, default_ui_nodes[i].name, enable); - } -} - -void -folder_browser_ui_scan_selection (FolderBrowser *fb) -{ - gboolean outgoing = FALSE; - guint32 enable_mask = 0; - - if (fb->selection_state == FB_SELSTATE_SINGLE || - fb->selection_state == FB_SELSTATE_MULTIPLE) { - GPtrArray *uids; - CamelMessageInfo *info; - guint32 temp_mask = 0; - int i; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - for (i = 0; i < uids->len; i++) { - info = camel_folder_get_message_info (fb->folder, uids->pdata[i]); - if (info == NULL) - continue; - - if (info->flags & CAMEL_MESSAGE_DELETED) - temp_mask |= HAS_DELETED; - else - temp_mask |= HAS_UNDELETED; - - if (info->flags & CAMEL_MESSAGE_SEEN) - temp_mask |= HAS_READ; - else - temp_mask |= HAS_UNREAD; - - if (info->flags & CAMEL_MESSAGE_FLAGGED) - temp_mask |= HAS_IMPORTANT; - else - temp_mask |= HAS_UNIMPORTANT; - - camel_folder_free_message_info (fb->folder, info); - g_free (uids->pdata[i]); - } - - g_ptr_array_free (uids, TRUE); - - /* yeah, the naming is a bit backwards, but we need to support - * the case when, say, both a deleted and an undeleted message - * are selected. Both the Delete and Undelete menu items should - * be sensitized, but the only good way to set the flags is as - * above. Anyway, the naming is a bit of a lie but it works out - * so that it's sensible both above and in the definition of - * the UI items, so deal with it. - */ - - enable_mask |= (~temp_mask & HAS_FLAGS); - } - - if (folder_browser_is_drafts (fb)) { - enable_mask |= IS_DRAFTS_FOLDER; - outgoing = TRUE; - } - - if (folder_browser_is_outbox (fb)) { - enable_mask |= IS_OUTBOX_FOLDER; - outgoing = TRUE; - } - - if (folder_browser_is_sent (fb)) { - enable_mask |= IS_SENT_FOLDER; - outgoing = TRUE; - } - - if (fb->message_list && fb->message_list->threaded) - enable_mask |= IS_THREADED; - else - enable_mask |= NOT_THREADED; - - if (outgoing == FALSE) - enable_mask |= IS_INCOMING_FOLDER; - - switch (fb->selection_state) { - case FB_SELSTATE_SINGLE: - enable_mask |= SELECTION_SINGLE; - break; - case FB_SELSTATE_MULTIPLE: - enable_mask |= SELECTION_MULTIPLE; - break; - case FB_SELSTATE_NONE: - default: - enable_mask |= SELECTION_NONE; - break; - } - - fbui_sensitize_items (fb, enable_mask); -} - -void -folder_browser_ui_set_selection_state (FolderBrowser *fb, FolderBrowserSelectionState state) -{ - /* the state may be the same but with - * different messages selected, necessitating - * a recheck of the flags of the selected - * messages. - */ - - if (state == fb->selection_state && - state != FB_SELSTATE_SINGLE && - state != FB_SELSTATE_MULTIPLE) - return; - - fb->selection_state = state; - folder_browser_ui_scan_selection (fb); -} - -void -folder_browser_ui_message_loaded (FolderBrowser *fb) -{ - BonoboUIComponent *uic = fb->uicomp; - - if (uic) { - fb->selection_state = FB_SELSTATE_NONE; - folder_browser_ui_set_selection_state (fb, FB_SELSTATE_SINGLE); - } -} diff --git a/mail/folder-browser-ui.h b/mail/folder-browser-ui.h deleted file mode 100644 index 5c2bc1fa28..0000000000 --- a/mail/folder-browser-ui.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * folder-browser-ui.c: Sets up the Bonobo UI for FolderBrowsers - * - * Author: - * Peter Williams <peterw@ximian.com> - * - * (C) 2001 Ximian, Inc. - */ - -#ifndef _FOLDER_BROWSER_UI_H -#define _FOLDER_BROWSER_UI_H - -#include "folder-browser.h" - -void folder_browser_ui_add_message (FolderBrowser *fb); -void folder_browser_ui_add_list (FolderBrowser *fb); -void folder_browser_ui_add_global (FolderBrowser *fb); - -void folder_browser_ui_rm_list (FolderBrowser *fb); -void folder_browser_ui_rm_all (FolderBrowser *fb); - -/* these affect the sensitivity of UI elements */ -void folder_browser_ui_scan_selection (FolderBrowser *fb); -void folder_browser_ui_set_selection_state (FolderBrowser *fb, FolderBrowserSelectionState state); -void folder_browser_ui_message_loaded (FolderBrowser *fb); - -void folder_browser_ui_discard_view_menus (FolderBrowser *fb); -void folder_browser_ui_setup_view_menus (FolderBrowser *fb); -/* Set the sensitivity of a single item */ -void fbui_sensitise_item(FolderBrowser *fb, const char *item, int state); - -void folder_browser_ui_message_list_focus (FolderBrowser *fb); -void folder_browser_ui_message_list_unfocus (FolderBrowser *fb); - -#endif /* _FOLDER_BROWSER_UI_H */ diff --git a/mail/folder-browser.c b/mail/folder-browser.c deleted file mode 100644 index 3abdd4d065..0000000000 --- a/mail/folder-browser.c +++ /dev/null @@ -1,2692 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Miguel De Icaza <miguel@ximian.com> - * Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright 2000-2003 Ximian, Inc. (www.ximian.com) - * - * 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. - * - */ - - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <string.h> -#include <ctype.h> -#include <errno.h> - -#include <gdk/gdkkeysyms.h> -#include <gtk/gtkinvisible.h> -#include <gal/e-table/e-table.h> -#include <gal/util/e-util.h> -#include <gal/widgets/e-gui-utils.h> -#include <gal/widgets/e-popup-menu.h> - -#include <gconf/gconf.h> -#include <gconf/gconf-client.h> - -#include <libgnomeui/gnome-dialog-util.h> -#include <libgnomeui/gnome-pixmap.h> - -#include <gtkhtml/htmlengine.h> -#include <gtkhtml/htmlobject.h> -#include <gtkhtml/htmlinterval.h> -#include <gtkhtml/htmlengine-edit-cut-and-paste.h> - -#include "filter/vfolder-rule.h" -#include "filter/vfolder-context.h" -#include "filter/filter-option.h" -#include "filter/filter-input.h" -#include "filter/filter-label.h" - -#include "e-util/e-sexp.h" -#include "e-util/e-mktemp.h" -#include "e-util/e-meta.h" -#include "folder-browser.h" -#include "e-searching-tokenizer.h" -#include "mail.h" -#include "mail-callbacks.h" -#include "mail-tools.h" -#include "mail-ops.h" -#include "mail-vfolder.h" -#include "mail-autofilter.h" -#include "mail-mt.h" -#include "mail-folder-cache.h" -#include "folder-browser-ui.h" - -#include "mail-local.h" -#include "mail-config.h" - -#include <camel/camel-mime-message.h> -#include <camel/camel-stream-mem.h> - -/* maybe this shooudlnt be private ... */ -#include "camel/camel-search-private.h" - -#define d(x) - -#define PARENT_TYPE (gtk_table_get_type ()) - -static void folder_changed(CamelObject *o, void *event_data, void *data); -static void main_folder_changed(CamelObject *o, void *event_data, void *data); - -#define X_EVOLUTION_MESSAGE_TYPE "x-evolution-message" -#define MESSAGE_RFC822_TYPE "message/rfc822" -#define TEXT_URI_LIST_TYPE "text/uri-list" -#define TEXT_PLAIN_TYPE "text/plain" - -/* Drag & Drop types */ -enum DndTargetType { - DND_TARGET_TYPE_X_EVOLUTION_MESSAGE, - DND_TARGET_TYPE_MESSAGE_RFC822, - DND_TARGET_TYPE_TEXT_URI_LIST, -}; - -static GtkTargetEntry drag_types[] = { - { X_EVOLUTION_MESSAGE_TYPE, 0, DND_TARGET_TYPE_X_EVOLUTION_MESSAGE }, - { MESSAGE_RFC822_TYPE, 0, DND_TARGET_TYPE_MESSAGE_RFC822 }, - { TEXT_URI_LIST_TYPE, 0, DND_TARGET_TYPE_TEXT_URI_LIST }, -}; - -static const int num_drag_types = sizeof (drag_types) / sizeof (drag_types[0]); - -enum PasteTargetType { - PASTE_TARGET_TYPE_X_EVOLUTION_MESSAGE, - PASTE_TARGET_TYPE_TEXT_PLAIN, -}; - -static GtkTargetPair paste_types[] = { - { 0, 0, PASTE_TARGET_TYPE_X_EVOLUTION_MESSAGE }, - { GDK_SELECTION_TYPE_STRING, 0, PASTE_TARGET_TYPE_TEXT_PLAIN }, -}; - -static const int num_paste_types = sizeof (paste_types) / sizeof (paste_types[0]); - -static GdkAtom clipboard_atom = GDK_NONE; - -static GtkTableClass *parent_class = NULL; - -enum { - FOLDER_LOADED, - MESSAGE_LOADED, - LAST_SIGNAL -}; - -static guint folder_browser_signals [LAST_SIGNAL] = {0, }; - -static void -folder_browser_finalise (GObject *object) -{ - FolderBrowser *folder_browser; - - folder_browser = FOLDER_BROWSER (object); - - g_free (folder_browser->loading_uid); - g_free (folder_browser->pending_uid); - g_free (folder_browser->new_uid); - g_free (folder_browser->loaded_uid); - - g_free (folder_browser->uri); - folder_browser->uri = NULL; - - if (folder_browser->clipboard_selection) - g_byte_array_free (folder_browser->clipboard_selection, TRUE); - - if (folder_browser->sensitise_state) { - g_hash_table_destroy (folder_browser->sensitise_state); - folder_browser->sensitise_state = NULL; - } - - G_OBJECT_CLASS (parent_class)->finalize (object); -} - -static void -folder_browser_destroy (GtkObject *object) -{ - FolderBrowser *folder_browser; - CORBA_Environment ev; - - folder_browser = FOLDER_BROWSER (object); - - CORBA_exception_init (&ev); - - if (folder_browser->seen_id != 0) { - gtk_timeout_remove (folder_browser->seen_id); - folder_browser->seen_id = 0; - } - - if (folder_browser->loading_id != 0) { - gtk_timeout_remove(folder_browser->loading_id); - folder_browser->loading_id = 0; - } - - if (folder_browser->message_list) { - gtk_widget_destroy (GTK_WIDGET (folder_browser->message_list)); - folder_browser->message_list = NULL; - } - - if (folder_browser->mail_display) { - gtk_widget_destroy (GTK_WIDGET (folder_browser->mail_display)); - folder_browser->mail_display = NULL; - } - - if (folder_browser->view_instance) { - g_object_unref (folder_browser->view_instance); - folder_browser->view_instance = NULL; - } - - if (folder_browser->view_menus) { - g_object_unref (folder_browser->view_menus); - folder_browser->view_menus = NULL; - } - - /* wait for all outstanding async events against us */ - if (folder_browser->async_event) { - mail_async_event_destroy (folder_browser->async_event); - folder_browser->async_event = NULL; - } - - if (folder_browser->search_full) { - g_object_unref (folder_browser->search_full); - folder_browser->search_full = NULL; - } - - if (folder_browser->sensitize_timeout_id) { - g_source_remove (folder_browser->sensitize_timeout_id); - folder_browser->sensitize_timeout_id = 0; - } - - if (folder_browser->shell != CORBA_OBJECT_NIL) { - CORBA_Object_release (folder_browser->shell, &ev); - folder_browser->shell = CORBA_OBJECT_NIL; - } - - if (folder_browser->shell_view != CORBA_OBJECT_NIL) { - CORBA_Object_release (folder_browser->shell_view, &ev); - folder_browser->shell_view = CORBA_OBJECT_NIL; - } - - if (folder_browser->uicomp) { - bonobo_object_unref (BONOBO_OBJECT (folder_browser->uicomp)); - folder_browser->uicomp = NULL; - } - - if (folder_browser->invisible) { - g_object_unref (folder_browser->invisible); - folder_browser->invisible = NULL; - } - - if (folder_browser->get_id != -1) { - mail_msg_cancel (folder_browser->get_id); - folder_browser->get_id = -1; - } - - if (folder_browser->folder) { - camel_object_unhook_event (CAMEL_OBJECT (folder_browser->folder), "folder_changed", - folder_changed, folder_browser); - camel_object_unhook_event (CAMEL_OBJECT (folder_browser->folder), "message_changed", - folder_changed, folder_browser); - mail_sync_folder (folder_browser->folder, NULL, NULL); - camel_object_unref (folder_browser->folder); - folder_browser->folder = NULL; - } - - CORBA_exception_free (&ev); - - GTK_OBJECT_CLASS (parent_class)->destroy (object); -} - -static void -folder_browser_class_init (FolderBrowserClass *klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass); - - parent_class = g_type_class_ref(PARENT_TYPE); - - object_class->destroy = folder_browser_destroy; - gobject_class->finalize = folder_browser_finalise; - - folder_browser_signals[FOLDER_LOADED] = - g_signal_new ("folder_loaded", - FOLDER_BROWSER_TYPE, - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (FolderBrowserClass, folder_loaded), - NULL, - NULL, - g_cclosure_marshal_VOID__STRING, - G_TYPE_NONE, 1, G_TYPE_STRING); - - folder_browser_signals[MESSAGE_LOADED] = - g_signal_new ("message_loaded", - FOLDER_BROWSER_TYPE, - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (FolderBrowserClass, message_loaded), - NULL, - NULL, - g_cclosure_marshal_VOID__STRING, - G_TYPE_NONE, 1, G_TYPE_STRING); - - /* clipboard atom */ - if (!clipboard_atom) - clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE); - - if (!paste_types[0].target) - paste_types[0].target = gdk_atom_intern (X_EVOLUTION_MESSAGE_TYPE, FALSE); -} - -static void -add_uid (MessageList *ml, const char *uid, gpointer data) -{ - g_ptr_array_add ((GPtrArray *) data, g_strdup (uid)); -} - -static void -message_list_drag_data_get (ETree *tree, int row, ETreePath path, int col, - GdkDragContext *context, GtkSelectionData *selection_data, - guint info, guint time, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - GPtrArray *uids = NULL; - int i; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, add_uid, uids); - if (uids->len == 0) { - g_ptr_array_free (uids, TRUE); - return; - } - - switch (info) { - case DND_TARGET_TYPE_TEXT_URI_LIST: - { - const char *filename, *tmpdir; - CamelMimeMessage *message; - CamelMimeFilter *filter; - CamelStream *fstream; - CamelStreamFilter *stream; - char *uri_list; - int fd; - - tmpdir = e_mkdtemp ("drag-n-drop-XXXXXX"); - - if (!tmpdir) { - char *msg = g_strdup_printf (_("Could not create temporary " - "directory: %s"), - g_strerror (errno)); - gnome_error_dialog (msg); - /* cleanup and abort */ - for (i = 0; i < uids->len; i++) - g_free (uids->pdata[i]); - g_ptr_array_free (uids, TRUE); - g_free (msg); - return; - } - - message = camel_folder_get_message (fb->folder, uids->pdata[0], NULL); - g_free (uids->pdata[0]); - - if (uids->len == 1) { - filename = camel_mime_message_get_subject (message); - if (!filename) - filename = _("Unknown"); - } else - filename = "mbox"; - - uri_list = g_strdup_printf ("file://%s/%s", tmpdir, filename); - - fd = open (uri_list + 7, O_WRONLY | O_CREAT | O_EXCL, 0600); - if (fd == -1) { - /* cleanup and abort */ - camel_object_unref (message); - for (i = 1; i < uids->len; i++) - g_free (uids->pdata[i]); - g_ptr_array_free (uids, TRUE); - g_free (uri_list); - return; - } - - fstream = camel_stream_fs_new_with_fd (fd); - - stream = camel_stream_filter_new_with_stream (fstream); - filter = camel_mime_filter_from_new (); - camel_stream_filter_add (stream, filter); - camel_object_unref (filter); - - camel_stream_write (fstream, "From - \n", 8); - camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), CAMEL_STREAM (stream)); - camel_object_unref (message); - camel_stream_flush (CAMEL_STREAM (stream)); - - for (i = 1; i < uids->len; i++) { - message = camel_folder_get_message (fb->folder, uids->pdata[i], NULL); - camel_stream_write (fstream, "From - \n", 8); - camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), CAMEL_STREAM (stream)); - camel_object_unref (message); - camel_stream_flush (CAMEL_STREAM (stream)); - g_free (uids->pdata[i]); - } - - g_ptr_array_free (uids, TRUE); - - camel_object_unref (stream); - camel_object_unref (fstream); - - gtk_selection_data_set (selection_data, selection_data->target, 8, - uri_list, strlen (uri_list)); - g_free (uri_list); - } - break; - case DND_TARGET_TYPE_MESSAGE_RFC822: - { - CamelMimeFilter *filter; - CamelStream *stream; - CamelStream *mem; - - mem = camel_stream_mem_new (); - - stream = camel_stream_filter_new_with_stream (mem); - filter = camel_mime_filter_from_new (); - camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter); - camel_object_unref (filter); - - for (i = 0; i < uids->len; i++) { - CamelMimeMessage *message; - - message = camel_folder_get_message (fb->folder, uids->pdata[i], NULL); - g_free (uids->pdata[i]); - - if (message) { - camel_stream_write (mem, "From - \n", 8); - camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream); - camel_object_unref (message); - camel_stream_flush (stream); - } - } - - g_ptr_array_free (uids, TRUE); - camel_object_unref (stream); - - gtk_selection_data_set (selection_data, selection_data->target, 8, - CAMEL_STREAM_MEM (mem)->buffer->data, - CAMEL_STREAM_MEM (mem)->buffer->len); - - camel_object_unref (mem); - } - break; - case DND_TARGET_TYPE_X_EVOLUTION_MESSAGE: - { - GByteArray *array; - - /* format: "uri\0uid1\0uid2\0uid3\0...\0uidn" */ - - /* write the uri portion */ - array = g_byte_array_new (); - g_byte_array_append (array, fb->uri, strlen (fb->uri)); - g_byte_array_append (array, "", 1); - - /* write the uids */ - for (i = 0; i < uids->len; i++) { - g_byte_array_append (array, uids->pdata[i], strlen (uids->pdata[i])); - g_free (uids->pdata[i]); - - if (i + 1 < uids->len) - g_byte_array_append (array, "", 1); - } - - g_ptr_array_free (uids, TRUE); - - gtk_selection_data_set (selection_data, selection_data->target, 8, - array->data, array->len); - - g_byte_array_free (array, TRUE); - } - break; - default: - for (i = 0; i < uids->len; i++) - g_free (uids->pdata[i]); - - g_ptr_array_free (uids, TRUE); - break; - } -} - -static void -message_rfc822_dnd (CamelFolder *dest, CamelStream *stream, CamelException *ex) -{ - CamelMimeParser *mp; - - mp = camel_mime_parser_new (); - camel_mime_parser_scan_from (mp, TRUE); - camel_mime_parser_init_with_stream (mp, stream); - - while (camel_mime_parser_step (mp, 0, 0) == HSCAN_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 (CAMEL_OBJECT (msg)); - break; - } - - /* append the message to the folder... */ - info = g_new0 (CamelMessageInfo, 1); - camel_folder_append_message (dest, msg, info, NULL, ex); - camel_object_unref (CAMEL_OBJECT (msg)); - - if (camel_exception_is_set (ex)) - break; - - /* skip over the FROM_END state */ - camel_mime_parser_step (mp, 0, 0); - } - - camel_object_unref (CAMEL_OBJECT (mp)); -} - -static void -message_list_drag_data_received (ETree *tree, int row, ETreePath path, int col, - GdkDragContext *context, gint x, gint y, - GtkSelectionData *selection_data, guint info, - guint time, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - CamelFolder *folder = NULL; - char *tmp, *url, **urls; - GPtrArray *uids = NULL; - CamelStream *stream; - CamelException ex; - CamelURL *uri; - int i, fd; - - /* this means we are receiving no data */ - if (!selection_data->data || selection_data->length == -1) - return; - - camel_exception_init (&ex); - - switch (info) { - case DND_TARGET_TYPE_TEXT_URI_LIST: - tmp = g_strndup (selection_data->data, selection_data->length); - urls = g_strsplit (tmp, "\n", 0); - g_free (tmp); - - for (i = 0; urls[i] != NULL; i++) { - /* get the path component */ - url = g_strstrip (urls[i]); - - uri = camel_url_new (url, NULL); - g_free (url); - - if (!uri) - continue; - - url = uri->path; - uri->path = NULL; - camel_url_free (uri); - - fd = open (url, O_RDONLY); - if (fd == -1) { - g_free (url); - /* FIXME: okay, what do we do in this case? */ - continue; - } - - stream = camel_stream_fs_new_with_fd (fd); - message_rfc822_dnd (fb->folder, stream, &ex); - camel_object_unref (CAMEL_OBJECT (stream)); - - if (context->action == GDK_ACTION_MOVE && !camel_exception_is_set (&ex)) - unlink (url); - - g_free (url); - } - - g_free (urls); - break; - case DND_TARGET_TYPE_MESSAGE_RFC822: - /* write the message(s) out to a CamelStream so we can use it */ - stream = camel_stream_mem_new (); - camel_stream_write (stream, selection_data->data, selection_data->length); - camel_stream_reset (stream); - - message_rfc822_dnd (fb->folder, stream, &ex); - camel_object_unref (CAMEL_OBJECT (stream)); - break; - case DND_TARGET_TYPE_X_EVOLUTION_MESSAGE: - folder = mail_tools_x_evolution_message_parse (selection_data->data, selection_data->length, &uids); - if (folder == NULL) - goto fail; - - if (uids == NULL) { - camel_object_unref (CAMEL_OBJECT (folder)); - goto fail; - } - - mail_transfer_messages (folder, uids, context->action == GDK_ACTION_MOVE, - fb->uri, 0, NULL, NULL); - - camel_object_unref (CAMEL_OBJECT (folder)); - break; - } - - camel_exception_clear (&ex); - - gtk_drag_finish (context, TRUE, TRUE, GDK_CURRENT_TIME); - - fail: - camel_exception_clear (&ex); - - gtk_drag_finish (context, FALSE, TRUE, GDK_CURRENT_TIME); -} - -static void -selection_get (GtkWidget *widget, GtkSelectionData *selection_data, - guint info, guint time_stamp, FolderBrowser *fb) -{ - if (fb->clipboard_selection == NULL) - return; - - switch (info) { - default: - case PASTE_TARGET_TYPE_TEXT_PLAIN: - { - /* FIXME: this'll be fucking slow for the user... pthread this? */ - CamelFolder *source; - CamelStream *stream; - GByteArray *bytes; - GPtrArray *uids; - int i; - - bytes = fb->clipboard_selection; - - /* Note: source should == fb->folder, but we might as well use `source' instead of fb->folder */ - source = mail_tools_x_evolution_message_parse (bytes->data, bytes->len, &uids); - if (source == NULL) - return; - - if (uids == NULL) { - camel_object_unref (CAMEL_OBJECT (source)); - return; - } - - bytes = g_byte_array_new (); - stream = camel_stream_mem_new (); - camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (stream), bytes); - - for (i = 0; i < uids->len; i++) { - CamelMimeMessage *message; - - message = camel_folder_get_message (source, uids->pdata[i], NULL); - g_free (uids->pdata[i]); - - if (message) { - camel_stream_write (stream, "From - \n", 8); - camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream); - camel_object_unref (CAMEL_OBJECT (message)); - } - } - - g_ptr_array_free (uids, TRUE); - camel_object_unref (CAMEL_OBJECT (stream)); - camel_object_unref (CAMEL_OBJECT (source)); - - gtk_selection_data_set (selection_data, selection_data->target, 8, - bytes->data, bytes->len); - - g_byte_array_free (bytes, FALSE); - } - break; - case PASTE_TARGET_TYPE_X_EVOLUTION_MESSAGE: - /* we already have our data in the correct form */ - gtk_selection_data_set (selection_data, - selection_data->target, 8, - fb->clipboard_selection->data, - fb->clipboard_selection->len); - break; - } -} - -static void -selection_clear_event (GtkWidget *widget, GdkEventSelection *event, FolderBrowser *fb) -{ - if (fb->clipboard_selection != NULL) { - g_byte_array_free (fb->clipboard_selection, TRUE); - fb->clipboard_selection = NULL; - } -} - -static void -selection_received (GtkWidget *widget, GtkSelectionData *selection_data, - guint time, FolderBrowser *fb) -{ - CamelFolder *source = NULL; - GPtrArray *uids = NULL; - - if (selection_data == NULL || selection_data->length == -1) - return; - - source = mail_tools_x_evolution_message_parse (selection_data->data, selection_data->length, &uids); - if (source == NULL) - return; - - if (uids == NULL) { - camel_object_unref (CAMEL_OBJECT (source)); - return; - } - - mail_transfer_messages (source, uids, FALSE, fb->uri, 0, NULL, NULL); - - camel_object_unref (CAMEL_OBJECT (source)); -} - -void -folder_browser_copy (GtkWidget *menuitem, FolderBrowser *fb) -{ - GPtrArray *uids = NULL; - GByteArray *bytes; - gboolean cut; - int i; - - if (fb->message_list == NULL) - return; - - cut = menuitem == NULL; - - if (GTK_WIDGET_HAS_FOCUS (fb->mail_display->html)) { - gtk_html_copy (fb->mail_display->html); - return; - } - - if (fb->clipboard_selection) { - g_byte_array_free (fb->clipboard_selection, TRUE); - fb->clipboard_selection = NULL; - } - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, add_uid, uids); - - /* format: "uri\0uid1\0uid2\0uid3\0...\0uidn" */ - - /* write the uri portion */ - bytes = g_byte_array_new (); - g_byte_array_append (bytes, fb->uri, strlen (fb->uri)); - g_byte_array_append (bytes, "", 1); - - /* write the uids */ - camel_folder_freeze (fb->folder); - for (i = 0; i < uids->len; i++) { - if (cut) { - camel_folder_set_message_flags (fb->folder, uids->pdata[i], - CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED, - CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED); - } - g_byte_array_append (bytes, uids->pdata[i], strlen (uids->pdata[i])); - g_free (uids->pdata[i]); - - if (i + 1 < uids->len) - g_byte_array_append (bytes, "", 1); - } - camel_folder_thaw (fb->folder); - - g_ptr_array_free (uids, TRUE); - - fb->clipboard_selection = bytes; - - gtk_selection_owner_set (fb->invisible, clipboard_atom, GDK_CURRENT_TIME); -} - -void -folder_browser_cut (GtkWidget *menuitem, FolderBrowser *fb) -{ - folder_browser_copy (NULL, fb); -} - -void -folder_browser_paste (GtkWidget *menuitem, FolderBrowser *fb) -{ - gtk_selection_convert (fb->invisible, clipboard_atom, - paste_types[0].target, - GDK_CURRENT_TIME); -} - -/* all this crap so we can give the user a whoopee doo status bar */ -static void -update_status_bar (FolderBrowser *fb) -{ - extern CamelFolder *outbox_folder, *sent_folder; - CORBA_Environment ev; - int tmp, total; - GString *work; - - if (fb->folder == NULL - || fb->message_list == NULL - || fb->shell_view == CORBA_OBJECT_NIL) - return; - - if (!fb->message_list->hidedeleted || !camel_folder_has_summary_capability (fb->folder)) { - total = camel_folder_get_message_count (fb->folder); - } else { - GPtrArray *sum = camel_folder_get_summary (fb->folder); - int i; - - if (sum) { - total = 0; - for (i = 0; i < sum->len; i++) { - CamelMessageInfo *info = sum->pdata[i]; - - if ((info->flags & CAMEL_MESSAGE_DELETED) == 0) - total++; - } - camel_folder_free_summary (fb->folder, sum); - } else { - total = camel_folder_get_message_count (fb->folder); - } - } - - work = g_string_new (""); - g_string_append_printf (work, _("%d new"), camel_folder_get_unread_message_count (fb->folder)); - tmp = message_list_hidden (fb->message_list); - if (0 < tmp && tmp < total) { - g_string_append (work, _(", ")); - if (tmp < total / 2) - g_string_append_printf (work, _("%d hidden"), tmp); - else - g_string_append_printf (work, _("%d visible"), total - tmp); - } - tmp = e_selection_model_selected_count (e_tree_get_selection_model (fb->message_list->tree)); - if (tmp) { - g_string_append (work, _(", ")); - g_string_append_printf (work, _("%d selected"), tmp); - } - g_string_append (work, _(", ")); - - if (fb->folder == outbox_folder) - g_string_append_printf (work, _("%d unsent"), total); - else if (fb->folder == sent_folder) - g_string_append_printf (work, _("%d sent"), total); - else - g_string_append_printf (work, _("%d total"), total); - - CORBA_exception_init (&ev); - GNOME_Evolution_ShellView_setFolderBarLabel (fb->shell_view, work->str, &ev); - CORBA_exception_free (&ev); - - if (fb->update_status_bar_idle_id != 0) { - g_source_remove (fb->update_status_bar_idle_id); - fb->update_status_bar_idle_id = 0; - } - - g_string_free (work, TRUE); -} - -static gboolean -update_status_bar_idle_cb(gpointer data) -{ - FolderBrowser *fb = data; - -#if 0 - if (!GTK_OBJECT_DESTROYED (fb)) -#endif - update_status_bar (fb); - - fb->update_status_bar_idle_id = 0; - g_object_unref (fb); - - return FALSE; -} - -static void -update_status_bar_idle(FolderBrowser *fb) -{ - if (fb->update_status_bar_idle_id == 0) { - g_object_ref (fb); - fb->update_status_bar_idle_id = g_idle_add (update_status_bar_idle_cb, fb); - } -} - -static void main_folder_changed(CamelObject *o, void *event_data, void *data) -{ - FolderBrowser *fb = data; - - if (fb->message_list == NULL) - return; - - /* so some corba unref doesnt blow us away while we're busy */ - g_object_ref (fb); - update_status_bar (fb); - folder_browser_ui_scan_selection (fb); - g_object_unref (fb); -} - -static void folder_changed (CamelObject *obj, void *event_data, void *user_data) -{ - FolderBrowser *fb = user_data; - - mail_async_event_emit (fb->async_event, MAIL_ASYNC_GUI, - (MailAsyncFunc) main_folder_changed, - obj, NULL, user_data); -} - -static void -got_folder (char *uri, CamelFolder *folder, void *user_data) -{ - FolderBrowser *fb = user_data; - EMeta *meta; - - fb->get_id = -1; - - d(printf ("got folder '%s' = %p, previous folder was %p\n", uri, folder, fb->folder)); - - if (fb->message_list == NULL) - goto done; - - if (fb->folder) { - camel_object_unhook_event (fb->folder, "folder_changed", folder_changed, fb); - camel_object_unhook_event (fb->folder, "message_changed", folder_changed, fb); - camel_object_unref (fb->folder); - } - - if (folder) { - fb->folder = folder; - camel_object_ref (folder); - meta = mail_tool_get_meta_data(fb->uri); - if (meta != fb->meta) { - g_object_unref(fb->meta); - fb->meta = meta; - } else { - g_object_unref(meta); - } - } else { - fb->folder = NULL; - if (fb->meta) { - g_object_unref(fb->meta); - fb->meta = NULL; - } - goto done; - } - - - gtk_widget_set_sensitive (GTK_WIDGET (fb->search), camel_folder_has_search_capability (folder)); - message_list_set_folder (fb->message_list, folder, - folder_browser_is_drafts (fb) || - folder_browser_is_sent (fb) || - folder_browser_is_outbox (fb)); - - camel_object_hook_event (CAMEL_OBJECT (fb->folder), "folder_changed", - folder_changed, fb); - camel_object_hook_event (CAMEL_OBJECT (fb->folder), "message_changed", - folder_changed, fb); - - if (fb->view_instance != NULL && fb->view_menus != NULL) - folder_browser_ui_discard_view_menus (fb); - - folder_browser_ui_setup_view_menus (fb); - - /* when loading a new folder, nothing is selected initially */ - - if (fb->uicomp) - folder_browser_ui_set_selection_state (fb, FB_SELSTATE_NONE); - - done: - g_signal_emit (fb, folder_browser_signals[FOLDER_LOADED], 0, fb->uri); - g_object_unref (fb); -} - - -void -folder_browser_reload (FolderBrowser *fb) -{ - g_return_if_fail (IS_FOLDER_BROWSER (fb)); - - if (fb->folder) { - mail_refresh_folder (fb->folder, NULL, NULL); - } else if (fb->uri && fb->get_id == -1) { - g_object_ref (fb); - fb->get_id = mail_get_folder (fb->uri, 0, got_folder, fb, mail_thread_new); - } -} - -void -folder_browser_set_folder (FolderBrowser *fb, CamelFolder *folder, const char *uri) -{ - g_return_if_fail (IS_FOLDER_BROWSER (fb)); - g_return_if_fail (CAMEL_IS_FOLDER (folder)); - - if (fb->get_id != -1) { - /* FIXME: cancel the get_folder request? */ - } - - g_free (fb->uri); - fb->uri = g_strdup (uri); - - g_object_ref (fb); - got_folder (NULL, folder, fb); -} - -void -folder_browser_set_ui_component (FolderBrowser *fb, BonoboUIComponent *uicomp) -{ - g_return_if_fail (IS_FOLDER_BROWSER (fb)); - - if (fb->sensitize_timeout_id) { - g_source_remove (fb->sensitize_timeout_id); - fb->sensitize_timeout_id = 0; - } - - if (fb->sensitise_state) { - g_hash_table_destroy (fb->sensitise_state); - fb->sensitise_state = NULL; - } - - if (fb->uicomp) - bonobo_object_unref (BONOBO_OBJECT (fb->uicomp)); - - if (uicomp) - bonobo_object_ref (BONOBO_OBJECT (uicomp)); - - fb->uicomp = uicomp; -} - -void -folder_browser_set_shell_view(FolderBrowser *fb, GNOME_Evolution_ShellView shell_view) -{ - CORBA_Environment ev; - - CORBA_exception_init(&ev); - if (fb->shell_view != CORBA_OBJECT_NIL) - CORBA_Object_release (fb->shell_view, &ev); - CORBA_exception_free (&ev); - - fb->shell_view = CORBA_Object_duplicate (shell_view, &ev); - CORBA_exception_free (&ev); - - /* small hack, at this point we've just been activated */ - if (fb->shell_view != CORBA_OBJECT_NIL) - update_status_bar (fb); -} - -extern CamelFolder *drafts_folder, *sent_folder, *outbox_folder; - -/** - * folder_browser_is_drafts: - * @fb: a FolderBrowser - * - * Return value: %TRUE if @fb refers to /local/Drafts or any other - * configured Drafts folder. - **/ -gboolean -folder_browser_is_drafts (FolderBrowser *fb) -{ - gboolean is_drafts = FALSE; - EAccountList *accounts; - EAccount *account; - EIterator *iter; - - g_return_val_if_fail (IS_FOLDER_BROWSER (fb), FALSE); - - if (fb->uri == NULL || fb->folder == NULL) - return FALSE; - - if (fb->folder == drafts_folder) - return TRUE; - - accounts = mail_config_get_accounts (); - iter = e_list_get_iterator ((EList *) accounts); - while (e_iterator_is_valid (iter)) { - account = (EAccount *) e_iterator_get (iter); - if (account->drafts_folder_uri && - camel_store_uri_cmp (fb->folder->parent_store, account->drafts_folder_uri, fb->uri)) { - is_drafts = TRUE; - break; - } - - e_iterator_next (iter); - } - - g_object_unref (iter); - - return is_drafts; -} - -/** - * folder_browser_is_sent: - * @fb: a FolderBrowser - * - * Return value: %TRUE if @fb refers to /local/Sent or any other - * configured Sent folder. - **/ -gboolean -folder_browser_is_sent (FolderBrowser *fb) -{ - gboolean is_sent = FALSE; - EAccountList *accounts; - EAccount *account; - EIterator *iter; - - g_return_val_if_fail (IS_FOLDER_BROWSER (fb), FALSE); - - if (fb->uri == NULL || fb->folder == NULL) - return FALSE; - - if (fb->folder == sent_folder) - return TRUE; - - accounts = mail_config_get_accounts (); - iter = e_list_get_iterator ((EList *) accounts); - while (e_iterator_is_valid (iter)) { - account = (EAccount *) e_iterator_get (iter); - if (account->sent_folder_uri && - camel_store_uri_cmp (fb->folder->parent_store, account->sent_folder_uri, fb->uri)) { - is_sent = TRUE; - break; - } - - e_iterator_next (iter); - } - - g_object_unref (iter); - - return is_sent; -} - -/** - * folder_browser_is_outbox: - * @fb: a FolderBrowser - * - * Return value: %TRUE if @fb refers to /local/Outbox or any other - * configured Outbox folder. - **/ -gboolean -folder_browser_is_outbox (FolderBrowser *fb) -{ - /* There can be only one. */ - return fb->folder == outbox_folder; -} - -static int -save_cursor_pos (FolderBrowser *fb) -{ - ETreePath node; - GtkAdjustment *adj; - int row, y, height, paned_size; - GConfClient *gconf; - - node = e_tree_get_cursor (fb->message_list->tree); - if (!node) - return -1; - - row = e_tree_row_of_node (fb->message_list->tree, node); - - if (row == -1) - return 0; - - e_tree_get_cell_geometry (fb->message_list->tree, row, 0, - NULL, &y, NULL, &height); - - gconf = mail_config_get_gconf_client (); - paned_size = gconf_client_get_int (gconf, "/apps/evolution/mail/display/paned_size", NULL); - - adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (fb->message_list)); - y += adj->value - ((paned_size - height) / 2); - - return y; -} - -static void -set_cursor_pos (FolderBrowser *fb, int y) -{ - GtkAdjustment *adj; - - if (y == -1) - return; - - adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (fb->message_list)); - gtk_adjustment_set_value (adj, (gfloat)y); -} - -static gboolean do_message_selected(FolderBrowser *fb); - -void -folder_browser_set_message_preview (FolderBrowser *folder_browser, gboolean show_preview) -{ - GConfClient *gconf; - int paned_size, y; - - if (folder_browser->preview_shown == show_preview - || folder_browser->message_list == NULL) - return; - - folder_browser->preview_shown = show_preview; - - gconf = mail_config_get_gconf_client (); - paned_size = gconf_client_get_int (gconf, "/apps/evolution/mail/display/paned_size", NULL); - - if (show_preview) { - y = save_cursor_pos (folder_browser); - gtk_paned_set_position (GTK_PANED (folder_browser->vpaned), paned_size); - gtk_widget_show (GTK_WIDGET (folder_browser->mail_display)); - do_message_selected (folder_browser); - set_cursor_pos (folder_browser, y); - } else { - gtk_widget_hide (GTK_WIDGET (folder_browser->mail_display)); - mail_display_set_message (folder_browser->mail_display, NULL, NULL, NULL); - folder_browser_ui_message_loaded (folder_browser); - } -} - -enum { - ESB_SAVE, -}; - -static ESearchBarItem folder_browser_search_menu_items[] = { - E_FILTERBAR_ADVANCED, - { NULL, 0, NULL }, - E_FILTERBAR_SAVE, - E_FILTERBAR_EDIT, - { NULL, 0, NULL }, - { N_("Create _Virtual Folder From Search..."), ESB_SAVE, NULL }, - { NULL, -1, NULL } -}; - -static void -folder_browser_search_menu_activated (ESearchBar *esb, int id, FolderBrowser *fb) -{ - EFilterBar *efb = (EFilterBar *)esb; - - d(printf("menu activated\n")); - - switch (id) { - case ESB_SAVE: - d(printf("Save vfolder\n")); - if (efb->current_query) { - FilterRule *rule = vfolder_clone_rule(efb->current_query); - char *name, *text; - - text = e_search_bar_get_text(esb); - name = g_strdup_printf("%s %s", rule->name, (text&&text[0])?text:"''"); - g_free (text); - filter_rule_set_name(rule, name); - g_free (name); - - filter_rule_set_source(rule, FILTER_SOURCE_INCOMING); - vfolder_rule_add_source((VfolderRule *)rule, fb->uri); - vfolder_gui_add_rule((VfolderRule *)rule); - } - break; - } -} - -static void -folder_browser_config_search (EFilterBar *efb, FilterRule *rule, int id, const char *query, void *data) -{ - FolderBrowser *fb = FOLDER_BROWSER (data); - ESearchingTokenizer *st; - GList *partl; - struct _camel_search_words *words; - int i; - - st = E_SEARCHING_TOKENIZER (fb->mail_display->html->engine->ht); - - e_searching_tokenizer_set_secondary_search_string (st, NULL); - - /* we scan the parts of a rule, and set all the types we know about to the query string */ - partl = rule->parts; - while (partl) { - FilterPart *part = partl->data; - - if (!strcmp(part->name, "subject")) { - FilterInput *input = (FilterInput *)filter_part_find_element(part, "subject"); - if (input) - filter_input_set_value(input, query); - } else if (!strcmp(part->name, "body")) { - FilterInput *input = (FilterInput *)filter_part_find_element(part, "word"); - if (input) - filter_input_set_value(input, query); - - words = camel_search_words_split(query); - for (i=0;i<words->len;i++) - e_searching_tokenizer_add_secondary_search_string (st, words->words[i]->word); - camel_search_words_free (words); - } else if(!strcmp(part->name, "sender")) { - FilterInput *input = (FilterInput *)filter_part_find_element(part, "sender"); - if (input) - filter_input_set_value(input, query); - } else if(!strcmp(part->name, "to")) { - FilterInput *input = (FilterInput *)filter_part_find_element(part, "recipient"); - if (input) - filter_input_set_value(input, query); - } - - partl = partl->next; - } - - d(printf("configuring search for search string '%s', rule is '%s'\n", query, rule->name)); - - mail_display_redisplay (fb->mail_display, FALSE); -} - -static void -folder_browser_search_do_search (ESearchBar *esb, FolderBrowser *fb) -{ - char *search_word; - - if (fb->message_list == NULL) - return; - - d(printf("do search\n")); - - g_object_get (esb, "query", &search_word, NULL); - - message_list_set_search (fb->message_list, search_word); - - d(printf("query is %s\n", search_word)); - g_free (search_word); -} - -static void -folder_browser_query_changed (ESearchBar *esb, FolderBrowser *fb) -{ - int id; - - id = e_search_bar_get_item_id (esb); - if (id == E_FILTERBAR_ADVANCED_ID) - folder_browser_search_do_search (esb, fb); -} - -void -folder_browser_toggle_preview (BonoboUIComponent *component, - const char *path, - Bonobo_UIComponent_EventType type, - const char *state, - gpointer user_data) -{ - FolderBrowser *fb = user_data; - gboolean bstate; - GConfClient *gconf; - - if (type != Bonobo_UIComponent_STATE_CHANGED || fb->message_list == NULL) - return; - - bstate = atoi(state); - e_meta_set_bool(fb->meta, "show_preview", bstate); - - gconf = mail_config_get_gconf_client (); - gconf_client_set_bool (gconf, "/apps/evolution/mail/display/show_preview", bstate, NULL); - - folder_browser_set_message_preview (fb, bstate); -} - -void -folder_browser_toggle_threads (BonoboUIComponent *component, - const char *path, - Bonobo_UIComponent_EventType type, - const char *state, - gpointer user_data) -{ - FolderBrowser *fb = user_data; - int prev_state; - gboolean bstate; - GConfClient *gconf; - - if (type != Bonobo_UIComponent_STATE_CHANGED || fb->message_list == NULL) - return; - - bstate = atoi(state); - e_meta_set_bool(fb->meta, "thread_list", bstate); - - gconf = mail_config_get_gconf_client (); - gconf_client_set_bool (gconf, "/apps/evolution/mail/display/thread_list", bstate, NULL); - - message_list_set_threaded (fb->message_list, bstate); - - prev_state = fb->selection_state; - fb->selection_state = FB_SELSTATE_UNDEFINED; - folder_browser_ui_set_selection_state (fb, prev_state); -} - -void -folder_browser_toggle_hide_deleted (BonoboUIComponent *component, - const char *path, - Bonobo_UIComponent_EventType type, - const char *state, - gpointer user_data) -{ - FolderBrowser *fb = user_data; - GConfClient *gconf; - - if (type != Bonobo_UIComponent_STATE_CHANGED || fb->message_list == NULL) - return; - - gconf = mail_config_get_gconf_client (); - gconf_client_set_bool (gconf, "/apps/evolution/mail/display/show_deleted", - !atoi (state), NULL); - - if (!(fb->folder && (fb->folder->folder_flags & CAMEL_FOLDER_IS_TRASH))) - message_list_set_hidedeleted (fb->message_list, atoi (state)); -} - -void -folder_browser_toggle_caret_mode(BonoboUIComponent *component, - const char * path, - Bonobo_UIComponent_EventType type, - const char * state, - gpointer user_data) -{ - GConfClient *gconf; - - if (type != Bonobo_UIComponent_STATE_CHANGED) - return; - - gconf = mail_config_get_gconf_client (); - gconf_client_set_bool (gconf, "/apps/evolution/mail/display/caret_mode", - atoi(state), NULL); -} - -void -folder_browser_set_message_display_style (BonoboUIComponent *component, - const char *path, - Bonobo_UIComponent_EventType type, - const char *state, - gpointer user_data) -{ - extern char *message_display_styles[]; - FolderBrowser *fb = user_data; - GConfClient *gconf; - int i; - - if (type != Bonobo_UIComponent_STATE_CHANGED - || atoi (state) == 0 - || fb->message_list == NULL) - return; - - gconf = mail_config_get_gconf_client (); - - for (i = 0; i < MAIL_CONFIG_DISPLAY_MAX; i++) { - if (strstr (message_display_styles[i], path)) { - fb->mail_display->display_style = i; - mail_display_redisplay (fb->mail_display, TRUE); - - if (fb->pref_master) - gconf_client_set_int (gconf, "/apps/evolution/mail/display/message_style", i, NULL); - - return; - } - } -} - -void -folder_browser_charset_changed (BonoboUIComponent *component, - const char *path, - Bonobo_UIComponent_EventType type, - const char *state, - gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - const char *charset; - - if (type != Bonobo_UIComponent_STATE_CHANGED - || fb->message_list == NULL) - return; - - if (atoi (state)) { - /* Charset menu names are "Charset-%s" where %s is the charset name */ - charset = path + strlen ("Charset-"); - if (!strcmp (charset, FB_DEFAULT_CHARSET)) - charset = NULL; - - mail_display_set_charset (fb->mail_display, charset); - } -} - -static void vfolder_type_uid(CamelFolder *folder, const char *uid, const char *uri, int type); - -static void -vfolder_type_current(FolderBrowser *fb, int type) -{ - GPtrArray *uids; - int i; - - /* get uid */ - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - if (uids->len == 1) - vfolder_type_uid (fb->folder, (char *)uids->pdata[0], fb->uri, type); - - for (i = 0; i < uids->len; i++) - g_free (uids->pdata[i]); - g_ptr_array_free (uids, TRUE); -} - -/* external api to vfolder/filter on X, based on current message */ -void vfolder_subject (GtkWidget *w, FolderBrowser *fb) { vfolder_type_current(fb, AUTO_SUBJECT); } -void vfolder_sender (GtkWidget *w, FolderBrowser *fb) { vfolder_type_current(fb, AUTO_FROM); } -void vfolder_recipient (GtkWidget *w, FolderBrowser *fb) { vfolder_type_current(fb, AUTO_TO); } -void vfolder_mlist (GtkWidget *w, FolderBrowser *fb) { vfolder_type_current(fb, AUTO_MLIST); } - -static void filter_type_uid (CamelFolder *folder, const char *uid, const char *source, int type); - -static void -filter_type_current (FolderBrowser *fb, int type) -{ - GPtrArray *uids; - int i; - const char *source; - - if (folder_browser_is_sent (fb) || folder_browser_is_outbox (fb)) - source = FILTER_SOURCE_OUTGOING; - else - source = FILTER_SOURCE_INCOMING; - - /* get uid */ - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - if (uids->len == 1) - filter_type_uid (fb->folder, (char *)uids->pdata[0], source, type); - - for (i = 0; i < uids->len; i++) - g_free (uids->pdata[i]); - g_ptr_array_free (uids, TRUE); -} - -void filter_subject (GtkWidget *w, FolderBrowser *fb) { filter_type_current (fb, AUTO_SUBJECT); } -void filter_sender (GtkWidget *w, FolderBrowser *fb) { filter_type_current (fb, AUTO_FROM); } -void filter_recipient (GtkWidget *w, FolderBrowser *fb) { filter_type_current (fb, AUTO_TO); } -void filter_mlist (GtkWidget *w, FolderBrowser *fb) { filter_type_current (fb, AUTO_MLIST); } - -/* ************************************************************ */ - -/* popup api to vfolder/filter on X, based on current selection */ -struct _filter_data { - CamelFolder *folder; - const char *source; - char *uid; - int type; - char *uri; - char *mlist; -}; - -static void -filter_data_free (struct _filter_data *fdata) -{ - g_free (fdata->uid); - g_free (fdata->uri); - if (fdata->folder) - camel_object_unref (fdata->folder); - g_free (fdata->mlist); - g_free (fdata); -} - -static void -vfolder_type_got_message(CamelFolder *folder, const char *uid, CamelMimeMessage *msg, void *d) -{ - struct _filter_data *data = d; - - if (msg) - vfolder_gui_add_from_message(msg, data->type, data->uri); - - filter_data_free (data); -} - -static void -vfolder_type_uid(CamelFolder *folder, const char *uid, const char *uri, int type) -{ - struct _filter_data *data; - - data = g_malloc0(sizeof(*data)); - data->type = type; - data->uri = g_strdup(uri); - mail_get_message(folder, uid, vfolder_type_got_message, data, mail_thread_new); -} - -static void vfolder_subject_uid (GtkWidget *w, struct _filter_data *fdata) { vfolder_type_uid(fdata->folder, fdata->uid, fdata->uri, AUTO_SUBJECT); } -static void vfolder_sender_uid(GtkWidget *w, struct _filter_data *fdata) { vfolder_type_uid(fdata->folder, fdata->uid, fdata->uri, AUTO_FROM); } -static void vfolder_recipient_uid(GtkWidget *w, struct _filter_data *fdata) { vfolder_type_uid(fdata->folder, fdata->uid, fdata->uri, AUTO_TO); } -static void vfolder_mlist_uid(GtkWidget *w, struct _filter_data *fdata) { vfolder_type_uid(fdata->folder, fdata->uid, fdata->uri, AUTO_MLIST); } - -static void -filter_type_got_message(CamelFolder *folder, const char *uid, CamelMimeMessage *msg, void *d) -{ - struct _filter_data *data = d; - - if (msg) - filter_gui_add_from_message(msg, data->source, data->type); - - filter_data_free (data); -} - -static void -filter_type_uid(CamelFolder *folder, const char *uid, const char *source, int type) -{ - struct _filter_data *data; - - data = g_malloc0(sizeof(*data)); - data->type = type; - data->source = source; - mail_get_message(folder, uid, filter_type_got_message, data, mail_thread_new); -} - -static void filter_subject_uid (GtkWidget *w, struct _filter_data *fdata) { filter_type_uid(fdata->folder, fdata->uid, fdata->source, AUTO_SUBJECT); } -static void filter_sender_uid(GtkWidget *w, struct _filter_data *fdata) { filter_type_uid(fdata->folder, fdata->uid, fdata->source, AUTO_FROM); } -static void filter_recipient_uid(GtkWidget *w, struct _filter_data *fdata) { filter_type_uid(fdata->folder, fdata->uid, fdata->source, AUTO_TO); } -static void filter_mlist_uid(GtkWidget *w, struct _filter_data *fdata) { filter_type_uid(fdata->folder, fdata->uid, fdata->source, AUTO_MLIST); } - -void -hide_none(GtkWidget *w, FolderBrowser *fb) -{ - message_list_hide_clear (fb->message_list); -} - -void -hide_selected(GtkWidget *w, FolderBrowser *fb) -{ - GPtrArray *uids; - int i; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - message_list_hide_uids (fb->message_list, uids); - for (i = 0; i < uids->len; i++) - g_free (uids->pdata[i]); - g_ptr_array_free (uids, TRUE); -} - -void -hide_deleted(GtkWidget *w, FolderBrowser *fb) -{ - MessageList *ml = fb->message_list; - - message_list_hide_add(ml, "(match-all (system-flag \"deleted\"))", ML_HIDE_SAME, ML_HIDE_SAME); -} - -void -hide_read(GtkWidget *w, FolderBrowser *fb) -{ - MessageList *ml = fb->message_list; - - message_list_hide_add(ml, "(match-all (system-flag \"seen\"))", ML_HIDE_SAME, ML_HIDE_SAME); -} - -/* dum de dum, about the 3rd copy of this function throughout the mailer/camel */ -static const char * -strip_re(const char *subject) -{ - const unsigned char *s, *p; - - s = (unsigned char *) subject; - - while (*s) { - while(isspace (*s)) - s++; - if (s[0] == 0) - break; - if ((s[0] == 'r' || s[0] == 'R') - && (s[1] == 'e' || s[1] == 'E')) { - p = s+2; - while (isdigit(*p) || (ispunct(*p) && (*p != ':'))) - p++; - if (*p == ':') { - s = p + 1; - } else - break; - } else - break; - } - return (char *) s; -} - -void -hide_subject(GtkWidget *w, FolderBrowser *fb) -{ - const char *subject; - GString *expr; - - if (fb->mail_display->current_message) { - subject = camel_mime_message_get_subject(fb->mail_display->current_message); - if (subject) { - subject = strip_re(subject); - if (subject && subject[0]) { - expr = g_string_new ("(match-all (header-contains \"subject\" "); - e_sexp_encode_string (expr, subject); - g_string_append (expr, "))"); - message_list_hide_add (fb->message_list, expr->str, ML_HIDE_SAME, ML_HIDE_SAME); - g_string_free (expr, TRUE); - return; - } - } - } -} - -void -hide_sender (GtkWidget *w, FolderBrowser *fb) -{ - const CamelInternetAddress *from; - const char *real, *addr; - GString *expr; - - if (fb->mail_display->current_message) { - from = camel_mime_message_get_from (fb->mail_display->current_message); - if (camel_internet_address_get (from, 0, &real, &addr)) { - expr = g_string_new ("(match-all (header-contains \"from\" "); - e_sexp_encode_string (expr, addr); - g_string_append (expr, "))"); - message_list_hide_add (fb->message_list, expr->str, ML_HIDE_SAME, ML_HIDE_SAME); - g_string_free (expr, TRUE); - return; - } - } -} - -struct _label_data { - FolderBrowser *fb; - const char *label; -}; - -static void -set_msg_label (GtkWidget *widget, gpointer user_data) -{ - struct _label_data *data = user_data; - GPtrArray *uids; - int i; - - uids = g_ptr_array_new (); - message_list_foreach (data->fb->message_list, enumerate_msg, uids); - for (i = 0; i < uids->len; i++) - camel_folder_set_message_user_tag (data->fb->folder, uids->pdata[i], "label", data->label); - g_ptr_array_free (uids, TRUE); -} - -static void -label_closures_free (GPtrArray *closures) -{ - struct _label_data *data; - int i; - - for (i = 0; i < closures->len; i++) { - data = closures->pdata[i]; - g_object_unref (data->fb); - g_free (data); - } - g_ptr_array_free (closures, TRUE); -} - -static void -mark_as_seen_cb (GtkWidget *widget, void *user_data) -{ - mark_as_seen (NULL, user_data, NULL); -} - -static void -mark_as_unseen_cb (GtkWidget *widget, void *user_data) -{ - mark_as_unseen (NULL, user_data, NULL); -} - -static void -mark_as_important_cb (GtkWidget *widget, void *user_data) -{ - mark_as_important (NULL, user_data, NULL); -} - -static void -mark_as_unimportant_cb (GtkWidget *widget, void *user_data) -{ - mark_as_unimportant (NULL, user_data, NULL); -} - -enum { - SELECTION_SET = 1<<1, - CAN_MARK_READ = 1<<2, - CAN_MARK_UNREAD = 1<<3, - CAN_DELETE = 1<<4, - CAN_UNDELETE = 1<<5, - IS_MAILING_LIST = 1<<6, - CAN_RESEND = 1<<7, - CAN_MARK_IMPORTANT = 1<<8, - CAN_MARK_UNIMPORTANT = 1<<9, - CAN_FLAG_FOR_FOLLOWUP = 1<<10, - CAN_FLAG_COMPLETED = 1<<11, - CAN_CLEAR_FLAG = 1<<12, - CAN_ADD_SENDER = 1<<13 -}; - -#define MLIST_VFOLDER (3) -#define MLIST_FILTER (8) - -static EPopupMenu filter_menu[] = { - E_POPUP_ITEM_CC (N_("VFolder on _Subject"), G_CALLBACK (vfolder_subject_uid), NULL, SELECTION_SET), - E_POPUP_ITEM_CC (N_("VFolder on Se_nder"), G_CALLBACK (vfolder_sender_uid), NULL, SELECTION_SET), - E_POPUP_ITEM_CC (N_("VFolder on _Recipients"), G_CALLBACK (vfolder_recipient_uid), NULL, SELECTION_SET), - E_POPUP_ITEM_CC (N_("VFolder on Mailing _List"), G_CALLBACK (vfolder_mlist_uid), NULL, SELECTION_SET | IS_MAILING_LIST), - - E_POPUP_SEPARATOR, - - E_POPUP_ITEM_CC (N_("Filter on Sub_ject"), G_CALLBACK (filter_subject_uid), NULL, SELECTION_SET), - E_POPUP_ITEM_CC (N_("Filter on Sen_der"), G_CALLBACK (filter_sender_uid), NULL, SELECTION_SET), - E_POPUP_ITEM_CC (N_("Filter on Re_cipients"), G_CALLBACK (filter_recipient_uid), NULL, SELECTION_SET), - E_POPUP_ITEM_CC (N_("Filter on _Mailing List"), G_CALLBACK (filter_mlist_uid), NULL, SELECTION_SET | IS_MAILING_LIST), - - E_POPUP_TERMINATOR -}; - -static EPopupMenu label_menu[] = { - E_POPUP_PIXMAP_WIDGET_ITEM_CC (N_("None"), NULL, G_CALLBACK (set_msg_label), NULL, 0), - E_POPUP_SEPARATOR, - E_POPUP_PIXMAP_WIDGET_ITEM_CC (NULL, NULL, G_CALLBACK (set_msg_label), NULL, 0), - E_POPUP_PIXMAP_WIDGET_ITEM_CC (NULL, NULL, G_CALLBACK (set_msg_label), NULL, 0), - E_POPUP_PIXMAP_WIDGET_ITEM_CC (NULL, NULL, G_CALLBACK (set_msg_label), NULL, 0), - E_POPUP_PIXMAP_WIDGET_ITEM_CC (NULL, NULL, G_CALLBACK (set_msg_label), NULL, 0), - E_POPUP_PIXMAP_WIDGET_ITEM_CC (NULL, NULL, G_CALLBACK (set_msg_label), NULL, 0), - E_POPUP_TERMINATOR -}; - -static EPopupMenu context_menu[] = { - E_POPUP_ITEM (N_("_Open"), G_CALLBACK (open_msg), 0), - E_POPUP_ITEM (N_("_Edit as New Message..."), G_CALLBACK (resend_msg), CAN_RESEND), - E_POPUP_ITEM (N_("_Save As..."), G_CALLBACK (save_msg), 0), - E_POPUP_ITEM (N_("_Print"), G_CALLBACK (print_msg), 0), - - E_POPUP_SEPARATOR, - - E_POPUP_ITEM (N_("_Reply to Sender"), G_CALLBACK (reply_to_sender), 0), - E_POPUP_ITEM (N_("Reply to _List"), G_CALLBACK (reply_to_list), 0), - E_POPUP_ITEM (N_("Reply to _All"), G_CALLBACK (reply_to_all), 0), - E_POPUP_ITEM (N_("_Forward"), G_CALLBACK (forward), 0), - - E_POPUP_SEPARATOR, - - E_POPUP_ITEM (N_("Follo_w Up..."), G_CALLBACK (flag_for_followup), CAN_FLAG_FOR_FOLLOWUP), - E_POPUP_ITEM (N_("Fla_g Completed"), G_CALLBACK (flag_followup_completed), CAN_FLAG_COMPLETED), - E_POPUP_ITEM (N_("Cl_ear Flag"), G_CALLBACK (flag_followup_clear), CAN_CLEAR_FLAG), - - /* separator here? */ - - E_POPUP_ITEM (N_("Mar_k as Read"), G_CALLBACK (mark_as_seen_cb), CAN_MARK_READ), - E_POPUP_ITEM (N_("Mark as _Unread"), G_CALLBACK (mark_as_unseen_cb), CAN_MARK_UNREAD), - E_POPUP_ITEM (N_("Mark as _Important"), G_CALLBACK (mark_as_important_cb), CAN_MARK_IMPORTANT), - E_POPUP_ITEM (N_("_Mark as Unimportant"), G_CALLBACK (mark_as_unimportant_cb), CAN_MARK_UNIMPORTANT), - - E_POPUP_SEPARATOR, - - E_POPUP_ITEM (N_("_Delete"), G_CALLBACK (delete_msg), CAN_DELETE), - E_POPUP_ITEM (N_("U_ndelete"), G_CALLBACK (undelete_msg), CAN_UNDELETE), - - E_POPUP_SEPARATOR, - - E_POPUP_ITEM (N_("Mo_ve to Folder..."), G_CALLBACK (move_msg_cb), 0), - E_POPUP_ITEM (N_("_Copy to Folder..."), G_CALLBACK (copy_msg_cb), 0), - - E_POPUP_SEPARATOR, - - E_POPUP_SUBMENU (N_("Label"), label_menu, 0), - - E_POPUP_SEPARATOR, - - E_POPUP_ITEM (N_("Add Sender to Address_book"), G_CALLBACK (addrbook_sender), SELECTION_SET | CAN_ADD_SENDER), - - E_POPUP_SEPARATOR, - - E_POPUP_ITEM (N_("Appl_y Filters"), G_CALLBACK (apply_filters), 0), - - E_POPUP_SEPARATOR, - - E_POPUP_SUBMENU (N_("Crea_te Rule From Message"), filter_menu, SELECTION_SET), - - E_POPUP_TERMINATOR -}; - -/* Note: this must be kept in sync with the context_menu!!! */ -static char *context_pixmaps[] = { - NULL, /* Open */ - NULL, /* Edit */ - "save-as-16.png", - "print.xpm", - NULL, - "reply.xpm", - NULL, /* Reply to List */ - "reply_to_all.xpm", - "forward.xpm", - NULL, - "flag-for-followup-16.png", - NULL, /* Flag */ - NULL, /* Clear */ - "mail-read.xpm", - "mail-new.xpm", - "priority-high.xpm", - NULL, /* Mark as Unimportant */ - NULL, - "evolution-trash-mini.png", - "undelete_message-16.png", - NULL, - "move_message.xpm", - "copy_16_message.xpm", - NULL, - NULL, /* Label */ - NULL, - NULL, /* Add Sender to Addressbook */ - NULL, - NULL, /* Apply Filters */ - NULL, - NULL, /* Create Rule from Message */ -}; - -struct cmpf_data { - ETree *tree; - int row, col; -}; - -static void -context_menu_position_func (GtkMenu *menu, gint *x, gint *y, - gboolean *push_in, gpointer user_data) -{ - int tx, ty, tw, th; - struct cmpf_data *closure = user_data; - - gdk_window_get_origin (GTK_WIDGET (closure->tree)->window, x, y); - e_tree_get_cell_geometry (closure->tree, closure->row, closure->col, - &tx, &ty, &tw, &th); - *x += tx + tw / 2; - *y += ty + th / 2; -} - -static void -setup_popup_icons (void) -{ - int i; - - for (i = 0; context_menu[i].name; i++) { - if (context_pixmaps[i]) { - char *filename; - - filename = g_strdup_printf ("%s/%s", EVOLUTION_IMAGES, context_pixmaps[i]); - context_menu[i].pixmap_widget = gtk_image_new_from_file (filename); - g_free (filename); - } - } -} - -/* handle context menu over message-list */ -static int -on_right_click (ETree *tree, gint row, ETreePath path, gint col, GdkEvent *event, FolderBrowser *fb) -{ - struct _filter_data *fdata = NULL; - GPtrArray *uids, *closures; - CamelMessageInfo *info; - GSList *labels; - int enable_mask = 0; - int hide_mask = 0; - char *mlist = NULL; - GtkMenu *menu; - int i; - - if (!folder_browser_is_sent (fb)) { - enable_mask |= CAN_RESEND; - hide_mask |= CAN_RESEND; - } else { - enable_mask |= CAN_ADD_SENDER; - hide_mask |= CAN_ADD_SENDER; - } - - if (folder_browser_is_drafts (fb)) { - enable_mask |= CAN_ADD_SENDER; - hide_mask |= CAN_ADD_SENDER; - } - - if (fb->folder == outbox_folder) { - enable_mask |= CAN_ADD_SENDER; - hide_mask |= CAN_ADD_SENDER; - } - - enable_mask |= SELECTION_SET; - - /* get a list of uids */ - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - if (uids->len >= 1) { - /* gray-out any items we don't need */ - gboolean have_deleted = FALSE; - gboolean have_undeleted = FALSE; - gboolean have_seen = FALSE; - gboolean have_unseen = FALSE; - gboolean have_important = FALSE; - gboolean have_unimportant = FALSE; - gboolean have_flag_for_followup = FALSE; - gboolean have_flag_completed = FALSE; - gboolean have_flag_incomplete = FALSE; - gboolean have_unflagged = FALSE; - const char *tag; - - for (i = 0; i < uids->len; i++) { - info = camel_folder_get_message_info (fb->folder, uids->pdata[i]); - if (info == NULL) - continue; - - if (i == 0 && uids->len == 1) { - const char *mname, *p; - char c, *o; - - /* used by filter/vfolder from X callbacks */ - fdata = g_malloc0(sizeof(*fdata)); - fdata->uid = g_strdup(uids->pdata[i]); - fdata->uri = g_strdup(fb->uri); - fdata->folder = fb->folder; - camel_object_ref((CamelObject *)fdata->folder); - if (folder_browser_is_sent (fb) || folder_browser_is_outbox (fb)) - fdata->source = FILTER_SOURCE_OUTGOING; - else - fdata->source = FILTER_SOURCE_INCOMING; - - enable_mask &= ~SELECTION_SET; - mname = camel_message_info_mlist(info); - if (mname && mname[0]) { - fdata->mlist = g_strdup(mname); - - /* Escape the mailing list name before showing it */ - mlist = g_alloca ((strlen (mname) * 2) + 1); - p = mname; - o = mlist; - while ((c = *p++)) { - if (c == '_') - *o++ = '_'; - *o++ = c; - } - *o = 0; - } - } - - if (info->flags & CAMEL_MESSAGE_SEEN) - have_seen = TRUE; - else - have_unseen = TRUE; - - if (info->flags & CAMEL_MESSAGE_DELETED) - have_deleted = TRUE; - else - have_undeleted = TRUE; - - if (info->flags & CAMEL_MESSAGE_FLAGGED) - have_important = TRUE; - else - have_unimportant = TRUE; - - tag = camel_tag_get (&info->user_tags, "follow-up"); - if (tag && *tag) { - have_flag_for_followup = TRUE; - tag = camel_tag_get (&info->user_tags, "completed-on"); - if (tag && *tag) - have_flag_completed = TRUE; - else - have_flag_incomplete = TRUE; - } else - have_unflagged = TRUE; - - camel_folder_free_message_info (fb->folder, info); - - if (have_seen && have_unseen && have_deleted && have_undeleted) - break; - } - - if (!have_unseen) - enable_mask |= CAN_MARK_READ; - if (!have_seen) - enable_mask |= CAN_MARK_UNREAD; - - if (!have_undeleted) - enable_mask |= CAN_DELETE; - if (!have_deleted) - enable_mask |= CAN_UNDELETE; - - if (!have_unimportant) - enable_mask |= CAN_MARK_IMPORTANT; - if (!have_important) - enable_mask |= CAN_MARK_UNIMPORTANT; - - if (!have_flag_for_followup) - enable_mask |= CAN_CLEAR_FLAG; - if (!have_unflagged) - enable_mask |= CAN_FLAG_FOR_FOLLOWUP; - if (!have_flag_incomplete) - enable_mask |= CAN_FLAG_COMPLETED; - - /* - * Hide items that wont get used. - */ - if (!(have_unseen && have_seen)) { - if (have_seen) - hide_mask |= CAN_MARK_READ; - else - hide_mask |= CAN_MARK_UNREAD; - } - - if (!(have_undeleted && have_deleted)) { - if (have_deleted) - hide_mask |= CAN_DELETE; - else - hide_mask |= CAN_UNDELETE; - } - - if (!(have_important && have_unimportant)) { - if (have_important) - hide_mask |= CAN_MARK_IMPORTANT; - else - hide_mask |= CAN_MARK_UNIMPORTANT; - } - - if (!have_flag_for_followup) - hide_mask |= CAN_CLEAR_FLAG; - if (!have_unflagged) - hide_mask |= CAN_FLAG_FOR_FOLLOWUP; - if (!have_flag_incomplete) - hide_mask |= CAN_FLAG_COMPLETED; - } - - /* free uids */ - for (i = 0; i < uids->len; i++) - g_free (uids->pdata[i]); - g_ptr_array_free (uids, TRUE); - - /* generate the "Filter on Mailing List" menu item name */ - if (mlist == NULL) { - enable_mask |= IS_MAILING_LIST; - filter_menu[MLIST_FILTER].name = g_strdup (_("Filter on _Mailing List")); - filter_menu[MLIST_VFOLDER].name = g_strdup (_("VFolder on M_ailing List")); - } else { - filter_menu[MLIST_FILTER].name = g_strdup_printf (_("Filter on _Mailing List (%s)"), mlist); - filter_menu[MLIST_VFOLDER].name = g_strdup_printf (_("VFolder on M_ailing List (%s)"), mlist); - } - - /* create the label/colour menu */ - closures = g_ptr_array_new (); - label_menu[0].closure = g_new (struct _label_data, 1); - g_ptr_array_add (closures, label_menu[0].closure); - g_object_ref (fb); - ((struct _label_data *) label_menu[0].closure)->fb = fb; - ((struct _label_data *) label_menu[0].closure)->label = NULL; - - i = 0; - labels = mail_config_get_labels (); - while (labels != NULL && i < 5) { - struct _label_data *closure; - MailConfigLabel *label; - GdkPixmap *pixmap; - GdkColormap *map; - GdkColor colour; - GdkGC *gc; - - label = labels->data; - gdk_color_parse (label->colour, &colour); - map = gdk_colormap_get_system (); - gdk_color_alloc (map, &colour); - - pixmap = gdk_pixmap_new (GTK_WIDGET (fb)->window, 16, 16, -1); - gc = gdk_gc_new (GTK_WIDGET (fb)->window); - gdk_gc_set_foreground (gc, &colour); - gdk_draw_rectangle (pixmap, gc, TRUE, 0, 0, 16, 16); - gdk_gc_unref (gc); - - closure = g_new (struct _label_data, 1); - g_object_ref (fb); - closure->fb = fb; - closure->label = label->tag; - - g_ptr_array_add (closures, closure); - - label_menu[i + 2].name = label->name; - label_menu[i + 2].pixmap_widget = gtk_image_new_from_pixmap (pixmap, NULL); - label_menu[i + 2].closure = closure; - - i++; - labels = labels->next; - } - - setup_popup_icons (); - - for (i = 0; i < sizeof (filter_menu) / sizeof (filter_menu[0]); i++) - filter_menu[i].closure = fdata; - - menu = e_popup_menu_create (context_menu, enable_mask, hide_mask, fb); - e_auto_kill_popup_menu_on_selection_done (menu); - - g_object_set_data_full ((GObject *) menu, "label_closures", closures, (GtkDestroyNotify) label_closures_free); - - if (fdata) - g_object_set_data_full ((GObject *) menu, "filter_data", fdata, (GtkDestroyNotify) filter_data_free); - - if (event->type == GDK_KEY_PRESS) { - struct cmpf_data closure; - - closure.tree = tree; - closure.row = row; - closure.col = col; - gtk_menu_popup (menu, NULL, NULL, context_menu_position_func, - &closure, 0, event->key.time); - } else { - gtk_menu_popup (menu, NULL, NULL, NULL, NULL, - event->button.button, event->button.time); - } - - g_free (filter_menu[MLIST_FILTER].name); - g_free (filter_menu[MLIST_VFOLDER].name); - - return TRUE; -} - -static int -html_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer data) -{ - FolderBrowser *fb = data; - HTMLEngine *engine; - HTMLPoint *point; - ETreePath *path; - int row; - - if (event->type != GDK_BUTTON_PRESS || event->button != 3) - return FALSE; - - engine = GTK_HTML (widget)->engine; - point = html_engine_get_point_at (engine, event->x, event->y, FALSE); - - if (point) { - /* don't popup a menu if the mouse is hovering over a - url or a source image because those situations are - handled in mail-display.c's button_press_event - callback */ - const char *src, *url; - - url = html_object_get_url (point->object, point->offset); - src = html_object_get_src (point->object); - - if (url || src) { - html_point_destroy (point); - return FALSE; - } - - html_point_destroy (point); - } - - path = e_tree_get_cursor (fb->message_list->tree); - row = e_tree_row_of_node (fb->message_list->tree, path); - - on_right_click (fb->message_list->tree, row, path, 2, - (GdkEvent *) event, fb); - - return TRUE; -} - -static int -on_key_press (GtkWidget *widget, GdkEventKey *key, gpointer data) -{ - FolderBrowser *fb = data; - ETreePath *path; - int row; - - if (key->state & GDK_CONTROL_MASK) - return FALSE; - - path = e_tree_get_cursor (fb->message_list->tree); - row = e_tree_row_of_node (fb->message_list->tree, path); - - switch (key->keyval) { - case GDK_Delete: - case GDK_KP_Delete: - delete_msg (NULL, fb); - return TRUE; - - case GDK_Menu: - on_right_click (fb->message_list->tree, row, path, 2, - (GdkEvent *)key, fb); - return TRUE; - case '!': - toggle_as_important (NULL, fb, NULL); - return TRUE; - } - - return FALSE; -} - -static int -etree_key (ETree *tree, int row, ETreePath path, int col, GdkEvent *ev, FolderBrowser *fb) -{ - GtkAdjustment *vadj; - gfloat page_size; - - if ((ev->key.state & GDK_CONTROL_MASK) != 0) - return FALSE; - - vadj = gtk_scrolled_window_get_vadjustment (fb->mail_display->scroll); - page_size = vadj->page_size - vadj->step_increment; - - switch (ev->key.keyval) { - case GDK_space: - /* Work around Ximian 4939 */ - if (vadj->upper < vadj->page_size) - break; - if (vadj->value < vadj->upper - vadj->page_size - page_size) - vadj->value += page_size; - else - vadj->value = vadj->upper - vadj->page_size; - gtk_adjustment_value_changed (vadj); - break; - case GDK_BackSpace: - if (vadj->value > vadj->lower + page_size) - vadj->value -= page_size; - else - vadj->value = vadj->lower; - gtk_adjustment_value_changed (vadj); - break; - case GDK_Return: - case GDK_KP_Enter: - case GDK_ISO_Enter: - open_msg (NULL, fb); - break; - default: - return on_key_press ((GtkWidget *)tree, (GdkEventKey *)ev, fb); - } - - return TRUE; -} - -static void -on_double_click (ETree *tree, gint row, ETreePath path, gint col, GdkEvent *event, FolderBrowser *fb) -{ - /* Ignore double-clicks on columns where single-click doesn't - * just select. - */ - if (MESSAGE_LIST_COLUMN_IS_ACTIVE (col)) - return; - - open_msg (NULL, fb); -} - -static void -on_selection_changed (GtkObject *obj, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - FolderBrowserSelectionState state; - - /* we can get this signal at strange times... - * if no uicomp, don't even bother */ - - if (fb->uicomp == NULL) - return; - - switch (e_selection_model_selected_count (E_SELECTION_MODEL (obj))) { - case 0: - state = FB_SELSTATE_NONE; - break; - case 1: - state = FB_SELSTATE_SINGLE; - break; - default: - state = FB_SELSTATE_MULTIPLE; - break; - } - - folder_browser_ui_set_selection_state (fb, state); - - update_status_bar_idle (fb); -} - - -static void -on_cursor_activated (ETree *tree, int row, ETreePath path, gpointer user_data) -{ - on_selection_changed ((GtkObject *)tree, user_data); -} - -static gboolean -fb_resize_cb (GtkWidget *w, GdkEventButton *e, FolderBrowser *fb) -{ - GConfClient *gconf; - - if (GTK_WIDGET_REALIZED (w) && fb->preview_shown) { - gconf = mail_config_get_gconf_client (); - gconf_client_set_int (gconf, "/apps/evolution/mail/display/paned_size", gtk_paned_get_position (GTK_PANED (w)), NULL); - } - - return FALSE; -} - -/* hack to get around the fact setting the paned size doesn't work */ -static void -paned_realised(GtkWidget *w, FolderBrowser *fb) -{ - GConfClient *gconf; - int size; - - gconf = mail_config_get_gconf_client (); - size = gconf_client_get_int (gconf, "/apps/evolution/mail/display/paned_size", NULL); - gtk_paned_set_position (GTK_PANED (fb->vpaned), size); -} - -static void -folder_browser_gui_init (FolderBrowser *fb) -{ - extern RuleContext *search_context; - ESelectionModel *esm; - - /* The panned container */ - fb->vpaned = gtk_vpaned_new (); - g_signal_connect(fb->vpaned, "realize", G_CALLBACK(paned_realised), fb); - gtk_widget_show (fb->vpaned); - - gtk_table_attach (GTK_TABLE (fb), fb->vpaned, - 0, 1, 1, 3, - GTK_FILL | GTK_EXPAND, - GTK_FILL | GTK_EXPAND, - 0, 0); - - /* quick-search bar */ - if (search_context) { - const char *systemrules = g_object_get_data (G_OBJECT (search_context), "system"); - const char *userrules = g_object_get_data (G_OBJECT (search_context), "user"); - - fb->search = e_filter_bar_new (search_context, systemrules, userrules, - folder_browser_config_search, fb); - e_search_bar_set_menu ((ESearchBar *)fb->search, folder_browser_search_menu_items); - } - - gtk_widget_show (GTK_WIDGET (fb->search)); - - g_signal_connect (fb->search, "menu_activated", - G_CALLBACK (folder_browser_search_menu_activated), fb); - g_signal_connect (fb->search, "search_activated", - G_CALLBACK (folder_browser_search_do_search), fb); - g_signal_connect (fb->search, "query_changed", - G_CALLBACK (folder_browser_query_changed), fb); - - gtk_table_attach (GTK_TABLE (fb), GTK_WIDGET (fb->search), - 0, 1, 0, 1, - GTK_FILL | GTK_EXPAND, - 0, - 0, 0); - - esm = e_tree_get_selection_model (E_TREE (fb->message_list->tree)); - g_signal_connect (esm, "selection_changed", G_CALLBACK (on_selection_changed), fb); - g_signal_connect (esm, "cursor_activated", G_CALLBACK (on_cursor_activated), fb); - fb->selection_state = FB_SELSTATE_NONE; /* default to none */ - - gtk_paned_add1 (GTK_PANED (fb->vpaned), GTK_WIDGET (fb->message_list)); - gtk_widget_show (GTK_WIDGET (fb->message_list)); - - fb->paned_resize_id = g_signal_connect (fb->vpaned, "button_release_event", - G_CALLBACK (fb_resize_cb), fb); - - gtk_paned_add2 (GTK_PANED (fb->vpaned), GTK_WIDGET (fb->mail_display)); - gtk_widget_show (GTK_WIDGET (fb->mail_display)); - gtk_widget_show (GTK_WIDGET (fb)); -} - -/* mark the message seen if the current message still matches */ -static gint -do_mark_seen (gpointer data) -{ - FolderBrowser *fb = FOLDER_BROWSER (data); - - if (fb->new_uid && fb->loaded_uid && !strcmp (fb->new_uid, fb->loaded_uid)) { - camel_folder_set_message_flags (fb->folder, fb->new_uid, - CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN); - } - - return FALSE; -} - -/* callback when we have the message to display, after async loading it (see below) */ -/* if we have pending uid's, it means another was selected before we finished displaying - the last one - so we cycle through and start loading the pending one immediately now */ -static void -done_message_selected (CamelFolder *folder, const char *uid, CamelMimeMessage *msg, void *data) -{ - FolderBrowser *fb = data; - CamelMessageInfo *info; - GConfClient *gconf; - int timeout; - - if (folder != fb->folder || fb->mail_display == NULL) - return; - - gconf = mail_config_get_gconf_client (); - timeout = gconf_client_get_int (gconf, "/apps/evolution/mail/display/mark_seen_timeout", NULL); - - info = camel_folder_get_message_info (fb->folder, uid); - mail_display_set_message (fb->mail_display, (CamelMedium *) msg, fb->folder, info); - if (info) - camel_folder_free_message_info (fb->folder, info); - - /* FIXME: should this signal be emitted here?? */ - g_signal_emit (fb, folder_browser_signals[MESSAGE_LOADED], 0, uid); - - /* pain, if we have pending stuff, re-run */ - if (fb->pending_uid) { - g_free (fb->loading_uid); - fb->loading_uid = fb->pending_uid; - fb->pending_uid = NULL; - - mail_get_message (fb->folder, fb->loading_uid, done_message_selected, fb, mail_thread_new); - return; - } - - g_free (fb->loaded_uid); - fb->loaded_uid = fb->loading_uid; - fb->loading_uid = NULL; - - folder_browser_ui_message_loaded (fb); - - /* if we are still on the same message, do the 'idle read' thing */ - if (fb->seen_id) - gtk_timeout_remove (fb->seen_id); - - if (msg && gconf_client_get_bool (gconf, "/apps/evolution/mail/display/mark_seen", NULL)) { - if (timeout > 0) - fb->seen_id = gtk_timeout_add (timeout, do_mark_seen, fb); - else - do_mark_seen (fb); - } -} - -/* ok we waited enough, display it anyway (see below) */ -static gboolean -do_message_selected (FolderBrowser *fb) -{ - d(printf ("%p: selecting uid %s (delayed)\n", fb, fb->new_uid ? fb->new_uid : "NONE")); - - fb->loading_id = 0; - - /* if we are loading, then set a pending, but leave the loading, coudl cancel here (?) */ - if (fb->loading_uid) { - if (fb->new_uid == NULL || fb->pending_uid == NULL || strcmp(fb->pending_uid, fb->new_uid) != 0) { - g_free (fb->pending_uid); - fb->pending_uid = g_strdup (fb->new_uid); - } - } else { - if (fb->new_uid) { - if (fb->loaded_uid == NULL || strcmp(fb->new_uid, fb->loaded_uid) != 0) { - fb->loading_uid = g_strdup (fb->new_uid); - mail_get_message (fb->folder, fb->loading_uid, done_message_selected, fb, mail_thread_new); - } - } else { - mail_display_set_message (fb->mail_display, NULL, NULL, NULL); - } - } - - return FALSE; -} - -/* when a message is selected, wait a while before trying to display it */ -static void -on_message_selected (MessageList *ml, const char *uid, FolderBrowser *fb) -{ - d(printf ("%p: selecting uid %s (direct)\n", fb, uid ? uid : "NONE")); - - if (fb->loading_id != 0) - gtk_timeout_remove (fb->loading_id); - - g_free (fb->new_uid); - fb->new_uid = g_strdup (uid); - - if (fb->preview_shown) - fb->loading_id = gtk_timeout_add (100, (GtkFunction)do_message_selected, fb); -} - -static gboolean -on_message_list_focus_in (GtkWidget *widget, GdkEventFocus *event, gpointer user_data) -{ - FolderBrowser *fb = (FolderBrowser *) user_data; - - d(printf ("got focus!\n")); - folder_browser_ui_message_list_focus (fb); - - return FALSE; -} - -static gboolean -on_message_list_focus_out (GtkWidget *widget, GdkEventFocus *event, gpointer user_data) -{ - FolderBrowser *fb = (FolderBrowser *) user_data; - - d(printf ("got unfocus!\n")); - folder_browser_ui_message_list_unfocus (fb); - - return FALSE; -} - -static void -folder_browser_init (FolderBrowser *fb) -{ - fb->async_event = mail_async_event_new (); - fb->get_id = -1; -} - -static void -my_folder_browser_init (FolderBrowser *fb) -{ - int i; - - fb->view_instance = NULL; - fb->view_menus = NULL; - - fb->pref_master = FALSE; - - /* - * Setup parent class fields. - */ - GTK_TABLE (fb)->homogeneous = FALSE; - gtk_table_resize (GTK_TABLE (fb), 1, 2); - - /* - * Our instance data - */ - fb->message_list = (MessageList *)message_list_new (); - fb->mail_display = (MailDisplay *)mail_display_new (); - - fb->preview_shown = TRUE; - - g_signal_connect (fb->mail_display->html, "key_press_event", - G_CALLBACK (on_key_press), fb); - g_signal_connect (fb->mail_display->html, "button_press_event", - G_CALLBACK (html_button_press_event), fb); - - g_signal_connect (fb->message_list->tree, "key_press", - G_CALLBACK (etree_key), fb); - - g_signal_connect (fb->message_list->tree, "right_click", - G_CALLBACK (on_right_click), fb); - - g_signal_connect (fb->message_list->tree, "double_click", - G_CALLBACK (on_double_click), fb); - - g_signal_connect (fb->message_list, "focus_in_event", - G_CALLBACK (on_message_list_focus_in), fb); - - g_signal_connect (fb->message_list, "focus_out_event", - G_CALLBACK (on_message_list_focus_out), fb); - - g_signal_connect (fb->message_list, "message_selected", - G_CALLBACK (on_message_selected), fb); - - /* drag & drop */ - e_tree_drag_source_set (fb->message_list->tree, GDK_BUTTON1_MASK, - drag_types, num_drag_types, GDK_ACTION_MOVE | GDK_ACTION_COPY); - - g_signal_connect (fb->message_list->tree, "tree_drag_data_get", - G_CALLBACK (message_list_drag_data_get), fb); - - e_tree_drag_dest_set (fb->message_list->tree, GTK_DEST_DEFAULT_ALL, - drag_types, num_drag_types, GDK_ACTION_MOVE | GDK_ACTION_COPY); - - g_signal_connect (fb->message_list->tree, "tree_drag_data_received", - G_CALLBACK (message_list_drag_data_received), fb); - - /* cut, copy & paste */ - fb->invisible = gtk_invisible_new (); - g_object_ref (fb->invisible); - gtk_object_sink ((GtkObject *) fb->invisible); - - for (i = 0; i < num_paste_types; i++) - gtk_selection_add_target (fb->invisible, clipboard_atom, - paste_types[i].target, - paste_types[i].info); - - g_signal_connect (fb->invisible, "selection_get", - G_CALLBACK (selection_get), fb); - g_signal_connect (fb->invisible, "selection_clear_event", - G_CALLBACK (selection_clear_event), fb); - g_signal_connect (fb->invisible, "selection_received", - G_CALLBACK (selection_received), fb); - - folder_browser_gui_init (fb); -} - -GtkWidget * -folder_browser_new (const GNOME_Evolution_Shell shell, const char *uri) -{ - CORBA_Environment ev; - FolderBrowser *folder_browser; - - CORBA_exception_init (&ev); - - folder_browser = g_object_new (folder_browser_get_type (), NULL); - - my_folder_browser_init (folder_browser); - - folder_browser->shell = CORBA_Object_duplicate (shell, &ev); - if (ev._major != CORBA_NO_EXCEPTION) { - folder_browser->shell = CORBA_OBJECT_NIL; - gtk_widget_destroy (GTK_WIDGET (folder_browser)); - CORBA_exception_free (&ev); - return NULL; - } - - CORBA_exception_free (&ev); - - if (uri) { - folder_browser->uri = g_strdup (uri); - folder_browser->meta = mail_tool_get_meta_data(uri); - g_object_ref (folder_browser); - folder_browser->get_id = mail_get_folder (folder_browser->uri, 0, got_folder, - folder_browser, mail_thread_new); - } - - return GTK_WIDGET (folder_browser); -} - - -E_MAKE_TYPE (folder_browser, "FolderBrowser", FolderBrowser, folder_browser_class_init, folder_browser_init, PARENT_TYPE); diff --git a/mail/folder-browser.h b/mail/folder-browser.h deleted file mode 100644 index 12d21cd526..0000000000 --- a/mail/folder-browser.h +++ /dev/null @@ -1,194 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ - - -#ifndef _FOLDER_BROWSER_H_ -#define _FOLDER_BROWSER_H_ - -#include <gtk/gtktable.h> -#include "camel/camel-stream.h" -#include <bonobo/bonobo-property-bag.h> -#include <bonobo/bonobo-ui-component.h> -#include <widgets/misc/e-filter-bar.h> -#include "widgets/menus/gal-view-menus.h" -#include "filter/filter-rule.h" -#include "filter/filter-context.h" /*eek*/ -#include "message-list.h" -#include "mail-display.h" -#include "mail-types.h" -#include "shell/Evolution.h" - -#define FOLDER_BROWSER_TYPE (folder_browser_get_type ()) -#define FOLDER_BROWSER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), FOLDER_BROWSER_TYPE, FolderBrowser)) -#define FOLDER_BROWSER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), FOLDER_BROWSER_TYPE, FolderBrowserClass)) -#define IS_FOLDER_BROWSER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), FOLDER_BROWSER_TYPE)) -#define IS_FOLDER_BROWSER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), FOLDER_BROWSER_TYPE)) - -#define FB_DEFAULT_CHARSET _("Default") - -#define FOLDER_BROWSER_IS_DESTROYED(fb) (!fb || !fb->message_list || !fb->mail_display || !fb->folder) - -typedef enum _FolderBrowserSelectionState { - FB_SELSTATE_NONE, - FB_SELSTATE_SINGLE, - FB_SELSTATE_MULTIPLE, - FB_SELSTATE_UNDEFINED -} FolderBrowserSelectionState; - -struct _FolderBrowser { - GtkTable parent; - - BonoboPropertyBag *properties; - - GNOME_Evolution_Shell shell; - GNOME_Evolution_ShellView shell_view; - - BonoboUIComponent *uicomp; - - /* - * The current URI being displayed by the FolderBrowser - */ - char *uri; - CamelFolder *folder; - int unread_count; /* last known unread message count */ - - /* async loading stuff */ - char *loading_uid; /* what uid am i loading now */ - char *pending_uid; /* what uid should i load next */ - char *new_uid; /* place to save the next uid during idle timeout */ - char *loaded_uid; /* what we have loaded */ - guint loading_id; - guint seen_id; - - gulong paned_resize_id; - - /* a folder we are expunging, dont use other than to compare the pointer value */ - CamelFolder *expunging; - int expunge_mlfocussed; /* true if the ml was focussed before we expunged */ - - MessageList *message_list; - MailDisplay *mail_display; - GtkWidget *vpaned; - - EFilterBar *search; - FilterRule *search_full; /* if we have a full search active */ - - struct _EMeta *meta; /* various per-folder meta-data */ - - guint32 preview_shown : 1; - guint32 threaded : 1; - guint32 pref_master : 1; - - FolderBrowserSelectionState selection_state; - GSList *sensitize_changes; - GHashTable *sensitise_state; /* the last sent sensitise state, to avoid much bonobo overhead */ - int sensitize_timeout_id; - int update_status_bar_idle_id; - - /* View instance and the menu handler object */ - GalViewInstance *view_instance; - GalViewMenus *view_menus; - - GtkWidget *invisible; - GByteArray *clipboard_selection; - - /* for async events */ - struct _MailAsyncEvent *async_event; - - int get_id; /* for getting folder op */ - - /* info used by popup for filter/vfolder */ - struct _popup_filter_data *popup; -}; - -typedef struct { - GtkTableClass parent_class; - - /* signals */ - void (*folder_loaded) (FolderBrowser *fb, const char *uri); - void (*message_loaded) (FolderBrowser *fb, const char *uid); -} FolderBrowserClass; - -struct fb_ondemand_closure { - FilterRule *rule; - FolderBrowser *fb; - gchar *path; -}; - -GtkType folder_browser_get_type (void); -GtkWidget *folder_browser_new (const GNOME_Evolution_Shell shell, - const char *uri); - -void folder_browser_set_folder (FolderBrowser *fb, CamelFolder *folder, const char *uri); - -void folder_browser_set_ui_component (FolderBrowser *fb, BonoboUIComponent *uicomp); -void folder_browser_set_shell_view (FolderBrowser *fb, GNOME_Evolution_ShellView shell_view); - -void folder_browser_set_message_preview (FolderBrowser *folder_browser, - gboolean show_message_preview); -void folder_browser_clear_search (FolderBrowser *fb); - -void folder_browser_cut (GtkWidget *widget, FolderBrowser *fb); -void folder_browser_copy (GtkWidget *widget, FolderBrowser *fb); -void folder_browser_paste (GtkWidget *widget, FolderBrowser *fb); - -void folder_browser_reload (FolderBrowser *fb); - -/* callbacks for functions on the folder-browser */ -void vfolder_subject (GtkWidget *w, FolderBrowser *fb); -void vfolder_sender (GtkWidget *w, FolderBrowser *fb); -void vfolder_recipient (GtkWidget *w, FolderBrowser *fb); -void vfolder_mlist (GtkWidget *w, FolderBrowser *fb); - -void filter_subject (GtkWidget *w, FolderBrowser *fb); -void filter_sender (GtkWidget *w, FolderBrowser *fb); -void filter_recipient (GtkWidget *w, FolderBrowser *fb); -void filter_mlist (GtkWidget *w, FolderBrowser *fb); - -void hide_read(GtkWidget *w, FolderBrowser *fb); -void hide_deleted(GtkWidget *w, FolderBrowser *fb); -void hide_selected(GtkWidget *w, FolderBrowser *fb); -void hide_none(GtkWidget *w, FolderBrowser *fb); -void hide_subject(GtkWidget *w, FolderBrowser *fb); -void hide_sender(GtkWidget *w, FolderBrowser *fb); - -void folder_browser_toggle_preview (BonoboUIComponent *component, - const char *path, - Bonobo_UIComponent_EventType type, - const char *state, - gpointer user_data); - -void folder_browser_toggle_threads (BonoboUIComponent *component, - const char *path, - Bonobo_UIComponent_EventType type, - const char *state, - gpointer user_data); - -void folder_browser_toggle_hide_deleted (BonoboUIComponent *component, - const char *path, - Bonobo_UIComponent_EventType type, - const char *state, - gpointer user_data); - -void folder_browser_toggle_caret_mode (BonoboUIComponent *component, - const char *path, - Bonobo_UIComponent_EventType type, - const char *state, - gpointer user_data); - -void folder_browser_set_message_display_style (BonoboUIComponent *component, - const char *path, - Bonobo_UIComponent_EventType type, - const char *state, - gpointer user_data); - -void folder_browser_charset_changed (BonoboUIComponent *component, - const char *path, - Bonobo_UIComponent_EventType type, - const char *state, - gpointer user_data); - -gboolean folder_browser_is_drafts (FolderBrowser *fb); -gboolean folder_browser_is_sent (FolderBrowser *fb); -gboolean folder_browser_is_outbox (FolderBrowser *fb); - -#endif /* _FOLDER_BROWSER_H_ */ diff --git a/mail/mail-account-gui.c b/mail/mail-account-gui.c index 2bba8944e6..6d965ece6b 100644 --- a/mail/mail-account-gui.c +++ b/mail/mail-account-gui.c @@ -1862,7 +1862,6 @@ mail_account_gui_save (MailAccountGui *gui) { EAccount *account, *new; CamelProvider *provider = NULL; - CamelURL *source_url = NULL, *url; gboolean is_new = FALSE; const char *new_name; gboolean is_storage; @@ -1907,10 +1906,8 @@ mail_account_gui_save (MailAccountGui *gui) /* source */ save_service (&gui->source, gui->extra_config, new->source); - if (new->source->url) { + if (new->source->url) provider = camel_session_get_provider (session, new->source->url, NULL); - source_url = provider ? camel_url_new (new->source->url, NULL) : NULL; - } new->source->auto_check = gtk_toggle_button_get_active (gui->source_auto_check); if (new->source->auto_check) @@ -1924,34 +1921,21 @@ mail_account_gui_save (MailAccountGui *gui) save_service (&gui->transport, NULL, new->transport); /* Check to make sure that the Drafts folder uri is "valid" before assigning it */ - url = source_url && gui->drafts_folder_uri ? camel_url_new (gui->drafts_folder_uri, NULL) : NULL; - if (mail_config_get_account_by_source_url (gui->drafts_folder_uri) || - (url && provider->url_equal (source_url, url))) { + if (mail_config_get_account_by_source_url (gui->drafts_folder_uri)) { new->drafts_folder_uri = g_strdup (gui->drafts_folder_uri); } else { /* assign defaults - the uri is unknown to us (probably pointed to an old source url) */ new->drafts_folder_uri = g_strdup (default_drafts_folder_uri); } - if (url) - camel_url_free (url); - /* Check to make sure that the Sent folder uri is "valid" before assigning it */ - url = source_url && gui->sent_folder_uri ? camel_url_new (gui->sent_folder_uri, NULL) : NULL; - if (mail_config_get_account_by_source_url (gui->sent_folder_uri) || - (url && provider->url_equal (source_url, url))) { + if (mail_config_get_account_by_source_url (gui->sent_folder_uri)) { new->sent_folder_uri = g_strdup (gui->sent_folder_uri); } else { /* assign defaults - the uri is unknown to us (probably pointed to an old source url) */ new->sent_folder_uri = g_strdup (default_sent_folder_uri); } - if (url) - camel_url_free (url); - - if (source_url) - camel_url_free (source_url); - new->always_cc = gtk_toggle_button_get_active (gui->always_cc); new->cc_addrs = g_strdup (gtk_entry_get_text (gui->cc_addrs)); new->always_bcc = gtk_toggle_button_get_active (gui->always_bcc); diff --git a/mail/mail-callbacks.c b/mail/mail-callbacks.c deleted file mode 100644 index 21690c55ce..0000000000 --- a/mail/mail-callbacks.c +++ /dev/null @@ -1,3239 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* mail-ops.c: callbacks for the mail toolbar/menus */ - -/* - * Authors: - * Dan Winship <danw@ximian.com> - * Peter Williams <peterw@ximian.com> - * Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright 2000 Ximian, Inc. (www.ximian.com) - * - * 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 - */ - - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <time.h> -#include <errno.h> - -#include <gtkhtml/gtkhtml.h> - -#include <gconf/gconf.h> -#include <gconf/gconf-client.h> - -#include <gtk/gtkmessagedialog.h> - -#include <libgnomeprint/gnome-print-job.h> -#include <libgnomeprintui/gnome-print-dialog.h> -#include <libgnomeprintui/gnome-print-job-preview.h> - -#include <bonobo/bonobo-widget.h> -#include <bonobo/bonobo-socket.h> -#include <gal/e-table/e-table.h> -#include <e-util/e-dialog-utils.h> -#include <filter/filter-editor.h> - -#include "mail.h" -#include "message-browser.h" -#include "mail-callbacks.h" -#include "mail-config.h" -#include "mail-accounts.h" -#include "mail-config-druid.h" -#include "mail-mt.h" -#include "mail-tools.h" -#include "mail-ops.h" -#include "mail-local.h" -#include "mail-search.h" -#include "mail-send-recv.h" -#include "mail-vfolder.h" -#include "mail-folder-cache.h" -#include "folder-browser.h" -#include "subscribe-dialog.h" -#include "message-tag-editor.h" -#include "message-tag-followup.h" - -#include "Evolution.h" -#include "evolution-storage.h" - -#include "evolution-shell-client.h" - -#define d(x) x - -#define FB_WINDOW(fb) GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (fb), GTK_TYPE_WINDOW)) - - -/* default is default gtk response - if again is != NULL, a checkbox "dont show this again" will appear, and the result stored in *again -*/ -static gboolean -e_question (GtkWindow *parent, int def, gboolean *again, const char *fmt, ...) -{ - GtkWidget *mbox, *check = NULL; - va_list ap; - int button; - char *str; - - va_start (ap, fmt); - str = g_strdup_vprintf (fmt, ap); - va_end (ap); - mbox = gtk_message_dialog_new (parent, GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, - "%s", str); - g_free (str); - gtk_dialog_set_default_response ((GtkDialog *) mbox, def); - if (again) { - check = gtk_check_button_new_with_label (_("Don't show this message again.")); - gtk_box_pack_start ((GtkBox *)((GtkDialog *) mbox)->vbox, check, TRUE, TRUE, 10); - gtk_widget_show (check); - } - - button = gtk_dialog_run ((GtkDialog *) mbox); - if (again) - *again = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check)); - gtk_widget_destroy (mbox); - - return button == GTK_RESPONSE_YES; -} - - -struct _composer_callback_data { - unsigned int ref_count; - - CamelFolder *drafts_folder; - char *drafts_uid; - - CamelFolder *folder; - guint32 flags, set; - char *uid; -}; - -static struct _composer_callback_data * -ccd_new (void) -{ - struct _composer_callback_data *ccd; - - ccd = g_new (struct _composer_callback_data, 1); - ccd->ref_count = 1; - ccd->drafts_folder = NULL; - ccd->drafts_uid = NULL; - ccd->folder = NULL; - ccd->flags = 0; - ccd->set = 0; - ccd->uid = NULL; - - return ccd; -} - -static void -free_ccd (struct _composer_callback_data *ccd) -{ - if (ccd->drafts_folder) - camel_object_unref (ccd->drafts_folder); - g_free (ccd->drafts_uid); - - if (ccd->folder) - camel_object_unref (ccd->folder); - g_free (ccd->uid); - g_free (ccd); -} - -static void -ccd_ref (struct _composer_callback_data *ccd) -{ - ccd->ref_count++; -} - -static void -ccd_unref (struct _composer_callback_data *ccd) -{ - ccd->ref_count--; - if (ccd->ref_count == 0) - free_ccd (ccd); -} - - -static void -composer_destroy_cb (gpointer user_data, GObject *deadbeef) -{ - ccd_unref (user_data); -} - - -static void -druid_destroy_cb (gpointer user_data, GObject *deadbeef) -{ - gtk_main_quit (); -} - -static gboolean -configure_mail (FolderBrowser *fb) -{ - MailConfigDruid *druid; - - if (e_question (FB_WINDOW (fb), GTK_RESPONSE_YES, NULL, - _("You have not configured the mail client.\n" - "You need to do this before you can send,\n" - "receive or compose mail.\n" - "Would you like to configure it now?"))) { - druid = mail_config_druid_new (); - g_object_weak_ref ((GObject *) druid, (GWeakNotify) druid_destroy_cb, NULL); - gtk_widget_show ((GtkWidget *) druid); - gtk_grab_add ((GtkWidget *) druid); - gtk_main (); - } - - return mail_config_is_configured (); -} - -static gboolean -check_send_configuration (FolderBrowser *fb) -{ - EAccount *account; - - if (!mail_config_is_configured ()) { - if (fb == NULL) { - e_notice (NULL, GTK_MESSAGE_WARNING, - _("You need to configure an account\nbefore you can compose mail.")); - return FALSE; - } - - if (!configure_mail (fb)) - return FALSE; - } - - /* Get the default account */ - account = mail_config_get_default_account (); - - /* Check for an identity */ - if (!account) { - e_notice (FB_WINDOW (fb), GTK_MESSAGE_WARNING, - _("You need to configure an identity\nbefore you can compose mail.")); - return FALSE; - } - - /* Check for a transport */ - if (!account->transport->url) { - e_notice (FB_WINDOW (fb), GTK_MESSAGE_WARNING, - _("You need to configure a mail transport\n" - "before you can compose mail.")); - return FALSE; - } - - return TRUE; -} - -static gboolean -ask_confirm_for_unwanted_html_mail (EMsgComposer *composer, EDestination **recipients) -{ - gboolean show_again, res; - GConfClient *gconf; - GString *str; - int i; - - gconf = mail_config_get_gconf_client (); - - if (!gconf_client_get_bool (gconf, "/apps/evolution/mail/prompts/unwanted_html", NULL)) - return TRUE; - - /* FIXME: this wording sucks */ - str = g_string_new (_("You are sending an HTML-formatted message. Please make sure that\n" - "the following recipients are willing and able to receive HTML mail:\n")); - for (i = 0; recipients[i] != NULL; ++i) { - if (!e_destination_get_html_mail_pref (recipients[i])) { - const char *name; - - name = e_destination_get_textrep (recipients[i], FALSE); - - g_string_append_printf (str, " %s\n", name); - } - } - - g_string_append (str, _("Send anyway?")); - res = e_question ((GtkWindow *) composer, GTK_RESPONSE_YES, &show_again, "%s", str->str); - g_string_free (str, TRUE); - - gconf_client_set_bool (gconf, "/apps/evolution/mail/prompts/unwanted_html", show_again, NULL); - - return res; -} - -static gboolean -ask_confirm_for_empty_subject (EMsgComposer *composer) -{ - gboolean show_again, res; - GConfClient *gconf; - - gconf = mail_config_get_gconf_client (); - - if (!gconf_client_get_bool (gconf, "/apps/evolution/mail/prompts/empty_subject", NULL)) - return TRUE; - - res = e_question ((GtkWindow *) composer, GTK_RESPONSE_YES, &show_again, - _("This message has no subject.\nReally send?")); - - gconf_client_set_bool (gconf, "/apps/evolution/mail/prompts/empty_subject", show_again, NULL); - - return res; -} - -static gboolean -ask_confirm_for_only_bcc (EMsgComposer *composer, gboolean hidden_list_case) -{ - gboolean show_again, res; - const char *first_text; - GConfClient *gconf; - - gconf = mail_config_get_gconf_client (); - - if (!gconf_client_get_bool (gconf, "/apps/evolution/mail/prompts/only_bcc", NULL)) - return TRUE; - - /* If the user is mailing a hidden contact list, it is possible for - them to create a message with only Bcc recipients without really - realizing it. To try to avoid being totally confusing, I've changed - this dialog to provide slightly different text in that case, to - better explain what the hell is going on. */ - - if (hidden_list_case) { - first_text = _("Since the contact list you are sending to " - "is configured to hide the list's addresses, " - "this message will contain only Bcc recipients."); - } else { - first_text = _("This message contains only Bcc recipients."); - } - - res = e_question ((GtkWindow *) composer, GTK_RESPONSE_YES, &show_again, - "%s\n%s", first_text, - _("It is possible that the mail server may reveal the recipients " - "by adding an Apparently-To header.\nSend anyway?")); - - gconf_client_set_bool (gconf, "/apps/evolution/mail/prompts/only_bcc", show_again, NULL); - - return res; -} - - -struct _send_data { - struct _composer_callback_data *ccd; - EMsgComposer *composer; - gboolean send; -}; - -static void -composer_send_queued_cb (CamelFolder *folder, CamelMimeMessage *msg, CamelMessageInfo *info, - int queued, const char *appended_uid, void *data) -{ - struct _composer_callback_data *ccd; - struct _send_data *send = data; - - ccd = send->ccd; - - if (queued) { - if (ccd && ccd->drafts_folder) { - /* delete the old draft message */ - camel_folder_set_message_flags (ccd->drafts_folder, ccd->drafts_uid, - CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, - CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN); - camel_object_unref (ccd->drafts_folder); - ccd->drafts_folder = NULL; - g_free (ccd->drafts_uid); - ccd->drafts_uid = NULL; - } - - if (ccd && ccd->folder) { - /* set any replied flags etc */ - camel_folder_set_message_flags (ccd->folder, ccd->uid, ccd->flags, ccd->set); - camel_object_unref (ccd->folder); - ccd->folder = NULL; - g_free (ccd->uid); - ccd->uid = NULL; - } - - gtk_widget_destroy (GTK_WIDGET (send->composer)); - - if (send->send && camel_session_is_online (session)) { - /* queue a message send */ - mail_send (); - } - } else { - if (!ccd) { - ccd = ccd_new (); - - /* disconnect the previous signal handlers */ - g_signal_handlers_disconnect_matched(send->composer, G_SIGNAL_MATCH_FUNC, 0, - 0, NULL, composer_send_cb, NULL); - g_signal_handlers_disconnect_matched(send->composer, G_SIGNAL_MATCH_FUNC, 0, - 0, NULL, composer_save_draft_cb, NULL); - - /* reconnect to the signals using a non-NULL ccd for the callback data */ - g_signal_connect (send->composer, "send", G_CALLBACK (composer_send_cb), ccd); - g_signal_connect (send->composer, "save-draft", G_CALLBACK (composer_save_draft_cb), ccd); - - g_object_weak_ref ((GObject *) send->composer, (GWeakNotify) composer_destroy_cb, ccd); - } - - e_msg_composer_set_enable_autosave (send->composer, TRUE); - gtk_widget_show (GTK_WIDGET (send->composer)); - } - - camel_message_info_free (info); - - if (send->ccd) - ccd_unref (send->ccd); - - g_object_unref(send->composer); - g_free (send); -} - -static CamelMimeMessage * -composer_get_message (EMsgComposer *composer, gboolean post, gboolean save_html_object_data) -{ - CamelMimeMessage *message = NULL; - EDestination **recipients, **recipients_bcc; - gboolean send_html, confirm_html; - CamelInternetAddress *cia; - int hidden = 0, shown = 0; - int num = 0, num_bcc = 0; - const char *subject; - GConfClient *gconf; - EAccount *account; - int i; - - gconf = mail_config_get_gconf_client (); - - /* We should do all of the validity checks based on the composer, and not on - the created message, as extra interaction may occur when we get the message - (e.g. to get a passphrase to sign a message) */ - - /* get the message recipients */ - recipients = e_msg_composer_get_recipients (composer); - - cia = camel_internet_address_new (); - - /* see which ones are visible/present, etc */ - if (recipients) { - for (i = 0; recipients[i] != NULL; i++) { - const char *addr = e_destination_get_address (recipients[i]); - - if (addr && addr[0]) { - camel_address_decode ((CamelAddress *) cia, addr); - if (camel_address_length ((CamelAddress *) cia) > 0) { - camel_address_remove ((CamelAddress *) cia, -1); - num++; - if (e_destination_is_evolution_list (recipients[i]) - && !e_destination_list_show_addresses (recipients[i])) { - hidden++; - } else { - shown++; - } - } - } - } - } - - recipients_bcc = e_msg_composer_get_bcc (composer); - if (recipients_bcc) { - for (i = 0; recipients_bcc[i] != NULL; i++) { - const char *addr = e_destination_get_address (recipients_bcc[i]); - - if (addr && addr[0]) { - camel_address_decode ((CamelAddress *) cia, addr); - if (camel_address_length ((CamelAddress *) cia) > 0) { - camel_address_remove ((CamelAddress *) cia, -1); - num_bcc++; - } - } - } - - e_destination_freev (recipients_bcc); - } - - camel_object_unref (cia); - - /* I'm sensing a lack of love, er, I mean recipients. */ - if (num == 0 && !post) { - e_notice ((GtkWindow *) composer, GTK_MESSAGE_WARNING, - _("You must specify recipients in order to send this message.")); - goto finished; - } - - if (num > 0 && (num == num_bcc || shown == 0)) { - /* this means that the only recipients are Bcc's */ - if (!ask_confirm_for_only_bcc (composer, shown == 0)) - goto finished; - } - - send_html = gconf_client_get_bool (gconf, "/apps/evolution/mail/composer/send_html", NULL); - confirm_html = gconf_client_get_bool (gconf, "/apps/evolution/mail/prompts/unwanted_html", NULL); - - /* Only show this warning if our default is to send html. If it isn't, we've - manually switched into html mode in the composer and (presumably) had a good - reason for doing this. */ - if (e_msg_composer_get_send_html (composer) && send_html && confirm_html) { - gboolean html_problem = FALSE; - - if (recipients) { - for (i = 0; recipients[i] != NULL && !html_problem; i++) { - if (!e_destination_get_html_mail_pref (recipients[i])) - html_problem = TRUE; - } - } - - if (html_problem) { - html_problem = !ask_confirm_for_unwanted_html_mail (composer, recipients); - if (html_problem) - goto finished; - } - } - - /* Check for no subject */ - subject = e_msg_composer_get_subject (composer); - if (subject == NULL || subject[0] == '\0') { - if (!ask_confirm_for_empty_subject (composer)) - goto finished; - } - - /* actually get the message now, this will sign/encrypt etc */ - message = e_msg_composer_get_message (composer, save_html_object_data); - if (message == NULL) - goto finished; - - /* Add info about the sending account */ - account = e_msg_composer_get_preferred_account (composer); - - if (account) { - camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Account", account->name); - camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Transport", account->transport->url); - camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Fcc", account->sent_folder_uri); - if (account->id->organization && *account->id->organization) - camel_medium_set_header (CAMEL_MEDIUM (message), "Organization", account->id->organization); - } - - /* Get the message recipients and 'touch' them, boosting their use scores */ - if (recipients) - e_destination_touchv (recipients); - - finished: - - if (recipients) - e_destination_freev (recipients); - - return message; -} - -static void -got_post_folder (char *uri, CamelFolder *folder, void *data) -{ - CamelFolder **fp = data; - - *fp = folder; - - if (folder) - camel_object_ref (folder); -} - -void -composer_send_cb (EMsgComposer *composer, gpointer user_data) -{ - extern CamelFolder *outbox_folder; - CamelMimeMessage *message; - CamelMessageInfo *info; - struct _send_data *send; - gboolean post = FALSE; - CamelFolder *folder; - XEvolution *xev; - char *url; - - url = e_msg_composer_hdrs_get_post_to ((EMsgComposerHdrs *) composer->hdrs); - if (url && *url) { - post = TRUE; - - mail_msg_wait (mail_get_folder (url, 0, got_post_folder, &folder, mail_thread_new)); - - if (!folder) { - g_free (url); - return; - } - } else { - folder = outbox_folder; - camel_object_ref (folder); - } - - g_free (url); - - message = composer_get_message (composer, post, FALSE); - if (!message) - return; - - if (post) { - /* Remove the X-Evolution* headers if we are in Post-To mode */ - xev = mail_tool_remove_xevolution_headers (message); - mail_tool_destroy_xevolution (xev); - } - - info = camel_message_info_new (); - info->flags = CAMEL_MESSAGE_SEEN; - - send = g_malloc (sizeof (*send)); - send->ccd = user_data; - if (send->ccd) - ccd_ref (send->ccd); - send->send = !post; - send->composer = composer; - g_object_ref (composer); - gtk_widget_hide (GTK_WIDGET (composer)); - - e_msg_composer_set_enable_autosave (composer, FALSE); - - mail_append_mail (folder, message, info, composer_send_queued_cb, send); - camel_object_unref (message); - camel_object_unref (folder); -} - -struct _save_draft_info { - struct _composer_callback_data *ccd; - EMsgComposer *composer; - int quit; -}; - -static void -save_draft_done (CamelFolder *folder, CamelMimeMessage *msg, CamelMessageInfo *info, int ok, - const char *appended_uid, void *user_data) -{ - struct _save_draft_info *sdi = user_data; - struct _composer_callback_data *ccd; - CORBA_Environment ev; - - if (!ok) - goto done; - CORBA_exception_init (&ev); - GNOME_GtkHTML_Editor_Engine_runCommand (sdi->composer->editor_engine, "saved", &ev); - CORBA_exception_free (&ev); - - if ((ccd = sdi->ccd) == NULL) { - ccd = ccd_new (); - - /* disconnect the previous signal handlers */ - g_signal_handlers_disconnect_by_func (sdi->composer, G_CALLBACK (composer_send_cb), NULL); - g_signal_handlers_disconnect_by_func (sdi->composer, G_CALLBACK (composer_save_draft_cb), NULL); - - /* reconnect to the signals using a non-NULL ccd for the callback data */ - g_signal_connect (sdi->composer, "send", G_CALLBACK (composer_send_cb), ccd); - g_signal_connect (sdi->composer, "save-draft", G_CALLBACK (composer_save_draft_cb), ccd); - - g_object_weak_ref ((GObject *) sdi->composer, (GWeakNotify) composer_destroy_cb, ccd); - } - - if (ccd->drafts_folder) { - /* delete the original draft message */ - camel_folder_set_message_flags (ccd->drafts_folder, ccd->drafts_uid, - CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, - CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN); - camel_object_unref (ccd->drafts_folder); - ccd->drafts_folder = NULL; - g_free (ccd->drafts_uid); - ccd->drafts_uid = NULL; - } - - if (ccd->folder) { - /* set the replied flags etc */ - camel_folder_set_message_flags (ccd->folder, ccd->uid, ccd->flags, ccd->set); - camel_object_unref (ccd->folder); - ccd->folder = NULL; - g_free (ccd->uid); - ccd->uid = NULL; - } - - if (appended_uid) { - camel_object_ref (folder); - ccd->drafts_folder = folder; - ccd->drafts_uid = g_strdup (appended_uid); - } - - if (sdi->quit) - gtk_widget_destroy (GTK_WIDGET (sdi->composer)); - - done: - g_object_unref (sdi->composer); - if (sdi->ccd) - ccd_unref (sdi->ccd); - g_free (info); - g_free (sdi); -} - -static void -use_default_drafts_cb (int reply, gpointer data) -{ - extern CamelFolder *drafts_folder; - CamelFolder **folder = data; - - if (reply == 0) { - *folder = drafts_folder; - camel_object_ref (drafts_folder); - } -} - -static void -save_draft_folder (char *uri, CamelFolder *folder, gpointer data) -{ - CamelFolder **save = data; - - if (folder) { - *save = folder; - camel_object_ref (folder); - } -} - -void -composer_save_draft_cb (EMsgComposer *composer, int quit, gpointer user_data) -{ - extern char *default_drafts_folder_uri; - extern CamelFolder *drafts_folder; - struct _save_draft_info *sdi; - CamelFolder *folder = NULL; - CamelMimeMessage *msg; - CamelMessageInfo *info; - EAccount *account; - - account = e_msg_composer_get_preferred_account (composer); - if (account && account->drafts_folder_uri && - strcmp (account->drafts_folder_uri, default_drafts_folder_uri) != 0) { - int id; - - id = mail_get_folder (account->drafts_folder_uri, 0, save_draft_folder, &folder, mail_thread_new); - mail_msg_wait (id); - - if (!folder) { - if (!e_question ((GtkWindow *) composer, GTK_RESPONSE_YES, NULL, - _("Unable to open the drafts folder for this account.\n" - "Would you like to use the default drafts folder?"))) - return; - - folder = drafts_folder; - camel_object_ref (drafts_folder); - } - } else { - folder = drafts_folder; - camel_object_ref (folder); - } - - msg = e_msg_composer_get_message_draft (composer); - - info = g_new0 (CamelMessageInfo, 1); - info->flags = CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN; - - sdi = g_malloc (sizeof (struct _save_draft_info)); - sdi->composer = composer; - g_object_ref (composer); - sdi->ccd = user_data; - if (sdi->ccd) - ccd_ref (sdi->ccd); - sdi->quit = quit; - - mail_append_mail (folder, msg, info, save_draft_done, sdi); - camel_object_unref (folder); - camel_object_unref (msg); -} - -static GtkWidget * -create_msg_composer (EAccount *account, gboolean post, const char *url) -{ - EMsgComposer *composer; - GConfClient *gconf; - gboolean send_html; - - /* Make sure that we've actually been passed in an account. If one has - * not been passed in, grab the default account. - */ - if (account == NULL) - account = mail_config_get_default_account (); - - gconf = mail_config_get_gconf_client (); - send_html = gconf_client_get_bool (gconf, "/apps/evolution/mail/composer/send_html", NULL); - - if (post) - composer = e_msg_composer_new_post (); - else if (url) - composer = e_msg_composer_new_from_url (url); - else - composer = e_msg_composer_new (); - - if (composer) { - e_msg_composer_hdrs_set_from_account (E_MSG_COMPOSER_HDRS (composer->hdrs), account->name); - e_msg_composer_set_send_html (composer, send_html); - e_msg_composer_unset_changed (composer); - e_msg_composer_drop_editor_undo (composer); - return GTK_WIDGET (composer); - } else - return NULL; -} - -void -compose_msg (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - struct _composer_callback_data *ccd; - GtkWidget *composer; - EAccount *account; - - if (FOLDER_BROWSER_IS_DESTROYED (fb) || !check_send_configuration (fb)) - return; - - /* Figure out which account we want to initially compose from */ - account = mail_config_get_account_by_source_url (fb->uri); - - composer = create_msg_composer (account, FALSE, NULL); - if (!composer) - return; - - ccd = ccd_new (); - - g_signal_connect (composer, "send", G_CALLBACK (composer_send_cb), ccd); - g_signal_connect (composer, "save-draft", G_CALLBACK (composer_save_draft_cb), ccd); - - g_object_weak_ref ((GObject *) composer, (GWeakNotify) composer_destroy_cb, ccd); - - gtk_widget_show (composer); -} - -/* Send according to a mailto (RFC 2368) URL. */ -void -send_to_url (const char *url, const char *parent_uri) -{ - struct _composer_callback_data *ccd; - GtkWidget *composer; - EAccount *account = NULL; - - /* FIXME: no way to get folder browser? Not without - * big pain in the ass, as far as I can tell */ - if (!check_send_configuration (NULL)) - return; - - if (parent_uri) - account = mail_config_get_account_by_source_url (parent_uri); - - composer = create_msg_composer (account, FALSE, url); - - if (!composer) - return; - - ccd = ccd_new (); - - g_signal_connect (composer, "send", G_CALLBACK (composer_send_cb), ccd); - g_signal_connect (composer, "save-draft", G_CALLBACK (composer_save_draft_cb), ccd); - - g_object_weak_ref ((GObject *) composer, (GWeakNotify) composer_destroy_cb, ccd); - - gtk_widget_show (composer); -} - -static GList * -list_add_addresses (GList *list, const CamelInternetAddress *cia, GHashTable *account_hash, - GHashTable *rcpt_hash, EAccount **me) -{ - const char *name, *addr; - EAccount *account; - int i; - - for (i = 0; camel_internet_address_get (cia, i, &name, &addr); i++) { - /* Here I'll check to see if the cc:'d address is the address - of the sender, and if so, don't add it to the cc: list; this - is to fix Bugzilla bug #455. */ - account = g_hash_table_lookup (account_hash, addr); - if (account && me && !*me) - *me = account; - - if (!account && !g_hash_table_lookup (rcpt_hash, addr)) { - EDestination *dest; - - dest = e_destination_new (); - e_destination_set_name (dest, name); - e_destination_set_email (dest, addr); - - list = g_list_append (list, dest); - g_hash_table_insert (rcpt_hash, (char *) addr, GINT_TO_POINTER (1)); - } - } - - return list; -} - -static EAccount * -guess_me (const CamelInternetAddress *to, const CamelInternetAddress *cc, GHashTable *account_hash) -{ - EAccount *account = NULL; - const char *addr; - int i; - - /* "optimization" */ - if (!to && !cc) - return NULL; - - if (to) { - for (i = 0; camel_internet_address_get (to, i, NULL, &addr); i++) { - account = g_hash_table_lookup (account_hash, addr); - if (account) - goto found; - } - } - - if (cc) { - for (i = 0; camel_internet_address_get (cc, i, NULL, &addr); i++) { - account = g_hash_table_lookup (account_hash, addr); - if (account) - goto found; - } - } - - found: - - return account; -} - -static EAccount * -guess_me_from_accounts (const CamelInternetAddress *to, const CamelInternetAddress *cc, EAccountList *accounts) -{ - EAccount *account, *def; - GHashTable *account_hash; - EIterator *iter; - - account_hash = g_hash_table_new (camel_strcase_hash, camel_strcase_equal); - - /* add the default account to the hash first */ - if ((def = mail_config_get_default_account ())) { - if (def->id->address) - g_hash_table_insert (account_hash, (char *) def->id->address, (void *) def); - } - - iter = e_list_get_iterator ((EList *) accounts); - while (e_iterator_is_valid (iter)) { - account = (EAccount *) e_iterator_get (iter); - - if (account->id->address) { - EAccount *acnt; - - /* Accounts with identical email addresses that are enabled - * take precedence over the accounts that aren't. If all - * accounts with matching email addresses are disabled, then - * the first one in the list takes precedence. The default - * account always takes precedence no matter what. - */ - acnt = g_hash_table_lookup (account_hash, account->id->address); - if (acnt && acnt != def && !acnt->enabled && account->enabled) { - g_hash_table_remove (account_hash, acnt->id->address); - acnt = NULL; - } - - if (!acnt) - g_hash_table_insert (account_hash, (char *) account->id->address, (void *) account); - } - - e_iterator_next (iter); - } - - g_object_unref (iter); - - account = guess_me (to, cc, account_hash); - - g_hash_table_destroy (account_hash); - - return account; -} - -inline static void -mail_ignore (EMsgComposer *composer, const char *name, const char *address) -{ - e_msg_composer_ignore (composer, name && *name ? name : address); -} - -static void -mail_ignore_address (EMsgComposer *composer, const CamelInternetAddress *addr) -{ - const char *name, *address; - int i, max; - - max = camel_address_length (CAMEL_ADDRESS (addr)); - for (i = 0; i < max; i++) { - camel_internet_address_get (addr, i, &name, &address); - mail_ignore (composer, name, address); - } -} - -#define MAX_SUBJECT_LEN 1024 - -static EMsgComposer * -mail_generate_reply (CamelFolder *folder, CamelMimeMessage *message, const char *uid, int mode) -{ - const CamelInternetAddress *reply_to, *sender, *to_addrs, *cc_addrs; - const char *name = NULL, *address = NULL, *source = NULL; - const char *message_id, *references, *mlist = NULL; - char *text = NULL, *subject, format[256]; - EAccount *def, *account, *me = NULL; - EAccountList *accounts = NULL; - GHashTable *account_hash = NULL; - CamelMessageInfo *info = NULL; - GList *to = NULL, *cc = NULL; - EDestination **tov, **ccv; - EMsgComposer *composer; - CamelMimePart *part; - GConfClient *gconf; - EIterator *iter; - time_t date; - int date_ofs; - char *url; - - gconf = mail_config_get_gconf_client (); - - if (mode == REPLY_POST) { - composer = e_msg_composer_new_post (); - if (composer != NULL) { - url = mail_tools_folder_to_url (folder); - e_msg_composer_hdrs_set_post_to ((EMsgComposerHdrs *) composer->hdrs, url); - g_free (url); - } - } else - composer = e_msg_composer_new (); - - if (!composer) - return NULL; - - e_msg_composer_add_message_attachments (composer, message, TRUE); - - /* Set the recipients */ - accounts = mail_config_get_accounts (); - account_hash = g_hash_table_new (camel_strcase_hash, camel_strcase_equal); - - /* add the default account to the hash first */ - if ((def = mail_config_get_default_account ())) { - if (def->id->address) - g_hash_table_insert (account_hash, (char *) def->id->address, (void *) def); - } - - iter = e_list_get_iterator ((EList *) accounts); - while (e_iterator_is_valid (iter)) { - account = (EAccount *) e_iterator_get (iter); - - if (account->id->address) { - EAccount *acnt; - - /* Accounts with identical email addresses that are enabled - * take precedence over the accounts that aren't. If all - * accounts with matching email addresses are disabled, then - * the first one in the list takes precedence. The default - * account always takes precedence no matter what. - */ - acnt = g_hash_table_lookup (account_hash, account->id->address); - if (acnt && acnt != def && !acnt->enabled && account->enabled) { - g_hash_table_remove (account_hash, acnt->id->address); - acnt = NULL; - } - - if (!acnt) - g_hash_table_insert (account_hash, (char *) account->id->address, (void *) account); - } - - e_iterator_next (iter); - } - - g_object_unref (iter); - - to_addrs = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO); - cc_addrs = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC); - mail_ignore_address (composer, to_addrs); - mail_ignore_address (composer, cc_addrs); - - if (mode == REPLY_LIST) { - /* make sure we can reply to an mlist */ - info = camel_folder_get_message_info (folder, uid); - if (!(mlist = camel_message_info_mlist (info)) || *mlist == '\0') { - camel_folder_free_message_info (folder, info); - mode = REPLY_ALL; - info = NULL; - } - } - - determine_recipients: - if (mode == REPLY_LIST) { - EDestination *dest; - int i, max; - - /* look through the recipients to find the *real* mailing list address */ - d(printf ("we are looking for the mailing list called: %s\n", mlist)); - - max = camel_address_length (CAMEL_ADDRESS (to_addrs)); - for (i = 0; i < max; i++) { - camel_internet_address_get (to_addrs, i, &name, &address); - if (!strcasecmp (address, mlist)) - break; - } - - if (i == max) { - max = camel_address_length (CAMEL_ADDRESS (cc_addrs)); - for (i = 0; i < max; i++) { - camel_internet_address_get (cc_addrs, i, &name, &address); - if (!strcasecmp (address, mlist)) - break; - } - } - - if (address && i != max) { - dest = e_destination_new (); - e_destination_set_name (dest, name); - e_destination_set_email (dest, address); - - to = g_list_append (to, dest); - } else { - /* mailing list address wasn't found */ - if (strchr (mlist, '@')) { - /* mlist string has an @, maybe it's valid? */ - dest = e_destination_new (); - e_destination_set_email (dest, mlist); - - to = g_list_append (to, dest); - } else { - /* give up and just reply to all recipients? */ - mode = REPLY_ALL; - camel_folder_free_message_info (folder, info); - goto determine_recipients; - } - } - - me = guess_me (to_addrs, cc_addrs, account_hash); - camel_folder_free_message_info (folder, info); - } else { - GHashTable *rcpt_hash; - EDestination *dest; - - rcpt_hash = g_hash_table_new (camel_strcase_hash, camel_strcase_equal); - - reply_to = camel_mime_message_get_reply_to (message); - if (!reply_to) - reply_to = camel_mime_message_get_from (message); - - if (reply_to) { - int i; - - for (i = 0; camel_internet_address_get (reply_to, i, &name, &address); i++) { - /* ignore references to the Reply-To address in the To and Cc lists */ - if (address && !(mode == REPLY_ALL && g_hash_table_lookup (account_hash, address))) { - /* In the case that we are doing a Reply-To-All, we do not want - to include the user's email address because replying to oneself - is kinda silly. */ - dest = e_destination_new (); - e_destination_set_name (dest, name); - e_destination_set_email (dest, address); - to = g_list_append (to, dest); - g_hash_table_insert (rcpt_hash, (char *) address, GINT_TO_POINTER (1)); - mail_ignore (composer, name, address); - } - } - } - - if (mode == REPLY_ALL) { - cc = list_add_addresses (cc, to_addrs, account_hash, rcpt_hash, me ? NULL : &me); - cc = list_add_addresses (cc, cc_addrs, account_hash, rcpt_hash, me ? NULL : &me); - - /* promote the first Cc: address to To: if To: is empty */ - if (to == NULL && cc != NULL) { - to = cc; - cc = g_list_remove_link (cc, to); - } - } else { - me = guess_me (to_addrs, cc_addrs, account_hash); - } - - g_hash_table_destroy (rcpt_hash); - } - - g_hash_table_destroy (account_hash); - - if (!me) { - /* default 'me' to the source account... */ - if ((source = camel_mime_message_get_source (message))) - me = mail_config_get_account_by_source_url (source); - } - - /* set body text here as we want all ignored words to take effect */ - switch (gconf_client_get_int (gconf, "/apps/evolution/mail/format/reply_style", NULL)) { - case MAIL_CONFIG_REPLY_DO_NOT_QUOTE: - /* do nothing */ - break; - case MAIL_CONFIG_REPLY_ATTACH: - /* attach the original message as an attachment */ - part = mail_tool_make_message_attachment (message); - e_msg_composer_attach (composer, part); - camel_object_unref (part); - break; - case MAIL_CONFIG_REPLY_QUOTED: - default: - /* do what any sane user would want when replying... */ - sender = camel_mime_message_get_from (message); - if (sender != NULL && camel_address_length (CAMEL_ADDRESS (sender)) > 0) { - camel_internet_address_get (sender, 0, &name, &address); - } else { - name = _("an unknown sender"); - } - - date = camel_mime_message_get_date (message, &date_ofs); - /* Convert to UTC */ - date += (date_ofs / 100) * 60 * 60; - date += (date_ofs % 100) * 60; - - /* translators: attribution string used when quoting messages */ - e_utf8_strftime (format, sizeof (format), _("On %a, %Y-%m-%d at %H:%M %%+05d, %%s wrote:"), gmtime (&date)); - text = mail_tool_quote_message (message, format, date_ofs, name && *name ? name : address); - mail_ignore (composer, name, address); - if (text) { - e_msg_composer_set_body_text (composer, text); - g_free (text); - } - break; - } - - /* Set the subject of the new message. */ - subject = (char *) camel_mime_message_get_subject (message); - if (!subject) - subject = g_strdup (""); - else { - if (!strncasecmp (subject, "Re: ", 4)) - subject = g_strndup (subject, MAX_SUBJECT_LEN); - else { - if (strlen (subject) < MAX_SUBJECT_LEN) { - subject = g_strdup_printf ("Re: %s", subject); - } else { - /* We can't use %.*s because it depends on the locale being C/POSIX - or UTF-8 to work correctly in glibc */ - char *sub; - - /*subject = g_strdup_printf ("Re: %.*s...", MAX_SUBJECT_LEN, subject);*/ - sub = g_malloc (MAX_SUBJECT_LEN + 8); - memcpy (sub, "Re: ", 4); - memcpy (sub + 4, subject, MAX_SUBJECT_LEN); - memcpy (sub + 4 + MAX_SUBJECT_LEN, "...", 4); - subject = sub; - } - } - } - - tov = e_destination_list_to_vector (to); - ccv = e_destination_list_to_vector (cc); - - g_list_free (to); - g_list_free (cc); - - e_msg_composer_set_headers (composer, me ? me->name : NULL, tov, ccv, NULL, subject); - - e_destination_freev (tov); - e_destination_freev (ccv); - - g_free (subject); - - /* Add In-Reply-To and References. */ - message_id = camel_medium_get_header (CAMEL_MEDIUM (message), "Message-Id"); - references = camel_medium_get_header (CAMEL_MEDIUM (message), "References"); - if (message_id) { - char *reply_refs; - - e_msg_composer_add_header (composer, "In-Reply-To", message_id); - - if (references) - reply_refs = g_strdup_printf ("%s %s", references, message_id); - else - reply_refs = g_strdup (message_id); - - e_msg_composer_add_header (composer, "References", reply_refs); - g_free (reply_refs); - } else if (references) { - e_msg_composer_add_header (composer, "References", references); - } - - e_msg_composer_drop_editor_undo (composer); - - return composer; -} - -static void -requeue_mail_reply (CamelFolder *folder, const char *uid, CamelMimeMessage *msg, void *data) -{ - int mode = GPOINTER_TO_INT (data); - - if (msg != NULL) - mail_reply (folder, msg, uid, mode); -} - -void -mail_reply (CamelFolder *folder, CamelMimeMessage *msg, const char *uid, int mode) -{ - struct _composer_callback_data *ccd; - EMsgComposer *composer; - - g_return_if_fail (CAMEL_IS_FOLDER (folder)); - g_return_if_fail (uid != NULL); - - if (!msg) { - mail_get_message (folder, uid, requeue_mail_reply, - GINT_TO_POINTER (mode), mail_thread_new); - return; - } - - composer = mail_generate_reply (folder, msg, uid, mode); - if (!composer) - return; - - ccd = ccd_new (); - - camel_object_ref (folder); - ccd->folder = folder; - ccd->uid = g_strdup (uid); - ccd->flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN; - if (mode == REPLY_LIST || mode == REPLY_ALL) - ccd->flags |= CAMEL_MESSAGE_ANSWERED_ALL; - ccd->set = ccd->flags; - - g_signal_connect (composer, "send", G_CALLBACK (composer_send_cb), ccd); - g_signal_connect (composer, "save-draft", G_CALLBACK (composer_save_draft_cb), ccd); - - g_object_weak_ref ((GObject *) composer, (GWeakNotify) composer_destroy_cb, ccd); - - gtk_widget_show (GTK_WIDGET (composer)); - e_msg_composer_unset_changed (composer); -} - -void -reply_to_sender (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb) || !check_send_configuration (fb)) - return; - - mail_reply (fb->folder, NULL, fb->message_list->cursor_uid, REPLY_SENDER); -} - -void -reply_to_list (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb) || !check_send_configuration (fb)) - return; - - mail_reply (fb->folder, NULL, fb->message_list->cursor_uid, REPLY_LIST); -} - -void -reply_to_all (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb) || !check_send_configuration (fb)) - return; - - mail_reply(fb->folder, NULL, fb->message_list->cursor_uid, REPLY_ALL); -} - -void -enumerate_msg (MessageList *ml, const char *uid, gpointer data) -{ - g_return_if_fail (ml != NULL); - - g_ptr_array_add ((GPtrArray *) data, g_strdup (uid)); -} - - -static EMsgComposer * -forward_get_composer (CamelMimeMessage *message, const char *subject) -{ - struct _composer_callback_data *ccd; - EAccount *account = NULL; - EMsgComposer *composer; - - if (message) { - const CamelInternetAddress *to_addrs, *cc_addrs; - EAccountList *accounts; - - accounts = mail_config_get_accounts (); - to_addrs = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO); - cc_addrs = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC); - - account = guess_me_from_accounts (to_addrs, cc_addrs, accounts); - - if (!account) { - const char *source; - - source = camel_mime_message_get_source (message); - account = mail_config_get_account_by_source_url (source); - } - } - - if (!account) - account = mail_config_get_default_account (); - - composer = e_msg_composer_new (); - if (composer) { - ccd = ccd_new (); - - g_signal_connect (composer, "send", G_CALLBACK (composer_send_cb), ccd); - g_signal_connect (composer, "save-draft", G_CALLBACK (composer_save_draft_cb), ccd); - - g_object_weak_ref ((GObject *) composer, (GWeakNotify) composer_destroy_cb, ccd); - - e_msg_composer_set_headers (composer, account->name, NULL, NULL, NULL, subject); - } else { - g_warning ("Could not create composer"); - } - - return composer; -} - -static void -do_forward_non_attached (CamelFolder *folder, GPtrArray *uids, GPtrArray *messages, void *data) -{ - MailConfigForwardStyle style = GPOINTER_TO_INT (data); - CamelMimeMessage *message; - char *subject, *text; - int i; - - if (messages->len == 0) - return; - - for (i = 0; i < messages->len; i++) { - message = messages->pdata[i]; - subject = mail_tool_generate_forward_subject (message); - text = mail_tool_forward_message (message, style == MAIL_CONFIG_FORWARD_QUOTED); - - if (text) { - EMsgComposer *composer = forward_get_composer (message, subject); - if (composer) { - CamelDataWrapper *wrapper; - - e_msg_composer_set_body_text (composer, text); - - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (message)); - if (CAMEL_IS_MULTIPART (wrapper)) - e_msg_composer_add_message_attachments (composer, message, FALSE); - - gtk_widget_show (GTK_WIDGET (composer)); - e_msg_composer_unset_changed (composer); - e_msg_composer_drop_editor_undo (composer); - } - g_free (text); - } - - g_free (subject); - } -} - -static void -forward_message (FolderBrowser *fb, MailConfigForwardStyle style) -{ - GPtrArray *uids; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - if (!check_send_configuration (fb)) - return; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - mail_get_messages (fb->folder, uids, do_forward_non_attached, GINT_TO_POINTER (style)); -} - -void -forward_inline (GtkWidget *widget, gpointer user_data) -{ - forward_message (user_data, MAIL_CONFIG_FORWARD_INLINE); -} - -void -forward_quoted (GtkWidget *widget, gpointer user_data) -{ - forward_message (user_data, MAIL_CONFIG_FORWARD_QUOTED); -} - -static void -do_forward_attach (CamelFolder *folder, GPtrArray *messages, CamelMimePart *part, char *subject, void *data) -{ - CamelMimeMessage *message = data; - - if (part) { - EMsgComposer *composer = forward_get_composer (message, subject); - if (composer) { - e_msg_composer_attach (composer, part); - gtk_widget_show (GTK_WIDGET (composer)); - e_msg_composer_unset_changed (composer); - e_msg_composer_drop_editor_undo (composer); - } - } -} - -void -forward_attached (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = (FolderBrowser *) user_data; - GPtrArray *uids; - - if (FOLDER_BROWSER_IS_DESTROYED (fb) || !check_send_configuration (fb)) - return; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - mail_build_attachment (fb->folder, uids, do_forward_attach, - uids->len == 1 ? fb->mail_display->current_message : NULL); -} - -void -forward (GtkWidget *widget, gpointer user_data) -{ - MailConfigForwardStyle style; - GConfClient *gconf; - - gconf = mail_config_get_gconf_client (); - style = gconf_client_get_int (gconf, "/apps/evolution/mail/format/forward_style", NULL); - - if (style == MAIL_CONFIG_FORWARD_ATTACHED) - forward_attached (widget, user_data); - else - forward_message (user_data, style); -} - - -void -post_to_url (const char *url) -{ - struct _composer_callback_data *ccd; - GtkWidget *composer; - EAccount *account = NULL; - - /* FIXME: no way to get folder browser? Not without - * big pain in the ass, as far as I can tell */ - if (!check_send_configuration (NULL)) - return; - - if (url) - account = mail_config_get_account_by_source_url (url); - - composer = create_msg_composer (account, TRUE, NULL); - if (!composer) - return; - - e_msg_composer_hdrs_set_post_to ((EMsgComposerHdrs *) ((EMsgComposer *) composer)->hdrs, url); - - ccd = ccd_new (); - - g_signal_connect (composer, "send", G_CALLBACK (composer_send_cb), ccd); - g_signal_connect (composer, "save-draft", G_CALLBACK (composer_save_draft_cb), ccd); - - g_object_weak_ref ((GObject *) composer, (GWeakNotify) composer_destroy_cb, ccd); - - gtk_widget_show (composer); -} - -void -post_message (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - char *url; - - if (FOLDER_BROWSER_IS_DESTROYED (fb) || !check_send_configuration (fb)) - return; - - url = mail_tools_folder_to_url (fb->folder); - post_to_url (url); - g_free (url); -} - -void -post_reply (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb) || !check_send_configuration (fb)) - return; - - mail_reply (fb->folder, NULL, fb->message_list->cursor_uid, REPLY_POST); -} - - -static EMsgComposer * -redirect_get_composer (CamelMimeMessage *message) -{ - const CamelInternetAddress *to_addrs, *cc_addrs; - struct _composer_callback_data *ccd; - EAccountList *accounts = NULL; - EAccount *account = NULL; - EMsgComposer *composer; - - g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); - - accounts = mail_config_get_accounts (); - to_addrs = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO); - cc_addrs = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC); - - account = guess_me_from_accounts (to_addrs, cc_addrs, accounts); - - if (!account) { - const char *source; - - source = camel_mime_message_get_source (message); - account = mail_config_get_account_by_source_url (source); - } - - if (!account) - account = mail_config_get_default_account (); - - /* QMail will refuse to send a message if it finds one of - it's Delivered-To headers in the message, so remove all - Delivered-To headers. Fixes bug #23635. */ - while (camel_medium_get_header (CAMEL_MEDIUM (message), "Delivered-To")) - camel_medium_remove_header (CAMEL_MEDIUM (message), "Delivered-To"); - - composer = e_msg_composer_new_redirect (message, account->name); - if (composer) { - ccd = ccd_new (); - - g_signal_connect (composer, "send", G_CALLBACK (composer_send_cb), ccd); - g_signal_connect (composer, "save-draft", G_CALLBACK (composer_save_draft_cb), ccd); - - g_object_weak_ref ((GObject *) composer, (GWeakNotify) composer_destroy_cb, ccd); - } else { - g_warning ("Could not create composer"); - } - - return composer; -} - -static void -do_redirect (CamelFolder *folder, const char *uid, CamelMimeMessage *message, void *data) -{ - EMsgComposer *composer; - - if (!message) - return; - - composer = redirect_get_composer (message); - if (composer) { - CamelDataWrapper *wrapper; - - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (message)); - if (CAMEL_IS_MULTIPART (wrapper)) - e_msg_composer_add_message_attachments (composer, message, FALSE); - - gtk_widget_show (GTK_WIDGET (composer)); - e_msg_composer_unset_changed (composer); - e_msg_composer_drop_editor_undo (composer); - } -} - -void -redirect (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = (FolderBrowser *) user_data; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - if (!check_send_configuration (fb)) - return; - - mail_get_message (fb->folder, fb->message_list->cursor_uid, - do_redirect, NULL, mail_thread_new); -} - -static void -transfer_msg_done (gboolean ok, void *data) -{ - FolderBrowser *fb = data; - gboolean hide_deleted; - GConfClient *gconf; - int row; - - if (ok && !FOLDER_BROWSER_IS_DESTROYED (fb)) { - gconf = mail_config_get_gconf_client (); - hide_deleted = !gconf_client_get_bool (gconf, "/apps/evolution/mail/display/show_deleted", NULL); - - row = e_tree_row_of_node (fb->message_list->tree, - e_tree_get_cursor (fb->message_list->tree)); - - /* If this is the last message and deleted messages - are hidden, select the previous */ - if ((row + 1 == e_tree_row_count (fb->message_list->tree)) && hide_deleted) - message_list_select (fb->message_list, MESSAGE_LIST_SELECT_PREVIOUS, - 0, CAMEL_MESSAGE_DELETED, FALSE); - else - message_list_select (fb->message_list, MESSAGE_LIST_SELECT_NEXT, - 0, 0, FALSE); - } - - g_object_unref (fb); -} - -static void -transfer_msg (FolderBrowser *fb, gboolean delete_from_source) -{ - static const char *allowed_types[] = { "mail/*", "vtrash", NULL }; - extern EvolutionShellClient *global_shell_client; - GNOME_Evolution_Folder *folder; - static char *last_uri = NULL; - GPtrArray *uids; - char *desc; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - if (last_uri == NULL) - last_uri = g_strdup (fb->uri); - - if (delete_from_source) - desc = _("Move message(s) to"); - else - desc = _("Copy message(s) to"); - - evolution_shell_client_user_select_folder (global_shell_client, - GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (fb))), - desc, last_uri, allowed_types, - &folder); - if (!folder) - return; - - if (strcmp (last_uri, folder->evolutionUri) != 0) { - g_free (last_uri); - last_uri = g_strdup (folder->evolutionUri); - } - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - if (delete_from_source) { - g_object_ref (fb); - mail_transfer_messages (fb->folder, uids, delete_from_source, - folder->physicalUri, 0, - transfer_msg_done, fb); - } else { - mail_transfer_messages (fb->folder, uids, delete_from_source, - folder->physicalUri, 0, NULL, NULL); - } - - CORBA_free (folder); -} - -void -move_msg_cb (GtkWidget *widget, gpointer user_data) -{ - transfer_msg (user_data, TRUE); -} - -void -move_msg (BonoboUIComponent *uih, void *user_data, const char *path) -{ - transfer_msg (user_data, TRUE); -} - -void -copy_msg_cb (GtkWidget *widget, gpointer user_data) -{ - transfer_msg (user_data, FALSE); -} - -void -copy_msg (BonoboUIComponent *uih, void *user_data, const char *path) -{ - transfer_msg (user_data, FALSE); -} - -/* Copied from e-shell-view.c */ -static GtkWidget * -find_socket (GtkContainer *container) -{ - GList *children, *tmp; - - children = gtk_container_get_children (container); - while (children) { - if (BONOBO_IS_SOCKET (children->data)) - return children->data; - else if (GTK_IS_CONTAINER (children->data)) { - GtkWidget *socket = find_socket (children->data); - if (socket) - return socket; - } - tmp = children->next; - g_list_free_1 (children); - children = tmp; - } - - return NULL; -} - -static void -popup_listener_cb (BonoboListener *listener, - const char *event_name, - const CORBA_any *any, - CORBA_Environment *ev, - gpointer user_data) -{ - char *type = bonobo_event_subtype (event_name); - - if (!strcmp (type, "Destroy")) { - gtk_widget_destroy (GTK_WIDGET (user_data)); - } - - g_free (type); -} - -void -addrbook_sender (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - const char *addr_str; - CamelMessageInfo *info; - GtkWidget *win; - GtkWidget *control; - GtkWidget *socket; - GPtrArray *uids; - int i; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - if (uids->len != 1) - goto done; - - info = camel_folder_get_message_info (fb->folder, uids->pdata[0]); - if (info == NULL || (addr_str = camel_message_info_from (info)) == NULL) - goto done; - - win = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_title (GTK_WINDOW (win), _("Sender")); - - control = bonobo_widget_new_control ("OAFIID:GNOME_Evolution_Addressbook_AddressPopup", - CORBA_OBJECT_NIL); - bonobo_widget_set_property (BONOBO_WIDGET (control), - "email", TC_CORBA_string, addr_str, - NULL); - - bonobo_event_source_client_add_listener (bonobo_widget_get_objref (BONOBO_WIDGET (control)), - popup_listener_cb, NULL, NULL, win); - - socket = find_socket (GTK_CONTAINER (control)); - - g_object_weak_ref ((GObject *) socket, (GWeakNotify) gtk_widget_destroy, win); - - gtk_container_add (GTK_CONTAINER (win), control); - gtk_widget_show_all (win); - -done: - for (i = 0; i < uids->len; i++) - g_free (uids->pdata[i]); - g_ptr_array_free (uids, TRUE); -} - -void -add_sender_to_addrbook (BonoboUIComponent *uih, void *user_data, const char *path) -{ - addrbook_sender (NULL, user_data); -} - -void -apply_filters (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - GPtrArray *uids; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - mail_filter_on_demand (fb->folder, uids); -} - -void -select_all (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - ESelectionModel *etsm; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - if (GTK_WIDGET_HAS_FOCUS (fb->mail_display->html)) { - gtk_html_select_all (fb->mail_display->html); - } else { - etsm = e_tree_get_selection_model (fb->message_list->tree); - - e_selection_model_select_all (etsm); - } -} - -/* Thread selection */ - -typedef struct thread_select_info { - MessageList *ml; - GPtrArray *paths; -} thread_select_info_t; - -static gboolean -select_node (ETreeModel *tm, ETreePath path, gpointer user_data) -{ - thread_select_info_t *tsi = (thread_select_info_t *) user_data; - - g_ptr_array_add (tsi->paths, path); - return FALSE; /*not done yet*/ -} - -static void -thread_select_foreach (ETreePath path, gpointer user_data) -{ - thread_select_info_t *tsi = (thread_select_info_t *) user_data; - ETreeModel *tm = tsi->ml->model; - ETreePath node; - - /* @path part of the initial selection. If it has children, - * we select them as well. If it doesn't, we select its siblings and - * their children (ie, the current node must be inside the thread - * that the user wants to mark. - */ - - if (e_tree_model_node_get_first_child (tm, path)) - node = path; - else { - node = e_tree_model_node_get_parent (tm, path); - - /* Let's make an exception: if no parent, then we're about - * to mark the whole tree. No. */ - if (e_tree_model_node_is_root (tm, node)) - node = path; - } - - e_tree_model_node_traverse (tm, node, select_node, tsi); -} - -void -select_thread (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - ETreeSelectionModel *selection_model; - thread_select_info_t tsi; - int i; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - /* For every selected item, select the thread containing it. - * We can't alter the selection while iterating through it, - * so build up a list of paths. - */ - - tsi.ml = fb->message_list; - tsi.paths = g_ptr_array_new (); - - e_tree_selected_path_foreach (fb->message_list->tree, thread_select_foreach, &tsi); - - selection_model = E_TREE_SELECTION_MODEL (e_tree_get_selection_model (fb->message_list->tree)); - - for (i = 0; i < tsi.paths->len; i++) - e_tree_selection_model_add_to_selection (selection_model, - tsi.paths->pdata[i]); - g_ptr_array_free (tsi.paths, TRUE); -} - -void -invert_selection (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - ESelectionModel *etsm; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - etsm = e_tree_get_selection_model (fb->message_list->tree); - - e_selection_model_invert_selection (etsm); -} - -/* flag all selected messages. Return number flagged */ -static int -flag_messages (FolderBrowser *fb, guint32 mask, guint32 set) -{ - GPtrArray *uids; - int i; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return 0; - - /* could just use specific callback but i'm lazy */ - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - camel_folder_freeze (fb->folder); - for (i = 0; i < uids->len; i++) { - camel_folder_set_message_flags (fb->folder, uids->pdata[i], mask, set); - g_free (uids->pdata[i]); - } - - camel_folder_thaw (fb->folder); - - g_ptr_array_free (uids, TRUE); - - return i; -} - -static int -toggle_flags (FolderBrowser *fb, guint32 mask) -{ - GPtrArray *uids; - int i; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return 0; - - /* could just use specific callback but i'm lazy */ - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - camel_folder_freeze (fb->folder); - for (i = 0; i < uids->len; i++) { - guint32 flags; - - flags = ~(camel_folder_get_message_flags (fb->folder, uids->pdata[i])); - - /* if we're flagging a message important, always undelete it too */ - if (mask & flags & CAMEL_MESSAGE_FLAGGED) { - flags &= ~CAMEL_MESSAGE_DELETED; - mask |= CAMEL_MESSAGE_DELETED; - } - - /* if we're flagging a message deleted, mark it seen. If - * we're undeleting it, we also want it to be seen, so always do this. - */ - if (mask & CAMEL_MESSAGE_DELETED) { - flags |= CAMEL_MESSAGE_SEEN; - mask |= CAMEL_MESSAGE_SEEN; - } - - camel_folder_set_message_flags (fb->folder, uids->pdata[i], mask, flags); - - g_free (uids->pdata[i]); - } - camel_folder_thaw (fb->folder); - - g_ptr_array_free (uids, TRUE); - - return i; -} - -void -mark_as_seen (BonoboUIComponent *uih, void *user_data, const char *path) -{ - flag_messages (FOLDER_BROWSER (user_data), CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN); -} - -void -mark_as_unseen (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - /* Remove the automatic mark-as-read timer first */ - if (fb->seen_id) { - g_source_remove (fb->seen_id); - fb->seen_id = 0; - } - - flag_messages (fb, CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED, 0); -} - -void -mark_all_as_seen (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - GPtrArray *uids; - int i; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - uids = camel_folder_get_uids (fb->folder); - camel_folder_freeze (fb->folder); - for (i = 0; i < uids->len; i++) - camel_folder_set_message_flags (fb->folder, uids->pdata[i], CAMEL_MESSAGE_SEEN, ~0); - camel_folder_free_uids (fb->folder, uids); - camel_folder_thaw (fb->folder); -} - -void -mark_as_important (BonoboUIComponent *uih, void *user_data, const char *path) -{ - flag_messages (FOLDER_BROWSER (user_data), CAMEL_MESSAGE_FLAGGED|CAMEL_MESSAGE_DELETED, CAMEL_MESSAGE_FLAGGED); -} - -void -mark_as_unimportant (BonoboUIComponent *uih, void *user_data, const char *path) -{ - flag_messages (FOLDER_BROWSER (user_data), CAMEL_MESSAGE_FLAGGED, 0); -} - -void -toggle_as_important (BonoboUIComponent *uih, void *user_data, const char *path) -{ - toggle_flags (FOLDER_BROWSER (user_data), CAMEL_MESSAGE_FLAGGED); -} - - -struct _tag_editor_data { - MessageTagEditor *editor; - FolderBrowser *fb; - GPtrArray *uids; -}; - -static void -tag_editor_response(GtkWidget *gd, int button, struct _tag_editor_data *data) -{ - CamelFolder *folder; - CamelTag *tags, *t; - GPtrArray *uids; - int i; - - /*if (FOLDER_BROWSER_IS_DESTROYED (data->fb)) - goto done;*/ - - if (button == GTK_RESPONSE_OK - && (tags = message_tag_editor_get_tag_list (data->editor))) { - folder = data->fb->folder; - uids = data->uids; - - camel_folder_freeze (folder); - for (i = 0; i < uids->len; i++) { - for (t = tags; t; t = t->next) - camel_folder_set_message_user_tag (folder, uids->pdata[i], t->name, t->value); - } - camel_folder_thaw (folder); - camel_tag_list_free (&tags); - } - - gtk_widget_destroy(gd); - - g_object_unref (data->fb); - g_ptr_array_free (data->uids, TRUE); - g_free (data); -} - -void -flag_for_followup (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - struct _tag_editor_data *data; - GtkWidget *editor; - GPtrArray *uids; - int i; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - editor = (GtkWidget *) message_tag_followup_new (); - - data = g_new (struct _tag_editor_data, 1); - data->editor = MESSAGE_TAG_EDITOR (editor); - gtk_widget_ref (GTK_WIDGET (fb)); - data->fb = fb; - data->uids = uids; - - for (i = 0; i < uids->len; i++) { - CamelMessageInfo *info; - - info = camel_folder_get_message_info (fb->folder, uids->pdata[i]); - message_tag_followup_append_message (MESSAGE_TAG_FOLLOWUP (editor), - camel_message_info_from (info), - camel_message_info_subject (info)); - } - - g_signal_connect(editor, "response", G_CALLBACK(tag_editor_response), data); - - /* special-case... */ - if (uids->len == 1) { - CamelMessageInfo *info; - - info = camel_folder_get_message_info (fb->folder, uids->pdata[0]); - if (info) { - if (info->user_tags) - message_tag_editor_set_tag_list (MESSAGE_TAG_EDITOR (editor), info->user_tags); - camel_folder_free_message_info (fb->folder, info); - } - } - - gtk_widget_show (editor); -} - -void -flag_followup_completed (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - GPtrArray *uids; - char *now; - int i; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - now = header_format_date (time (NULL), 0); - - camel_folder_freeze (fb->folder); - for (i = 0; i < uids->len; i++) { - const char *tag; - - tag = camel_folder_get_message_user_tag (fb->folder, uids->pdata[i], "follow-up"); - if (tag == NULL || *tag == '\0') - continue; - - camel_folder_set_message_user_tag (fb->folder, uids->pdata[i], "completed-on", now); - } - camel_folder_thaw (fb->folder); - - g_free (now); - - g_ptr_array_free (uids, TRUE); -} - -void -flag_followup_clear (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - GPtrArray *uids; - int i; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - camel_folder_freeze (fb->folder); - for (i = 0; i < uids->len; i++) { - camel_folder_set_message_user_tag (fb->folder, uids->pdata[i], "follow-up", ""); - camel_folder_set_message_user_tag (fb->folder, uids->pdata[i], "due-by", ""); - camel_folder_set_message_user_tag (fb->folder, uids->pdata[i], "completed-on", ""); - } - camel_folder_thaw (fb->folder); - - g_ptr_array_free (uids, TRUE); -} - -void -zoom_in (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - gtk_html_zoom_in (fb->mail_display->html); -} - -void -zoom_out (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - gtk_html_zoom_out (fb->mail_display->html); -} - -void -zoom_reset (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - gtk_html_zoom_reset (fb->mail_display->html); -} - -static void -do_edit_messages (CamelFolder *folder, GPtrArray *uids, GPtrArray *messages, void *data) -{ - FolderBrowser *fb = (FolderBrowser *) data; - int i; - - if (messages == NULL) - return; - - for (i = 0; i < messages->len; i++) { - struct _composer_callback_data *ccd; - EMsgComposer *composer; - - camel_medium_remove_header (CAMEL_MEDIUM (messages->pdata[i]), "X-Mailer"); - - composer = e_msg_composer_new_with_message (messages->pdata[i]); - e_msg_composer_unset_changed (composer); - e_msg_composer_drop_editor_undo (composer); - - if (composer) { - ccd = ccd_new (); - if (folder_browser_is_drafts (fb)) { - camel_object_ref (folder); - ccd->drafts_folder = folder; - ccd->drafts_uid = g_strdup (uids->pdata[i]); - } - - g_signal_connect (composer, "send", G_CALLBACK (composer_send_cb), ccd); - g_signal_connect (composer, "save-draft", G_CALLBACK (composer_save_draft_cb), ccd); - - g_object_weak_ref ((GObject *) composer, (GWeakNotify) composer_destroy_cb, ccd); - - gtk_widget_show (GTK_WIDGET (composer)); - } - } -} - -static gboolean -are_you_sure (const char *msg, GPtrArray *uids, FolderBrowser *fb) -{ - GtkWidget *dialog; - int button, i; - - dialog = gtk_message_dialog_new (FB_WINDOW (fb), GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, - msg, uids->len); - button = gtk_dialog_run ((GtkDialog *) dialog); - gtk_widget_destroy (dialog); - - if (button != GTK_RESPONSE_OK) { - for (i = 0; i < uids->len; i++) - g_free (uids->pdata[i]); - g_ptr_array_free (uids, TRUE); - } - - return button == GTK_RESPONSE_OK; -} - -static void -edit_msg_internal (FolderBrowser *fb) -{ - GPtrArray *uids; - - if (!check_send_configuration (fb)) - return; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - if (uids->len > 10 && !are_you_sure (_("Are you sure you want to edit all %d messages?"), uids, fb)) - return; - - mail_get_messages (fb->folder, uids, do_edit_messages, fb); -} - -void -edit_msg (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - if (!folder_browser_is_drafts (fb)) { - e_notice(FB_WINDOW(fb), GTK_MESSAGE_ERROR, - _("You may only edit messages saved\nin the Drafts folder.")); - return; - } - - edit_msg_internal (fb); -} - -static void -do_resend_messages (CamelFolder *folder, GPtrArray *uids, GPtrArray *messages, void *data) -{ - int i; - - for (i = 0; i < messages->len; i++) { - /* generate a new Message-Id because they need to be unique */ - camel_mime_message_set_message_id (messages->pdata[i], NULL); - } - - /* "Resend" should open up the composer to let the user edit the message */ - do_edit_messages (folder, uids, messages, data); -} - - -void -resend_msg (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - GPtrArray *uids; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - if (!folder_browser_is_sent (fb)) { - e_notice (FB_WINDOW (fb), GTK_MESSAGE_ERROR, - _("You may only resend messages\nin the Sent folder.")); - return; - } - - if (!check_send_configuration (fb)) - return; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - if (uids->len > 10 && !are_you_sure (_("Are you sure you want to resend all %d messages?"), uids, fb)) - return; - - mail_get_messages (fb->folder, uids, do_resend_messages, fb); -} - - -void -search_msg (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - GtkWidget *w; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - if (fb->mail_display->current_message == NULL) { - GtkWidget *dialog; - - dialog = gtk_message_dialog_new (FB_WINDOW(fb), GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, - _("No Message Selected")); - g_signal_connect_swapped (dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); - gtk_widget_show (dialog); - return; - } - - w = mail_search_new (fb->mail_display); - gtk_widget_show_all (w); -} - -void -load_images (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - mail_display_load_images (fb->mail_display); -} - -static void -save_msg_ok (GtkWidget *widget, gpointer user_data) -{ - CamelFolder *folder; - GPtrArray *uids; - const char *path; - struct stat st; - gboolean ret = TRUE; - - path = gtk_file_selection_get_filename (GTK_FILE_SELECTION (user_data)); - if (path[0] == '\0') - return; - - /* make sure we can actually save to it... */ - if (stat (path, &st) != -1 && !S_ISREG (st.st_mode)) - return; - - if (access(path, F_OK) == 0) { - if (access(path, W_OK) != 0) { - e_notice(GTK_WINDOW(user_data), GTK_MESSAGE_ERROR, - _("Cannot save to `%s'\n %s"), path, g_strerror(errno)); - return; - } - - ret = e_question(GTK_WINDOW(user_data), GTK_RESPONSE_NO, NULL, - _("`%s' already exists.\nOverwrite it?"), path); - } - - if (ret) { - folder = g_object_get_data ((GObject *) user_data, "folder"); - uids = g_object_steal_data (G_OBJECT (user_data), "uids"); - mail_save_messages (folder, uids, path, NULL, NULL); - gtk_widget_destroy (GTK_WIDGET (user_data)); - } -} - -static void -save_msg_destroy (gpointer user_data) -{ - GPtrArray *uids = user_data; - - if (uids) { - int i; - - for (i = 0; i < uids->len; i++) - g_free (uids->pdata[i]); - - g_ptr_array_free (uids, TRUE); - } -} - -void -save_msg (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - GtkFileSelection *filesel; - GPtrArray *uids; - char *title, *path; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - if (uids->len == 1) - title = _("Save Message As..."); - else - title = _("Save Messages As..."); - - filesel = GTK_FILE_SELECTION (gtk_file_selection_new (title)); - path = g_strdup_printf ("%s/", g_get_home_dir ()); - gtk_file_selection_set_filename (filesel, path); - g_free (path); - - g_object_set_data_full ((GObject *) filesel, "uids", uids, save_msg_destroy); - g_object_set_data ((GObject *) filesel, "folder", fb->folder); - - g_signal_connect (filesel->ok_button, "clicked", G_CALLBACK (save_msg_ok), filesel); - g_signal_connect_swapped (filesel->cancel_button, "clicked", - G_CALLBACK (gtk_widget_destroy), filesel); - - gtk_widget_show (GTK_WIDGET (filesel)); -} - -void -colour_msg (GtkWidget *widget, gpointer user_data) -{ - /* FIXME: implement me? */ -} - -void -delete_msg (GtkWidget *button, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - gboolean hide_deleted; - GConfClient *gconf; - int deleted, row; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - deleted = flag_messages (fb, CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, - CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN); - - /* Select the next message if we are only deleting one message */ - if (deleted == 1) { - row = e_tree_row_of_node (fb->message_list->tree, - e_tree_get_cursor (fb->message_list->tree)); - - gconf = mail_config_get_gconf_client (); - hide_deleted = !gconf_client_get_bool (gconf, "/apps/evolution/mail/display/show_deleted", NULL); - - /* If this is the last message and deleted messages - are hidden, select the previous */ - if ((row + 1 == e_tree_row_count (fb->message_list->tree)) && hide_deleted) - message_list_select (fb->message_list, MESSAGE_LIST_SELECT_PREVIOUS, - 0, CAMEL_MESSAGE_DELETED, FALSE); - else - message_list_select (fb->message_list, MESSAGE_LIST_SELECT_NEXT, - 0, 0, FALSE); - } -} - -void -undelete_msg (GtkWidget *button, gpointer user_data) -{ - flag_messages (FOLDER_BROWSER (user_data), CAMEL_MESSAGE_DELETED, 0); -} - -void -next_msg (GtkWidget *button, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - message_list_select (fb->message_list, MESSAGE_LIST_SELECT_NEXT, 0, 0, FALSE); -} - -void -next_unread_msg (GtkWidget *button, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - message_list_select (fb->message_list, MESSAGE_LIST_SELECT_NEXT, 0, CAMEL_MESSAGE_SEEN, TRUE); -} - -void -next_flagged_msg (GtkWidget *button, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - message_list_select (fb->message_list, MESSAGE_LIST_SELECT_NEXT, - CAMEL_MESSAGE_FLAGGED, CAMEL_MESSAGE_FLAGGED, FALSE); -} - -void -next_thread (GtkWidget *button, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - message_list_select_next_thread (fb->message_list); -} - -void -previous_msg (GtkWidget *button, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - message_list_select (fb->message_list, MESSAGE_LIST_SELECT_PREVIOUS, - 0, 0, FALSE); -} - -void -previous_unread_msg (GtkWidget *button, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - message_list_select (fb->message_list, MESSAGE_LIST_SELECT_PREVIOUS, - 0, CAMEL_MESSAGE_SEEN, TRUE); -} - -void -previous_flagged_msg (GtkWidget *button, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - message_list_select (fb->message_list, MESSAGE_LIST_SELECT_PREVIOUS, - CAMEL_MESSAGE_FLAGGED, CAMEL_MESSAGE_FLAGGED, TRUE); -} - -static void -expunged_folder (CamelFolder *f, void *data) -{ - FolderBrowser *fb = data; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - fb->expunging = NULL; - gtk_widget_set_sensitive (GTK_WIDGET (fb->message_list), TRUE); - - /* FIXME: we should check that the focus hasn't changed in the - * mean time, otherwise we steal the focus unecessarily. - * Check :get_toplevel()->focus_widget? */ - if (fb->expunge_mlfocussed) - gtk_widget_grab_focus((GtkWidget *)fb->message_list); -} - -static gboolean -confirm_expunge (FolderBrowser *fb) -{ - gboolean res, show_again; - GConfClient *gconf; - - gconf = mail_config_get_gconf_client (); - - if (!gconf_client_get_bool (gconf, "/apps/evolution/mail/prompts/expunge", NULL)) - return TRUE; - - res = e_question (FB_WINDOW (fb), GTK_RESPONSE_NO, &show_again, - _("This operation will permanently erase all messages marked as\n" - "deleted. If you continue, you will not be able to recover these messages.\n" - "\nReally erase these messages?")); - - gconf_client_set_bool (gconf, "/apps/evolution/mail/prompts/expunge", show_again, NULL); - - return res; -} - -void -expunge_folder (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - if (fb->folder && (fb->expunging == NULL || fb->folder != fb->expunging) && confirm_expunge (fb)) { - CamelMessageInfo *info; - GtkWindow *top; - GtkWidget *focus; - - /* disable the message list so user can't click on them while we expunge */ - - /* nasty hack to find out if some widget inside the message list is focussed ... */ - top = GTK_WINDOW (gtk_widget_get_toplevel((GtkWidget *)fb->message_list)); - focus = top?top->focus_widget:NULL; - while (focus && focus != (GtkWidget *)fb->message_list) - focus = focus->parent; - fb->expunge_mlfocussed = focus == (GtkWidget *)fb->message_list; - gtk_widget_set_sensitive (GTK_WIDGET (fb->message_list), FALSE); - - /* Only blank the mail display if the message being - viewed is one of those to be expunged */ - if (fb->loaded_uid) { - info = camel_folder_get_message_info (fb->folder, fb->loaded_uid); - - if (!info || info->flags & CAMEL_MESSAGE_DELETED) - mail_display_set_message (fb->mail_display, NULL, NULL, NULL); - } - - fb->expunging = fb->folder; - mail_expunge_folder (fb->folder, expunged_folder, fb); - } -} - -/********************** Begin Filter Editor ********************/ - -static GtkWidget *filter_editor = NULL; - -static void -filter_editor_response (GtkWidget *dialog, int button, FolderBrowser *fb) -{ - FilterContext *fc; - - if (button == GTK_RESPONSE_ACCEPT) { - char *user; - - fc = g_object_get_data(G_OBJECT(dialog), "context"); - user = g_strdup_printf ("%s/filters.xml", evolution_dir); - rule_context_save ((RuleContext *)fc, user); - g_free (user); - } - - gtk_widget_destroy(dialog); - - filter_editor = NULL; -} - -static const char *filter_source_names[] = { - "incoming", - "outgoing", - NULL, -}; - -void -filter_edit (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - FilterContext *fc; - char *user, *system; - - if (filter_editor) { - gdk_window_raise (GTK_WIDGET (filter_editor)->window); - return; - } - - fc = filter_context_new (); - user = g_strdup_printf ("%s/filters.xml", evolution_dir); - system = EVOLUTION_PRIVDATADIR "/filtertypes.xml"; - rule_context_load ((RuleContext *)fc, system, user); - g_free (user); - - if (((RuleContext *)fc)->error) { - e_notice(FB_WINDOW (fb), GTK_MESSAGE_ERROR, - _("Error loading filter information:\n%s"), - ((RuleContext *)fc)->error); - return; - } - - filter_editor = (GtkWidget *)filter_editor_new (fc, filter_source_names); - /* FIXME: maybe this needs destroy func? */ - gtk_window_set_transient_for ((GtkWindow *) filter_editor, FB_WINDOW (fb)); - gtk_window_set_title (GTK_WINDOW (filter_editor), _("Filters")); - g_object_set_data_full ((GObject *) filter_editor, "context", fc, (GtkDestroyNotify) g_object_unref); - g_signal_connect (filter_editor, "response", G_CALLBACK (filter_editor_response), fb); - gtk_widget_show (GTK_WIDGET (filter_editor)); -} - -/********************** End Filter Editor ********************/ - -void -vfolder_edit_vfolders (BonoboUIComponent *uih, void *user_data, const char *path) -{ - vfolder_edit (); -} - - -/* static void -header_print_cb (GtkHTML *html, GnomePrintContext *print_context, - double x, double y, double width, double height, gpointer user_data) -{ - printf ("header_print_cb %f,%f x %f,%f\n", x, y, width, height); - - gnome_print_newpath (print_context); - gnome_print_setlinewidth (print_context, 12.0); - gnome_print_setrgbcolor (print_context, 1.0, 0.0, 0.0); - gnome_print_moveto (print_context, x, y); - gnome_print_lineto (print_context, x+width, y-height); - gnome_print_strokepath (print_context); -} */ - -struct footer_info { - GnomeFont *local_font; - gint page_num, pages; -}; - -static void -footer_print_cb (GtkHTML *html, GnomePrintContext *print_context, - double x, double y, double width, double height, gpointer user_data) -{ - struct footer_info *info = (struct footer_info *) user_data; - - if (info->local_font) { - char *text = g_strdup_printf (_("Page %d of %d"), info->page_num, info->pages); - /*gdouble tw = gnome_font_get_width_string (info->local_font, text);*/ - /* FIXME: work out how to measure this */ - gdouble tw = strlen (text) * 8; - - gnome_print_gsave (print_context); - gnome_print_newpath (print_context); - gnome_print_setrgbcolor (print_context, .0, .0, .0); - gnome_print_moveto (print_context, x + width - tw, y - gnome_font_get_ascender (info->local_font)); - gnome_print_setfont (print_context, info->local_font); - gnome_print_show (print_context, text); - gnome_print_grestore (print_context); - - g_free (text); - info->page_num++; - } -} - -static void -footer_info_free (struct footer_info *info) -{ - if (info->local_font) - gnome_font_unref (info->local_font); - g_free (info); -} - -static struct footer_info * -footer_info_new (GtkHTML *html, GnomePrintContext *pc, gdouble *line) -{ - struct footer_info *info; - - info = g_new (struct footer_info, 1); - info->local_font = gnome_font_find_closest ("Helvetica", 10.0); - - if (info->local_font) - *line = gnome_font_get_ascender (info->local_font) - gnome_font_get_descender (info->local_font); - - info->page_num = 1; - info->pages = gtk_html_print_get_pages_num (html, pc, 0.0, *line); - - return info; -} - -static void -do_mail_print (FolderBrowser *fb, gboolean preview) -{ - GtkHTML *html; - GtkWidget *w = NULL; - GnomePrintContext *print_context; - GnomePrintJob *print_master; - GnomePrintConfig *config = NULL; - GtkDialog *dialog; - gdouble line = 0.0; - struct footer_info *info; - - if (!preview) { - dialog = (GtkDialog *) gnome_print_dialog_new (NULL, _("Print Message"), GNOME_PRINT_DIALOG_COPIES); - gtk_dialog_set_default_response (dialog, GNOME_PRINT_DIALOG_RESPONSE_PRINT); - gtk_window_set_transient_for ((GtkWindow *) dialog, (GtkWindow *) gtk_widget_get_toplevel ((GtkWidget *) fb)); - - switch (gtk_dialog_run (dialog)) { - case GNOME_PRINT_DIALOG_RESPONSE_PRINT: - break; - case GNOME_PRINT_DIALOG_RESPONSE_PREVIEW: - preview = TRUE; - break; - default: - gtk_widget_destroy ((GtkWidget *) dialog); - return; - } - - config = gnome_print_dialog_get_config ((GnomePrintDialog *) dialog); - gtk_widget_destroy ((GtkWidget *)dialog); - } - - if (config) { - print_master = gnome_print_job_new (config); - gnome_print_config_unref (config); - } else - print_master = gnome_print_job_new (NULL); - - /* paper size settings? */ - /*gnome_print_master_set_paper (print_master, paper);*/ - print_context = gnome_print_job_get_context (print_master); - - html = GTK_HTML (gtk_html_new ()); - gtk_widget_set_name (GTK_WIDGET (html), "EvolutionMailPrintHTMLWidget"); - mail_display_initialize_gtkhtml (fb->mail_display, html); - - /* Set our 'printing' flag to true and render. This causes us - to ignoring any adjustments we made to accomodate the - user's theme. */ - fb->mail_display->printing = TRUE; - - if (!GTK_WIDGET_REALIZED (GTK_WIDGET (html))) { - /* gtk widgets don't like to be realized outside top level widget - so we put new html widget into gtk window */ - w = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_container_add (GTK_CONTAINER (w), GTK_WIDGET (html)); - gtk_widget_realize (GTK_WIDGET (html)); - } - mail_display_render (fb->mail_display, html, TRUE); - gtk_html_print_set_master (html, print_master); - - info = footer_info_new (html, print_context, &line); - gtk_html_print_with_header_footer (html, print_context, 0.0, line, NULL, footer_print_cb, info); - footer_info_free (info); - - fb->mail_display->printing = FALSE; - - gnome_print_job_close (print_master); - gtk_widget_destroy (GTK_WIDGET (html)); - if (w) - gtk_widget_destroy (w); - - if (preview){ - GtkWidget *pw; - - pw = gnome_print_job_preview_new (print_master, _("Print Preview")); - gtk_widget_show (pw); - } else { - int result = gnome_print_job_print (print_master); - - if (result == -1) - e_notice (FB_WINDOW (fb), GTK_MESSAGE_ERROR, _("Printing of message failed")); - } - - g_object_unref (print_master); -} - -/* This is pretty evil. FolderBrowser's API should be extended to allow these sorts of - things to be done in a more natural way. */ - -/* <evil_code> */ - -struct blarg_this_sucks { - FolderBrowser *fb; - gboolean preview; -}; - -static void -done_message_selected (CamelFolder *folder, const char *uid, CamelMimeMessage *msg, void *data) -{ - struct blarg_this_sucks *blarg = data; - FolderBrowser *fb = blarg->fb; - gboolean preview = blarg->preview; - CamelMessageInfo *info; - - g_free (blarg); - - info = camel_folder_get_message_info (fb->folder, uid); - mail_display_set_message (fb->mail_display, (CamelMedium *) msg, fb->folder, info); - if (info) - camel_folder_free_message_info (fb->folder, info); - - g_free (fb->loaded_uid); - fb->loaded_uid = fb->loading_uid; - fb->loading_uid = NULL; - - if (msg) - do_mail_print (fb, preview); -} - -/* Ack! Most of this is copied from folder-browser.c */ -static void -do_mail_fetch_and_print (FolderBrowser *fb, gboolean preview) -{ - if (!fb->preview_shown || fb->mail_display->current_message == NULL) { - /* If the preview pane is closed, we have to do some - extra magic to load the message. */ - struct blarg_this_sucks *blarg = g_new (struct blarg_this_sucks, 1); - - blarg->fb = fb; - blarg->preview = preview; - - fb->loading_id = 0; - - /* if we are loading, then set a pending, but leave the loading, coudl cancel here (?) */ - if (fb->loading_uid) { - g_free (fb->pending_uid); - fb->pending_uid = g_strdup (fb->new_uid); - } else { - if (fb->new_uid) { - fb->loading_uid = g_strdup (fb->new_uid); - mail_get_message (fb->folder, fb->loading_uid, done_message_selected, blarg, mail_thread_new); - } else { - mail_display_set_message (fb->mail_display, NULL, NULL, NULL); - g_free (blarg); - } - } - } else { - do_mail_print (fb, preview); - } -} - -/* </evil_code> */ - - -void -print_msg (GtkWidget *button, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - do_mail_fetch_and_print (fb, FALSE); -} - -void -print_preview_msg (GtkWidget *button, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - do_mail_fetch_and_print (fb, TRUE); -} - -/******************** Begin Subscription Dialog ***************************/ - -static GtkObject *subscribe_dialog = NULL; - -static void -subscribe_dialog_destroy (GtkObject *dialog, GObject *deadbeef) -{ - if (subscribe_dialog) { - g_object_unref (subscribe_dialog); - subscribe_dialog = NULL; - } -} - -void -manage_subscriptions (BonoboUIComponent *uih, void *user_data, const char *path) -{ - if (!subscribe_dialog) { - subscribe_dialog = subscribe_dialog_new (); - - g_object_weak_ref ((GObject *) SUBSCRIBE_DIALOG (subscribe_dialog)->app, - (GWeakNotify) subscribe_dialog_destroy, subscribe_dialog); - g_object_ref(subscribe_dialog); - gtk_object_sink((GtkObject *)subscribe_dialog); - subscribe_dialog_show (subscribe_dialog); - } else { - gdk_window_raise (SUBSCRIBE_DIALOG (subscribe_dialog)->app->window); - } -} - -/******************** End Subscription Dialog ***************************/ - -static void -local_configure_done(const char *uri, CamelFolder *folder, void *data) -{ - FolderBrowser *fb = data; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) { - g_object_unref(fb); - return; - } - - if (folder == NULL) - folder = fb->folder; - - message_list_set_folder(fb->message_list, folder, FALSE); - g_object_unref(fb); -} - -void -configure_folder (BonoboUIComponent *uih, void *user_data, const char *path) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - if (fb->uri) { - if (strncmp (fb->uri, "vfolder:", 8) == 0) { - vfolder_edit_rule (fb->uri); - } else { - message_list_set_folder(fb->message_list, NULL, FALSE); - g_object_ref((GtkObject *)fb); - mail_local_reconfigure_folder(fb->uri, local_configure_done, fb); - } - } -} - -static void -do_view_messages(CamelFolder *folder, GPtrArray *uids, GPtrArray *msgs, void *data) -{ - FolderBrowser *fb = data; - int i; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - for (i = 0; i < uids->len && i < msgs->len; i++) { - char *uid = uids->pdata[i]; - CamelMimeMessage *msg = msgs->pdata[i]; - GtkWidget *mb; - - camel_folder_set_message_flags(folder, uid, CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN); - mb = message_browser_new(fb->shell, fb->uri, uid); - gtk_widget_show (mb); - } -} - -void -view_msg (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - GPtrArray *uids; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - uids = g_ptr_array_new (); - message_list_foreach (fb->message_list, enumerate_msg, uids); - - if (uids->len > 10 && !are_you_sure (_("Are you sure you want to open all %d messages in separate windows?"), uids, fb)) - return; - - mail_get_messages(fb->folder, uids, do_view_messages, fb); -} - -void -open_msg (GtkWidget *widget, gpointer user_data) -{ - FolderBrowser *fb = FOLDER_BROWSER (user_data); - extern CamelFolder *outbox_folder; - - if (FOLDER_BROWSER_IS_DESTROYED (fb)) - return; - - if (folder_browser_is_drafts (fb) || fb->folder == outbox_folder) - edit_msg_internal (fb); - else - view_msg (NULL, user_data); -} - -void -open_message (BonoboUIComponent *uih, void *user_data, const char *path) -{ - open_msg (NULL, user_data); -} - -void -edit_message (BonoboUIComponent *uih, void *user_data, const char *path) -{ - edit_msg (NULL, user_data); -} - -void -stop_threads (BonoboUIComponent *uih, void *user_data, const char *path) -{ - camel_operation_cancel (NULL); -} - -void -empty_trash (BonoboUIComponent *uih, void *user_data, const char *path) -{ - CamelProvider *provider; - EAccountList *accounts; - FolderBrowser *fb; - CamelException ex; - EAccount *account; - EIterator *iter; - - fb = user_data ? FOLDER_BROWSER (user_data) : NULL; - - if (fb && !confirm_expunge (fb)) - return; - - camel_exception_init (&ex); - - /* expunge all remote stores */ - accounts = mail_config_get_accounts (); - iter = e_list_get_iterator ((EList *) accounts); - while (e_iterator_is_valid (iter)) { - account = (EAccount *) e_iterator_get (iter); - - /* make sure this is a valid source */ - if (account->enabled && account->source->url) { - provider = camel_session_get_provider (session, account->source->url, &ex); - if (provider) { - /* make sure this store is a remote store */ - if (provider->flags & CAMEL_PROVIDER_IS_STORAGE && - provider->flags & CAMEL_PROVIDER_IS_REMOTE) { - mail_empty_trash (account, NULL, NULL); - } - } - - /* clear the exception for the next round */ - camel_exception_clear (&ex); - } - - e_iterator_next (iter); - } - - g_object_unref (iter); - - /* Now empty the local trash folder */ - mail_empty_trash (NULL, NULL, NULL); -} diff --git a/mail/mail-callbacks.h b/mail/mail-callbacks.h deleted file mode 100644 index 6b2c4573c9..0000000000 --- a/mail/mail-callbacks.h +++ /dev/null @@ -1,144 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright 2000 Ximian, Inc. (www.ximian.com) - * - * 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. - * - */ - -#ifndef MAIL_CALLBACKS_H -#define MAIL_CALLBACKS_H - -#include <camel/camel.h> -#include "composer/e-msg-composer.h" -#include <mail/mail-types.h> -#include "evolution-storage.h" - -#ifdef __cplusplus -extern "C" { -#pragma } -#endif /* __cplusplus */ - -/* these are the possible modes for replying */ -enum { - REPLY_SENDER, - REPLY_LIST, - REPLY_ALL, - REPLY_POST, - REPLY_NO_QUOTE = 0x80 /* dont quote reply */ -}; - -void enumerate_msg (MessageList *ml, const char *uid, gpointer data); - -void fetch_mail (GtkWidget *widget, gpointer user_data); -void send_queued_mail (GtkWidget *widget, gpointer user_data); - -void compose_msg (GtkWidget *widget, gpointer user_data); -void send_to_url (const char *url, const char *parent_uri); - -void forward_inline (GtkWidget *widget, gpointer user_data); -void forward_quoted (GtkWidget *widget, gpointer user_data); -void forward_attached (GtkWidget *widget, gpointer user_data); -void forward (GtkWidget *widget, gpointer user_data); - -void post_to_url (const char *url); -void post_message (GtkWidget *widget, gpointer user_data); -void post_reply (GtkWidget *widget, gpointer user_data); - -void redirect (GtkWidget *widget, gpointer user_data); - -void reply_to_sender (GtkWidget *widget, gpointer user_data); -void reply_to_list (GtkWidget *widget, gpointer user_data); -void reply_to_all (GtkWidget *widget, gpointer user_data); - -void colour_msg (GtkWidget *widget, gpointer user_data); -void delete_msg (GtkWidget *widget, gpointer user_data); -void undelete_msg (GtkWidget *widget, gpointer user_data); -void move_msg_cb (GtkWidget *widget, gpointer user_data); -void copy_msg_cb (GtkWidget *widget, gpointer user_data); -void addrbook_sender (GtkWidget *widget, gpointer user_data); -void apply_filters (GtkWidget *widget, gpointer user_data); -void print_msg (GtkWidget *widget, gpointer user_data); -void print_preview_msg (GtkWidget *widget, gpointer user_data); -void edit_msg (GtkWidget *widget, gpointer user_data); -void open_msg (GtkWidget *widget, gpointer user_data); -void save_msg (GtkWidget *widget, gpointer user_data); -void view_msg (GtkWidget *widget, gpointer user_data); -void view_digest (GtkWidget *widget, gpointer user_data); -void view_source (GtkWidget *widget, gpointer user_data); -void next_msg (GtkWidget *widget, gpointer user_data); -void next_unread_msg (GtkWidget *widget, gpointer user_data); -void next_flagged_msg (GtkWidget *widget, gpointer user_data); -void next_thread (GtkWidget *widget, gpointer user_data); -void previous_msg (GtkWidget *widget, gpointer user_data); -void previous_unread_msg (GtkWidget *widget, gpointer user_data); -void previous_flagged_msg (GtkWidget *widget, gpointer user_data); -void resend_msg (GtkWidget *widget, gpointer user_data); -void search_msg (GtkWidget *widget, gpointer user_data); -void load_images (GtkWidget *widget, gpointer user_data); - -void add_sender_to_addrbook (BonoboUIComponent *uih, void *user_data, const char *path); -void move_msg (BonoboUIComponent *uih, void *user_data, const char *path); -void copy_msg (BonoboUIComponent *uih, void *user_data, const char *path); -void select_all (BonoboUIComponent *uih, void *user_data, const char *path); -void select_thread (BonoboUIComponent *uih, void *user_data, const char *path); -void invert_selection (BonoboUIComponent *uih, void *user_data, const char *path); -void mark_as_seen (BonoboUIComponent *uih, void *user_data, const char *path); -void mark_all_as_seen (BonoboUIComponent *uih, void *user_data, const char *path); -void mark_as_unseen (BonoboUIComponent *uih, void *user_data, const char *path); -void mark_as_important (BonoboUIComponent *uih, void *user_data, const char *path); -void mark_as_unimportant (BonoboUIComponent *uih, void *user_data, const char *path); -void toggle_as_important (BonoboUIComponent *uih, void *user_data, const char *path); -void flag_for_followup (BonoboUIComponent *uih, void *user_data, const char *path); -void flag_followup_completed (BonoboUIComponent *uih, void *user_data, const char *path); -void flag_followup_clear (BonoboUIComponent *uih, void *user_data, const char *path); - -void zoom_in (BonoboUIComponent *uih, void *user_data, const char *path); -void zoom_out (BonoboUIComponent *uih, void *user_data, const char *path); -void zoom_reset (BonoboUIComponent *uih, void *user_data, const char *path); - -void edit_message (BonoboUIComponent *uih, void *user_data, const char *path); -void open_message (BonoboUIComponent *uih, void *user_data, const char *path); -void expunge_folder (BonoboUIComponent *uih, void *user_data, const char *path); -void filter_edit (BonoboUIComponent *uih, void *user_data, const char *path); -void vfolder_edit_vfolders (BonoboUIComponent *uih, void *user_data, const char *path); -void manage_subscriptions (BonoboUIComponent *uih, void *user_data, const char *path); - -void configure_folder (BonoboUIComponent *uih, void *user_data, const char *path); - -void stop_threads (BonoboUIComponent *uih, void *user_data, const char *path); - -void empty_trash (BonoboUIComponent *uih, void *user_data, const char *path); - -void mail_reply (CamelFolder *folder, CamelMimeMessage *msg, const char *uid, int mode); - -void composer_send_cb (EMsgComposer *composer, gpointer data); -void composer_save_draft_cb (EMsgComposer *composer, int quit, gpointer data); - -void forward_messages (CamelFolder *folder, GPtrArray *uids, gboolean inline); - -/* CamelStore callbacks */ -void folder_created (CamelStore *store, const char *prefix, CamelFolderInfo *fi); -void folder_deleted (CamelStore *store, CamelFolderInfo *fi); - -void mail_storage_create_folder (EvolutionStorage *storage, CamelStore *store, CamelFolderInfo *fi); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* ! MAIL_CALLBACKS_H */ diff --git a/mail/mail-display-stream.c b/mail/mail-display-stream.c deleted file mode 100644 index ae81401680..0000000000 --- a/mail/mail-display-stream.c +++ /dev/null @@ -1,104 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright 2001 Ximian, Inc. (www.ximian.com) - * - * 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. - * - */ - - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include "mail-display-stream.h" - - -static void mail_display_stream_class_init (MailDisplayStreamClass *klass); -static void mail_display_stream_init (CamelObject *object); -static void mail_display_stream_finalize (CamelObject *object); - -static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n); - - -static CamelStreamClass *parent_class = NULL; - - -CamelType -mail_display_stream_get_type (void) -{ - static CamelType type = CAMEL_INVALID_TYPE; - - if (type == CAMEL_INVALID_TYPE) { - type = camel_type_register (CAMEL_STREAM_TYPE, - "MailDisplayStream", - sizeof (MailDisplayStream), - sizeof (MailDisplayStreamClass), - (CamelObjectClassInitFunc) mail_display_stream_class_init, - NULL, - (CamelObjectInitFunc) mail_display_stream_init, - (CamelObjectFinalizeFunc) mail_display_stream_finalize); - } - - return type; -} - -static void -mail_display_stream_class_init (MailDisplayStreamClass *klass) -{ - CamelStreamClass *stream_class = CAMEL_STREAM_CLASS (klass); - - parent_class = (CamelStreamClass *) CAMEL_STREAM_TYPE; - - /* virtual method overload */ - stream_class->write = stream_write; -} - -static void -mail_display_stream_init (CamelObject *object) -{ - ; -} - -static void -mail_display_stream_finalize (CamelObject *object) -{ - ; -} - -static ssize_t -stream_write (CamelStream *stream, const char *buffer, size_t n) -{ - MailDisplayStream *dstream = MAIL_DISPLAY_STREAM (stream); - - gtk_html_write (dstream->html, dstream->html_stream, buffer, n); - - return (ssize_t) n; -} - - -CamelStream * -mail_display_stream_new (GtkHTML *html, GtkHTMLStream *html_stream) -{ - MailDisplayStream *new; - - new = MAIL_DISPLAY_STREAM (camel_object_new (MAIL_DISPLAY_STREAM_TYPE)); - new->html = html; - new->html_stream = html_stream; - - return CAMEL_STREAM (new); -} diff --git a/mail/mail-display.c b/mail/mail-display.c deleted file mode 100644 index 4793d0741a..0000000000 --- a/mail/mail-display.c +++ /dev/null @@ -1,2995 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Dan Winship <danw@ximian.com> - * Jeffrey Stedfast <fejj@ximian.com> - * Michael Zucchi <notzed@ximian.com> - * Miguel de Icaza <miguel@ximian.com> - * Larry Ewing <lewing@ximian.com> - * - * Copyright 2002 Ximian, Inc. (www.ximian.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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 Street #330, Boston, MA 02111-1307, USA. - * - */ - - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <string.h> -#include <sys/stat.h> -#include <ctype.h> -#include <fcntl.h> -#include <errno.h> - -#include <gtk/gtkinvisible.h> -#include <libgnome/gnome-program.h> - -#include <gconf/gconf.h> -#include <gconf/gconf-client.h> - -#include <libgnomevfs/gnome-vfs.h> -#include <libgnome/gnome-url.h> -#include <bonobo/bonobo-exception.h> -#include <bonobo/bonobo-control-frame.h> -#include <bonobo/bonobo-stream-memory.h> -#include <bonobo/bonobo-widget.h> -#include <bonobo/bonobo-socket.h> - -#include <gdk/gdkkeysyms.h> -#include <gdk-pixbuf/gdk-pixbuf.h> -#include <gdk-pixbuf/gdk-pixbuf-loader.h> -#include <gal/util/e-util.h> -#include <gal/widgets/e-gui-utils.h> -#include <gal/widgets/e-popup-menu.h> - -#include <gtkhtml/gtkhtml.h> -#include <gtkhtml/gtkhtml-embedded.h> -#include <gtkhtml/htmlengine.h> -#include <gtkhtml/htmlobject.h> -#include <gtkhtml/htmltext.h> -#include <gtkhtml/htmlinterval.h> -#include <gtkhtml/gtkhtml-stream.h> - -#include <libsoup/soup-message.h> - -#include "e-util/e-gui-utils.h" -#include "e-util/e-mktemp.h" -#include "addressbook/backend/ebook/e-book-util.h" - -#include "e-searching-tokenizer.h" -#include "folder-browser-factory.h" -#include "mail-display-stream.h" -#include "folder-browser.h" -#include "mail-config.h" -#include "mail-display.h" -#include "mail-format.h" -#include "mail-ops.h" -#include "mail-mt.h" -#include "mail.h" - -#include "camel/camel-data-cache.h" - -#define d(x) - -struct _MailDisplayPrivate { - - /* because we want to control resource usage, we need our own queues, etc */ - EDList fetch_active; - EDList fetch_queue; - - /* used to try and make some sense with progress reporting */ - int fetch_total; - int fetch_total_done; - - /* bit hackish, 'fake' an async message and processing, - so we can use that to get cancel and report progress */ - struct _mail_msg *fetch_msg ; - GIOChannel *fetch_cancel_channel; - guint fetch_cancel_watch; - - guint display_notify_id; -}; - -/* max number of connections to download images */ -#define FETCH_MAX_CONNECTIONS (4) - -/* path to http cache in fetch_cache */ -#define FETCH_HTTP_CACHE "http" - -/* for asynchronously downloading remote content */ -struct _remote_data { - struct _remote_data *next; - struct _remote_data *prev; - - MailDisplay *md; /* not ref'd */ - - SoupMessage *msg; - char *uri; - GtkHTML *html; - GtkHTMLStream *stream; - CamelStream *cstream; /* cache stream */ - size_t length; - size_t total; -}; - -static void fetch_remote(MailDisplay *md, const char *uri, GtkHTML *html, GtkHTMLStream *stream); -static void fetch_cancel(MailDisplay *md); -static void fetch_next(MailDisplay *md); -static void fetch_data(SoupMessage *req, void *data); -static void fetch_free(struct _remote_data *rd); -static void fetch_done(SoupMessage *req, void *data); - -/* global http cache, relies on external evolution_dir as well */ -static CamelDataCache *fetch_cache; - -#define PARENT_TYPE (gtk_vbox_get_type ()) - -static GtkObjectClass *mail_display_parent_class; - -struct _PixbufLoader { - CamelDataWrapper *wrapper; /* The data */ - CamelStream *mstream; - GdkPixbufLoader *loader; - GtkHTMLEmbedded *eb; - char *type; /* Type of data, in case the conversion fails */ - char *cid; /* Strdupped on creation, but not freed until - the hashtable is destroyed */ - GtkWidget *pixmap; - guint32 destroy_id; -}; -static GHashTable *thumbnail_cache = NULL; - -/* Drag & Drop types */ -#define TEXT_URI_LIST_TYPE "text/uri-list" - -enum DndTargetType { - DND_TARGET_TYPE_TEXT_URI_LIST, - DND_TARGET_TYPE_PART_MIME_TYPE -}; - -static GtkTargetEntry drag_types[] = { - { TEXT_URI_LIST_TYPE, 0, DND_TARGET_TYPE_TEXT_URI_LIST }, - { NULL, 0, DND_TARGET_TYPE_PART_MIME_TYPE } -}; - -static const int num_drag_types = sizeof (drag_types) / sizeof (drag_types[0]); - -/*----------------------------------------------------------------------* - * Callbacks - *----------------------------------------------------------------------*/ - -static void -write_data_written(CamelMimePart *part, char *name, int done, void *data) -{ - int *ret = data; - - /* should we popup a dialogue to say its done too? */ - *ret = done; -} - -static gboolean -write_data_to_file (CamelMimePart *part, const char *name, gboolean unique) -{ - int fd, ret = FALSE; - - g_return_val_if_fail (CAMEL_IS_MIME_PART (part), FALSE); - - fd = open (name, O_WRONLY | O_CREAT | O_EXCL, 0666); - if (fd == -1 && errno == EEXIST && !unique) { - GtkWidget *dialog; - int button; - - dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, - _("File `%s' already exists.\nOverwrite it?"), - name); - - g_object_set (dialog, "title", _("Overwrite file?"), "allow_grow", TRUE, NULL); - button = gtk_dialog_run ((GtkDialog *) dialog); - gtk_widget_destroy (dialog); - - if (button != GTK_RESPONSE_YES) - return FALSE; - } - - if (fd != -1) - close (fd); - - /* should this have progress of what its doing? */ - mail_msg_wait (mail_save_part (part, name, write_data_written, &ret)); - - return ret; -} - -static char * -make_safe_filename (const char *prefix, CamelMimePart *part) -{ - const char *name; - char *safe, *p; - - name = part ? camel_mime_part_get_filename (part) : NULL; - - if (!name) { - /* This is a filename. Translators take note. */ - name = _("attachment"); - } - - p = strrchr (name, '/'); - if (p) - safe = g_strdup_printf ("%s%s", prefix, p); - else - safe = g_strdup_printf ("%s/%s", prefix, name); - - p = strrchr (safe, '/'); - if (p) - e_filename_make_safe (p + 1); - - return safe; -} - -static void -save_data_cb (GtkWidget *widget, gpointer user_data) -{ - GtkFileSelection *file_select; - GConfClient *gconf; - char *dir; - - file_select = (GtkFileSelection *) gtk_widget_get_ancestor (widget, GTK_TYPE_FILE_SELECTION); - - /* uh, this doesn't really feel right, but i dont know what to do better */ - gtk_widget_hide (GTK_WIDGET (file_select)); - write_data_to_file (user_data, gtk_file_selection_get_filename (file_select), FALSE); - - /* preserve the pathname */ - dir = g_path_get_dirname (gtk_file_selection_get_filename (file_select)); - gconf = mail_config_get_gconf_client (); - gconf_client_set_string (gconf, "/apps/evolution/mail/save_dir", dir, NULL); - g_free (dir); - - gtk_widget_destroy (GTK_WIDGET (file_select)); -} - -static void -save_destroy_cb (CamelMimePart *part, GObject *deadbeef) -{ - camel_object_unref (part); -} - -static gboolean -idle_redisplay (gpointer data) -{ - MailDisplay *md = data; - - md->idle_id = 0; - mail_display_redisplay (md, FALSE); - - return FALSE; -} - -void -mail_display_queue_redisplay (MailDisplay *md) -{ - if (!md->idle_id) { - md->idle_id = g_idle_add_full (G_PRIORITY_LOW, idle_redisplay, - md, NULL); - } -} - -static void -mail_display_jump_to_anchor (MailDisplay *md, const char *url) -{ - char *anchor = strstr (url, "#"); - - g_return_if_fail (anchor != NULL); - - if (anchor) - gtk_html_jump_to_anchor (md->html, anchor + 1); -} - -static void -on_link_clicked (GtkHTML *html, const char *url, MailDisplay *md) -{ - if (!strncasecmp (url, "mailto:", 7)) { - send_to_url (url, NULL); - } else if (*url == '#') { - mail_display_jump_to_anchor (md, url); - } else { - GError *err = NULL; - - gnome_url_show (url, &err); - - if (err) { - g_warning ("gnome_url_show: %s", err->message); - g_error_free (err); - } - } -} - -static void -save_part (CamelMimePart *part) -{ - char *filename, *dir, *home, *base; - GtkFileSelection *file_select; - GConfClient *gconf; - - camel_object_ref (part); - - home = getenv ("HOME"); - gconf = mail_config_get_gconf_client (); - dir = gconf_client_get_string (gconf, "/apps/evolution/mail/save_dir", NULL); - filename = make_safe_filename (dir ? dir : (home ? home : ""), part); - g_free (dir); - - file_select = GTK_FILE_SELECTION (gtk_file_selection_new (_("Save Attachment"))); - gtk_file_selection_set_filename (file_select, filename); - /* set the GtkEntry with the locale filename by breaking abstraction */ - base = g_path_get_basename (filename); - gtk_entry_set_text (GTK_ENTRY (file_select->selection_entry), base); - g_free (filename); - g_free (base); - - g_signal_connect (file_select->ok_button, "clicked", - G_CALLBACK (save_data_cb), part); - - g_signal_connect_swapped (file_select->cancel_button, "clicked", - G_CALLBACK (gtk_widget_destroy), file_select); - - g_object_weak_ref ((GObject *) file_select, (GWeakNotify) save_destroy_cb, part); - - gtk_widget_show (GTK_WIDGET (file_select)); -} - -static void -save_cb (GtkWidget *widget, gpointer user_data) -{ - CamelMimePart *part = g_object_get_data ((GObject *) user_data, "CamelMimePart"); - - save_part (part); -} - -static void -launch_cb (GtkWidget *widget, gpointer user_data) -{ - CamelMimePart *part = g_object_get_data(user_data, "CamelMimePart"); - MailMimeHandler *handler; - GList *apps, *children, *c; - GnomeVFSMimeApplication *app; - char *command, *filename; - const char *tmpdir; - - handler = mail_lookup_handler (g_object_get_data(user_data, "mime_type")); - g_return_if_fail (handler != NULL && handler->applications != NULL); - - /* Yum. Too bad EPopupMenu doesn't allow per-item closures. */ - children = gtk_container_get_children (GTK_CONTAINER (widget->parent)); - /* We need to bypass the first 2 menu items */ - g_return_if_fail (children != NULL && children->next != NULL - && children->next->next != NULL && children->next->next->next != NULL); - - for (c = children->next->next->next, apps = handler->applications; c && apps; c = c->next, apps = apps->next) { - if (c->data == widget) - break; - } - g_list_free (children); - g_return_if_fail (c != NULL && apps != NULL); - app = apps->data; - - tmpdir = e_mkdtemp ("app-launcher-XXXXXX"); - - if (!tmpdir) { - GtkWidget *dialog; - - dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR, GTK_RESPONSE_CLOSE, - _("Could not create temporary directory: %s"), - g_strerror (errno)); - - /* FIXME: this should be async */ - gtk_dialog_run ((GtkDialog *) dialog); - gtk_widget_destroy (dialog); - return; - } - - filename = make_safe_filename (tmpdir, part); - - if (!write_data_to_file (part, filename, TRUE)) { - GtkWidget *dialog; - - dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR, GTK_RESPONSE_CLOSE, - _("Could not create temporary file '%s': %s"), - filename, g_strerror (errno)); - - /* FIXME: this should be async */ - gtk_dialog_run ((GtkDialog *) dialog); - gtk_widget_destroy (dialog); - g_free (filename); - return; - } - - command = g_strdup_printf ("%s %s%s &", app->command, - app->expects_uris == GNOME_VFS_MIME_APPLICATION_ARGUMENT_TYPE_URIS ? - "file://" : "", filename); - g_free (filename); - - system (command); - g_free (command); -} - -static void -inline_cb (GtkWidget *widget, gpointer user_data) -{ - MailDisplay *md = g_object_get_data (user_data, "MailDisplay"); - CamelMimePart *part = g_object_get_data (user_data, "CamelMimePart"); - - mail_part_toggle_displayed (part, md); - mail_display_queue_redisplay (md); -} - -static void -save_all_parts_cb (GtkWidget *widget, gpointer user_data) -{ - GtkFileSelection *dir_select = (GtkFileSelection *) - gtk_widget_get_ancestor (widget, GTK_TYPE_FILE_SELECTION); - const char *filename; - char *save_filename, *dir; - struct stat st; - int i; - GPtrArray *attachment_array; - CamelMimePart *part; - GConfClient *gconf; - - gtk_widget_hide (GTK_WIDGET (dir_select)); - - /* Get the selected directory name */ - filename = gtk_file_selection_get_filename (dir_select); - if (stat (filename, &st) == -1 || !S_ISDIR (st.st_mode)) { - GtkWidget *dialog; - - dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, - _("%s is not a valid directory name."), filename); - - /* FIXME: this should be async */ - gtk_dialog_run ((GtkDialog *) dialog); - gtk_widget_destroy (dialog); - gtk_widget_destroy (GTK_WIDGET (dir_select)); - return; - } else { - dir = g_strdup (filename); - } - - /* Now save the attachment one by one */ - attachment_array = (GPtrArray *)user_data; - for (i = 0; i < attachment_array->len; i++) { - part = g_ptr_array_index (attachment_array, i); - save_filename = make_safe_filename (dir, part); - write_data_to_file (part, save_filename, FALSE); - g_free (save_filename); - } - - /* preserve the pathname */ - gconf = mail_config_get_gconf_client (); - gconf_client_set_string (gconf, "/apps/evolution/mail/save_dir", dir, NULL); - g_free (dir); - - gtk_widget_destroy (GTK_WIDGET (dir_select)); -} - -static void -save_all_parts (GPtrArray *attachment_array) -{ - GtkFileSelection *dir_select; - char *dir, *home, *dir2; - GConfClient *gconf; - - g_return_if_fail (attachment_array != NULL); - - home = getenv ("HOME"); - gconf = mail_config_get_gconf_client (); - dir = gconf_client_get_string (gconf, "/apps/evolution/mail/save_dir", NULL); - dir = dir ? dir : (home ? g_strdup (home) : g_strdup ("")); - - /* Make sure dir2 has a '/' as its tail */ - dir2 = g_strdup_printf ("%s/", dir); - g_free (dir); - - dir_select = GTK_FILE_SELECTION ( - gtk_file_selection_new (_("Select Directory for Attachments"))); - gtk_file_selection_set_filename (dir_select, dir2); - gtk_widget_set_sensitive (dir_select->file_list, FALSE); - gtk_widget_hide (dir_select->selection_entry); - g_free (dir2); - - g_signal_connect (dir_select->ok_button, "clicked", - G_CALLBACK (save_all_parts_cb), attachment_array); - g_signal_connect_swapped (dir_select->cancel_button, - "clicked", - G_CALLBACK (gtk_widget_destroy), - dir_select); - - gtk_widget_show (GTK_WIDGET (dir_select)); -} - -static void -save_all_cb (GtkWidget *widget, gpointer user_data) -{ - MailDisplay *md = g_object_get_data (user_data, "MailDisplay"); - GPtrArray *attachment_array; - - if (md == NULL) { - g_warning ("No MailDisplay!"); - return; - } - - attachment_array = g_datalist_get_data (md->data, "attachment_array"); - save_all_parts (attachment_array); -} - -static void -inline_toggle(MailDisplay *md, CamelMimePart *part) -{ - g_return_if_fail(md != NULL); - - mail_part_toggle_displayed (part, md); - mail_display_queue_redisplay (md); -} - -static void -inline_button_clicked(GtkWidget *widget, CamelMimePart *part) -{ - inline_toggle((MailDisplay *)g_object_get_data((GObject *)widget, "MailDisplay"), part); -} - -static gboolean -inline_button_press(GtkWidget *widget, GdkEventKey *event, CamelMimePart *part) -{ - if (event->keyval != GDK_Return) - return FALSE; - - inline_toggle((MailDisplay *)g_object_get_data((GObject *)widget, "MailDisplay"), part); - - return TRUE; -} - -static void -popup_menu_placement_callback(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data) -{ - GtkWidget *widget = (GtkWidget*) user_data; - - gdk_window_get_origin (gtk_widget_get_parent_window (widget), x, y); - *x += widget->allocation.x + widget->allocation.width; - *y += widget->allocation.y; - - return; -} - -static gboolean -pixmap_press (GtkWidget *widget, GdkEvent *event, gpointer user_data) -{ - EPopupMenu *menu; - GtkMenu *gtk_menu; - EPopupMenu save_item = E_POPUP_ITEM (N_("Save Attachment..."), G_CALLBACK (save_cb), 0); - EPopupMenu save_all_item = E_POPUP_ITEM (N_("Save all attachments..."), G_CALLBACK (save_all_cb), 0); - EPopupMenu view_item = E_POPUP_ITEM (N_("View Inline"), G_CALLBACK (inline_cb), 2); - EPopupMenu open_item = E_POPUP_ITEM (N_("Open in %s..."), G_CALLBACK (launch_cb), 1); - MailDisplay *md; - CamelMimePart *part; - MailMimeHandler *handler; - int mask = 0, i, nitems; - int current_item = 0; - - if (event->type == GDK_BUTTON_PRESS) { -#ifdef USE_OLD_DISPLAY_STYLE - if (event->button.button != 3) { - gtk_propagate_event (GTK_WIDGET (user_data), - (GdkEvent *)event); - return TRUE; - } -#endif - - if (event->button.button != 1 && event->button.button != 3) { - gtk_propagate_event (GTK_WIDGET (user_data), - (GdkEvent *)event); - return TRUE; - } - /* Stop the signal, since we don't want the button's class method to - mess up our popup. */ - g_signal_stop_emission_by_name (widget, "button_press_event"); - } else { - if (event->key.keyval != GDK_Return) - return FALSE; - } - - part = g_object_get_data ((GObject *) widget, "CamelMimePart"); - handler = mail_lookup_handler (g_object_get_data ((GObject *) widget, "mime_type")); - - if (handler && handler->applications) - nitems = g_list_length (handler->applications) + 3; - else - nitems = 4; - menu = g_new0 (EPopupMenu, nitems + 1); - - /* Save item */ - memcpy (&menu[current_item], &save_item, sizeof (menu[current_item])); - menu[current_item].name = g_strdup (_(menu[current_item].name)); - current_item++; - - /* Save All item */ - memcpy (&menu[current_item], &save_all_item, sizeof (menu[current_item])); - menu[current_item].name = g_strdup (_(menu[current_item].name)); - current_item++; - - /* Inline view item */ - memcpy (&menu[current_item], &view_item, sizeof (menu[current_item])); - if (handler && handler->builtin) { - md = g_object_get_data ((GObject *) widget, "MailDisplay"); - - if (!mail_part_is_displayed_inline (part, md)) { - if (handler->component) { - Bonobo_ActivationProperty *prop; - char *name; - - prop = bonobo_server_info_prop_find (handler->component, "name"); - if (!prop) { - prop = bonobo_server_info_prop_find (handler->component, - "description"); - } - if (prop && prop->v._d == Bonobo_ACTIVATION_P_STRING) - name = prop->v._u.value_string; - else - name = "bonobo"; - menu[current_item].name = g_strdup_printf (_("View Inline (via %s)"), name); - } else - menu[current_item].name = g_strdup (_(menu[current_item].name)); - } else - menu[current_item].name = g_strdup (_("Hide")); - } else { - menu[current_item].name = g_strdup (_(menu[current_item].name)); - mask |= 2; - } - current_item++; - - /* External views */ - if (handler && handler->applications) { - GnomeVFSMimeApplication *app; - GList *apps; - int i; - - apps = handler->applications; - for (i = current_item; i < nitems; i++, apps = apps->next) { - app = apps->data; - memcpy (&menu[i], &open_item, sizeof (menu[i])); - menu[i].name = g_strdup_printf (_(menu[i].name), app->name); - current_item++; - } - } else { - memcpy (&menu[current_item], &open_item, sizeof (menu[current_item])); - menu[current_item].name = g_strdup_printf (_(menu[current_item].name), _("External Viewer")); - mask |= 1; - } - - gtk_menu = e_popup_menu_create (menu, mask, 0, widget); - e_auto_kill_popup_menu_on_selection_done (gtk_menu); - - if (event->type == GDK_BUTTON_PRESS) - gtk_menu_popup (gtk_menu, NULL, NULL, NULL, (gpointer)widget, event->button.button, event->button.time); - else - gtk_menu_popup (gtk_menu, NULL, NULL, popup_menu_placement_callback, (gpointer)widget, 0, event->key.time); - - for (i = 1; i < nitems; i++) - g_free (menu[i].name); - g_free (menu); - - return TRUE; -} - -static gboolean -pixbuf_uncache (gpointer key) -{ - GdkPixbuf *pixbuf; - - pixbuf = g_hash_table_lookup (thumbnail_cache, key); - g_object_unref (pixbuf); - g_hash_table_remove (thumbnail_cache, key); - g_free (key); - return FALSE; -} - -static gint -pixbuf_gen_idle (struct _PixbufLoader *pbl) -{ - GdkPixbuf *pixbuf, *mini; - gboolean error = FALSE; - char tmp[4096]; - int len, width, height, ratio; - gpointer orig_key; - - /* Get the pixbuf from the cache */ - if (g_hash_table_lookup_extended (thumbnail_cache, pbl->cid, - &orig_key, (gpointer *)&mini)) { - width = gdk_pixbuf_get_width (mini); - height = gdk_pixbuf_get_height (mini); - - gtk_image_set_from_pixbuf ((GtkImage *) pbl->pixmap, mini); - gtk_widget_set_size_request (pbl->pixmap, width, height); - - /* Restart the cache-cleaning timer */ - g_source_remove_by_user_data (orig_key); - g_timeout_add (5 * 60 * 1000, pixbuf_uncache, orig_key); - - if (pbl->loader) { - gdk_pixbuf_loader_close (pbl->loader, NULL); - g_object_unref (pbl->loader); - camel_object_unref (pbl->mstream); - } - - g_signal_handler_disconnect (pbl->eb, pbl->destroy_id); - g_free (pbl->type); - g_free (pbl->cid); - g_free (pbl); - - return FALSE; - } - - /* Not in cache, so get a pixbuf from the wrapper */ - - if (!GTK_IS_WIDGET (pbl->pixmap)) { - /* Widget has died */ - if (pbl->mstream) - camel_object_unref (pbl->mstream); - - if (pbl->loader) { - gdk_pixbuf_loader_close (pbl->loader, NULL); - g_object_unref (pbl->loader); - } - - g_signal_handler_disconnect (pbl->eb, pbl->destroy_id); - g_free (pbl->type); - g_free (pbl->cid); - g_free (pbl); - - return FALSE; - } - - if (pbl->mstream) { - if (pbl->loader == NULL) - pbl->loader = gdk_pixbuf_loader_new (); - - len = camel_stream_read (pbl->mstream, tmp, 4096); - if (len > 0) { - error = !gdk_pixbuf_loader_write (pbl->loader, tmp, len, NULL); - if (!error) - return TRUE; - } else if (!camel_stream_eos (pbl->mstream)) - error = TRUE; - } - - if (error || !pbl->mstream) { - if (pbl->type) - pixbuf = e_icon_for_mime_type (pbl->type, 24); - else - pixbuf = gdk_pixbuf_new_from_file (EVOLUTION_ICONSDIR "/pgp-signature-nokey.png", NULL); - } else - pixbuf = gdk_pixbuf_loader_get_pixbuf (pbl->loader); - - if (pixbuf == NULL) { - /* pixbuf is non-existant */ - if (pbl->mstream) - camel_object_unref (pbl->mstream); - - if (pbl->loader) { - gdk_pixbuf_loader_close (pbl->loader, NULL); - g_object_unref (pbl->loader); - } - - g_signal_handler_disconnect (pbl->eb, pbl->destroy_id); - g_free (pbl->type); - g_free (pbl->cid); - g_free (pbl); - - return FALSE; - } - - width = gdk_pixbuf_get_width (pixbuf); - height = gdk_pixbuf_get_height (pixbuf); - - if (width >= height) { - if (width > 24) { - ratio = width / 24; - width = 24; - height /= ratio; - } - } else { - if (height > 24) { - ratio = height / 24; - height = 24; - width /= ratio; - } - } - - mini = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR); - if (error || !pbl->mstream) - g_object_unref (pixbuf); - - gtk_image_set_from_pixbuf ((GtkImage *) pbl->pixmap, mini); - - /* Add the pixbuf to the cache */ - g_hash_table_insert (thumbnail_cache, pbl->cid, mini); - g_timeout_add (5 * 60 * 1000, pixbuf_uncache, pbl->cid); - - g_signal_handler_disconnect (pbl->eb, pbl->destroy_id); - if (pbl->loader) { - gdk_pixbuf_loader_close (pbl->loader, NULL); - g_object_unref (pbl->loader); - camel_object_unref (pbl->mstream); - } - - g_free (pbl->type); - g_free (pbl); - - return FALSE; -} - -/* Stop the idle function and free the pbl structure - as the widget that the pixbuf was to be rendered to - has died on us. */ -static void -embeddable_destroy_cb (GtkObject *embeddable, struct _PixbufLoader *pbl) -{ - g_idle_remove_by_data (pbl); - if (pbl->mstream) - camel_object_unref (pbl->mstream); - - if (pbl->loader) { - gdk_pixbuf_loader_close (pbl->loader, NULL); - g_object_unref (pbl->loader); - } - - g_free (pbl->type); - g_free (pbl->cid); - g_free (pbl); -}; - -static GtkWidget * -get_embedded_for_component (const char *iid, MailDisplay *md) -{ - GtkWidget *embedded; - BonoboControlFrame *control_frame; - Bonobo_PropertyBag prop_bag; - - /* - * First try a control. - */ - embedded = bonobo_widget_new_control (iid, NULL); - if (embedded == NULL) { -#warning "what about bonobo_widget_new_subdoc?" -#if 0 - /* - * No control, try an embeddable instead. - */ - embedded = bonobo_widget_new_subdoc (iid, NULL); - if (embedded != NULL) { - /* FIXME: as of bonobo 0.18, there's an extra - * client_site dereference in the BonoboWidget - * destruction path that we have to balance out to - * prevent problems. - */ - bonobo_object_ref (BONOBO_OBJECT (bonobo_widget_get_client_site ( - BONOBO_WIDGET (embedded)))); - - return embedded; - } -#endif - } - - if (embedded == NULL) - return NULL; - - control_frame = bonobo_widget_get_control_frame (BONOBO_WIDGET (embedded)); - - prop_bag = bonobo_control_frame_get_control_property_bag (control_frame, NULL); - - if (prop_bag != CORBA_OBJECT_NIL) { - CORBA_Environment ev; - /* - * Now we can take care of business. Currently, the only control - * that needs something passed to it through a property bag is - * the iTip control, and it needs only the From email address, - * but perhaps in the future we can generalize this section of code - * to pass a bunch of useful things to all embedded controls. - */ - const CamelInternetAddress *from; - char *from_address; - - CORBA_exception_init (&ev); - - from = camel_mime_message_get_from (md->current_message); - from_address = camel_address_encode ((CamelAddress *) from); - bonobo_property_bag_client_set_value_string ( - prop_bag, "from_address", - from_address, &ev); - g_free (from_address); - - Bonobo_Unknown_unref (prop_bag, &ev); - CORBA_exception_free (&ev); - } - - return embedded; -} - -static void * -save_url (MailDisplay *md, const char *url) -{ - GHashTable *urls; - CamelMimePart *part; - - urls = g_datalist_get_data (md->data, "part_urls"); - g_return_val_if_fail (url != NULL, NULL); - g_return_val_if_fail (urls != NULL, NULL); - - part = g_hash_table_lookup (urls, url); - if (part == NULL) { - CamelDataWrapper *wrapper; - CamelStream *stream = NULL; - const char *name; - - /* See if it's some piece of cached data if it is then pretend it - * is a mime part so that we can use the mime part saving routines. - * It is gross but it keeps duplicated code to a minimum and helps - * out with ref counting and the like. - */ - name = strrchr (url, '/'); - name = name ? name : url; - - if (fetch_cache) { - /* look in the soup cache */ - stream = camel_data_cache_get(fetch_cache, FETCH_HTTP_CACHE, url, NULL); - } else { - GByteArray *ba = NULL; - - urls = g_datalist_get_data (md->data, "data_urls"); - g_return_val_if_fail (urls != NULL, NULL); - - ba = g_hash_table_lookup (urls, url); - if (ba) { - /* we have to copy the data here since the ba may be long gone - * by the time the user actually saves the file - */ - stream = camel_stream_mem_new_with_buffer (ba->data, ba->len); - } - } - - if (stream) { - wrapper = camel_data_wrapper_new (); - camel_data_wrapper_construct_from_stream (wrapper, stream); - camel_object_unref (stream); - part = camel_mime_part_new (); - camel_medium_set_content_object (CAMEL_MEDIUM (part), wrapper); - camel_object_unref (wrapper); - camel_mime_part_set_filename (part, name); - } - } else { - camel_object_ref (part); - } - - if (part) { - CamelDataWrapper *data; - - g_return_val_if_fail (CAMEL_IS_MIME_PART (part), NULL); - - data = camel_medium_get_content_object ((CamelMedium *)part); - if (!mail_content_loaded (data, md, TRUE, NULL, NULL, NULL)) { - return NULL; - } - - save_part (part); - camel_object_unref (part); - return NULL; - } - - g_warning ("Data for url: \"%s\" not found", url); - - return NULL; -} - -static void -drag_data_get_cb (GtkWidget *widget, - GdkDragContext *drag_context, - GtkSelectionData *selection_data, - guint info, - guint time, - gpointer user_data) -{ - CamelMimePart *part = user_data; - const char *filename, *tmpdir; - char *uri_list; - - switch (info) { - case DND_TARGET_TYPE_TEXT_URI_LIST: - /* Kludge around Nautilus requesting the same data many times */ - uri_list = g_object_get_data ((GObject *) widget, "uri-list"); - if (uri_list) { - gtk_selection_data_set (selection_data, selection_data->target, 8, - uri_list, strlen (uri_list)); - return; - } - - tmpdir = e_mkdtemp ("drag-n-drop-XXXXXX"); - if (!tmpdir) { - GtkWidget *dialog; - - dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR, GTK_RESPONSE_CLOSE, - _("Could not create temporary directory: %s"), - g_strerror (errno)); - - /* FIXME: this should be async */ - gtk_dialog_run ((GtkDialog *) dialog); - gtk_widget_destroy (dialog); - } - - filename = camel_mime_part_get_filename (part); - /* This is the default filename used for dnd temporary target of attachment */ - if (!filename) - filename = _("Unknown"); - - uri_list = g_strdup_printf ("file://%s/%s", tmpdir, filename); - - if (!write_data_to_file (part, uri_list + 7, TRUE)) { - g_free (uri_list); - return; - } - - gtk_selection_data_set (selection_data, selection_data->target, 8, - uri_list, strlen (uri_list)); - - g_object_set_data_full ((GObject *) widget, "uri-list", uri_list, g_free); - break; - case DND_TARGET_TYPE_PART_MIME_TYPE: - if (header_content_type_is (((CamelDataWrapper *) part)->mime_type, "text", "*")) { - GByteArray *ba; - - ba = mail_format_get_data_wrapper_text ((CamelDataWrapper *) part, NULL); - if (ba) { - gtk_selection_data_set (selection_data, selection_data->target, 8, - ba->data, ba->len); - g_byte_array_free (ba, TRUE); - } - } else { - CamelDataWrapper *wrapper; - CamelStreamMem *mem; - - mem = (CamelStreamMem *) camel_stream_mem_new (); - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - camel_data_wrapper_decode_to_stream (wrapper, (CamelStream *) mem); - - gtk_selection_data_set (selection_data, selection_data->target, 8, - mem->buffer->data, mem->buffer->len); - - camel_object_unref (mem); - } - break; - default: - g_assert_not_reached (); - } -} - -static void -drag_data_delete_cb (GtkWidget *widget, - GdkDragContext *drag_context, - gpointer user_data) -{ - char *uri_list; - - uri_list = g_object_get_data ((GObject *) widget, "uri-list"); - if (uri_list) { - unlink (uri_list + 7); - g_object_set_data ((GObject *) widget, "uri-list", NULL); - } -} - -/* This is a wrapper function */ -void ptr_array_free_notify (gpointer array) -{ - g_ptr_array_free ((GPtrArray *) array, TRUE); -} - -static gboolean -do_attachment_header (GtkHTML *html, GtkHTMLEmbedded *eb, - CamelMimePart *part, MailDisplay *md) -{ - GtkWidget *button, *mainbox, *hbox, *arrow, *popup; - MailMimeHandler *handler; - struct _PixbufLoader *pbl; - GPtrArray *attachment_array; - - pbl = g_new0 (struct _PixbufLoader, 1); - if (strncasecmp (eb->type, "image/", 6) == 0) { - CamelDataWrapper *content; - - content = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - if (!camel_data_wrapper_is_offline (content)) { - pbl->mstream = camel_stream_mem_new (); - camel_data_wrapper_decode_to_stream (content, pbl->mstream); - camel_stream_reset (pbl->mstream); - } - } - - pbl->type = g_strdup (eb->type); - pbl->cid = g_strdup (eb->classid + 6); - pbl->pixmap = gtk_image_new(); - gtk_widget_set_size_request (pbl->pixmap, 24, 24); - pbl->eb = eb; - pbl->destroy_id = g_signal_connect (eb, "destroy", G_CALLBACK (embeddable_destroy_cb), pbl); - - g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) pixbuf_gen_idle, pbl, NULL); - - mainbox = gtk_hbox_new (FALSE, 0); - - button = gtk_button_new (); - g_object_set_data ((GObject *) button, "MailDisplay", md); - - handler = mail_lookup_handler (eb->type); - if (handler && handler->builtin) { - g_signal_connect (button, "clicked", G_CALLBACK (inline_button_clicked), part); - g_signal_connect (button, "key_press_event", G_CALLBACK (inline_button_press), part); - } else { - gtk_widget_set_sensitive (button, FALSE); - GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_FOCUS); - } - - /* Drag & Drop */ - drag_types[DND_TARGET_TYPE_PART_MIME_TYPE].target = header_content_type_simple (((CamelDataWrapper *) part)->mime_type); - camel_strdown (drag_types[DND_TARGET_TYPE_PART_MIME_TYPE].target); - - gtk_drag_source_set (button, GDK_BUTTON1_MASK, - drag_types, num_drag_types, - GDK_ACTION_COPY); - g_signal_connect (button, "drag-data-get", G_CALLBACK (drag_data_get_cb), part); - g_signal_connect (button, "drag-data-delete", G_CALLBACK (drag_data_delete_cb), part); - - g_free (drag_types[DND_TARGET_TYPE_PART_MIME_TYPE].target); - drag_types[DND_TARGET_TYPE_PART_MIME_TYPE].target = NULL; - - hbox = gtk_hbox_new (FALSE, 2); - gtk_container_set_border_width (GTK_CONTAINER (hbox), 2); - - /* should this be a gtk_arrow? */ - if (handler && mail_part_is_displayed_inline (part, md)) - arrow = gtk_image_new_from_stock (GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_BUTTON); - else - arrow = gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_BUTTON); - gtk_box_pack_start (GTK_BOX (hbox), arrow, TRUE, TRUE, 0); - gtk_box_pack_start (GTK_BOX (hbox), pbl->pixmap, TRUE, TRUE, 0); - gtk_container_add (GTK_CONTAINER (button), hbox); - - popup = gtk_button_new (); - gtk_container_add (GTK_CONTAINER (popup), - gtk_arrow_new (GTK_ARROW_DOWN, - GTK_SHADOW_ETCHED_IN)); - - g_object_set_data ((GObject *) popup, "MailDisplay", md); - g_object_set_data ((GObject *) popup, "CamelMimePart", part); - g_object_set_data_full ((GObject *) popup, "mime_type", g_strdup (eb->type), (GDestroyNotify) g_free); - - /* Save attachment pointer in an array for "save all attachment" use */ - attachment_array = g_datalist_get_data (md->data, "attachment_array"); - if (!attachment_array) { - attachment_array = g_ptr_array_new (); - g_datalist_set_data_full (md->data, "attachment_array", - attachment_array, (GDestroyNotify) ptr_array_free_notify); - } - /* Since the attachment pointer might have been added to the array before, - remove it first anyway to avoide duplication */ - g_ptr_array_remove (attachment_array, part); - g_ptr_array_add (attachment_array, part); - - - g_signal_connect (popup, "button_press_event", G_CALLBACK (pixmap_press), md->scroll); - g_signal_connect (popup, "key_press_event", G_CALLBACK (pixmap_press), md->scroll); - - gtk_box_pack_start (GTK_BOX (mainbox), button, TRUE, TRUE, 0); - gtk_box_pack_start (GTK_BOX (mainbox), popup, TRUE, TRUE, 0); - gtk_widget_show_all (mainbox); - - gtk_container_add (GTK_CONTAINER (eb), mainbox); - - return TRUE; -} - -static gboolean -do_external_viewer (GtkHTML *html, GtkHTMLEmbedded *eb, - CamelMimePart *part, MailDisplay *md) -{ - CamelDataWrapper *wrapper; - Bonobo_ServerInfo *component; - GtkWidget *embedded; - Bonobo_PersistStream persist; - CORBA_Environment ev; - CamelStreamMem *cstream; - BonoboStream *bstream; - MailMimeHandler *handler; - - handler = mail_lookup_handler (eb->type); - if (!handler || !handler->is_bonobo) - return FALSE; - - component = gnome_vfs_mime_get_default_component (eb->type); - if (!component) - return FALSE; - - embedded = get_embedded_for_component (component->iid, md); - CORBA_free (component); - if (!embedded) - return FALSE; - - persist = (Bonobo_PersistStream) Bonobo_Unknown_queryInterface ( - bonobo_widget_get_objref (BONOBO_WIDGET (embedded)), - "IDL:Bonobo/PersistStream:1.0", NULL); - - if (persist == CORBA_OBJECT_NIL) { - gtk_object_sink (GTK_OBJECT (embedded)); - return FALSE; - } - - /* Write the data to a CamelStreamMem... */ - cstream = (CamelStreamMem *) camel_stream_mem_new (); - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - camel_data_wrapper_decode_to_stream (wrapper, (CamelStream *)cstream); - - /* ...convert the CamelStreamMem to a BonoboStreamMem... */ - bstream = bonobo_stream_mem_create (cstream->buffer->data, cstream->buffer->len, TRUE, FALSE); - camel_object_unref (cstream); - - /* ...and hydrate the PersistStream from the BonoboStream. */ - CORBA_exception_init (&ev); - Bonobo_PersistStream_load (persist, - bonobo_object_corba_objref (BONOBO_OBJECT (bstream)), - eb->type, &ev); - bonobo_object_unref (BONOBO_OBJECT (bstream)); - Bonobo_Unknown_unref (persist, &ev); - CORBA_Object_release (persist, &ev); - - if (ev._major != CORBA_NO_EXCEPTION) { - gtk_object_sink (GTK_OBJECT (embedded)); - CORBA_exception_free (&ev); - return FALSE; - } - CORBA_exception_free (&ev); - - gtk_widget_show (embedded); - gtk_container_add (GTK_CONTAINER (eb), embedded); - - return TRUE; -} - -static gboolean -do_signature (GtkHTML *html, GtkHTMLEmbedded *eb, - CamelMimePart *part, MailDisplay *md) -{ - GtkWidget *button; - struct _PixbufLoader *pbl; - - pbl = g_new0 (struct _PixbufLoader, 1); - pbl->type = NULL; - pbl->cid = g_strdup (eb->classid); - pbl->pixmap = gtk_image_new (); - gtk_widget_set_size_request (pbl->pixmap, 24, 24); - pbl->eb = eb; - pbl->destroy_id = g_signal_connect (eb, "destroy", G_CALLBACK (embeddable_destroy_cb), pbl); - - g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) pixbuf_gen_idle, pbl, NULL); - - button = gtk_button_new (); - g_object_set_data ((GObject *) button, "MailDisplay", md); - g_signal_connect (button, "clicked", G_CALLBACK (inline_button_clicked), part); - g_signal_connect (button, "key_press_event", G_CALLBACK (inline_button_press), part); - - gtk_container_add (GTK_CONTAINER (button), pbl->pixmap); - gtk_widget_show_all (button); - gtk_container_add (GTK_CONTAINER (eb), button); - - return TRUE; -} - -static gboolean -on_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, gpointer data) -{ - MailDisplay *md = data; - GHashTable *urls; - CamelMimePart *part; - - if (!eb->classid) - return FALSE; - - urls = g_datalist_get_data (md->data, "part_urls"); - if (!urls) - return FALSE; - - if (!strncmp (eb->classid, "popup:", 6) && eb->type) { - part = g_hash_table_lookup (urls, eb->classid + 6); - if (!CAMEL_IS_MIME_PART (part)) - return FALSE; - return do_attachment_header (html, eb, part, md); - } else if (!strncmp (eb->classid, "signature:", 10)) { - part = g_hash_table_lookup (urls, eb->classid); - if (!CAMEL_IS_MIME_PART (part)) - return FALSE; - return do_signature (html, eb, part, md); - } else if (!strncmp (eb->classid, "cid:", 4) && eb->type) { - part = g_hash_table_lookup (urls, eb->classid); - if (!CAMEL_IS_MIME_PART (part)) - return FALSE; - return do_external_viewer (html, eb, part, md); - } - - return FALSE; -} - -static void -ebook_callback (EBook *book, const gchar *addr, ECard *card, gpointer data) -{ - MailDisplay *md = data; - - if (card && md->current_message) { - const CamelInternetAddress *from = camel_mime_message_get_from (md->current_message); - const char *md_name = NULL, *md_addr = NULL; - - /* We are extra anal, in case we are dealing with some sort of pathological message - w/o a From: header. */ - if (from != NULL && camel_internet_address_get (from, 0, &md_name, &md_addr)) { - if (md_addr != NULL && !strcmp (addr, md_addr)) - mail_display_load_images (md); - } - } -} - -static void -on_url_requested (GtkHTML *html, const char *url, GtkHTMLStream *handle, - gpointer user_data) -{ - MailDisplay *md = user_data; - GConfClient *gconf; - GHashTable *urls; - CamelMedium *medium; - GByteArray *ba; - - gconf = mail_config_get_gconf_client (); - - urls = g_datalist_get_data (md->data, "part_urls"); - g_return_if_fail (urls != NULL); - - /* See if it refers to a MIME part (cid: or http:) */ - medium = g_hash_table_lookup (urls, url); - if (medium) { - CamelContentType *content_type; - CamelDataWrapper *wrapper; - CamelStream *html_stream; - - g_return_if_fail (CAMEL_IS_MEDIUM (medium)); - - if (md->related) - g_hash_table_remove (md->related, medium); - - wrapper = camel_medium_get_content_object (medium); - if (!mail_content_loaded (wrapper, md, FALSE, url, html, handle)) - return; - - content_type = camel_data_wrapper_get_mime_type_field (wrapper); - - html_stream = mail_display_stream_new (html, handle); - - if (header_content_type_is (content_type, "text", "*")) { - mail_format_data_wrapper_write_to_stream (wrapper, TRUE, md, html_stream); - } else { - camel_data_wrapper_decode_to_stream (wrapper, html_stream); - } - - camel_object_unref (html_stream); - - gtk_html_end (html, handle, GTK_HTML_STREAM_OK); - return; - } - - urls = g_datalist_get_data (md->data, "data_urls"); - g_return_if_fail (urls != NULL); - - /* See if it's some piece of cached data */ - ba = g_hash_table_lookup (urls, url); - if (ba) { - if (ba->len) { - gtk_html_write (html, handle, ba->data, ba->len); - /* printf ("-- begin --\n"); - printf (ba->data); - printf ("-- end --\n"); */ - } - gtk_html_end (html, handle, GTK_HTML_STREAM_OK); - return; - } - - /* See if it's something we can load. */ - if (strncmp (url, "http:", 5) == 0 || strncmp (url, "https:", 6) == 0) { - int http_mode; - - http_mode = gconf_client_get_int (gconf, "/apps/evolution/mail/display/load_http_images", NULL); - if (http_mode == MAIL_CONFIG_HTTP_ALWAYS || - g_datalist_get_data (md->data, "load_images")) { - fetch_remote (md, url, html, handle); - } else if (http_mode == MAIL_CONFIG_HTTP_SOMETIMES && - !g_datalist_get_data (md->data, "checking_from")) { - const CamelInternetAddress *from; - const char *name, *addr; - - from = camel_mime_message_get_from (md->current_message); - g_datalist_set_data (md->data, "checking_from", GINT_TO_POINTER (1)); - - /* Make sure we aren't deal w/ some sort of a - pathological message w/o a From: header */ - if (from != NULL && camel_internet_address_get (from, 0, &name, &addr)) - e_book_query_address_default (addr, ebook_callback, md); - else - gtk_html_end (html, handle, GTK_HTML_STREAM_ERROR); - } - } -} - -/* for processing asynchronous url fetch cancels */ -static struct _mail_msg_op fetch_fake_op = { - NULL, NULL, NULL, NULL, -}; - -static gboolean -fetch_cancelled (GIOChannel *source, GIOCondition cond, void *user_data) -{ - fetch_cancel ((MailDisplay *) user_data); - - return FALSE; -} - -static void -fetch_next (MailDisplay *md) -{ - struct _remote_data *rd; - struct _MailDisplayPrivate *p = md->priv; - SoupMessage *msg; - SoupContext *ctx; - - /* if we're called and no more work to do, clean up, otherwise, setup */ - if (e_dlist_empty(&p->fetch_active) && e_dlist_empty(&p->fetch_queue)) { - if (p->fetch_msg) { - p->fetch_total = 0; - mail_disable_stop(); - camel_operation_end(p->fetch_msg->cancel); - camel_operation_unregister(p->fetch_msg->cancel); - mail_msg_free(p->fetch_msg); - p->fetch_msg = NULL; - g_source_remove(p->fetch_cancel_watch); - g_io_channel_unref(p->fetch_cancel_channel); - } - } else { - if (p->fetch_msg == NULL) { - p->fetch_total_done = 0; - p->fetch_msg = mail_msg_new(&fetch_fake_op, NULL, sizeof(*p->fetch_msg)); - camel_operation_register(p->fetch_msg->cancel); - camel_operation_start(p->fetch_msg->cancel, _("Downloading images")); - p->fetch_cancel_channel = g_io_channel_unix_new(camel_operation_cancel_fd(p->fetch_msg->cancel)); - p->fetch_cancel_watch = g_io_add_watch(p->fetch_cancel_channel, G_IO_IN, fetch_cancelled, md); - mail_enable_stop(); - } - } - - while (e_dlist_length(&p->fetch_active) < FETCH_MAX_CONNECTIONS - && (rd = (struct _remote_data *)e_dlist_remhead(&p->fetch_queue))) { - - ctx = soup_context_get(rd->uri); - rd->msg = msg = soup_message_new(ctx, SOUP_METHOD_GET); - - if (ctx) - soup_context_unref(ctx); - - soup_message_set_flags(msg, SOUP_MESSAGE_OVERWRITE_CHUNKS); - soup_message_add_handler(msg, SOUP_HANDLER_BODY_CHUNK, fetch_data, rd); - e_dlist_addtail(&p->fetch_active, (EDListNode *)rd); - soup_message_queue(msg, fetch_done, rd); - } -} - -static void fetch_remote(MailDisplay *md, const char *uri, GtkHTML *html, GtkHTMLStream *stream) -{ - struct _remote_data *rd; - CamelStream *cstream = NULL; - - if (fetch_cache) { - cstream = camel_data_cache_get(fetch_cache, FETCH_HTTP_CACHE, uri, NULL); - if (cstream) { - char buf[1024]; - ssize_t len; - - /* need to verify header? */ - - while (!camel_stream_eos(cstream)) { - len = camel_stream_read(cstream, buf, 1024); - if (len > 0) { - gtk_html_write(html, stream, buf, len); - } else if (len < 0) { - gtk_html_end(html, stream, GTK_HTML_STREAM_ERROR); - camel_object_unref(cstream); - return; - } - } - gtk_html_end(html, stream, GTK_HTML_STREAM_OK); - camel_object_unref(cstream); - return; - } - cstream = camel_data_cache_add(fetch_cache, FETCH_HTTP_CACHE, uri, NULL); - } - - rd = g_malloc0(sizeof(*rd)); - rd->md = md; /* dont ref */ - rd->uri = g_strdup(uri); - rd->html = html; - g_object_ref(html); - rd->stream = stream; - rd->cstream = cstream; - - md->priv->fetch_total++; - e_dlist_addtail(&md->priv->fetch_queue, (EDListNode *)rd); - - fetch_next(md); -} - -static void fetch_data(SoupMessage *req, void *data) -{ - struct _remote_data *rd = data, *wd; - struct _MailDisplayPrivate *p = rd->md->priv; - int count; - double complete; - - /* we could just hook into the header function for this, but i'm lazy today */ - if (rd->total == 0) { - const char *cl = soup_message_get_header(req->response_headers, "content-length"); - if (cl) - rd->total = strtoul(cl, 0, 10); - else - rd->total = 0; - } - rd->length += req->response.length; - - gtk_html_write(rd->html, rd->stream, req->response.body, req->response.length); - - /* copy to cache, clear cache if we get a cache failure */ - if (rd->cstream) { - if (camel_stream_write(rd->cstream, req->response.body, req->response.length) == -1) { - camel_data_cache_remove(fetch_cache, FETCH_HTTP_CACHE, rd->uri, NULL); - camel_object_unref(rd->cstream); - rd->cstream = NULL; - } - } - - /* update based on total active + finished totals */ - complete = 0.0; - wd = (struct _remote_data *)p->fetch_active.head; - count = e_dlist_length(&p->fetch_active); - while (wd->next) { - if (wd->total) - complete += (double)wd->length / wd->total / count; - wd = wd->next; - } - - d(printf("%s: %f total %f (%d,%d)\n", rd->uri, complete, (p->fetch_total_done + complete ) * 100.0 / p->fetch_total, p->fetch_total, p->fetch_total_done)); - - camel_operation_progress(p->fetch_msg->cancel, (p->fetch_total_done + complete ) * 100.0 / p->fetch_total); -} - -static void fetch_free(struct _remote_data *rd) -{ - g_object_unref(rd->html); - if (rd->cstream) - camel_object_unref(rd->cstream); - g_free(rd->uri); - g_free(rd); -} - -static void fetch_done(SoupMessage *req, void *data) -{ - struct _remote_data *rd = data; - MailDisplay *md = rd->md; - - if (SOUP_MESSAGE_IS_ERROR(req)) { - d(printf("Loading '%s' failed!\n", rd->uri)); - gtk_html_end(rd->html, rd->stream, GTK_HTML_STREAM_ERROR); - if (fetch_cache) - camel_data_cache_remove(fetch_cache, FETCH_HTTP_CACHE, rd->uri, NULL); - } else { - d(printf("Loading '%s' complete!\n", rd->uri)); - gtk_html_end(rd->html, rd->stream, GTK_HTML_STREAM_OK); - } - - e_dlist_remove((EDListNode *)rd); - fetch_free(rd); - md->priv->fetch_total_done++; - - fetch_next(md); -} - -static void fetch_cancel(MailDisplay *md) -{ - struct _remote_data *rd; - - /* first, clean up all the ones we haven't finished yet */ - while ((rd = (struct _remote_data *)e_dlist_remhead(&md->priv->fetch_queue))) { - gtk_html_end(rd->html, rd->stream, GTK_HTML_STREAM_ERROR); - if (fetch_cache) - camel_data_cache_remove(fetch_cache, FETCH_HTTP_CACHE, rd->uri, NULL); - fetch_free(rd); - } - - /* cancel the rest, cancellation will free it/etc */ - while (!e_dlist_empty(&md->priv->fetch_active)) { - rd = (struct _remote_data *)md->priv->fetch_active.head; - soup_message_cancel(rd->msg); - } -} - -struct _load_content_msg { - struct _mail_msg msg; - - MailDisplay *display; - GtkHTML *html; - - GtkHTMLStream *handle; - int redisplay_counter; - char *url; - CamelMimeMessage *message; - void (*callback)(MailDisplay *, gpointer); - gpointer data; -}; - -static char * -load_content_desc (struct _mail_msg *mm, int done) -{ - return g_strdup (_("Loading message content")); -} - -static void -load_content_load (struct _mail_msg *mm) -{ - struct _load_content_msg *m = (struct _load_content_msg *)mm; - - m->callback (m->display, m->data); -} - -static gboolean -try_part_urls (struct _load_content_msg *m) -{ - GHashTable *urls; - CamelMedium *medium; - - urls = g_datalist_get_data (m->display->data, "part_urls"); - g_return_val_if_fail (urls != NULL, FALSE); - - /* See if it refers to a MIME part (cid: or http:) */ - medium = g_hash_table_lookup (urls, m->url); - if (medium) { - CamelDataWrapper *data; - CamelStream *html_stream; - - g_return_val_if_fail (CAMEL_IS_MEDIUM (medium), FALSE); - - data = camel_medium_get_content_object (medium); - if (!mail_content_loaded (data, m->display, FALSE, m->url, m->html, m->handle)) { - g_warning ("This code should not be reached\n"); - return TRUE; - } - - html_stream = mail_display_stream_new (m->html, m->handle); - camel_data_wrapper_decode_to_stream (data, html_stream); - camel_object_unref (html_stream); - - gtk_html_end (m->html, m->handle, GTK_HTML_STREAM_OK); - return TRUE; - } - - return FALSE; -} - -static gboolean -try_data_urls (struct _load_content_msg *m) -{ - GHashTable *urls; - GByteArray *ba; - - urls = g_datalist_get_data (m->display->data, "data_urls"); - ba = g_hash_table_lookup (urls, m->url); - - if (ba) { - if (ba->len) - gtk_html_write (m->html, m->handle, ba->data, ba->len); - gtk_html_end (m->html, m->handle, GTK_HTML_STREAM_OK); - return TRUE; - } - - return FALSE; -} - -static void -load_content_loaded (struct _mail_msg *mm) -{ - struct _load_content_msg *m = (struct _load_content_msg *)mm; - - if (m->display->destroyed) - return; - - if (m->display->current_message == m->message) { - if (m->handle) { - if (m->redisplay_counter == m->display->redisplay_counter) { - if (!try_part_urls (m) && !try_data_urls (m)) - gtk_html_end (m->html, m->handle, GTK_HTML_STREAM_ERROR); - } - } else { - mail_display_redisplay (m->display, FALSE); - } - } -} - -static void -load_content_free (struct _mail_msg *mm) -{ - struct _load_content_msg *m = (struct _load_content_msg *)mm; - - g_free (m->url); - g_object_unref (m->html); - g_object_unref (m->display); - camel_object_unref (m->message); -} - -static struct _mail_msg_op load_content_op = { - load_content_desc, - load_content_load, - load_content_loaded, - load_content_free, -}; - -static void -stream_write_or_redisplay_when_loaded (MailDisplay *md, - GtkHTML *html, - gconstpointer key, - const gchar *url, - void (*callback)(MailDisplay *, gpointer), - GtkHTMLStream *handle, - gpointer data) -{ - struct _load_content_msg *m; - GHashTable *loading; - - if (md->destroyed) - return; - - loading = g_datalist_get_data (md->data, "loading"); - if (loading) { - if (g_hash_table_lookup (loading, key)) - return; - } else { - loading = g_hash_table_new (NULL, NULL); - g_datalist_set_data_full (md->data, "loading", loading, - (GDestroyNotify) g_hash_table_destroy); - } - g_hash_table_insert (loading, (gpointer) key, GINT_TO_POINTER (1)); - - m = mail_msg_new (&load_content_op, NULL, sizeof (*m)); - m->display = md; - g_object_ref((m->display)); - m->html = html; - g_object_ref((html)); - m->handle = handle; - m->url = g_strdup (url); - m->redisplay_counter = md->redisplay_counter; - m->message = md->current_message; - camel_object_ref (m->message); - m->callback = callback; - m->data = data; - - e_thread_put (mail_thread_queued, (EMsg *)m); - return; -} - -void -mail_display_stream_write_when_loaded (MailDisplay *md, - gconstpointer key, - const char *url, - void (*callback)(MailDisplay *, gpointer), - GtkHTML *html, - GtkHTMLStream *handle, - gpointer data) -{ - stream_write_or_redisplay_when_loaded (md, html, key, url, callback, handle, data); -} - -void -mail_display_redisplay_when_loaded (MailDisplay *md, - gconstpointer key, - void (*callback)(MailDisplay *, gpointer), - GtkHTML *html, - gpointer data) -{ - stream_write_or_redisplay_when_loaded (md, html, key, NULL, callback, NULL, data); -} - -void -mail_text_write (MailDisplayStream *stream, MailDisplay *md, CamelMimePart *part, - int idx, gboolean printing, const char *text) -{ - CamelStreamFilter *filtered_stream; - CamelMimeFilter *html_filter; - GConfClient *gconf; - guint32 flags, rgb; - GdkColor colour; - char *buf; - - gconf = mail_config_get_gconf_client (); - - flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES; - - if (!printing) - flags |= CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES; - - if (!printing && gconf_client_get_bool (gconf, "/apps/evolution/mail/display/mark_citations", NULL)) - flags |= CAMEL_MIME_FILTER_TOHTML_MARK_CITATION; - - buf = gconf_client_get_string (gconf, "/apps/evolution/mail/display/citation_colour", NULL); - gdk_color_parse (buf ? buf : "#737373", &colour); - g_free (buf); - - rgb = ((colour.red & 0xff00) << 8) | (colour.green & 0xff00) | ((colour.blue & 0xff00) >> 8); - html_filter = camel_mime_filter_tohtml_new (flags, rgb); - filtered_stream = camel_stream_filter_new_with_stream ((CamelStream *) stream); - camel_stream_filter_add (filtered_stream, html_filter); - camel_object_unref (html_filter); - - camel_stream_write ((CamelStream *) stream, "<tt>\n", 5); - camel_stream_write ((CamelStream *) filtered_stream, text, strlen (text)); - camel_stream_flush ((CamelStream *) filtered_stream); - camel_stream_write ((CamelStream *) stream, "</tt>\n", 6); - camel_object_unref (filtered_stream); - -#if 0 - /* this was the old way of doing it, I don't understand why we need iframes... */ - GByteArray *ba; - char *xed, *iframe; - char *btt = "<tt>\n"; - char *ett = "</tt>\n"; - char *htmltext; - guint32 flags; - - flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES; - - if (!printing) - flags |= CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES; - - if (!printing && mail_config_get_citation_highlight ()) - flags |= CAMEL_MIME_FILTER_TOHTML_MARK_CITATION; - - htmltext = camel_text_to_html (text, flags, mail_config_get_citation_color ()); - - ba = g_byte_array_new (); - g_byte_array_append (ba, (const guint8 *) btt, strlen (btt) + 1); - g_byte_array_append (ba, (const guint8 *) htmltext, strlen (htmltext) + 1); - g_byte_array_append (ba, (const guint8 *) ett, strlen (ett) + 1); - g_free (htmltext); - - xed = g_strdup_printf ("x-evolution-data:%p-%d", part, idx); - iframe = g_strdup_printf ("<iframe src=\"%s\" frameborder=0 scrolling=no>could not get %s</iframe>", xed, xed); - mail_display_add_url (md, "data_urls", xed, ba); - camel_stream_write ((CamelStream *) stream, iframe, strlen (iframe)); - g_free (iframe); -#endif -} - -void -mail_error_printf (MailDisplayStream *stream, const char *format, ...) -{ - /* FIXME: it'd be nice if camel-stream had a vprintf method... */ - char *buf, *htmltext; - va_list ap; - - va_start (ap, format); - buf = g_strdup_vprintf (format, ap); - va_end (ap); - - htmltext = camel_text_to_html (buf, CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | - CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); - g_free (buf); - - camel_stream_printf ((CamelStream *) stream, "<em><font color=red>"); - camel_stream_write ((CamelStream *) stream, htmltext, strlen (htmltext)); - camel_stream_printf ((CamelStream *) stream, "</font></em>"); - - g_free (htmltext); -} - - -#define COLOR_IS_LIGHT(r, g, b) ((r + g + b) > (128 * 3)) - -#define HTML_HEADER "<!doctype html public \"-//W3C//DTD HTML 4.0 TRANSITIONAL//EN\">\n<html>\n" \ - "<head>\n<meta name=\"generator\" content=\"Evolution Mail Component\">\n</head>\n" - -void -mail_display_render (MailDisplay *md, GtkHTML *html, gboolean reset_scroll) -{ - const char *flag, *completed; - GtkHTMLStream *html_stream; - MailDisplayStream *stream; - - g_return_if_fail (IS_MAIL_DISPLAY (md)); - g_return_if_fail (GTK_IS_HTML (html)); - - if (!md->html) { - /* we've been destroyed */ - return; - } - - html_stream = gtk_html_begin (html); - if (!reset_scroll) { - /* This is a hack until there's a clean way to do this. */ - GTK_HTML (md->html)->engine->newPage = FALSE; - } - - gtk_html_stream_write (html_stream, HTML_HEADER, sizeof (HTML_HEADER) - 1); - - if (md->current_message && md->display_style == MAIL_CONFIG_DISPLAY_SOURCE) - gtk_html_stream_write (html_stream, "<body>\n", 7); - else - gtk_html_stream_write (html_stream, "<body marginwidth=0 marginheight=0>\n", 36); - - flag = md->info ? camel_tag_get (&md->info->user_tags, "follow-up") : NULL; - completed = md->info ? camel_tag_get (&md->info->user_tags, "completed-on") : NULL; - if ((flag && *flag) && !(completed && *completed)) { - const char *due_by, *overdue = ""; - char bgcolor[7], fontcolor[7]; - time_t target_date, now; - GtkStyle *style = NULL; - char due_date[256]; - struct tm due; - int offset; - - /* my favorite thing to do... muck around with colors so we respect people's stupid themes. */ - /* FIXME: this is also in mail-format.c */ - style = gtk_widget_get_style (GTK_WIDGET (html)); - if (style && !md->printing) { - int state = GTK_WIDGET_STATE (GTK_WIDGET (html)); - gushort r, g, b; - - r = style->base[state].red / 256; - g = style->base[state].green / 256; - b = style->base[state].blue / 256; - - if (COLOR_IS_LIGHT (r, g, b)) { - r *= 1.0; - g *= 0.97; - b *= 0.75; - } else { - r = 255 - (1.0 * (255 - r)); - g = 255 - (0.97 * (255 - g)); - b = 255 - (0.75 * (255 - b)); - } - - sprintf (bgcolor, "%.2X%.2X%.2X", r, g, b); - - r = style->text[state].red / 256; - g = style->text[state].green / 256; - b = style->text[state].blue / 256; - - sprintf (fontcolor, "%.2X%.2X%.2X", r, g, b); - } else { - strcpy (bgcolor, "EEEEEE"); - strcpy (fontcolor, "000000"); - } - - due_by = camel_tag_get (&md->info->user_tags, "due-by"); - if (due_by && *due_by) { - target_date = header_decode_date (due_by, &offset); - now = time (NULL); - if (now >= target_date) - overdue = _("Overdue:"); - - localtime_r (&target_date, &due); - - e_utf8_strftime_fix_am_pm (due_date, sizeof (due_date), _("by %B %d, %Y, %l:%M %p"), &due); - } else { - due_date[0] = '\0'; - } - - gtk_html_stream_printf (html_stream, "<font color=\"#%s\">" - "<table width=\"100%%\" cellpadding=0 cellspacing=0><tr><td colspan=3 height=10></td></tr>" - "<tr><td width=10></td><td>" - "<table cellspacing=1 cellpadding=1 bgcolor=\"#000000\" width=\"100%%\"><tr><td>" - "<table cellspacing=0 bgcolor=\"#%s\" cellpadding=2 cellspacing=2 width=\"100%%\">" - "<tr><td align=\"left\" width=20><img src=\"%s\" align=\"middle\"></td>" - "<td>%s%s%s%s %s</td></table></td></tr></table>" - "</td><td width=10></td></tr></table></font>", fontcolor, bgcolor, - mail_display_get_url_for_icon (md, EVOLUTION_IMAGES "/flag-for-followup-16.png"), - overdue ? "<b>" : "", overdue, overdue ? "</b> " : "", - flag, due_date); - } - - if (md->current_message) { - stream = (MailDisplayStream *) mail_display_stream_new (html, html_stream); - - if (md->display_style == MAIL_CONFIG_DISPLAY_SOURCE) - mail_format_raw_message (md->current_message, md, stream); - else - mail_format_mime_message (md->current_message, md, stream); - - camel_object_unref (stream); - } - - gtk_html_stream_write (html_stream, "</body></html>\n", 15); - gtk_html_end (html, html_stream, GTK_HTML_STREAM_OK); -} - -/** - * mail_display_redisplay: - * @mail_display: the mail display object - * @reset_scroll: specifies whether or not to reset current scroll - * - * Force a redraw of the message display. - **/ -void -mail_display_redisplay (MailDisplay *md, gboolean reset_scroll) -{ - if (md->destroyed) - return; - - /* we're in effect stealing the queued redisplay */ - if (md->idle_id) { - g_source_remove(md->idle_id); - md->idle_id = 0; - } - - fetch_cancel(md); - - md->last_active = NULL; - md->redisplay_counter++; - /* printf ("md %p redisplay %d\n", md, md->redisplay_counter); */ - - mail_display_render (md, md->html, reset_scroll); -} - - -/** - * mail_display_set_message: - * @mail_display: the mail display object - * @medium: the input camel medium, or %NULL - * @folder: CamelFolder - * @info: message info - * - * Makes the mail_display object show the contents of the medium - * param. - **/ -void -mail_display_set_message (MailDisplay *md, CamelMedium *medium, CamelFolder *folder, CamelMessageInfo *info) -{ - /* For the moment, we deal only with CamelMimeMessage, but in - * the future, we should be able to deal with any medium. - */ - if (md->destroyed - || (medium && !CAMEL_IS_MIME_MESSAGE (medium))) - return; - - /* Clean up from previous message. */ - if (md->current_message) { - fetch_cancel (md); - camel_object_unref (md->current_message); - g_datalist_clear (md->data); - } - - if (medium) { - camel_object_ref (medium); - md->current_message = (CamelMimeMessage *) medium; - } else - md->current_message = NULL; - - if (md->folder && md->info) { - camel_folder_free_message_info (md->folder, md->info); - camel_object_unref (md->folder); - } - - if (folder && info) { - md->info = info; - md->folder = folder; - camel_object_ref (folder); - camel_folder_ref_message_info (folder, info); - } else { - md->info = NULL; - md->folder = NULL; - } - - g_datalist_init (md->data); - mail_display_redisplay (md, TRUE); -} - -/** - * mail_display_set_charset: - * @mail_display: the mail display object - * @charset: charset or %NULL - * - * Makes the mail_display object show the contents of the medium - * param. - **/ -void -mail_display_set_charset (MailDisplay *mail_display, const char *charset) -{ - g_free (mail_display->charset); - mail_display->charset = g_strdup (charset); - - mail_display_queue_redisplay (mail_display); -} - -/** - * mail_display_load_images: - * @md: the mail display object - * - * Load all HTTP images in the current message - **/ -void -mail_display_load_images (MailDisplay *md) -{ - g_datalist_set_data (md->data, "load_images", GINT_TO_POINTER (1)); - mail_display_redisplay (md, FALSE); -} - -/*----------------------------------------------------------------------* - * Standard Gtk+ Class functions - *----------------------------------------------------------------------*/ - -static void -mail_display_init (GObject *object) -{ - MailDisplay *mail_display = MAIL_DISPLAY (object); - GConfClient *gconf; - int style; - - mail_display->scroll = NULL; - mail_display->html = NULL; - mail_display->redisplay_counter = 0; - mail_display->last_active = NULL; - mail_display->idle_id = 0; - mail_display->selection = NULL; - mail_display->charset = NULL; - mail_display->current_message = NULL; - mail_display->folder = NULL; - mail_display->info = NULL; - mail_display->data = NULL; - - mail_display->invisible = gtk_invisible_new (); - g_object_ref (mail_display->invisible); - gtk_object_sink ((GtkObject *) mail_display->invisible); - - gconf = mail_config_get_gconf_client (); - style = gconf_client_get_int (gconf, "/apps/evolution/mail/format/message_display_style", NULL); - mail_display->display_style = style; - - mail_display->printing = FALSE; - - mail_display->priv = g_malloc0(sizeof(*mail_display->priv)); - e_dlist_init(&mail_display->priv->fetch_active); - e_dlist_init(&mail_display->priv->fetch_queue); -} - -static void -mail_display_destroy (GtkObject *object) -{ - MailDisplay *mail_display = MAIL_DISPLAY (object); - - if (mail_display->html) { - g_object_unref (mail_display->html); - mail_display->html = NULL; - } - - if (mail_display->current_message) { - camel_object_unref (mail_display->current_message); - g_datalist_clear (mail_display->data); - fetch_cancel(mail_display); - mail_display->current_message = NULL; - } - - g_free (mail_display->charset); - mail_display->charset = NULL; - g_free (mail_display->selection); - mail_display->selection = NULL; - - if (mail_display->folder) { - if (mail_display->info) - camel_folder_free_message_info (mail_display->folder, mail_display->info); - camel_object_unref (mail_display->folder); - mail_display->folder = NULL; - } - - g_free (mail_display->data); - mail_display->data = NULL; - - if (mail_display->idle_id) { - gtk_timeout_remove (mail_display->idle_id); - mail_display->idle_id = 0; - } - - if (mail_display->invisible) { - g_object_unref (mail_display->invisible); - mail_display->invisible = NULL; - } - - if (mail_display->priv && mail_display->priv->display_notify_id) { - GConfClient *gconf = mail_config_get_gconf_client (); - gconf_client_notify_remove (gconf, mail_display->priv->display_notify_id); - mail_display->priv->display_notify_id = 0; - } - - g_free (mail_display->priv); - mail_display->priv = NULL; - - mail_display->destroyed = TRUE; - - mail_display_parent_class->destroy (object); -} - -static void -invisible_selection_get_callback (GtkWidget *widget, - GtkSelectionData *selection_data, - guint info, - guint time, - void *data) -{ - MailDisplay *display; - - display = MAIL_DISPLAY (data); - - if (!display->selection) - return; - - g_assert (info == 1); - - gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING, 8, - display->selection, strlen (display->selection)); -} - -static gint -invisible_selection_clear_event_callback (GtkWidget *widget, - GdkEventSelection *event, - void *data) -{ - MailDisplay *display; - - display = MAIL_DISPLAY (data); - - g_free (display->selection); - display->selection = NULL; - - return TRUE; -} - -static void -mail_display_class_init (GtkObjectClass *object_class) -{ - object_class->destroy = mail_display_destroy; - - if (mail_display_parent_class == NULL) { - /* blah, this is an unecessary dependency ... */ - extern char *evolution_dir; - char *path; - - path = g_alloca (strlen (evolution_dir) + 16); - sprintf (path, "%s/cache", evolution_dir); - - /* cache expiry - 2 hour access, 1 day max */ - fetch_cache = camel_data_cache_new(path, 0, NULL); - camel_data_cache_set_expire_age(fetch_cache, 24*60*60); - camel_data_cache_set_expire_access(fetch_cache, 2*60*60); - - mail_display_parent_class = g_type_class_ref (PARENT_TYPE); - thumbnail_cache = g_hash_table_new (g_str_hash, g_str_equal); - } -} - -static void -link_open_in_browser (GtkWidget *w, MailDisplay *mail_display) -{ - if (!mail_display->html->pointer_url) - return; - - on_link_clicked (mail_display->html, mail_display->html->pointer_url, - mail_display); -} - -#if 0 -static void -link_save_as (GtkWidget *w, MailDisplay *mail_display) -{ - g_print ("FIXME save %s\n", mail_display->html->pointer_url); -} -#endif - -static void -link_copy_location (GtkWidget *w, MailDisplay *mail_display) -{ - GdkAtom clipboard_atom; - - g_free (mail_display->selection); - mail_display->selection = g_strdup (mail_display->html->pointer_url); - - clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE); - if (clipboard_atom == GDK_NONE) - return; /* failed */ - - /* We don't check the return values of the following since there is not - * much we can do if we cannot assert the selection. - */ - - gtk_selection_owner_set (GTK_WIDGET (mail_display->invisible), - GDK_SELECTION_PRIMARY, - GDK_CURRENT_TIME); - gtk_selection_owner_set (GTK_WIDGET (mail_display->invisible), - clipboard_atom, - GDK_CURRENT_TIME); -} - -static void -image_save_as (GtkWidget *w, MailDisplay *mail_display) -{ - const char *src; - - src = g_object_get_data ((GObject *) mail_display, "current_src_uri"); - - save_url (mail_display, src); -} - -enum { - /* - * This is used to mask the link specific menu items. - */ - MASK_URL = 1, - - /* - * This is used to mask src specific menu items. - */ - MASK_SRC = 2 -}; - -#define SEPARATOR { "", NULL, (NULL), NULL, 0 } -#define TERMINATOR { NULL, NULL, (NULL), NULL, 0 } - -static EPopupMenu link_menu [] = { - E_POPUP_ITEM (N_("Open Link in Browser"), G_CALLBACK (link_open_in_browser), MASK_URL), - E_POPUP_ITEM (N_("Copy Link Location"), G_CALLBACK (link_copy_location), MASK_URL), -#if 0 - E_POPUP_ITEM (N_("Save Link as (FIXME)"), G_CALLBACK (link_save_as), MASK_URL), -#endif - E_POPUP_ITEM (N_("Save Image as..."), G_CALLBACK (image_save_as), MASK_SRC), - - TERMINATOR -}; - - -/* - * Create a window and popup our widget, with reasonable semantics for the popup - * disappearing, etc. - */ - -typedef struct _PopupInfo PopupInfo; -struct _PopupInfo { - GtkWidget *w; - GtkWidget *win; - guint destroy_timeout; - guint widget_destroy_handle; - Bonobo_Listener listener; - gboolean hidden; -}; - -/* Aiieee! Global Data! */ -static GtkWidget *the_popup = NULL; - -static void -popup_window_destroy_cb (PopupInfo *pop, GObject *deadbeef) -{ - the_popup = NULL; - - if (pop->destroy_timeout != 0) - g_source_remove(pop->destroy_timeout); - - bonobo_event_source_client_remove_listener (bonobo_widget_get_objref (BONOBO_WIDGET (pop->w)), - pop->listener, - NULL); - CORBA_Object_release (pop->listener, NULL); - g_object_unref(pop->w); - g_free (pop); -} - -static int -popup_timeout_cb (gpointer user_data) -{ - PopupInfo *pop = (PopupInfo *) user_data; - - pop->destroy_timeout = 0; - gtk_widget_destroy (pop->win); - - return 0; -} - -static int -popup_enter_cb (GtkWidget *w, GdkEventCrossing *ev, gpointer user_data) -{ - PopupInfo *pop = (PopupInfo *) user_data; - - if (pop->destroy_timeout) - gtk_timeout_remove (pop->destroy_timeout); - pop->destroy_timeout = 0; - - return 0; -} - -static int -popup_leave_cb (GtkWidget *w, GdkEventCrossing *ev, gpointer user_data) -{ - PopupInfo *pop = (PopupInfo *) user_data; - - if (pop->destroy_timeout) - gtk_timeout_remove (pop->destroy_timeout); - - if (!pop->hidden) - pop->destroy_timeout = gtk_timeout_add (500, popup_timeout_cb, pop); - - return 0; -} - -static void -popup_realize_cb (GtkWidget *widget, gpointer user_data) -{ - PopupInfo *pop = (PopupInfo *) user_data; - - gtk_widget_add_events (pop->win, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); - - if (pop->destroy_timeout == 0) { - if (!pop->hidden) { - pop->destroy_timeout = gtk_timeout_add (5000, popup_timeout_cb, pop); - } else { - pop->destroy_timeout = 0; - } - } -} - -static void -popup_size_allocate_cb (GtkWidget *widget, GtkAllocation *alloc, gpointer user_data) -{ - gtk_window_set_position (GTK_WINDOW (widget), GTK_WIN_POS_MOUSE); -} - -static PopupInfo * -make_popup_window (GtkWidget *w) -{ - PopupInfo *pop = g_new0 (PopupInfo, 1); - GtkWidget *fr; - - /* Only allow for one popup at a time. Ugly. */ - if (the_popup) - gtk_widget_destroy (the_popup); - - pop->w = w; - g_object_ref(w); - the_popup = pop->win = gtk_window_new (GTK_WINDOW_POPUP); - fr = gtk_frame_new (NULL); - - gtk_container_add (GTK_CONTAINER (pop->win), fr); - gtk_container_add (GTK_CONTAINER (fr), w); - - gtk_window_set_resizable (GTK_WINDOW (pop->win), FALSE); - - g_signal_connect (pop->win, "enter_notify_event", G_CALLBACK (popup_enter_cb), pop); - g_signal_connect (pop->win, "leave_notify_event", G_CALLBACK (popup_leave_cb), pop); - g_signal_connect_after (pop->win, "realize", G_CALLBACK (popup_realize_cb), pop); - g_signal_connect (pop->win, "size_allocate", G_CALLBACK (popup_size_allocate_cb), pop); - - g_object_weak_ref ((GObject *) pop->win, (GWeakNotify) popup_window_destroy_cb, pop); - - gtk_widget_show (w); - gtk_widget_show (fr); - gtk_widget_show (pop->win); - - return pop; -} - -static void -listener_cb (BonoboListener *listener, - char *event_name, - CORBA_any *any, - CORBA_Environment *ev, - gpointer user_data) -{ - PopupInfo *pop; - char *type; - - pop = user_data; - - if (pop->destroy_timeout) - gtk_timeout_remove (pop->destroy_timeout); - pop->destroy_timeout = 0; - - type = bonobo_event_subtype (event_name); - - if (!strcmp (type, "Destroy")) { - gtk_widget_destroy (GTK_WIDGET (pop->win)); - } else if (!strcmp (type, "Hide")) { - pop->hidden = TRUE; - gtk_widget_hide (GTK_WIDGET (pop->win)); - } - - g_free (type); -} - -static int -html_button_press_event (GtkWidget *widget, GdkEventButton *event, MailDisplay *mail_display) -{ - g_return_val_if_fail (widget != NULL, FALSE); - g_return_val_if_fail (event != NULL, FALSE); - - if (event->type == GDK_BUTTON_PRESS) { - if (event->button == 3) { - HTMLEngine *e; - HTMLPoint *point; - GtkWidget *popup_thing; - - e = GTK_HTML (widget)->engine; - point = html_engine_get_point_at (e, event->x, event->y, FALSE); - - if (point) { - const char *url, *src; - - url = html_object_get_url (point->object, point->offset); - src = html_object_get_src (point->object); - - if (url && !strncasecmp (url, "mailto:", 7)) { - PopupInfo *pop; - char *url_decoded; - - url_decoded = gtk_html_get_url_object_relative (GTK_HTML (widget), - point->object, - url); - camel_url_decode (url_decoded); - - popup_thing = bonobo_widget_new_control ("OAFIID:GNOME_Evolution_Addressbook_AddressPopup", - CORBA_OBJECT_NIL); - - bonobo_widget_set_property (BONOBO_WIDGET (popup_thing), - "email", TC_CORBA_string, url_decoded+7, - NULL); - g_free (url_decoded); - - pop = make_popup_window (popup_thing); - - pop->listener = bonobo_event_source_client_add_listener_full( - bonobo_widget_get_objref (BONOBO_WIDGET (popup_thing)), - g_cclosure_new (G_CALLBACK (listener_cb), pop, NULL), - NULL, NULL); - } else if (url || src) { - int hide_mask = 0; - - if (!url) - hide_mask |= MASK_URL; - - if (!src) - hide_mask |= MASK_SRC; - - g_free (g_object_get_data ((GObject *) mail_display, "current_src_uri")); - g_object_set_data ((GObject *) mail_display, "current_src_uri", - gtk_html_get_url_object_relative (GTK_HTML (widget), - point->object, - src)); - - e_popup_menu_run (link_menu, (GdkEvent *) event, 0, hide_mask, mail_display); - } - - html_point_destroy (point); - return TRUE; - } - } - } - - return FALSE; -} - -static inline void -set_underline (HTMLEngine *e, HTMLObject *o, gboolean underline) -{ - HTMLText *text = HTML_TEXT (o); - - html_text_set_font_style (text, e, underline - ? html_text_get_font_style (text) | GTK_HTML_FONT_STYLE_UNDERLINE - : html_text_get_font_style (text) & ~GTK_HTML_FONT_STYLE_UNDERLINE); - html_engine_queue_draw (e, o); -} - -static void -update_active (GtkWidget *widget, gint x, gint y, MailDisplay *mail_display) -{ - HTMLEngine *e; - HTMLPoint *point; - const gchar *email; - - e = GTK_HTML (widget)->engine; - - point = html_engine_get_point_at (e, x, y, FALSE); - if (mail_display->last_active && (!point || mail_display->last_active != point->object)) { - set_underline (e, HTML_OBJECT (mail_display->last_active), FALSE); - mail_display->last_active = NULL; - } - if (point) { - email = (const gchar *) html_object_get_data (point->object, "email"); - if (email && html_object_is_text (point->object)) { - set_underline (e, point->object, TRUE); - mail_display->last_active = point->object; - } - html_point_destroy (point); - } -} - -static int -html_enter_notify_event (GtkWidget *widget, GdkEventCrossing *event, MailDisplay *mail_display) -{ - update_active (widget, event->x, event->y, mail_display); - - return FALSE; -} - -static int -html_motion_notify_event (GtkWidget *widget, GdkEventMotion *event, MailDisplay *mail_display) -{ - int x, y; - - g_return_val_if_fail (widget != NULL, 0); - g_return_val_if_fail (GTK_IS_HTML (widget), 0); - g_return_val_if_fail (event != NULL, 0); - - if (event->is_hint) - gdk_window_get_pointer (GTK_LAYOUT (widget)->bin_window, &x, &y, NULL); - else { - x = event->x; - y = event->y; - } - - update_active (widget, x, y, mail_display); - - return FALSE; -} - -static void -html_iframe_created (GtkWidget *w, GtkHTML *iframe, MailDisplay *mail_display) -{ - g_signal_connect (iframe, "button_press_event", - G_CALLBACK (html_button_press_event), mail_display); - g_signal_connect (iframe, "motion_notify_event", - G_CALLBACK (html_motion_notify_event), mail_display); - g_signal_connect (iframe, "enter_notify_event", - G_CALLBACK (html_enter_notify_event), mail_display); -} - -static GNOME_Evolution_ShellView -retrieve_shell_view_interface_from_control (BonoboControl *control) -{ - Bonobo_ControlFrame control_frame; - GNOME_Evolution_ShellView shell_view_interface; - CORBA_Environment ev; - - control_frame = bonobo_control_get_control_frame (control, NULL); - - if (control_frame == NULL) - return CORBA_OBJECT_NIL; - - CORBA_exception_init (&ev); - shell_view_interface = Bonobo_Unknown_queryInterface (control_frame, - "IDL:GNOME/Evolution/ShellView:1.0", - &ev); - - if (BONOBO_EX (&ev)) - shell_view_interface = CORBA_OBJECT_NIL; - - CORBA_exception_free (&ev); - - return shell_view_interface; -} - -static void -set_status_message (const char *message, int busy) -{ - EList *controls; - EIterator *it; - - controls = folder_browser_factory_get_control_list (); - for (it = e_list_get_iterator (controls); e_iterator_is_valid (it); e_iterator_next (it)) { - BonoboControl *control; - GNOME_Evolution_ShellView shell_view_interface; - CORBA_Environment ev; - - control = BONOBO_CONTROL (e_iterator_get (it)); - - shell_view_interface = retrieve_shell_view_interface_from_control (control); - - CORBA_exception_init (&ev); - - if (shell_view_interface != CORBA_OBJECT_NIL) { - if (message != NULL) - GNOME_Evolution_ShellView_setMessage (shell_view_interface, - message[0] ? message: "", - busy, - &ev); - } - - CORBA_exception_free (&ev); - - bonobo_object_release_unref (shell_view_interface, NULL); - - /* yeah we only set the first one. Why? Because it seems to leave - random ones lying around otherwise. Shrug. */ - break; - } - - g_object_unref (it); -} - -/* For now show every url but possibly limit it to showing only http: - or ftp: urls */ -static void -html_on_url (GtkHTML *html, const char *url, MailDisplay *mail_display) -{ - static char *previous_url = NULL; - - /* This all looks silly but yes, this is the proper way to mix - GtkHTML's on_url with BonoboUIComponent statusbar */ - if (!url || (previous_url && (strcmp (url, previous_url) != 0))) - set_status_message ("", FALSE); - if (url) { - set_status_message (url, FALSE); - g_free (previous_url); - previous_url = g_strdup (url); - } -} - -/* If if a gconf setting for the mail display has changed redisplay to pick up the changes */ -static void -display_notify (GConfClient *gconf, guint cnxn_id, GConfEntry *entry, gpointer data) -{ - MailDisplay *md = data; - gchar *tkey; - - g_return_if_fail (entry != NULL); - g_return_if_fail (gconf_entry_get_key (entry) != NULL); - g_return_if_fail (gconf_entry_get_value (entry) != NULL); - - tkey = strrchr (entry->key, '/'); - - g_return_if_fail (tkey != NULL); - - if (!strcmp (tkey, "/animate_images")) { - gtk_html_set_animate (md->html, gconf_value_get_bool (gconf_entry_get_value(entry))); - } else if (!strcmp (tkey, "/citation_color") - || !strcmp (tkey, "/mark_citations")) { - mail_display_queue_redisplay (md); - } else if (!strcmp (tkey, "/caret_mode")) { - gtk_html_set_caret_mode(md->html, gconf_value_get_bool (gconf_entry_get_value(entry))); - } -} - -GtkWidget * -mail_display_new (void) -{ - MailDisplay *mail_display = g_object_new (mail_display_get_type (), NULL); - GtkWidget *scroll, *html; - GdkAtom clipboard_atom; - HTMLTokenizer *tok; - GConfClient *gconf; - - gtk_box_set_homogeneous (GTK_BOX (mail_display), FALSE); - gtk_widget_show (GTK_WIDGET (mail_display)); - - scroll = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN); - gtk_box_pack_start_defaults (GTK_BOX (mail_display), scroll); - gtk_widget_show (scroll); - - html = gtk_html_new (); - tok = e_searching_tokenizer_new (); - html_engine_set_tokenizer (GTK_HTML (html)->engine, tok); - g_object_unref (tok); - - mail_display_initialize_gtkhtml (mail_display, GTK_HTML (html)); - - gtk_container_add (GTK_CONTAINER (scroll), html); - gtk_widget_show (GTK_WIDGET (html)); - - g_signal_connect (mail_display->invisible, "selection_get", - G_CALLBACK (invisible_selection_get_callback), mail_display); - g_signal_connect (mail_display->invisible, "selection_clear_event", - G_CALLBACK (invisible_selection_clear_event_callback), mail_display); - - gtk_selection_add_target (mail_display->invisible, - GDK_SELECTION_PRIMARY, GDK_SELECTION_TYPE_STRING, 1); - - clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE); - if (clipboard_atom != GDK_NONE) - gtk_selection_add_target (mail_display->invisible, - clipboard_atom, GDK_SELECTION_TYPE_STRING, 1); - - gconf = mail_config_get_gconf_client (); - gtk_html_set_animate (GTK_HTML (html), gconf_client_get_bool (gconf, "/apps/evolution/mail/display/animate_images", NULL)); - gtk_html_set_caret_mode (GTK_HTML (html), gconf_client_get_bool (gconf, "/apps/evolution/mail/display/caret_mode", NULL)); - - gconf_client_add_dir (gconf, "/apps/evolution/mail/display",GCONF_CLIENT_PRELOAD_NONE, NULL); - mail_display->priv->display_notify_id = gconf_client_notify_add (gconf, "/apps/evolution/mail/display", - display_notify, mail_display, NULL, NULL); - - mail_display->scroll = GTK_SCROLLED_WINDOW (scroll); - mail_display->html = GTK_HTML (html); - g_object_ref (mail_display->html); - mail_display->last_active = NULL; - mail_display->data = g_new0 (GData *, 1); - g_datalist_init (mail_display->data); - - return GTK_WIDGET (mail_display); -} - -void -mail_display_initialize_gtkhtml (MailDisplay *mail_display, GtkHTML *html) -{ - gtk_html_set_default_content_type (GTK_HTML (html), "text/html; charset=utf-8"); - - gtk_html_set_editable (GTK_HTML (html), FALSE); - - g_signal_connect (html, "url_requested", - G_CALLBACK (on_url_requested), - mail_display); - g_signal_connect (html, "object_requested", - G_CALLBACK (on_object_requested), - mail_display); - g_signal_connect (html, "link_clicked", - G_CALLBACK (on_link_clicked), - mail_display); - g_signal_connect (html, "button_press_event", - G_CALLBACK (html_button_press_event), mail_display); - g_signal_connect (html, "motion_notify_event", - G_CALLBACK (html_motion_notify_event), mail_display); - g_signal_connect (html, "enter_notify_event", - G_CALLBACK (html_enter_notify_event), mail_display); - g_signal_connect (html, "iframe_created", - G_CALLBACK (html_iframe_created), mail_display); - g_signal_connect (html, "on_url", - G_CALLBACK (html_on_url), mail_display); -} - -static void -free_url (gpointer key, gpointer value, gpointer data) -{ - g_free (key); - if (data) - g_byte_array_free (value, TRUE); -} - -static void -free_data_urls (gpointer urls) -{ - g_hash_table_foreach (urls, free_url, GINT_TO_POINTER (1)); - g_hash_table_destroy (urls); -} - -char * -mail_display_add_url (MailDisplay *md, const char *kind, char *url, gpointer data) -{ - GHashTable *urls; - gpointer old_key, old_value; - - urls = g_datalist_get_data (md->data, kind); - if (!urls) { - urls = g_hash_table_new (g_str_hash, g_str_equal); - g_datalist_set_data_full (md->data, "data_urls", urls, - free_data_urls); - } - - if (g_hash_table_lookup_extended (urls, url, &old_key, &old_value)) { - g_free (url); - url = old_key; - } - - g_hash_table_insert (urls, url, data); - - return url; -} - -const char * -mail_display_get_url_for_icon (MailDisplay *md, const char *icon_name) -{ - char *icon_path, buf[1024], *url; - int fd, nread; - GByteArray *ba; - - /* FIXME: cache */ - - if (*icon_name == '/') - icon_path = g_strdup (icon_name); - else { - icon_path = gnome_program_locate_file (NULL, GNOME_FILE_DOMAIN_PIXMAP, - icon_name, TRUE, NULL); - if (!icon_path) - return "file:///dev/null"; - } - - fd = open (icon_path, O_RDONLY); - g_free (icon_path); - if (fd == -1) - return "file:///dev/null"; - - ba = g_byte_array_new (); - while (1) { - nread = read (fd, buf, sizeof (buf)); - if (nread < 1) - break; - g_byte_array_append (ba, buf, nread); - } - close (fd); - - url = g_strdup_printf ("x-evolution-data:%p", ba); - - return mail_display_add_url (md, "data_urls", url, ba); -} - - -struct _location_url_stack { - struct _location_url_stack *parent; - CamelURL *url; -}; - -void -mail_display_push_content_location (MailDisplay *md, const char *location) -{ - struct _location_url_stack *node; - CamelURL *url; - - url = camel_url_new (location, NULL); - node = g_new (struct _location_url_stack, 1); - node->parent = md->urls; - node->url = url; - md->urls = node; -} - -CamelURL * -mail_display_get_content_location (MailDisplay *md) -{ - return md->urls ? md->urls->url : NULL; -} - -void -mail_display_pop_content_location (MailDisplay *md) -{ - struct _location_url_stack *node; - - if (!md->urls) { - g_warning ("content-location stack underflow!"); - return; - } - - node = md->urls; - md->urls = node->parent; - - if (node->url) - camel_url_free (node->url); - - g_free (node); -} - -E_MAKE_TYPE (mail_display, "MailDisplay", MailDisplay, mail_display_class_init, mail_display_init, PARENT_TYPE); diff --git a/mail/mail-display.h b/mail/mail-display.h deleted file mode 100644 index fe95c95490..0000000000 --- a/mail/mail-display.h +++ /dev/null @@ -1,137 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright 2002 Ximian, Inc. (www.ximian.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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 Street #330, Boston, MA 02111-1307, USA. - * - */ - - -#ifndef _MAIL_DISPLAY_H_ -#define _MAIL_DISPLAY_H_ - -#include <gtk/gtkvbox.h> -#include <gtk/gtkscrolledwindow.h> -#include <gtkhtml/gtkhtml.h> -#include <gtkhtml/gtkhtml-stream.h> - -#include <camel/camel-stream.h> -#include <camel/camel-mime-message.h> -#include <camel/camel-medium.h> -#include <camel/camel-folder.h> - -#include "mail-types.h" -#include "mail-config.h" /*display_style*/ -#include "mail-display-stream.h" - -#define MAIL_DISPLAY_TYPE (mail_display_get_type ()) -#define MAIL_DISPLAY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MAIL_DISPLAY_TYPE, MailDisplay)) -#define MAIL_DISPLAY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), MAIL_DISPLAY_TYPE, MailDisplayClass)) -#define IS_MAIL_DISPLAY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MAIL_DISPLAY_TYPE)) -#define IS_MAIL_DISPLAY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MAIL_DISPLAY_TYPE)) - -struct _MailDisplay { - GtkVBox parent; - - struct _MailDisplayPrivate *priv; - - GtkScrolledWindow *scroll; - GtkHTML *html; - /* GtkHTMLStream *stream; */ - gint redisplay_counter; - gpointer last_active; - guint idle_id; - - char *charset; - - char *selection; - - CamelMimeMessage *current_message; - CamelMessageInfo *info; - CamelFolder *folder; - GData **data; - - /* stack of Content-Location URLs used for combining with a - relative URL Content-Location on a leaf part in order to - construct the full URL */ - struct _location_url_stack *urls; - - GHashTable *related; /* related parts not displayed yet */ - - /* Sigh. This shouldn't be needed. I haven't figured out why it is - though. */ - GtkWidget *invisible; - - MailConfigDisplayStyle display_style; - - guint printing : 1; - guint destroyed: 1; -}; - -typedef struct { - GtkVBoxClass parent_class; -} MailDisplayClass; - -GtkType mail_display_get_type (void); -GtkWidget * mail_display_new (void); - -void mail_display_initialize_gtkhtml (MailDisplay *mail_display, GtkHTML *html); - -void mail_display_queue_redisplay (MailDisplay *mail_display); -void mail_display_render (MailDisplay *mail_display, GtkHTML *html, gboolean reset_scroll); -void mail_display_redisplay (MailDisplay *mail_display, gboolean reset_scroll); -void mail_display_redisplay_when_loaded (MailDisplay *md, - gconstpointer key, - void (*callback)(MailDisplay *, gpointer), - GtkHTML *html, - gpointer data); -void mail_display_stream_write_when_loaded (MailDisplay *md, - gconstpointer key, - const gchar *url, - void (*callback)(MailDisplay *, gpointer), - GtkHTML *html, - GtkHTMLStream *handle, - gpointer data); - -void mail_display_set_message (MailDisplay *mail_display, - CamelMedium *medium, - CamelFolder *folder, - CamelMessageInfo *info); - -void mail_display_set_charset (MailDisplay *mail_display, - const char *charset); - -void mail_display_load_images (MailDisplay *mail_display); - -void mail_text_write (MailDisplayStream *stream, - MailDisplay *md, - CamelMimePart *part, - gint idx, - gboolean printing, - const char *text); -void mail_error_printf (MailDisplayStream *stream, - const char *format, ...); - -char *mail_display_add_url (MailDisplay *md, const char *kind, char *url, gpointer data); - -const char *mail_display_get_url_for_icon (MailDisplay *md, const char *icon_name); - -void mail_display_push_content_location (MailDisplay *md, const char *location); -CamelURL *mail_display_get_content_location (MailDisplay *md); -void mail_display_pop_content_location (MailDisplay *md); - -#endif /* _MAIL_DISPLAY_H_ */ diff --git a/mail/mail-font-prefs.c b/mail/mail-font-prefs.c deleted file mode 100644 index 0d26f1ccbd..0000000000 --- a/mail/mail-font-prefs.c +++ /dev/null @@ -1,130 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Larry Ewing <lewing@ximian.com> - * - * Copyright 2002 Ximian, Inc. (www.ximian.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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 Street #330, Boston, MA 02111-1307, USA. - * - */ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <gtk/gtk.h> -#include <gtkhtml/gtkhtml-propmanager.h> - -#include "mail-font-prefs.h" - -static GtkVBoxClass *parent_class = NULL; - -GtkWidget * -mail_font_prefs_new (void) -{ - MailFontPrefs *new; - - new = MAIL_FONT_PREFS (g_object_new (mail_font_prefs_get_type ()), NULL); - - return GTK_WIDGET (new); -} - -void -mail_font_prefs_apply (MailFontPrefs *prefs) -{ - gtk_html_propmanager_apply (prefs->pman); -} - -static void -font_prefs_changed (GtkHTMLPropmanager *pman, MailFontPrefs *prefs) -{ - if (prefs->control) - evolution_config_control_changed (prefs->control); -} - -static void -mail_font_prefs_destroy (GtkObject *object) -{ - MailFontPrefs *prefs = (MailFontPrefs *) object; - - if (prefs->pman) { - g_object_unref(prefs->pman); - g_object_unref(prefs->gui); - prefs->pman = NULL; - } - - if (GTK_OBJECT_CLASS (parent_class)->finalize) - (* GTK_OBJECT_CLASS (parent_class)->finalize) (object); -} - -static void -mail_font_prefs_init (MailFontPrefs *prefs) -{ - GtkWidget *toplevel; - GladeXML *gui; - - gui = glade_xml_new (EVOLUTION_GLADEDIR "/mail-config.glade", "font_tab", NULL); - prefs->gui = gui; - - prefs->pman = GTK_HTML_PROPMANAGER (gtk_html_propmanager_new (NULL)); - gtk_html_propmanager_set_gui (prefs->pman, gui, NULL); - g_object_ref(prefs->pman); - gtk_object_sink (GTK_OBJECT (prefs->pman)); - - g_signal_connect(prefs->pman, "changed", font_prefs_changed, prefs); - - /* get our toplevel widget */ - toplevel = glade_xml_get_widget (gui, "toplevel"); - - /* reparent */ - g_object_ref (toplevel); - gtk_container_remove (GTK_CONTAINER (toplevel->parent), toplevel); - gtk_container_add (GTK_CONTAINER (prefs), toplevel); - g_object_unref (toplevel); -} - -static void -mail_font_prefs_class_init (MailFontPrefsClass *klass) -{ - GtkObjectClass *object_class; - - object_class = (GtkObjectClass *) klass; - parent_class = g_type_class_ref(gtk_vbox_get_type ()); - - object_class->destroy = mail_font_prefs_destroy; -} - -GtkType -mail_font_prefs_get_type (void) -{ - static GType type = 0; - - if (!type) { - GTypeInfo type_info = { - sizeof (MailFontPrefsClass), - NULL, NULL, - (GClassInitFunc) mail_font_prefs_class_init, - NULL, NULL, - sizeof (MailFontPrefs), - 0, - (GInstanceInitFunc) mail_font_prefs_init, - }; - - type = g_type_register_static (gtk_vbox_get_type (), "MailFontPrefs", &type_info, 0); - } - - return type; -} - - diff --git a/mail/mail-font-prefs.h b/mail/mail-font-prefs.h deleted file mode 100644 index 14a1d20b40..0000000000 --- a/mail/mail-font-prefs.h +++ /dev/null @@ -1,66 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright 2002 Ximian, Inc. (www.ximian.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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 Street #330, Boston, MA 02111-1307, USA. - * - */ -#ifndef __MAIL_FONT_PREFS_H__ -#define __MAIL_FONT_PREFS_H__ - -#ifdef __cplusplus -extern "C" { -#pragma } -#endif - -#include <gtk/gtk.h> -#include <gtkhtml/gtkhtml-propmanager.h> - -#include <shell/Evolution.h> -#include "evolution-config-control.h" - -#define MAIL_FONT_PREFS_TYPE (mail_font_prefs_get_type()) -#define MAIL_FONT_PREFS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MAIL_FONT_PREFS_TYPE, MailFontPrefs)) -#define MAIL_FONT_PREFS_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), MAIL_FONT_PREFS_TYPE, MailFontPrefsClass)) -#define IS_MAIL_FONT_PREFS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MAIL_FONT_PREFS_TYPE)) -#define IS_MAIL_FONT_PREFS_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MAIL_FONT_PREFS_TYPE)) - -typedef struct _MailFontPrefs MailFontPrefs; -typedef struct _MailFontPrefsClass MailFontPrefsClass; - -struct _MailFontPrefs { - GtkVBox parent_object; - - GtkHTMLPropmanager *pman; - GladeXML *gui; - EvolutionConfigControl *control; -}; - -struct _MailFontPrefsClass { - GtkVBoxClass parent_object; -}; - -GtkType mail_font_prefs_get_type (void); -GtkWidget * mail_font_prefs_new (void); -void mail_font_prefs_apply (MailFontPrefs *prefs); - -#define MAIL_FONT_PREFS_CONTROL_ID "OAFIID:GNOME_Evolution_Mail_FontPrefs_ConfigControl" - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif /* __MAIL_FONT_PREFS_H__ */ diff --git a/mail/mail-format.c b/mail/mail-format.c deleted file mode 100644 index 92428af592..0000000000 --- a/mail/mail-format.c +++ /dev/null @@ -1,2131 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Dan Winship <danw@ximian.com> - * Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright 2000-2003 Ximian, Inc. (www.ximian.com) - * - * 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. - * - */ - - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <string.h> /* for strstr */ -#include <ctype.h> -#include <fcntl.h> - -#include <gconf/gconf.h> -#include <gconf/gconf-client.h> - -#include <libgnome/gnome-util.h> -#include <libgnomevfs/gnome-vfs-mime-handlers.h> -#include <shell/e-setup.h> - -#include <gal/util/e-iconv.h> - -#include <camel/camel-mime-utils.h> -#include <camel/camel-stream-null.h> -#include <camel/camel-stream-filter.h> -#include <camel/camel-multipart-signed.h> -#include <camel/camel-mime-filter-enriched.h> -#include <camel/camel-mime-filter-tohtml.h> -#include <camel/camel-mime-filter-windows.h> - -#include <e-util/e-trie.h> -#include <e-util/e-time-utils.h> - -#include "mail.h" -#include "mail-tools.h" -#include "mail-display.h" -#include "mail-format.h" -#include "mail-mt.h" -#include "mail-crypto.h" - - -#define STANDARD_ISSUE_TABLE_OPEN "<table cellspacing=0 cellpadding=10 width=\"100%\">" - - -static gboolean handle_text_plain (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); -static gboolean handle_text_enriched (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); -static gboolean handle_text_html (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); -static gboolean handle_image (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); -static gboolean handle_multipart_mixed (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); -static gboolean handle_multipart_related (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); -static gboolean handle_multipart_alternative (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); -static gboolean handle_multipart_appledouble (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); -static gboolean handle_multipart_encrypted (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); -static gboolean handle_multipart_signed (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); -static gboolean handle_message_rfc822 (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); -static gboolean handle_message_external_body (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); - -static gboolean handle_via_bonobo (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); - -/* writes the header info for a mime message into an html stream */ -static void write_headers (MailDisplayStream *stream, MailDisplay *md, CamelMimeMessage *message); - -/* dispatch html printing via mimetype */ -static gboolean format_mime_part (CamelMimePart *part, MailDisplay *md, MailDisplayStream *stream); - -static void -free_url (gpointer key, gpointer value, gpointer data) -{ - g_free (key); - if (data) - g_byte_array_free (value, TRUE); -} - -static void -free_part_urls (gpointer urls) -{ - g_hash_table_foreach (urls, free_url, NULL); - g_hash_table_destroy (urls); -} - -static void -free_data_urls (gpointer urls) -{ - g_hash_table_foreach (urls, free_url, GINT_TO_POINTER (1)); - g_hash_table_destroy (urls); -} - -/** - * mail_format_mime_message: - * @mime_message: the input mime message - * @md: the MailDisplay to render into - * - * Writes a CamelMimeMessage out into a MailDisplay - **/ -void -mail_format_mime_message (CamelMimeMessage *mime_message, MailDisplay *md, - MailDisplayStream *stream) -{ - GHashTable *hash; - - g_return_if_fail (CAMEL_IS_MIME_MESSAGE (mime_message)); - - hash = g_datalist_get_data (md->data, "part_urls"); - if (!hash) { - hash = g_hash_table_new (g_str_hash, g_str_equal); - g_datalist_set_data_full (md->data, "part_urls", hash, - free_part_urls); - } - hash = g_datalist_get_data (md->data, "data_urls"); - if (!hash) { - hash = g_hash_table_new (g_str_hash, g_str_equal); - g_datalist_set_data_full (md->data, "data_urls", hash, - free_data_urls); - } - - hash = g_datalist_get_data (md->data, "attachment_states"); - if (!hash) { - hash = g_hash_table_new (NULL, NULL); - g_datalist_set_data_full (md->data, "attachment_states", hash, - (GDestroyNotify) g_hash_table_destroy); - } - hash = g_datalist_get_data (md->data, "fake_parts"); - if (!hash) { - hash = g_hash_table_new (NULL, NULL); - g_datalist_set_data_full (md->data, "fake_parts", hash, - (GDestroyNotify) g_hash_table_destroy); - } - - write_headers (stream, md, mime_message); - format_mime_part (CAMEL_MIME_PART (mime_message), md, stream); -} - - -/** - * mail_format_raw_message: - * @mime_message: the input mime message - * @md: the MailDisplay to render into - * - * Writes a CamelMimeMessage source out into a MailDisplay - **/ -void -mail_format_raw_message (CamelMimeMessage *mime_message, MailDisplay *md, - MailDisplayStream *stream) -{ - CamelStreamFilter *filtered_stream; - CamelMimeFilter *html_filter; - CamelDataWrapper *wrapper; - guint32 flags; - - g_return_if_fail (CAMEL_IS_MIME_MESSAGE (mime_message)); - - wrapper = CAMEL_DATA_WRAPPER (mime_message); - if (!mail_content_loaded (wrapper, md, TRUE, NULL, md->html, NULL)) - return; - - filtered_stream = camel_stream_filter_new_with_stream ((CamelStream *) stream); - - flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES | - CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT; - html_filter = camel_mime_filter_tohtml_new (flags, 0); - camel_stream_filter_add (filtered_stream, html_filter); - camel_object_unref (html_filter); - - camel_stream_write_string ((CamelStream *) stream, STANDARD_ISSUE_TABLE_OPEN "<tr><td><tt>"); - - mail_format_data_wrapper_write_to_stream (wrapper, FALSE, md, (CamelStream *) filtered_stream); - camel_object_unref (filtered_stream); - - camel_stream_write_string ((CamelStream *) stream, "</tt></td></tr></table>"); -} - -static const char * -get_cid (CamelMimePart *part, MailDisplay *md) -{ - static int fake_cid_counter = 0; - char *cid; - - /* If we have a real Content-ID, use it. If we don't, - * make a (syntactically invalid, unique) fake one. - */ - if (camel_mime_part_get_content_id (part)) { - cid = g_strdup_printf ("cid:%s", camel_mime_part_get_content_id (part)); - } else - cid = g_strdup_printf ("cid:@@@%d", fake_cid_counter++); - - return mail_display_add_url (md, "part_urls", cid, part); -} - -static const char * -get_location (CamelMimePart *part, MailDisplay *md) -{ - CamelURL *base; - const char *loc; - char *location; - - base = mail_display_get_content_location (md); - - loc = camel_mime_part_get_content_location (part); - if (!loc) { - if (!base) - return NULL; - - location = camel_url_to_string (base, 0); - return mail_display_add_url (md, "part_urls", location, part); - } - - /* kludge: If the multipart/related does not have a - Content-Location header and the HTML part doesn't contain a - Content-Location header either, then we will end up - generating a invalid unique identifier in the form of - "cid:@@@%d" for use in GtkHTML's iframe src url. This means - that when GtkHTML requests a relative URL, it will request - "cid:/%s" */ - mail_display_add_url (md, "part_urls", g_strdup_printf ("cid:/%s", loc), part); - - if (!strchr (loc, ':') && base) { - CamelURL *url; - - mail_display_add_url (md, "part_urls", g_strdup (loc), part); - - url = camel_url_new_with_base (base, loc); - location = camel_url_to_string (url, 0); - camel_url_free (url); - } else { - location = g_strdup (loc); - } - - return mail_display_add_url (md, "part_urls", location, part); -} - - -static GHashTable *mime_handler_table, *mime_function_table; - -static void -setup_mime_tables (void) -{ - mime_handler_table = g_hash_table_new (g_str_hash, g_str_equal); - mime_function_table = g_hash_table_new (g_str_hash, g_str_equal); - - g_hash_table_insert (mime_function_table, "text/plain", - handle_text_plain); - g_hash_table_insert (mime_function_table, "text/richtext", - handle_text_enriched); - g_hash_table_insert (mime_function_table, "text/enriched", - handle_text_enriched); - g_hash_table_insert (mime_function_table, "text/html", - handle_text_html); - - g_hash_table_insert (mime_function_table, "image/gif", - handle_image); - g_hash_table_insert (mime_function_table, "image/jpeg", - handle_image); - g_hash_table_insert (mime_function_table, "image/png", - handle_image); - g_hash_table_insert (mime_function_table, "image/x-png", - handle_image); - g_hash_table_insert (mime_function_table, "image/tiff", - handle_image); - g_hash_table_insert (mime_function_table, "image/x-bmp", - handle_image); - g_hash_table_insert (mime_function_table, "image/bmp", - handle_image); - g_hash_table_insert (mime_function_table, "image/x-cmu-raster", - handle_image); - g_hash_table_insert (mime_function_table, "image/x-ico", - handle_image); - g_hash_table_insert (mime_function_table, "image/x-portable-anymap", - handle_image); - g_hash_table_insert (mime_function_table, "image/x-portable-bitmap", - handle_image); - g_hash_table_insert (mime_function_table, "image/x-portable-graymap", - handle_image); - g_hash_table_insert (mime_function_table, "image/x-portable-pixmap", - handle_image); - g_hash_table_insert (mime_function_table, "image/x-xpixmap", - handle_image); - - g_hash_table_insert (mime_function_table, "message/rfc822", - handle_message_rfc822); - g_hash_table_insert (mime_function_table, "message/news", - handle_message_rfc822); - g_hash_table_insert (mime_function_table, "message/external-body", - handle_message_external_body); - - g_hash_table_insert (mime_function_table, "multipart/alternative", - handle_multipart_alternative); - g_hash_table_insert (mime_function_table, "multipart/related", - handle_multipart_related); - g_hash_table_insert (mime_function_table, "multipart/mixed", - handle_multipart_mixed); - g_hash_table_insert (mime_function_table, "multipart/appledouble", - handle_multipart_appledouble); - g_hash_table_insert (mime_function_table, "multipart/encrypted", - handle_multipart_encrypted); - g_hash_table_insert (mime_function_table, "multipart/signed", - handle_multipart_signed); - - /* RFC 2046 says unrecognized text subtypes can be treated - * as text/plain (as long as you recognize the character set), - * and unrecognized multipart subtypes as multipart/mixed. */ - g_hash_table_insert (mime_function_table, "text/*", - handle_text_plain); - g_hash_table_insert (mime_function_table, "multipart/*", - handle_multipart_mixed); -} - -static gboolean -component_supports (Bonobo_ServerInfo *component, const char *mime_type) -{ - Bonobo_ActivationProperty *prop; - CORBA_sequence_CORBA_string stringv; - int i; - - prop = bonobo_server_info_prop_find (component, "repo_ids"); - if (!prop || prop->v._d != Bonobo_ACTIVATION_P_STRINGV) - return FALSE; - - stringv = prop->v._u.value_stringv; - for (i = 0; i < stringv._length; i++) { - if (!strcasecmp ("IDL:Bonobo/PersistStream:1.0", stringv._buffer[i])) - break; - } - - /* got to end of list with no persist stream? */ - - if (i >= stringv._length) - return FALSE; - - prop = bonobo_server_info_prop_find (component, - "bonobo:supported_mime_types"); - if (!prop || prop->v._d != Bonobo_ACTIVATION_P_STRINGV) - return FALSE; - - stringv = prop->v._u.value_stringv; - for (i = 0; i < stringv._length; i++) { - if (!strcasecmp (mime_type, stringv._buffer[i])) - return TRUE; - } - - return FALSE; -} - -static gboolean -mime_type_uses_evolution_component (const char *mime_type) -{ - return (!strcmp (mime_type, "text/x-vcard") || !strcmp (mime_type, "text/calendar")); -} - -static gboolean -mime_type_can_use_component (const char *mime_type) -{ - const char **mime_types; - int i; - - mime_types = mail_config_get_allowable_mime_types (); - for (i = 0; mime_types[i]; i++) { - if (!strcmp (mime_types[i], mime_type)) - return TRUE; - } - - return FALSE; -} - -/** - * mail_lookup_handler: - * @mime_type: a MIME type - * - * Looks up the MIME type in its own tables and GNOME-VFS's and returns - * a MailMimeHandler structure detailing the component, application, - * and built-in handlers (if any) for that MIME type. (If the component - * is non-%NULL, the built-in handler will always be handle_via_bonobo().) - * The MailMimeHandler's @generic field is set if the match was for the - * MIME supertype rather than the exact type. - * - * Return value: a MailMimeHandler (which should not be freed), or %NULL - * if no handlers are available. - **/ -MailMimeHandler * -mail_lookup_handler (const char *mime_type) -{ - MailMimeHandler *handler; - char *mime_type_main; - const char *p; - GList *components, *iter; - - if (mime_handler_table == NULL) - setup_mime_tables (); - - /* See if we've already found it. */ - handler = g_hash_table_lookup (mime_handler_table, mime_type); - if (handler) - return handler; - - /* Special case MIME type: application/octet-stream - * The point of this type is that there isn't a handler. - */ - if (strcmp (mime_type, "application/octet-stream") == 0) - return NULL; - - /* No. Create a new one and look up application and full type - * handler. If we find a builtin, create the handler and - * register it. - */ - handler = g_new0 (MailMimeHandler, 1); - handler->applications = - gnome_vfs_mime_get_short_list_applications (mime_type); - handler->builtin = - g_hash_table_lookup (mime_function_table, mime_type); - - if (handler->builtin) { - handler->generic = FALSE; - handler->is_bonobo = FALSE; - goto reg; - } - - /* only allow using a bonobo component if it is an evo-component or the user has - * specified that we can use a bonobo-component by setting the gconf key */ - if (mime_type_uses_evolution_component (mime_type) || mime_type_can_use_component (mime_type)) { - /* Try for the first matching component. (we don't use get_short_list_comps - * as that will return NULL if the oaf files don't have the short_list properties - * defined). */ - components = gnome_vfs_mime_get_all_components (mime_type); - for (iter = components; iter; iter = iter->next) { - if (component_supports (iter->data, mime_type)) { - handler->generic = FALSE; - handler->is_bonobo = TRUE; - handler->builtin = handle_via_bonobo; - handler->component = Bonobo_ServerInfo_duplicate (iter->data); - gnome_vfs_mime_component_list_free (components); - goto reg; - } - } - - gnome_vfs_mime_component_list_free (components); - } - - /* Try for a generic builtin match. */ - p = strchr (mime_type, '/'); - if (p == NULL) - p = mime_type + strlen (mime_type); - mime_type_main = g_alloca ((p - mime_type) + 3); - memcpy (mime_type_main, mime_type, p - mime_type); - memcpy (mime_type_main + (p - mime_type), "/*", 3); - - handler->builtin = g_hash_table_lookup (mime_function_table, - mime_type_main); - - if (handler->builtin) { - handler->generic = TRUE; - handler->is_bonobo = FALSE; - if (handler->component) { - CORBA_free (handler->component); - handler->component = NULL; - } - goto reg; - } - - /* Try for a generic component match. */ - if (handler->component) { - handler->generic = TRUE; - handler->is_bonobo = TRUE; - handler->builtin = handle_via_bonobo; - goto reg; - } - - /* If we at least got an application list, use that. */ - if (handler->applications) { - handler->generic = TRUE; - handler->is_bonobo = FALSE; - goto reg; - } - - /* Nada. */ - g_free (handler); - return NULL; - - reg: - g_hash_table_insert (mime_handler_table, g_strdup (mime_type), handler); - - return handler; -} - -/* An "anonymous" MIME part is one that we shouldn't call attention - * to the existence of, but simply display. - */ -static gboolean -is_anonymous (CamelMimePart *part, const char *mime_type) -{ - /* FIXME: should use CamelContentType stuff */ - if (!strncasecmp (mime_type, "multipart/", 10) || - !strncasecmp (mime_type, "message/", 8)) - return TRUE; - - if (!strncasecmp (mime_type, "text/", 5) && - !camel_mime_part_get_filename (part)) - return TRUE; - - return FALSE; -} - -/** - * mail_part_is_inline: - * @part: a CamelMimePart - * - * Return value: whether or not the part should/will be displayed inline. - **/ -gboolean -mail_part_is_inline (CamelMimePart *part) -{ - const char *disposition; - CamelContentType *content_type; - gboolean anon; - char *type; - - /* If it has an explicit disposition, return that. */ - disposition = camel_mime_part_get_disposition (part); - if (disposition) - return strcasecmp (disposition, "inline") == 0; - - /* Certain types should default to inline. FIXME: this should - * be customizable. - */ - content_type = camel_mime_part_get_content_type (part); - if (!header_content_type_is (content_type, "message", "*")) - return TRUE; - - /* Otherwise, display it inline if it's "anonymous", and - * as an attachment otherwise. - */ - type = header_content_type_simple (content_type); - anon = is_anonymous (part, type); - g_free (type); - - return anon; -} - -enum inline_states { - I_VALID = (1 << 0), - I_ACTUALLY = (1 << 1), - I_DISPLAYED = (1 << 2) -}; - -static int -get_inline_flags (CamelMimePart *part, MailDisplay *md) -{ - GHashTable *asht; - int val; - - /* check if we already know. */ - - asht = g_datalist_get_data (md->data, "attachment_states"); - val = GPOINTER_TO_INT (g_hash_table_lookup (asht, part)); - if (val) - return val; - - /* ok, we don't know. Figure it out. */ - - if (mail_part_is_inline (part)) - val = (I_VALID | I_ACTUALLY | I_DISPLAYED); - else - val = (I_VALID); - - g_hash_table_insert (asht, part, GINT_TO_POINTER (val)); - - return val; -} - -gboolean -mail_part_is_displayed_inline (CamelMimePart *part, MailDisplay *md) -{ - return (gboolean) (get_inline_flags (part, md) & I_DISPLAYED); -} - -void -mail_part_toggle_displayed (CamelMimePart *part, MailDisplay *md) -{ - GHashTable *asht = g_datalist_get_data (md->data, "attachment_states"); - gpointer ostate, opart; - int state; - - if (g_hash_table_lookup_extended (asht, part, &opart, &ostate)) { - g_hash_table_remove (asht, part); - - state = GPOINTER_TO_INT (ostate); - - if (state & I_DISPLAYED) - state &= ~I_DISPLAYED; - else - state |= I_DISPLAYED; - } else { - state = I_VALID | I_DISPLAYED; - } - - g_hash_table_insert (asht, part, GINT_TO_POINTER (state)); -} - -static void -mail_part_set_default_displayed_inline (CamelMimePart *part, MailDisplay *md, - gboolean displayed) -{ - GHashTable *asht = g_datalist_get_data (md->data, "attachment_states"); - int state; - - if (g_hash_table_lookup (asht, part)) - return; - - state = I_VALID | (displayed ? I_DISPLAYED : 0); - g_hash_table_insert (asht, part, GINT_TO_POINTER (state)); -} - -static void -attachment_header (CamelMimePart *part, const char *mime_type, MailDisplay *md, - MailDisplayStream *stream) -{ - char *htmlinfo; - const char *info; - - /* Start the table, create the pop-up object. */ - camel_stream_write_string ((CamelStream *) stream, "<table cellspacing=0 cellpadding=0><tr><td>" - "<table width=10 cellspacing=0 cellpadding=0>" - "<tr><td></td></tr></table></td>"); - - if (!md->printing) { - camel_stream_printf ((CamelStream *) stream, "<td><object classid=\"popup:%s\"" - "type=\"%s\"></object></td>", get_cid (part, md), mime_type); - } - - camel_stream_write_string ((CamelStream *) stream, "<td><table width=3 cellspacing=0 cellpadding=0>" - "<tr><td></td></tr></table></td><td><font size=-1>"); - - /* Write the MIME type */ - info = gnome_vfs_mime_get_description (mime_type); - htmlinfo = camel_text_to_html (info ? info : mime_type, 0, 0); - camel_stream_printf ((CamelStream *) stream, _("%s attachment"), htmlinfo); - g_free (htmlinfo); - - /* Write the name, if we have it. */ - info = camel_mime_part_get_filename (part); - if (info) { - htmlinfo = camel_text_to_html (info, 0, 0); - camel_stream_printf ((CamelStream *) stream, " (%s)", htmlinfo); - g_free (htmlinfo); - } - - /* Write a description, if we have one. */ - info = camel_mime_part_get_description (part); - if (info) { - htmlinfo = camel_text_to_html (info, md->printing ? 0 : CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); - camel_stream_printf ((CamelStream *) stream, ", \"%s\"", htmlinfo); - g_free (htmlinfo); - } - - camel_stream_write_string ((CamelStream *) stream, "</font></td></tr><tr><td height=10>" - "<table cellspacing=0 cellpadding=0><tr><td height=10>" - "<a name=\"glue\"></td></tr></table></td></tr></table>\n"); -} - -static gboolean -format_mime_part (CamelMimePart *part, MailDisplay *md, - MailDisplayStream *stream) -{ - CamelDataWrapper *wrapper; - MailMimeHandler *handler; - gboolean output; - int inline_flags; - char *mime_type; - - /* Record URLs associated with this part */ - get_cid (part, md); - get_location (part, md); - - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - - if (CAMEL_IS_MULTIPART (wrapper) && - camel_multipart_get_number (CAMEL_MULTIPART (wrapper)) == 0) { - mail_error_printf (stream, "\n%s\n", _("Could not parse MIME message. Displaying as source.")); - if (mail_content_loaded (wrapper, md, TRUE, NULL, md->html, NULL)) - handle_text_plain (part, "text/plain", md, stream); - return TRUE; - } - - mime_type = camel_data_wrapper_get_mime_type (wrapper); - camel_strdown (mime_type); - - handler = mail_lookup_handler (mime_type); - if (!handler) { - char *id_type; - - /* Special case MIME types that we know that we can't - * display but are some kind of plain text to prevent - * evil infinite recursion. - */ - - if (!strcmp (mime_type, "application/mac-binhex40")) { - handler = NULL; - } else if (!strcmp (mime_type, "application/octet-stream")) { - /* only sniff application/octet-stream parts */ - id_type = mail_identify_mime_part (part, md); - if (id_type) { - g_free (mime_type); - mime_type = id_type; - handler = mail_lookup_handler (id_type); - } - } - } - - inline_flags = get_inline_flags (part, md); - - /* No header for anonymous inline parts. */ - if (!((inline_flags & I_ACTUALLY) && is_anonymous (part, mime_type))) - attachment_header (part, mime_type, md, stream); - - if (handler && handler->builtin && inline_flags & I_DISPLAYED && - mail_content_loaded (wrapper, md, TRUE, NULL, md->html, NULL)) - output = (*handler->builtin) (part, mime_type, md, stream); - else - output = TRUE; - - g_free (mime_type); - return output; -} - -/* flags for write_field_to_stream */ -enum { - WRITE_BOLD=1, - WRITE_NOCOLUMNS=2, -}; - -static void -write_field_row_begin (MailDisplayStream *stream, const char *name, int flags) -{ - gboolean bold = (flags & WRITE_BOLD); - gboolean nocolumns = (flags & WRITE_NOCOLUMNS); - - if (nocolumns) { - camel_stream_printf ((CamelStream *) stream, "<tr><td>%s%s:%s ", - bold ? "<b>" : "", name, bold ? "</b>" : ""); - } else { - camel_stream_printf ((CamelStream *) stream, - "<tr><%s align=\"right\" valign=\"top\">%s:" - "<b> </%s><td>", bold ? "th" : "td", - name, bold ? "th" : "td"); - } -} - -static void -write_date (MailDisplayStream *stream, CamelMimeMessage *message, int flags) -{ - const char *datestr; - - datestr = camel_medium_get_header (CAMEL_MEDIUM (message), "Date"); - - if (datestr) { - int msg_offset; - time_t msg_date; - struct tm local; - int local_tz; - - msg_date = header_decode_date(datestr, &msg_offset); - e_localtime_with_offset(msg_date, &local, &local_tz); - - write_field_row_begin(stream, _("Date"), flags); - camel_stream_printf((CamelStream *)stream, "%s", datestr); - - /* Convert message offset to minutes (e.g. -0400 --> -240) */ - msg_offset = ((msg_offset / 100) * 60) + (msg_offset % 100); - /* Turn into offset from localtime, not UTC */ - msg_offset -= local_tz / 60; - - if (msg_offset) { - /* Message timezone different from local. Show both */ - char buf[30]; - - msg_offset += (local.tm_hour * 60) + local.tm_min; - - if (msg_offset >= (24 * 60) || msg_offset < 0) { - /* Timezone conversion crossed midnight. Show day */ - /* translators: strftime format for local time equivalent in Date header display */ - e_utf8_strftime(buf, 29, _("<I> (%a, %R %Z)</I>"), &local); - } else { - e_utf8_strftime(buf, 29, _("<I> (%R %Z)</I>"), &local); - } - - /* I doubt any locales put '%' in time representation - but just in case... */ - camel_stream_printf((CamelStream *)stream, "%s", buf); - } - - camel_stream_printf ((CamelStream *) stream, "</td> </tr>"); - } -} - -static void -write_text_header (MailDisplayStream *stream, const char *name, const char *value, int flags) -{ - char *encoded; - - if (value && *value) - encoded = camel_text_to_html (value, CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | - CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES | - CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); - else - encoded = ""; - - write_field_row_begin (stream, name, flags); - - camel_stream_printf ((CamelStream *) stream, "%s</td></tr>", encoded); - - if (value && *value) - g_free (encoded); -} - -static void -write_address (MailDisplay *md, MailDisplayStream *stream, - const CamelInternetAddress *addr, const char *field_name, int flags) -{ - const char *name, *email; - int i; - - if (addr == NULL || !camel_internet_address_get (addr, 0, NULL, NULL)) - return; - - write_field_row_begin (stream, field_name, flags); - - i = 0; - while (camel_internet_address_get (addr, i, &name, &email)) { - CamelInternetAddress *subaddr; - char *addr_txt, *addr_url; - gboolean have_name = name && *name; - gboolean have_email = email && *email; - char *name_disp = NULL; - char *email_disp = NULL; - - subaddr = camel_internet_address_new (); - camel_internet_address_add (subaddr, name, email); - addr_txt = camel_address_format (CAMEL_ADDRESS (subaddr)); - addr_url = camel_url_encode (addr_txt, NULL); - camel_object_unref (subaddr); - - if (have_name) { - name_disp = camel_text_to_html (name, 0, 0); - } - - if (have_email) { - email_disp = camel_text_to_html (email, 0, 0); - } - - if (i) - camel_stream_write_string ((CamelStream *) stream, ", "); - - if (have_email || have_name) { - if (!have_email) - email_disp = g_strdup ("???"); - - if (have_name) { - if (md->printing) { - camel_stream_printf ((CamelStream *) stream, - "%s <%s>", name_disp, email_disp); - } else { - camel_stream_printf ((CamelStream *) stream, - "%s <<a href=\"mailto:%s\">%s</a>>", - name_disp, addr_url, email_disp); - } - } else { - if (md->printing) { - camel_stream_write_string ((CamelStream *) stream, email_disp); - } else { - camel_stream_printf ((CamelStream *) stream, - "<a href=\"mailto:%s\">%s</a>", - addr_url, email_disp); - } - } - } else { - camel_stream_printf ((CamelStream *) stream, "<i>%s</i>", _("Bad Address")); - } - - g_free (name_disp); - g_free (email_disp); - g_free (addr_txt); - g_free (addr_url); - - i++; - } - - camel_stream_write_string ((CamelStream *) stream, "</td></tr>"); -} - -/* order of these must match write_header code */ -static char *default_headers[] = { - "From", "Reply-To", "To", "Cc", "Bcc", "Subject", "Date", -}; - -/* return index of header in default_headers array */ -static int -default_header_index (const char *name) -{ - int i; - - for (i = 0; i < sizeof (default_headers) / sizeof (default_headers[0]); i++) - if (!strcasecmp (name, default_headers[i])) - return i; - - return -1; -} - -/* index is index of header in default_headers array */ -static void -write_default_header (CamelMimeMessage *message, MailDisplay *md, - MailDisplayStream *stream, - int index, int flags) -{ - switch (index) { - case 0: - write_address (md, stream, - camel_mime_message_get_from (message), _("From"), flags | WRITE_BOLD); - break; - case 1: - write_address (md, stream, - camel_mime_message_get_reply_to (message), _("Reply-To"), flags | WRITE_BOLD); - break; - case 2: - write_address (md, stream, - camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO), - _("To"), flags | WRITE_BOLD); - break; - case 3: - write_address (md, stream, - camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC), - _("Cc"), flags | WRITE_BOLD); - break; - case 4: - write_address (md, stream, - camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC), - _("Bcc"), flags | WRITE_BOLD); - break; - case 5: - write_text_header (stream, _("Subject"), camel_mime_message_get_subject (message), - flags | WRITE_BOLD); - break; - case 6: - write_date (stream, message, flags | WRITE_BOLD); - break; - default: - g_assert_not_reached (); - } -} - -static gboolean -write_xmailer_header (CamelMimeMessage *message, MailDisplay *md, - MailDisplayStream *stream, int xmask) -{ - const char *xmailer, *evolution; - - xmailer = camel_medium_get_header (CAMEL_MEDIUM (message), "X-Mailer"); - if (!xmailer) { - xmailer = camel_medium_get_header (CAMEL_MEDIUM (message), "User-Agent"); - if (!xmailer) - return FALSE; - } - while (isspace ((unsigned char)*xmailer)) - xmailer++; - - evolution = strstr (xmailer, "Evolution"); - if ((xmask & MAIL_CONFIG_XMAILER_OTHER) || - (evolution && (xmask & MAIL_CONFIG_XMAILER_EVO))) - write_text_header (stream, _("Mailer"), xmailer, WRITE_BOLD); - - return evolution != NULL && (xmask & MAIL_CONFIG_XMAILER_RUPERT_APPROVED); -} - -#define COLOR_IS_LIGHT(r, g, b) ((r + g + b) > (128 * 3)) - -static void -write_headers (MailDisplayStream *stream, MailDisplay *md, CamelMimeMessage *message) -{ - gboolean full = (md->display_style == MAIL_CONFIG_DISPLAY_FULL_HEADERS); - char bgcolor[7], fontcolor[7]; - GtkStyle *style = NULL; - gboolean evo_icon = FALSE; - GConfClient *gconf; - int xmask, i; - - gconf = mail_config_get_gconf_client (); - xmask = gconf_client_get_int (gconf, "/apps/evolution/mail/display/xmailer_mask", NULL); - - /* My favorite thing to do... muck around with colors so we respect people's stupid themes. - However, we only do this if we are rendering to the screen -- we ignore the theme - when we are printing. */ - style = gtk_widget_get_style (GTK_WIDGET (md->html)); - if (style && !md->printing) { - int state = GTK_WIDGET_STATE (GTK_WIDGET (md->html)); - gushort r, g, b; - - r = style->base[state].red / 256; - g = style->base[state].green / 256; - b = style->base[state].blue / 256; - - if (COLOR_IS_LIGHT (r, g, b)) { - r *= 0.92; - g *= 0.92; - b *= 0.92; - } else { - r = 255 - (0.92 * (255 - r)); - g = 255 - (0.92 * (255 - g)); - b = 255 - (0.92 * (255 - b)); - } - - sprintf (bgcolor, "%.2X%.2X%.2X", r, g, b); - - r = style->text[state].red / 256; - g = style->text[state].green / 256; - b = style->text[state].blue / 256; - - sprintf (fontcolor, "%.2X%.2X%.2X", r, g, b); - } else { - strcpy (bgcolor, "EEEEEE"); - strcpy (fontcolor, "000000"); - } - - camel_stream_write_string ((CamelStream *) stream, - "<table width=\"100%\" cellpadding=0 cellspacing=0>"); - - /* Top margin */ - camel_stream_write_string ((CamelStream *) stream, "<tr><td colspan=3 height=10>" - "<table cellpadding=0 cellspacing=0><tr><td height=10>" - "<a name=\"glue\"></td></tr></table></td></tr>"); - - /* Left margin */ - camel_stream_write_string ((CamelStream *) stream, "<tr><td><table width=10 " - "cellpadding=0 cellspacing=0><tr><td></td></tr></table></td>"); - - /* Black border */ - camel_stream_write_string ((CamelStream *) stream, "<td width=\"100%\"><table bgcolor=\"#000000\" " - "width=\"100%\" cellspacing=0 cellpadding=1>"); - - /* Main header box */ - camel_stream_printf ((CamelStream *) stream, "<tr><td><table bgcolor=\"#%s\" width=\"100%%\" " - "cellpadding=0 cellspacing=0>" - /* Internal header table */ - "<tr valign=top><td><table><font color=\"#%s\">\n", - bgcolor, fontcolor); - - if (full) { - struct _header_raw *header; - const char *charset; - CamelContentType *ct; - char *value; - - ct = camel_mime_part_get_content_type (CAMEL_MIME_PART (message)); - charset = header_content_type_param (ct, "charset"); - charset = e_iconv_charset_name (charset); - - header = CAMEL_MIME_PART (message)->headers; - while (header) { - i = default_header_index (header->name); - if (i == -1) { - value = header_decode_string (header->value, charset); - write_text_header (stream, header->name, value, WRITE_NOCOLUMNS); - g_free (value); - } else - write_default_header (message, md, stream, i, WRITE_NOCOLUMNS); - header = header->next; - } - } else { - for (i = 0; i < sizeof (default_headers) / sizeof (default_headers[0]); i++) - write_default_header (message, md, stream, i, 0); - if (xmask != MAIL_CONFIG_XMAILER_NONE) - evo_icon = write_xmailer_header (message, md, stream, xmask); - } - - /* Close off the internal header table */ - camel_stream_write_string ((CamelStream *) stream, "</font></table></td>"); - - if (!md->printing && evo_icon) { - camel_stream_printf ((CamelStream *) stream, "<td align=right><table><tr><td width=16>" - "<img src=\"%s\"></td></tr></table></td>", - mail_display_get_url_for_icon (md, EVOLUTION_ICONSDIR "/monkey-16.png")); - } - - camel_stream_write_string ((CamelStream *) stream, - /* Main header box */ - "</tr></table>" - /* Black border */ - "</td></tr></table></td>" - /* Right margin */ - "<td><table width=10 cellpadding=0 cellspacing=0>" - "<tr><td></td></tr></table></td>" - "</tr></table>\n"); -} - -static void -load_offline_content (MailDisplay *md, gpointer data) -{ - CamelDataWrapper *wrapper = data; - CamelStream *stream; - - stream = camel_stream_null_new (); - camel_data_wrapper_write_to_stream (wrapper, stream); - camel_object_unref (stream); - camel_object_unref (wrapper); -} - -gboolean -mail_content_loaded (CamelDataWrapper *wrapper, MailDisplay *md, gboolean redisplay, const char *url, - GtkHTML *html, GtkHTMLStream *stream) -{ - if (!camel_data_wrapper_is_offline (wrapper)) - return TRUE; - - camel_object_ref (wrapper); - if (redisplay) { - mail_display_redisplay_when_loaded (md, wrapper, load_offline_content, - html, wrapper); - } else { - mail_display_stream_write_when_loaded (md, wrapper, url, load_offline_content, - html, stream, wrapper); - } - - return FALSE; -} - - -ssize_t -mail_format_data_wrapper_write_to_stream (CamelDataWrapper *wrapper, gboolean decode, MailDisplay *mail_display, CamelStream *stream) -{ - CamelStreamFilter *filter_stream; - CamelMimeFilterCharset *filter; - CamelContentType *content_type; - GConfClient *gconf; - ssize_t nwritten; - char *charset; - - gconf = mail_config_get_gconf_client (); - - content_type = camel_data_wrapper_get_mime_type_field (wrapper); - - /* find out the charset the user wants to override to */ - if (mail_display && mail_display->charset) { - /* user override charset */ - charset = g_strdup (mail_display->charset); - } else if (content_type && (charset = (char *) header_content_type_param (content_type, "charset"))) { - /* try to use the charset declared in the Content-Type header */ - if (!strncasecmp (charset, "iso-8859-", 9)) { - /* Since a few Windows mailers like to claim they sent - * out iso-8859-# encoded text when they really sent - * out windows-cp125#, do some simple sanity checking - * before we move on... */ - CamelMimeFilterWindows *windows; - CamelStream *null; - - null = camel_stream_null_new (); - filter_stream = camel_stream_filter_new_with_stream (null); - camel_object_unref (null); - - windows = (CamelMimeFilterWindows *) camel_mime_filter_windows_new (charset); - camel_stream_filter_add (filter_stream, (CamelMimeFilter *) windows); - - if (decode) - camel_data_wrapper_decode_to_stream (wrapper, (CamelStream *) filter_stream); - else - camel_data_wrapper_write_to_stream (wrapper, (CamelStream *) filter_stream); - camel_stream_flush ((CamelStream *) filter_stream); - camel_object_unref (filter_stream); - - charset = g_strdup (camel_mime_filter_windows_real_charset (windows)); - camel_object_unref (windows); - } else { - charset = g_strdup (charset); - } - } else { - /* default to user's locale charset? */ - charset = gconf_client_get_string (gconf, "/apps/evolution/mail/format/charset", NULL); - } - - filter_stream = camel_stream_filter_new_with_stream (stream); - - if ((filter = camel_mime_filter_charset_new_convert (charset, "UTF-8"))) { - camel_stream_filter_add (filter_stream, (CamelMimeFilter *) filter); - camel_object_unref (filter); - } - - g_free (charset); - - if (decode) - nwritten = camel_data_wrapper_decode_to_stream (wrapper, (CamelStream *) filter_stream); - else - nwritten = camel_data_wrapper_write_to_stream (wrapper, (CamelStream *) filter_stream); - camel_stream_flush ((CamelStream *) filter_stream); - camel_object_unref (filter_stream); - - return nwritten; -} - -/* Return the contents of a data wrapper, or %NULL if it contains only - * whitespace. - */ -GByteArray * -mail_format_get_data_wrapper_text (CamelDataWrapper *wrapper, MailDisplay *mail_display) -{ - CamelStream *memstream; - GByteArray *ba; - char *text, *end; - - memstream = camel_stream_mem_new (); - ba = g_byte_array_new (); - camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (memstream), ba); - - mail_format_data_wrapper_write_to_stream (wrapper, TRUE, mail_display, memstream); - camel_object_unref (memstream); - - for (text = ba->data, end = text + ba->len; text < end; text++) { - if (!isspace ((unsigned char) *text)) - break; - } - - if (text >= end) { - g_byte_array_free (ba, TRUE); - return NULL; - } - - return ba; -} - -static void -write_hr (MailDisplayStream *stream) -{ - camel_stream_write_string ((CamelStream *) stream, STANDARD_ISSUE_TABLE_OPEN - "<tr><td width=\"100%\"><hr noshadow size=1>" - "</td></tr></table>\n"); -} - -/*----------------------------------------------------------------------* - * Mime handling functions - *----------------------------------------------------------------------*/ - -static gboolean -handle_text_plain (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream) -{ - CamelStreamFilter *filtered_stream; - CamelMimeFilter *html_filter; - CamelDataWrapper *wrapper; - CamelContentType *type; - const char *format; - GConfClient *gconf; - guint32 flags, rgb = 0; - GdkColor colour; - char *buf; - - gconf = mail_config_get_gconf_client (); - - flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES; - if (!md->printing) { - flags |= CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES; - if (gconf_client_get_bool (gconf, "/apps/evolution/mail/display/mark_citations", NULL)) { - flags |= CAMEL_MIME_FILTER_TOHTML_MARK_CITATION; - - buf = gconf_client_get_string (gconf, "/apps/evolution/mail/display/citation_colour", NULL); - gdk_color_parse (buf ? buf : "#737373", &colour); - g_free (buf); - - rgb = ((colour.red & 0xff00) << 8) | (colour.green & 0xff00) | ((colour.blue & 0xff00) >> 8); - } - } - - /* Check for RFC 2646 flowed text. */ - type = camel_mime_part_get_content_type (part); - if (header_content_type_is (type, "text", "plain")) { - format = header_content_type_param (type, "format"); - - if (format && !strcasecmp (format, "flowed")) - flags |= CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED; - } - - html_filter = camel_mime_filter_tohtml_new (flags, rgb); - filtered_stream = camel_stream_filter_new_with_stream ((CamelStream *) stream); - camel_stream_filter_add (filtered_stream, html_filter); - camel_object_unref (html_filter); - - camel_stream_write_string ((CamelStream *) stream, STANDARD_ISSUE_TABLE_OPEN "<tr><td><tt>\n"); - - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - mail_format_data_wrapper_write_to_stream (wrapper, TRUE, md, (CamelStream *) filtered_stream); - - camel_stream_write_string ((CamelStream *) stream, "</tt></td></tr></table>\n"); - - camel_object_unref (filtered_stream); - - return TRUE; -} - -/* text/enriched (RFC 1896) or text/richtext (included in RFC 1341) */ -static gboolean -handle_text_enriched (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream) -{ - CamelStreamFilter *filtered_stream; - CamelMimeFilter *enriched; - CamelDataWrapper *wrapper; - guint32 flags = 0; - - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - - if (!strcasecmp (mime_type, "text/richtext")) { - flags = CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT; - camel_stream_write_string ((CamelStream *) stream, "\n<!-- text/richtext -->\n"); - } else { - camel_stream_write_string ((CamelStream *) stream, "\n<!-- text/enriched -->\n"); - } - - enriched = camel_mime_filter_enriched_new (flags); - filtered_stream = camel_stream_filter_new_with_stream ((CamelStream *) stream); - camel_stream_filter_add (filtered_stream, enriched); - camel_object_unref (enriched); - - camel_stream_write_string ((CamelStream *) stream, STANDARD_ISSUE_TABLE_OPEN "<tr><td><tt>\n"); - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - mail_format_data_wrapper_write_to_stream (wrapper, TRUE, md, (CamelStream *) filtered_stream); - - camel_stream_write_string ((CamelStream *) stream, "</tt></td></tr></table>\n"); - camel_object_unref (filtered_stream); - - return TRUE; -} - -static gboolean -handle_text_html (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream) -{ - const char *location, *base; - - camel_stream_write_string ((CamelStream *) stream, "\n<!-- text/html -->\n"); - - if ((base = camel_medium_get_header (CAMEL_MEDIUM (part), "Content-Base"))) { - char *base_url; - size_t len; - - len = strlen (base); - - if (*base == '"' && *(base + len - 1) == '"') { - len -= 2; - base_url = g_alloca (len + 1); - memcpy (base_url, base + 1, len); - base_url[len] = '\0'; - base = base_url; - } - - gtk_html_set_base (md->html, base); - } - - location = get_location (part, md); - if (!location) - location = get_cid (part, md); - - camel_stream_printf ((CamelStream *) stream, "<iframe src=\"%s\" frameborder=0 " - "scrolling=no>could not get %s</iframe>", location, location); - - return TRUE; -} - -static gboolean -handle_image (CamelMimePart *part, const char *mime_type, MailDisplay *md, MailDisplayStream *stream) -{ - camel_stream_printf ((CamelStream *) stream, "<img hspace=10 vspace=10 src=\"%s\">", - get_cid (part, md)); - return TRUE; -} - -static gboolean -handle_multipart_mixed (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream) -{ - CamelDataWrapper *wrapper = - camel_medium_get_content_object (CAMEL_MEDIUM (part)); - CamelMultipart *mp; - int i, nparts; - gboolean output = FALSE; - - if (!CAMEL_IS_MULTIPART (wrapper)) { - mail_error_printf (stream, "\n%s\n", _("Could not parse MIME message. Displaying as source.")); - if (mail_content_loaded (wrapper, md, TRUE, NULL, md->html, NULL)) - handle_text_plain (part, "text/plain", md, stream); - return TRUE; - } - - mp = CAMEL_MULTIPART (wrapper); - - nparts = camel_multipart_get_number (mp); - for (i = 0; i < nparts; i++) { - if (i != 0 && output) - write_hr (stream); - - part = camel_multipart_get_part (mp, i); - - output = format_mime_part (part, md, stream); - } - - return TRUE; -} - -static gboolean -handle_multipart_encrypted (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream) -{ - CamelMultipartEncrypted *mpe; - CamelMimePart *mime_part; - CamelCipherContext *cipher; - CamelDataWrapper *wrapper; - const char *protocol; - CamelException ex; - gboolean handled; - - /* Currently we only handle RFC2015-style PGP encryption. */ - protocol = header_content_type_param (((CamelDataWrapper *) part)->mime_type, "protocol"); - if (!protocol || strcmp (protocol, "application/pgp-encrypted") != 0) - return handle_multipart_mixed (part, mime_type, md, stream); - - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - - mpe = CAMEL_MULTIPART_ENCRYPTED (wrapper); - - camel_exception_init (&ex); - cipher = camel_gpg_context_new (session); - mime_part = camel_multipart_encrypted_decrypt (mpe, cipher, &ex); - camel_object_unref (cipher); - - if (camel_exception_is_set (&ex)) { - mail_error_printf (stream, "\n%s\n", camel_exception_get_description (&ex)); - camel_exception_clear (&ex); - return TRUE; - } - - handled = format_mime_part (mime_part, md, stream); - camel_object_unref (mime_part); - - return handled; -} - -static gboolean -handle_multipart_signed (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream) -{ - CamelMimePart *subpart; - CamelDataWrapper *wrapper; - CamelMultipartSigned *mps; - gboolean output = FALSE; - - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - - if (!CAMEL_IS_MULTIPART_SIGNED (wrapper)) { - mail_error_printf (stream, "\n%s\n", _("Could not parse MIME message. Displaying as source.")); - if (mail_content_loaded (wrapper, md, TRUE, NULL, md->html, NULL)) - handle_text_plain (part, "text/plain", md, stream); - return TRUE; - } - - mps = CAMEL_MULTIPART_SIGNED (wrapper); - - /* if subpart & signature is null, what do we do? just write it out raw? - multipart_signed will, if it cannot parse properly, put everything in the first part - this includes: more or less than 2 parts */ - - /* output the content */ - subpart = camel_multipart_get_part ((CamelMultipart *) mps, CAMEL_MULTIPART_SIGNED_CONTENT); - if (subpart == NULL) - return FALSE; - - output = format_mime_part (subpart, md, stream); - - /* now handle the signature */ - subpart = camel_multipart_get_part ((CamelMultipart *) mps, CAMEL_MULTIPART_SIGNED_SIGNATURE); - if (subpart == NULL) - return FALSE; - - mail_part_set_default_displayed_inline (subpart, md, FALSE); - - if (!mail_part_is_displayed_inline (subpart, md) && !md->printing) { - char *url; - - /* Write out the click-for-info object */ - url = g_strdup_printf ("signature:%p/%lu", subpart, - (unsigned long)time (NULL)); - camel_stream_printf ((CamelStream *) stream, - "<br><table cellspacing=0 cellpadding=0>" - "<tr><td><table width=10 cellspacing=0 cellpadding=0>" - "<tr><td></td></tr></table></td>" - "<td><object classid=\"%s\"></object></td>" - "<td><table width=3 cellspacing=0 cellpadding=0>" - "<tr><td></td></tr></table></td>" - "<td><font size=-1>", url); - mail_display_add_url (md, "part_urls", url, subpart); - - camel_stream_write_string ((CamelStream *) stream, - _("This message is digitally signed. " - "Click the lock icon for more information.")); - - camel_stream_write_string ((CamelStream *) stream, "</font></td></tr><tr><td height=10>" - "<table cellspacing=0 cellpadding=0><tr>" - "<td height=10><a name=\"glue\"></td></tr>" - "</table></td></tr></table>\n"); - } else { - CamelCipherValidity *valid = NULL; - CamelException ex; - const char *message = NULL; - gboolean good = FALSE; - CamelCipherContext *cipher; - - /* Write out the verification results */ - /* TODO: use the right context for the right message ... */ - camel_exception_init (&ex); - cipher = camel_gpg_context_new (session); - if (cipher) { - valid = camel_multipart_signed_verify (mps, cipher, &ex); - camel_object_unref (cipher); - if (valid) { - good = camel_cipher_validity_get_valid (valid); - message = camel_cipher_validity_get_description (valid); - } else { - message = camel_exception_get_description (&ex); - } - } else { - message = _("Could not create a PGP verfication context"); - } - - if (good) { - camel_stream_printf ((CamelStream *) stream, "<table><tr valign=top><td>" - "<img src=\"%s\"></td><td>%s<br><br>", - mail_display_get_url_for_icon (md, EVOLUTION_ICONSDIR - "/pgp-signature-ok.png"), - _("This message is digitally signed and " - "has been found to be authentic.")); - } else { - camel_stream_printf ((CamelStream *) stream, "<table><tr valign=top><td>" - "<img src=\"%s\"></td><td>%s<br><br>", - mail_display_get_url_for_icon (md, EVOLUTION_ICONSDIR - "/pgp-signature-bad.png"), - _("This message is digitally signed but can " - "not be proven to be authentic.")); - } - - if (message) { - camel_stream_printf ((CamelStream *) stream, "<font size=-1%s>", good || - md->printing ? "" : " color=red"); - mail_text_write (stream, md, part, 0, md->printing, message); - camel_stream_write_string ((CamelStream *) stream, "</font>"); - } - - camel_stream_write_string ((CamelStream *) stream, "</td></tr></table>"); - camel_exception_clear (&ex); - camel_cipher_validity_free (valid); - } - - return TRUE; -} - -/* As seen in RFC 2387! */ -static gboolean -handle_multipart_related (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream) -{ - CamelDataWrapper *wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - CamelMultipart *mp; - CamelMimePart *body_part, *display_part = NULL; - CamelContentType *content_type; - const char *location, *start; - int i, nparts; - GHashTable *related_save; - int ret; - - if (!CAMEL_IS_MULTIPART (wrapper)) { - mail_error_printf (stream, "\n%s\n", _("Could not parse MIME message. Displaying as source.")); - if (mail_content_loaded (wrapper, md, TRUE, NULL, md->html, NULL)) - handle_text_plain (part, "text/plain", md, stream); - return TRUE; - } - - mp = CAMEL_MULTIPART (wrapper); - nparts = camel_multipart_get_number (mp); - - content_type = camel_mime_part_get_content_type (part); - start = header_content_type_param (content_type, "start"); - if (start) { - int len; - - /* The "start" parameter includes <>s, which Content-Id - * does not. - */ - len = strlen (start) - 2; - - for (i = 0; i < nparts; i++) { - const char *cid; - - body_part = camel_multipart_get_part (mp, i); - cid = camel_mime_part_get_content_id (body_part); - - if (cid && !strncmp (cid, start + 1, len) && strlen (cid) == len) { - display_part = body_part; - break; - } - } - } else { - /* No start parameter, so it defaults to the first part. */ - display_part = camel_multipart_get_part (mp, 0); - } - - if (!display_part) { - /* Oops. Hrmph. */ - return handle_multipart_mixed (part, mime_type, md, stream); - } - - /* setup a 'stack' of related parts */ - related_save = md->related; - md->related = g_hash_table_new(NULL, NULL); - - location = camel_mime_part_get_content_location (part); - if (location) - mail_display_push_content_location (md, location); - - /* Record the Content-ID/Content-Location of any non-displayed parts. */ - for (i = 0; i < nparts; i++) { - body_part = camel_multipart_get_part (mp, i); - if (body_part == display_part) - continue; - - get_cid (body_part, md); - get_location (body_part, md); - g_hash_table_insert (md->related, body_part, body_part); - } - - /* Now, display the displayed part. */ - ret = format_mime_part (display_part, md, stream); - - /* FIXME: flush html stream via gtkhtml_stream_flush which doens't exist yet ... */ - while (gtk_events_pending ()) - gtk_main_iteration (); - - /* Now check for related parts which didn't display, display them as attachments */ - for (i = 0; i < nparts; i++) { - body_part = camel_multipart_get_part (mp, i); - if (body_part == display_part) - continue; - - if (g_hash_table_lookup (md->related, body_part)) { - if (ret) - write_hr (stream); - ret |= format_mime_part (body_part, md, stream); - } - } - - g_hash_table_destroy (md->related); - md->related = related_save; - - if (location) - mail_display_pop_content_location (md); - - return ret; -} - -/* RFC 2046 says "display the last part that you are able to display". */ -static CamelMimePart * -find_preferred_alternative (CamelMultipart *multipart, gboolean want_plain) -{ - int i, nparts; - CamelMimePart *preferred_part = NULL; - MailMimeHandler *handler; - - nparts = camel_multipart_get_number (multipart); - for (i = 0; i < nparts; i++) { - CamelMimePart *part = camel_multipart_get_part (multipart, i); - CamelContentType *type = camel_mime_part_get_content_type (part); - char *mime_type = header_content_type_simple (type); - - camel_strdown (mime_type); - if (want_plain && !strcmp (mime_type, "text/plain")) - return part; - handler = mail_lookup_handler (mime_type); - if (handler && (!preferred_part || !handler->generic)) - preferred_part = part; - g_free (mime_type); - } - - return preferred_part; -} - -static gboolean -handle_multipart_alternative (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream) -{ - CamelDataWrapper *wrapper = - camel_medium_get_content_object (CAMEL_MEDIUM (part)); - CamelMultipart *multipart; - CamelMimePart *mime_part; - - if (!CAMEL_IS_MULTIPART (wrapper)) { - mail_error_printf (stream, "\n%s\n", _("Could not parse MIME message. Displaying as source.")); - if (mail_content_loaded (wrapper, md, TRUE, NULL, md->html, NULL)) - handle_text_plain (part, "text/plain", md, stream); - return TRUE; - } - - multipart = CAMEL_MULTIPART (wrapper); - - mime_part = find_preferred_alternative (multipart, FALSE); - if (mime_part) - return format_mime_part (mime_part, md, stream); - else - return handle_multipart_mixed (part, mime_type, md, stream); -} - -/* RFC 1740 */ -static gboolean -handle_multipart_appledouble (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream) -{ - CamelDataWrapper *wrapper = - camel_medium_get_content_object (CAMEL_MEDIUM (part)); - CamelMultipart *multipart; - - if (!CAMEL_IS_MULTIPART (wrapper)) { - mail_error_printf (stream, "\n%s\n", _("Could not parse MIME message. Displaying as source.")); - if (mail_content_loaded (wrapper, md, TRUE, NULL, md->html, NULL)) - handle_text_plain (part, "text/plain", md, stream); - return TRUE; - } - - multipart = CAMEL_MULTIPART (wrapper); - - /* The first part is application/applefile and is not useful - * to us. The second part _may_ be displayable data. Most - * likely it's application/octet-stream though. - */ - part = camel_multipart_get_part (multipart, 1); - return format_mime_part (part, md, stream); -} - -static gboolean -handle_message_rfc822 (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream) -{ - CamelDataWrapper *wrapper = - camel_medium_get_content_object (CAMEL_MEDIUM (part)); - - g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (wrapper), FALSE); - - camel_stream_write_string ((CamelStream *) stream, "<blockquote>"); - mail_format_mime_message (CAMEL_MIME_MESSAGE (wrapper), md, stream); - camel_stream_write_string ((CamelStream *) stream, "</blockquote>"); - - return TRUE; -} - -static gboolean -handle_message_external_body (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream) -{ - CamelContentType *type; - const char *access_type; - char *url = NULL, *desc = NULL; - - type = camel_mime_part_get_content_type (part); - access_type = header_content_type_param (type, "access-type"); - if (!access_type) - goto fallback; - - if (!strcasecmp (access_type, "ftp") || - !strcasecmp (access_type, "anon-ftp")) { - const char *name, *site, *dir, *mode, *ftype; - char *path; - - name = header_content_type_param (type, "name"); - site = header_content_type_param (type, "site"); - if (name == NULL || site == NULL) - goto fallback; - dir = header_content_type_param (type, "directory"); - mode = header_content_type_param (type, "mode"); - - /* Generate the path. */ - if (dir) { - const char *p = dir + strlen (dir); - - path = g_strdup_printf ("%s%s%s%s", - *dir == '/' ? "" : "/", - dir, - *p == '/' ? "" : "/", - name); - } else { - path = g_strdup_printf ("%s%s", - *name == '/' ? "" : "/", - name); - } - - if (mode && *mode == 'A') - ftype = ";type=A"; - else if (mode && *mode == 'I') - ftype = ";type=I"; - else - ftype = ""; - - url = g_strdup_printf ("ftp://%s%s%s", site, path, ftype); - g_free (path); - desc = g_strdup_printf (_("Pointer to FTP site (%s)"), url); - } else if (!g_ascii_strcasecmp (access_type, "local-file")) { - const char *name, *site; - - name = header_content_type_param (type, "name"); - if (name == NULL) - goto fallback; - site = header_content_type_param (type, "site"); - - url = g_strdup_printf ("file://%s%s", *name == '/' ? "" : "/", name); - if (site) { - desc = g_strdup_printf(_("Pointer to local file (%s) " - "valid at site \"%s\""), name, site); - } else { - desc = g_strdup_printf(_("Pointer to local file (%s)"), name); - } - } else if (!strcasecmp (access_type, "URL")) { - const char *urlparam; - char *s, *d; - - /* RFC 2017 */ - - urlparam = header_content_type_param (type, "url"); - if (urlparam == NULL) - goto fallback; - - /* For obscure MIMEy reasons, the URL may be split into - * multiple words, and needs to be rejoined. (The URL - * must have any real whitespace %-encoded, so we just - * get rid of all of it. - */ - url = g_strdup (urlparam); - s = d = url; - - while (*s) { - if (!isspace ((unsigned char)*s)) - *d++ = *s; - s++; - } - *d = *s; - - desc = g_strdup_printf (_("Pointer to remote data (%s)"), url); - } - - fallback: - if (!desc) { - if (access_type) - desc = g_strdup_printf (_("Pointer to unknown external data (\"%s\" type)"), access_type); - else - desc = g_strdup (_("Malformed external-body part.")); - } - -#if 0 /* FIXME */ - handle_mystery (part, md, url, "gnome-globe.png", desc, - url ? "open it in a browser" : NULL); -#endif - - g_free (desc); - g_free (url); - - return TRUE; -} - -static gboolean -handle_via_bonobo (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream) -{ - if (!md->printing) { - camel_stream_printf ((CamelStream *) stream, - "<object classid=\"%s\" type=\"%s\"></object>", - get_cid (part, md), mime_type); - } - - return TRUE; -} - -/** - * mail_get_message_rfc822: - * @message: the message - * @want_plain: whether the caller prefers plain to html - * @cite: whether or not to cite the message text - * - * See mail_get_message_body() below for more details. - * - * Return value: an HTML string representing the text parts of @message. - **/ -static char * -mail_get_message_rfc822 (CamelMimeMessage *message, gboolean want_plain, gboolean cite) -{ - CamelDataWrapper *contents; - GString *retval; - const CamelInternetAddress *cia; - char *text, *citation, *buf, *html; - time_t date_val; - int offset; - - contents = camel_medium_get_content_object (CAMEL_MEDIUM (message)); - text = mail_get_message_body (contents, want_plain, cite); - if (!text) - text = g_strdup (""); - citation = cite ? "> " : ""; - retval = g_string_new (NULL); - - /* Kludge: if text starts with "<PRE>", wrap it around the - * headers too so we won't get a blank line between them for the - * <P> to <PRE> switch. - */ - if (!strncasecmp (text, "<pre>", 5)) - g_string_append_printf (retval, "<PRE>"); - - /* create credits */ - cia = camel_mime_message_get_from (message); - buf = camel_address_format (CAMEL_ADDRESS (cia)); - if (buf) { - html = camel_text_to_html (buf, CAMEL_MIME_FILTER_TOHTML_CONVERT_NL, 0); - g_string_append_printf (retval, "%s<b>From:</b> %s<br>", - citation, html); - g_free (html); - g_free (buf); - } - - cia = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO); - buf = camel_address_format (CAMEL_ADDRESS (cia)); - if (buf) { - html = camel_text_to_html (buf, CAMEL_MIME_FILTER_TOHTML_CONVERT_NL, 0); - g_string_append_printf (retval, "%s<b>To:</b> %s<br>", - citation, html); - g_free (html); - g_free (buf); - } - - cia = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC); - buf = camel_address_format (CAMEL_ADDRESS (cia)); - if (buf) { - html = camel_text_to_html (buf, CAMEL_MIME_FILTER_TOHTML_CONVERT_NL, 0); - g_string_append_printf (retval, "%s<b>Cc:</b> %s<br>", - citation, html); - g_free (html); - g_free (buf); - } - - buf = (char *) camel_mime_message_get_subject (message); - if (buf) { - html = camel_text_to_html (buf, CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | - CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); - g_string_append_printf (retval, "%s<b>Subject:</b> %s<br>", - citation, html); - g_free (html); - } - - date_val = camel_mime_message_get_date (message, &offset); - buf = header_format_date (date_val, offset); - html = camel_text_to_html (buf, CAMEL_MIME_FILTER_TOHTML_CONVERT_NL, 0); - g_string_append_printf (retval, "%s<b>Date:</b> %s<br>", citation, html); - g_free (html); - g_free (buf); - - if (!strncasecmp (text, "<pre>", 5)) - g_string_append_printf (retval, "%s<br>%s", citation, text + 5); - else - g_string_append_printf (retval, "%s<br>%s", citation, text); - g_free (text); - - buf = retval->str; - g_string_free (retval, FALSE); - - return buf; -} - -/** - * mail_get_message_body: - * @data: the message or mime part content - * @want_plain: whether the caller prefers plain to html - * @cite: whether or not to cite the message text - * - * This creates an HTML string representing @data. If @want_plain is %TRUE, - * it will be an HTML string that looks like a text/plain representation - * of @data (but it will still be HTML). - * - * If @cite is %TRUE, the message will be cited as a reply, using "> "s. - * - * Return value: the HTML string, which the caller must free, or - * %NULL if @data doesn't include any data which should be forwarded or - * replied to. - **/ -char * -mail_get_message_body (CamelDataWrapper *data, gboolean want_plain, gboolean cite) -{ - char *subtext, *old, *div, *text = NULL; - CamelContentType *mime_type; - CamelCipherContext *cipher; - GByteArray *bytes = NULL; - CamelMimePart *subpart; - CamelMultipart *mp; - int i, nparts; - - mime_type = camel_data_wrapper_get_mime_type_field (data); - - /* If it is message/rfc822 or message/news, extract the - * important headers and recursively process the body. - */ - if (header_content_type_is (mime_type, "message", "rfc822") || - header_content_type_is (mime_type, "message", "news")) - return mail_get_message_rfc822 (CAMEL_MIME_MESSAGE (data), want_plain, cite); - - /* If it's a vcard or icalendar, ignore it. */ - if (header_content_type_is (mime_type, "text", "x-vcard") || - header_content_type_is (mime_type, "text", "calendar")) - return NULL; - - /* Get the body data for other text/ or message/ types */ - if (header_content_type_is (mime_type, "text", "*") || - header_content_type_is (mime_type, "message", "*")) { - bytes = mail_format_get_data_wrapper_text (data, NULL); - - if (bytes) { - g_byte_array_append (bytes, "", 1); - text = bytes->data; - g_byte_array_free (bytes, FALSE); - } - - if (text && !header_content_type_is(mime_type, "text", "html")) { - char *html; - - if (header_content_type_is(mime_type, "text", "richtext")) - html = camel_enriched_to_html(text, CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT); - else if (header_content_type_is(mime_type, "text", "enriched")) - html = camel_enriched_to_html(text, 0); - else - html = camel_text_to_html (text, CAMEL_MIME_FILTER_TOHTML_PRE | - CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | - (cite ? CAMEL_MIME_FILTER_TOHTML_CITE : 0), 0); - g_free(text); - text = html; - } - return text; - } - - /* If it's not message and it's not text, and it's not - * multipart, we don't want to deal with it. - */ - if (!header_content_type_is (mime_type, "multipart", "*")) - return NULL; - - mp = CAMEL_MULTIPART (data); - - if (CAMEL_IS_MULTIPART_ENCRYPTED (mp)) { - cipher = camel_gpg_context_new (session); - subpart = camel_multipart_encrypted_decrypt (CAMEL_MULTIPART_ENCRYPTED (mp), - cipher, NULL); - if (!subpart) - return NULL; - - data = camel_medium_get_content_object (CAMEL_MEDIUM (subpart)); - return mail_get_message_body (data, want_plain, cite); - } else if (header_content_type_is (mime_type, "multipart", "alternative")) { - /* Pick our favorite alternative and reply to it. */ - - subpart = find_preferred_alternative (mp, want_plain); - if (!subpart) - return NULL; - - data = camel_medium_get_content_object (CAMEL_MEDIUM (subpart)); - return mail_get_message_body (data, want_plain, cite); - } - - /* Otherwise, concatenate all the parts that we can. */ - if (want_plain) { - if (cite) - div = "<br>\n> ----<br>\n> <br>\n"; - else - div = "<br>\n----<br>\n<br>\n"; - } else - div = "<br><hr><br>"; - - nparts = camel_multipart_get_number (mp); - for (i = 0; i < nparts; i++) { - subpart = camel_multipart_get_part (mp, i); - - /* only add to the body contents if it is marked as "inline" */ - if (!mail_part_is_inline (subpart)) - continue; - - data = camel_medium_get_content_object (CAMEL_MEDIUM (subpart)); - subtext = mail_get_message_body (data, want_plain, cite); - if (!subtext) - continue; - - if (text) { - old = text; - text = g_strdup_printf ("%s%s%s", old, div, subtext); - g_free (subtext); - g_free (old); - } else - text = subtext; - } - - return text; -} diff --git a/mail/mail-format.h b/mail/mail-format.h deleted file mode 100644 index e39148ca83..0000000000 --- a/mail/mail-format.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright 2003 Ximian, Inc. (www.ximian.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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 Street #330, Boston, MA 02111-1307, USA. - * - */ - - -#ifndef __MAIL_FORMAT_H__ -#define __MAIL_FORMAT_H__ - -#include <camel/camel.h> -#include <gtkhtml/gtkhtml.h> - -#include "mail-display.h" -#include "mail-display-stream.h" - -GByteArray *mail_format_get_data_wrapper_text (CamelDataWrapper *data, - MailDisplay *mail_display); - -ssize_t mail_format_data_wrapper_write_to_stream (CamelDataWrapper *wrapper, - gboolean decode, - MailDisplay *mail_display, - CamelStream *stream); - -void mail_format_mime_message (CamelMimeMessage *mime_message, - MailDisplay *md, MailDisplayStream *stream); -void mail_format_raw_message (CamelMimeMessage *mime_message, - MailDisplay *md, MailDisplayStream *stream); - -gboolean mail_content_loaded (CamelDataWrapper *wrapper, - MailDisplay *display, - gboolean redisplay, - const char *url, - GtkHTML *html, - GtkHTMLStream *handle); - -typedef gboolean (*MailMimeHandlerFn) (CamelMimePart *part, const char *mime_type, - MailDisplay *md, MailDisplayStream *stream); -typedef struct { - Bonobo_ServerInfo *component; - GList *applications; - MailMimeHandlerFn builtin; - guint generic : 1; - guint is_bonobo : 1; -} MailMimeHandler; - -MailMimeHandler *mail_lookup_handler (const char *mime_type); - -gboolean mail_part_is_inline (CamelMimePart *part); -gboolean mail_part_is_displayed_inline (CamelMimePart *part, MailDisplay *md); -void mail_part_toggle_displayed (CamelMimePart *part, MailDisplay *md); - -char *mail_get_message_body (CamelDataWrapper *data, gboolean want_plain, gboolean cite); - -#endif /* __MAIL_FORMAT_H__ */ diff --git a/mail/mail-identify.c b/mail/mail-identify.c deleted file mode 100644 index 2e3f517145..0000000000 --- a/mail/mail-identify.c +++ /dev/null @@ -1,130 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ - -/* - * Author : - * Dan Winship <danw@ximian.com> - * - * Copyright 2000, Ximian, Inc. (www.ximian.com) - * - * 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. - * - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <stdlib.h> -#include <string.h> - -#include <glib.h> -#include <libgnomevfs/gnome-vfs-utils.h> -#include <libgnomevfs/gnome-vfs-mime.h> -#include <libgnomevfs/gnome-vfs-mime-utils.h> -#include "mail.h" - -static const char *identify_by_magic (CamelDataWrapper *data, MailDisplay *md); - -/** - * mail_identify_mime_part: - * @part: a CamelMimePart - * @md: the MailDisplay @part is being shown in - * - * Try to identify the MIME type of the data in @part (which presumably - * doesn't have a useful Content-Type). - * - * Return value: the MIME type, which the caller must free, or %NULL - * if it could not be identified. - **/ -char * -mail_identify_mime_part (CamelMimePart *part, MailDisplay *md) -{ - const char *filename, *name_type = NULL, *magic_type = NULL; - CamelDataWrapper *data; - - filename = camel_mime_part_get_filename (part); - if (filename) { - /* GNOME-VFS will misidentify TNEF attachments as MPEG */ - if (!strcmp (filename, "winmail.dat")) - return g_strdup ("application/vnd.ms-tnef"); - - name_type = gnome_vfs_mime_type_from_name (filename); - } - - data = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - if (!camel_data_wrapper_is_offline (data)) - magic_type = identify_by_magic (data, md); - - if (magic_type && name_type) { - /* If GNOME-VFS doesn't recognize the data by magic, but it - * contains English words, it will call it text/plain. If the - * filename-based check came up with something different, use - * that instead. - */ - if (!strcmp (magic_type, "text/plain")) - return g_strdup (name_type); - - /* If if returns "application/octet-stream" try to - * do better with the filename check. - */ - if (!strcmp (magic_type, "application/octet-stream")) - return g_strdup (name_type); - } - - /* If the MIME part data was online, and the magic check - * returned something, use that, since it's more reliable. - */ - if (magic_type) - return g_strdup (magic_type); - - /* Otherwise try guessing based on the filename */ - if (name_type) - return g_strdup (name_type); - - /* Another possibility to try is the x-mac-type / x-mac-creator - * parameter to Content-Type used by some Mac email clients. That - * would require a Mac type to mime type conversion table. - */ - -#if 0 - /* If the data part is offline, then we didn't try magic - * before, so force it to be loaded so we can try again later. - * FIXME: In a perfect world, we would not load the content - * just to identify the MIME type. - */ - /* This is disabled as it just frustrates users more than it helps, - see discussion in bug #11778 */ - if (camel_data_wrapper_is_offline (data)) - mail_content_loaded (data, md, TRUE, NULL, NULL, NULL); -#endif - - return NULL; -} - -static const char * -identify_by_magic (CamelDataWrapper *data, MailDisplay *md) -{ - CamelStreamMem *memstream; - const char *type; - - memstream = (CamelStreamMem *)camel_stream_mem_new(); - if (camel_data_wrapper_write_to_stream (data, (CamelStream *)memstream) > 0) - type = gnome_vfs_get_mime_type_for_data(memstream->buffer->data, memstream->buffer->len); - else - type = NULL; - camel_object_unref(memstream); - - return type; -} diff --git a/mail/mail-local.c b/mail/mail-local.c index 26de1dbc2a..0ec4f5a8e9 100644 --- a/mail/mail-local.c +++ b/mail/mail-local.c @@ -558,13 +558,50 @@ mlf_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args) return 0; } +static gboolean +mlf_meta_set(CamelObject *obj, const char *name, const char *value) +{ + MailLocalFolder *mlf = MAIL_LOCAL_FOLDER(obj); + CamelFolder *f; + gboolean res; + + LOCAL_FOLDER_LOCK(mlf); + f = mlf->real_folder; + camel_object_ref(f); + LOCAL_FOLDER_UNLOCK(mlf); + + /* We must write this ourselves, since MailLocalFolder is not persistent itself */ + if ((res = camel_object_meta_set(f, name, value))) + camel_object_state_write(f); + camel_object_unref(f); + + return res; +} + +static char * +mlf_meta_get(CamelObject *obj, const char *name) +{ + MailLocalFolder *mlf = MAIL_LOCAL_FOLDER(obj); + CamelFolder *f; + char * res; + + LOCAL_FOLDER_LOCK(mlf); + f = mlf->real_folder; + camel_object_ref(f); + LOCAL_FOLDER_UNLOCK(mlf); + + res = camel_object_meta_get(f, name); + camel_object_unref(f); + + return res; +} + static void mlf_class_init (CamelObjectClass *camel_object_class) { CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS (camel_object_class); /* override all the functions subclassed in providers/local/ */ - camel_folder_class->refresh_info = mlf_refresh_info; camel_folder_class->sync = mlf_sync; camel_folder_class->expunge = mlf_expunge; @@ -581,6 +618,9 @@ mlf_class_init (CamelObjectClass *camel_object_class) camel_folder_class->rename = mlf_rename; camel_object_class->getv = mlf_getv; + + camel_object_class->meta_get = mlf_meta_get; + camel_object_class->meta_set = mlf_meta_set; } static void diff --git a/mail/mail-mt.c b/mail/mail-mt.c index 0cef1f2b30..66b8d68130 100644 --- a/mail/mail-mt.c +++ b/mail/mail-mt.c @@ -529,21 +529,28 @@ void mail_msg_cleanup(void) e_msgport_destroy(mail_gui_reply_port); } -void mail_msg_init(void) +static guint +em_channel_setup(EMsgPort **port, GIOChannel **channel, GIOFunc func) { - mail_gui_reply_port = e_msgport_new(); - mail_gui_reply_channel = g_io_channel_unix_new(e_msgport_fd(mail_gui_reply_port)); - g_io_add_watch(mail_gui_reply_channel, G_IO_IN, mail_msgport_replied, mail_gui_reply_port); + GSource *source; + guint id; - mail_gui_port = e_msgport_new(); - mail_gui_channel = g_io_channel_unix_new(e_msgport_fd(mail_gui_port)); - mail_gui_watch = g_io_add_watch(mail_gui_channel, G_IO_IN, mail_msgport_received, mail_gui_port); + *port = e_msgport_new(); + *channel = g_io_channel_unix_new(e_msgport_fd(*port)); + source = g_io_create_watch(*channel, G_IO_IN); + g_source_set_callback(source, (GSourceFunc)func, *port, NULL); + g_source_set_can_recurse(source, FALSE); + id = g_source_attach(source, NULL); + g_source_unref(source); - /* experimental temporary */ - mail_gui_port2 = e_msgport_new(); - mail_gui_channel2 = g_io_channel_unix_new(e_msgport_fd(mail_gui_port2)); - mail_gui_watch2 = g_io_add_watch(mail_gui_channel2, G_IO_IN, mail_msgport_received2, mail_gui_port2); + return id; +} +void mail_msg_init(void) +{ + em_channel_setup(&mail_gui_reply_port, &mail_gui_reply_channel, mail_msgport_replied); + mail_gui_watch = em_channel_setup(&mail_gui_port, &mail_gui_channel, mail_msgport_received); + mail_gui_watch2 = em_channel_setup(&mail_gui_port2, &mail_gui_channel2, mail_msgport_received2); mail_thread_queued = e_thread_new(E_THREAD_QUEUE); e_thread_set_msg_destroy(mail_thread_queued, mail_msg_destroy, 0); diff --git a/mail/mail-ops.c b/mail/mail-ops.c index 489dda4402..41fe74df55 100644 --- a/mail/mail-ops.c +++ b/mail/mail-ops.c @@ -43,13 +43,14 @@ #include "mail-vfolder.h" #include "mail-session.h" #include "composer/e-msg-composer.h" -#include "folder-browser.h" #include "filter/filter-filter.h" #include "mail-mt.h" #include "mail-folder-cache.h" +#include "em-utils.h" + #define w(x) #define d(x) @@ -150,17 +151,12 @@ static void filter_folder_free (struct _mail_msg *mm) { struct _filter_mail_msg *m = (struct _filter_mail_msg *)mm; - int i; if (m->source_folder) camel_object_unref (m->source_folder); - if (m->source_uids) { - for (i = 0; i < m->source_uids->len; i++) - g_free (m->source_uids->pdata[i]); - - g_ptr_array_free (m->source_uids, TRUE); - } + if (m->source_uids) + em_utils_uids_free (m->source_uids); if (m->cancel) camel_operation_unref (m->cancel); @@ -1001,14 +997,10 @@ static void transfer_messages_free (struct _mail_msg *mm) { struct _transfer_msg *m = (struct _transfer_msg *)mm; - int i; - + camel_object_unref (m->source); g_free (m->dest_uri); - for (i = 0; i < m->uids->len; i++) - g_free (m->uids->pdata[i]); - g_ptr_array_free (m->uids, TRUE); - + em_utils_uids_free (m->uids); } static struct _mail_msg_op transfer_messages_op = { @@ -1854,10 +1846,8 @@ static void get_messages_free(struct _mail_msg *mm) { struct _get_messages_msg *m = (struct _get_messages_msg *)mm; int i; - - for (i=0;i<m->uids->len;i++) - g_free(m->uids->pdata[i]); - g_ptr_array_free(m->uids, TRUE); + + em_utils_uids_free (m->uids); for (i=0;i<m->messages->len;i++) { if (m->messages->pdata[i]) camel_object_unref(m->messages->pdata[i]); @@ -2007,11 +1997,8 @@ static void save_messages_saved(struct _mail_msg *mm) static void save_messages_free(struct _mail_msg *mm) { struct _save_messages_msg *m = (struct _save_messages_msg *)mm; - int i; - - for (i=0;i<m->uids->len;i++) - g_free(m->uids->pdata[i]); - g_ptr_array_free(m->uids, TRUE); + + em_utils_uids_free (m->uids); camel_object_unref(m->folder); g_free(m->path); } diff --git a/mail/mail-ops.h b/mail/mail-ops.h index f81829db45..e15d7729c2 100644 --- a/mail/mail-ops.h +++ b/mail/mail-ops.h @@ -30,6 +30,7 @@ extern "C" { #pragma } #endif /* __cplusplus */ +#include "camel/camel-store.h" #include "camel/camel-folder.h" #include "camel/camel-filter-driver.h" #include "camel/camel-mime-message.h" diff --git a/mail/mail-search.c b/mail/mail-search.c deleted file mode 100644 index 424ccee991..0000000000 --- a/mail/mail-search.c +++ /dev/null @@ -1,403 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Jon Trowbridge <trow@ximian.com> - * - * Copyright 2001-2003 Ximian, Inc. (www.ximian.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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 Street #330, Boston, MA 02111-1307, USA. - * - */ - - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <string.h> - -#include "mail-search.h" -#include "e-searching-tokenizer.h" -#include <gtkhtml/gtkhtml-search.h> -#include <gtkhtml/htmlengine.h> -#include <libgnomeui/gnome-window-icon.h> -#include <gdk/gdkkeysyms.h> - - -static ESearchingTokenizer *mail_search_tokenizer (MailSearch *ms); -static void mail_search_redisplay_message (MailSearch *ms); - - -static GtkObjectClass *parent_class = NULL; - - -static void -mail_search_finalise (GObject *obj) -{ - MailSearch *ms = MAIL_SEARCH (obj); - - g_free (ms->last_search); - g_object_weak_unref ((GObject *) ms->mail, (GWeakNotify) gtk_widget_destroy, ms); - g_object_unref (ms->mail); - - G_OBJECT_CLASS (parent_class)->finalize (obj); -} - -static void -mail_search_destroy (GtkObject *obj) -{ - MailSearch *ms = (MailSearch *) obj; - ESearchingTokenizer *st = mail_search_tokenizer (ms); - - if (ms->begin_handler) { - g_signal_handler_disconnect (ms->mail->html->engine->ht, ms->begin_handler); - ms->begin_handler = 0; - g_signal_handler_disconnect (ms->mail->html->engine->ht, ms->match_handler); - ms->match_handler = 0; - - e_searching_tokenizer_set_primary_search_string (st, NULL); - mail_search_redisplay_message (ms); - } - - GTK_OBJECT_CLASS (parent_class)->destroy (obj); -} - -static void -mail_search_class_init (MailSearchClass *klass) -{ - GObjectClass *object_class = (GObjectClass *) klass; - GtkObjectClass *gtk_object_class = (GtkObjectClass *) klass; - - parent_class = (GtkObjectClass *) g_type_class_ref (GTK_TYPE_DIALOG); - - object_class->finalize = mail_search_finalise; - - gtk_object_class->destroy = mail_search_destroy; -} - -static void -mail_search_init (MailSearch *ms) -{ - -} - -GtkType -mail_search_get_type (void) -{ - static GType type = 0; - - if (!type) { - static const GTypeInfo info = { - sizeof (MailSearchClass), - NULL, NULL, - (GClassInitFunc) mail_search_class_init, - NULL, NULL, - sizeof (MailSearch), - 0, - (GInstanceInitFunc) mail_search_init, - }; - - type = g_type_register_static (GTK_TYPE_DIALOG, "MailSearch", &info, 0); - } - - return type; -} - -/* - * Convenience - */ - -static ESearchingTokenizer * -mail_search_tokenizer (MailSearch *ms) -{ - return E_SEARCHING_TOKENIZER (ms->mail->html->engine->ht); -} - -static void -mail_search_redisplay_message (MailSearch *ms) -{ - mail_display_redisplay (ms->mail, FALSE); -} - -static void -mail_search_set_subject (MailSearch *ms, const char *subject) -{ - char *utf8_subject = NULL; - - if (subject && *subject) { - utf8_subject = g_strdup (subject); - - if (g_utf8_validate (utf8_subject, -1, NULL)) { -#define ARBITRARY_CUTOFF 40 - if (g_utf8_strlen (utf8_subject, -1) > ARBITRARY_CUTOFF + 3) { - char *p = g_utf8_offset_to_pointer (utf8_subject, ARBITRARY_CUTOFF); - - strcpy (p, "..."); - } - } else { - /* If the subject contains bad utf8, don't show anything in the frame label. */ - g_free (utf8_subject); - utf8_subject = NULL; - } - } else { - utf8_subject = g_strdup (_("(Untitled Message)")); - } - - gtk_frame_set_label (GTK_FRAME (ms->msg_frame), utf8_subject); - - g_free (utf8_subject); -} - -/* - * Construct Objects - */ - -static void -toggled_case_cb (GtkToggleButton *b, MailSearch *ms) -{ - ms->case_sensitive = gtk_toggle_button_get_active (b); - - e_searching_tokenizer_set_primary_case_sensitivity (mail_search_tokenizer (ms), - ms->case_sensitive); - mail_search_redisplay_message (ms); - -} - -#if 0 -static void -toggled_fwd_cb (GtkToggleButton *b, MailSearch *ms) -{ - ms->search_forward = gtk_toggle_button_get_active (b); - gtk_html_engine_search_set_forward (ms->mail->html, ms->search_forward); -} -#endif - -static void -dialog_response_cb (GtkWidget *widget, int button, MailSearch *ms) -{ - ESearchingTokenizer *st = mail_search_tokenizer (ms); - - if (button == GTK_RESPONSE_ACCEPT) { - char *search_text; - - search_text = gtk_editable_get_chars (GTK_EDITABLE (ms->entry), 0, -1); - g_strstrip (search_text); - - if (search_text && *search_text) { - if (ms->last_search && !strcmp (ms->last_search, search_text)) { - - if (!gtk_html_engine_search_next (ms->mail->html)) { - g_free (ms->last_search); - ms->last_search = NULL; - } - } else { - g_free (ms->last_search); - ms->last_search = NULL; - - e_searching_tokenizer_set_primary_search_string (st, search_text); - e_searching_tokenizer_set_primary_case_sensitivity (st, ms->case_sensitive); - - mail_search_redisplay_message (ms); - - if (gtk_html_engine_search (ms->mail->html, search_text, - ms->case_sensitive, ms->search_forward, - FALSE)) { - ms->last_search = g_strdup (search_text); - } - } - } - - g_free (search_text); - } else if (button == GTK_RESPONSE_CLOSE) { - gtk_widget_destroy (widget); - } -} - -static void -begin_cb (ESearchingTokenizer *st, char *foo, MailSearch *ms) -{ - const char *subject; - - if (ms && ms->mail && ms->mail->current_message) { - subject = ms->mail->current_message->subject; - - if (subject == NULL) - subject = _("Untitled Message"); - } else { - subject = _("Empty Message"); - } - - gtk_label_set_text (GTK_LABEL (ms->count_label), "0"); - mail_search_set_subject (ms, subject); -} - -static void -match_cb (ESearchingTokenizer *st, MailSearch *ms) -{ - char buf[16]; - - g_snprintf (buf, 16, "%d", e_searching_tokenizer_match_count (st)); - gtk_label_set_text (GTK_LABEL (ms->count_label), buf); -} - -static void -entry_run_search (GtkWidget *w, MailSearch *ms) -{ - /* run search when enter pressed on widget */ - gtk_dialog_response ((GtkDialog *) ms, GTK_RESPONSE_ACCEPT); -} - -void -mail_search_construct (MailSearch *ms, MailDisplay *mail) -{ - GtkWidget *find_hbox; - GtkWidget *matches_hbox; - GtkWidget *toggles_hbox; - GtkWidget *frame_vbox; - GtkWidget *entry; - GtkWidget *count_label; - GtkWidget *case_check; -#if 0 - GtkWidget *fwd_check; -#endif - GtkWidget *button; - GtkWidget *msg_hbox; - GtkWidget *msg_frame; - GtkAccelGroup *accel_group; - - g_return_if_fail (ms != NULL && IS_MAIL_SEARCH (ms)); - g_return_if_fail (mail != NULL && IS_MAIL_DISPLAY (mail)); - - /* Basic set-up */ - - ms->mail = mail; - g_object_ref (mail); - - gtk_window_set_title ((GtkWindow *) ms, _("Find in Message")); - - button = gtk_dialog_add_button ((GtkDialog *) ms, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE); - gtk_dialog_set_default_response ((GtkDialog *) ms, GTK_RESPONSE_ACCEPT); - accel_group = gtk_accel_group_new (); - gtk_window_add_accel_group (GTK_WINDOW (ms), accel_group); - gtk_widget_add_accelerator (button, "activate", accel_group, GDK_Escape, 0, GTK_ACCEL_LOCKED); - - gtk_dialog_add_button ((GtkDialog *) ms, GTK_STOCK_FIND, GTK_RESPONSE_ACCEPT); - - ms->search_forward = TRUE; - ms->case_sensitive = FALSE; - - ms->begin_handler = g_signal_connect (ms->mail->html->engine->ht, "begin", - G_CALLBACK (begin_cb), ms); - ms->match_handler = g_signal_connect (ms->mail->html->engine->ht, "match", - G_CALLBACK (match_cb), ms); - - /* Construct the dialog contents. */ - - msg_hbox = gtk_hbox_new (FALSE, 3); - find_hbox = gtk_hbox_new (FALSE, 3); - matches_hbox = gtk_hbox_new (FALSE, 3); - toggles_hbox = gtk_hbox_new (FALSE, 3); - frame_vbox = gtk_vbox_new (FALSE, 3); - gtk_container_set_border_width ((GtkContainer *) frame_vbox, 3); - - entry = gtk_entry_new (); - count_label = gtk_label_new ("0"); - - msg_frame = gtk_frame_new (NULL); - gtk_container_set_border_width ((GtkContainer *) msg_frame, 6); - - case_check = gtk_check_button_new_with_label (_("Case Sensitive")); -#if 0 - fwd_check = gtk_check_button_new_with_label (_("Search Forward")); -#endif - - ms->entry = entry; - ms->count_label = count_label; - - ms->msg_frame = msg_frame; - - if (mail->current_message->subject && *mail->current_message->subject) - mail_search_set_subject (ms, mail->current_message->subject); - else - mail_search_set_subject (ms, NULL); - -#if 0 - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fwd_check), ms->search_forward); -#endif - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (case_check), ms->case_sensitive); - - gtk_box_pack_start (GTK_BOX (msg_hbox), GTK_WIDGET (msg_frame), TRUE, TRUE, 0); - - gtk_box_pack_start (GTK_BOX (find_hbox), gtk_label_new (_("Find:")), FALSE, FALSE, 3); - gtk_box_pack_start (GTK_BOX (find_hbox), entry, TRUE, TRUE, 3); - - gtk_box_pack_start (GTK_BOX (matches_hbox), gtk_hbox_new (FALSE, 0), TRUE, TRUE, 0); - gtk_box_pack_start (GTK_BOX (matches_hbox), gtk_label_new (_("Matches:")), FALSE, FALSE, 3); - gtk_box_pack_start (GTK_BOX (matches_hbox), count_label, FALSE, FALSE, 3); - gtk_box_pack_start (GTK_BOX (matches_hbox), gtk_hbox_new (FALSE, 0), TRUE, TRUE, 0); - - gtk_box_pack_start (GTK_BOX (toggles_hbox), case_check, FALSE, FALSE, 3); - - /* - * Disabling the forward/backward search button because there are problems with it - * (related to how gtkhtml handles searches), the GUI freeze is upon us, and I - * don't know if they'll get resolved for 1.0. Hopefully getting this fixed can - * be a 1.1 item. - */ - -#if 0 - gtk_box_pack_start (GTK_BOX (toggles_hbox), fwd_check, FALSE, FALSE, 3); -#endif - - gtk_box_pack_start (GTK_BOX (frame_vbox), find_hbox, FALSE, FALSE, 3); - gtk_box_pack_start (GTK_BOX (frame_vbox), matches_hbox, FALSE, FALSE, 3); - gtk_box_pack_start (GTK_BOX (frame_vbox), toggles_hbox, FALSE, FALSE, 3); - - gtk_container_add (GTK_CONTAINER (msg_frame), GTK_WIDGET (frame_vbox)); - - gtk_box_pack_start (GTK_BOX (GTK_DIALOG (ms)->vbox), msg_hbox, TRUE, TRUE, 0); - - gtk_widget_grab_focus (entry); /* Give focus to entry by default */ - g_signal_connect (entry, "activate", G_CALLBACK (entry_run_search), ms); - gnome_window_icon_set_from_file (GTK_WINDOW (ms), EVOLUTION_ICONSDIR "/find-message.xpm"); - - gtk_widget_show_all (msg_hbox); - gtk_widget_show_all (find_hbox); - gtk_widget_show_all (matches_hbox); - gtk_widget_show_all (toggles_hbox); - - /* Hook up signals */ - - g_signal_connect (case_check, "toggled", G_CALLBACK (toggled_case_cb), ms); -#if 0 - g_signal_connect (fwd_check, "toggled", G_CALLBACK (toggled_fwd_cb), ms); -#endif - g_signal_connect (ms, "response", G_CALLBACK (dialog_response_cb), ms); - - g_object_weak_ref ((GObject *) ms->mail, (GWeakNotify) gtk_widget_destroy, ms); -} - -GtkWidget * -mail_search_new (MailDisplay *mail) -{ - GtkWidget *widget; - - g_return_val_if_fail (IS_MAIL_DISPLAY (mail), NULL); - - widget = g_object_new (mail_search_get_type (), NULL); - mail_search_construct (MAIL_SEARCH (widget), mail); - - return widget; -} - diff --git a/mail/mail-search.glade b/mail/mail-search.glade new file mode 100644 index 0000000000..10b84e99a2 --- /dev/null +++ b/mail/mail-search.glade @@ -0,0 +1,197 @@ +<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> +<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> + +<glade-interface> + +<widget class="GtkDialog" id="search_message_dialog"> + <property name="visible">True</property> + <property name="title" translatable="yes">Find in Message</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_CENTER</property> + <property name="modal">False</property> + <property name="resizable">False</property> + <property name="destroy_with_parent">False</property> + <property name="has_separator">True</property> + + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + + <child> + <widget class="GtkButton" id="button1"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-cancel</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="response_id">-6</property> + </widget> + </child> + + <child> + <widget class="GtkButton" id="button2"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-find</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="response_id">-3</property> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + + <child> + <widget class="GtkFrame" id="frame1"> + <property name="border_width">6</property> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="label_yalign">0.5</property> + <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property> + + <child> + <widget class="GtkVBox" id="vbox1"> + <property name="border_width">6</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child> + <widget class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child> + <widget class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes">Find:</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="search_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char" translatable="yes">*</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="search_matches_label"> + <property name="visible">True</property> + <property name="label" translatable="yes"></property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkCheckButton" id="search_case_check"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Case Sensitive</property> + <property name="use_underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + </child> + + <child> + <widget class="GtkLabel" id="search_subject_label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Search</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + </child> +</widget> + +</glade-interface> diff --git a/mail/mail-search.h b/mail/mail-search.h deleted file mode 100644 index cbcf563636..0000000000 --- a/mail/mail-search.h +++ /dev/null @@ -1,75 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Jon Trowbridge <trow@ximian.com> - * - * Copyright 2001-2003 Ximian, Inc. (www.ximian.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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 Street #330, Boston, MA 02111-1307, USA. - * - */ - - -#ifndef _MAIL_SEARCH_H_ -#define _MAIL_SEARCH_H_ - -#ifdef _cplusplus -extern "C" { -#pragma } -#endif /* _cplusplus */ - -#include <gtk/gtkdialog.h> -#include "mail-display.h" - -#define MAIL_SEARCH_TYPE (mail_search_get_type ()) -#define MAIL_SEARCH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MAIL_SEARCH_TYPE, MailSearch)) -#define MAIL_SEARCH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), MAIL_SEARCH_TYPE, MailSearch)) -#define IS_MAIL_SEARCH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MAIL_SEARCH_TYPE)) -#define IS_MAIL_SEARCH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MAIL_SEARCH_TYPE)) - -typedef struct _MailSearch MailSearch; -typedef struct _MailSearchClass MailSearchClass; - -struct _MailSearch { - GtkDialog parent; - - MailDisplay *mail; - - GtkWidget *entry; - GtkWidget *msg_frame; - GtkWidget *count_label; - - gboolean search_forward, case_sensitive; - char *last_search; - - guint begin_handler; - guint match_handler; -}; - -struct _MailSearchClass { - GtkDialogClass parent_class; - -}; - -GtkType mail_search_get_type (void); - -void mail_search_construct (MailSearch *, MailDisplay *); -GtkWidget *mail_search_new (MailDisplay *); - - -#ifdef _cplusplus -} -#endif /* _cplusplus */ - -#endif /* _MAIL_SEARCH_H_ */ diff --git a/mail/mail-send-recv.c b/mail/mail-send-recv.c index 469f0494c2..890434daa5 100644 --- a/mail/mail-send-recv.c +++ b/mail/mail-send-recv.c @@ -36,6 +36,7 @@ #include "e-util/e-gtk-utils.h" +#include "widgets/misc/e-clipped-label.h" #include "filter/filter-filter.h" #include "camel/camel-filter-driver.h" #include "camel/camel-folder.h" @@ -110,7 +111,7 @@ struct _send_info { send_state_t state; GtkProgressBar *bar; GtkButton *stop; - GtkLabel *status; + EClippedLabel *status; int timeout_id; char *what; @@ -143,7 +144,7 @@ receive_cancel(GtkButton *button, struct _send_info *info) if (info->state == SEND_ACTIVE) { camel_operation_cancel(info->cancel); if (info->status) - gtk_label_set_text(info->status, _("Cancelling...")); + e_clipped_label_set_text(info->status, _("Cancelling...")); info->state = SEND_CANCELLED; } if (info->stop) @@ -294,7 +295,8 @@ build_dialogue (EAccountList *accounts, CamelFolder *outbox, const char *destina GList *list = NULL; struct _send_data *data; GtkWidget *send_icon, *recv_icon; - GtkLabel *label, *status_label; + GtkLabel *label; + EClippedLabel *status_label; GtkProgressBar *bar; GtkButton *stop; GtkHSeparator *line; @@ -386,9 +388,9 @@ build_dialogue (EAccountList *accounts, CamelFolder *outbox, const char *destina stop = (GtkButton *)e_gtk_button_new_with_icon(_("Cancel"), GTK_STOCK_CANCEL); - status_label = (GtkLabel *)gtk_label_new ((info->type == SEND_UPDATE) ? _("Updating...") : - _("Waiting...")); - + status_label = (EClippedLabel *)e_clipped_label_new((info->type == SEND_UPDATE)?_("Updating..."):_("Waiting..."), + PANGO_WEIGHT_NORMAL, 1.0); + /* g_object_set(data->label, "bold", TRUE, NULL); */ gtk_misc_set_alignment (GTK_MISC (label), 0, .5); gtk_misc_set_alignment (GTK_MISC (status_label), 0, .5); @@ -443,8 +445,8 @@ build_dialogue (EAccountList *accounts, CamelFolder *outbox, const char *destina bar = (GtkProgressBar *)gtk_progress_bar_new (); stop = (GtkButton *)e_gtk_button_new_with_icon(_("Cancel"), GTK_STOCK_CANCEL); - status_label = (GtkLabel *)gtk_label_new (_("Waiting...")); - + status_label = (EClippedLabel *)e_clipped_label_new(_("Waiting..."), PANGO_WEIGHT_NORMAL, 1.0); + gtk_misc_set_alignment (GTK_MISC (label), 0, .5); gtk_misc_set_alignment (GTK_MISC (status_label), 0, .5); @@ -538,7 +540,8 @@ static int operation_status_timeout(void *data) if (info->bar) { gtk_progress_bar_set_fraction((GtkProgressBar *)info->bar, (gfloat)(info->pc/100.0)); - gtk_label_set_text(info->status, info->what); + if (info->what) + e_clipped_label_set_text(info->status, info->what); return TRUE; } @@ -574,11 +577,11 @@ receive_done (char *uri, void *data) switch(info->state) { case SEND_CANCELLED: - gtk_label_set_text(info->status, _("Cancelled.")); + e_clipped_label_set_text(info->status, _("Cancelled.")); break; default: info->state = SEND_COMPLETE; - gtk_label_set_text(info->status, _("Complete.")); + e_clipped_label_set_text(info->status, _("Complete")); } } diff --git a/mail/mail-tools.c b/mail/mail-tools.c index 758a4b512b..99fd749d78 100644 --- a/mail/mail-tools.c +++ b/mail/mail-tools.c @@ -48,12 +48,12 @@ #include "mail.h" /*session*/ #include "mail-config.h" #include "mail-vfolder.h" -#include "mail-format.h" #include "mail-tools.h" #include "mail-local.h" #include "mail-mt.h" #include "mail-folder-cache.h" + /* **************************************** */ CamelFolder * @@ -345,135 +345,6 @@ mail_tool_uri_to_folder (const char *uri, guint32 flags, CamelException *ex) return folder; } -/** - * mail_tool_quote_message: - * @message: mime message to quote - * @fmt: credits format - example: "On %s, %s wrote:\n" - * @Varargs: arguments - * - * Returns an allocated buffer containing the quoted message. - */ -gchar * -mail_tool_quote_message (CamelMimeMessage *message, const char *fmt, ...) -{ - CamelDataWrapper *contents; - gboolean want_plain; - char *text, *colour; - GConfClient *gconf; - - gconf = mail_config_get_gconf_client (); - - contents = camel_medium_get_content_object (CAMEL_MEDIUM (message)); - /* We pass "want_plain" for "cite", since if it's HTML, we'll - * do the citing ourself below. - */ - /* FIXME the citing logic has changed and we basically never want_plain - * to be true now, but I don't want to remove all that logic until I - * am sure --Larry - */ - want_plain = FALSE; - text = mail_get_message_body (contents, want_plain, FALSE); - - /* Set the quoted reply text. */ - if (text) { - char *sig, *p, *ret_text, *credits = NULL; - - /* look for the signature and strip it off */ - sig = text; - while ((p = strstr (sig, "\n-- \n"))) - sig = p + 1; - - if (sig != text) - *sig = '\0'; - - /* create credits */ - if (fmt) { - va_list ap; - - va_start (ap, fmt); - credits = g_strdup_vprintf (fmt, ap); - va_end (ap); - } - - colour = gconf_client_get_string (gconf, "/apps/evolution/mail/display/citation_colour", NULL); - - ret_text = g_strdup_printf ("%s<!--+GtkHTML:<DATA class=\"ClueFlow\" key=\"orig\" value=\"1\">-->" - "<font color=\"%s\">\n%s%s%s</font>" - "<!--+GtkHTML:<DATA class=\"ClueFlow\" clear=\"orig\">-->", - credits ? credits : "", - colour ? colour : "#737373", - want_plain ? "" : "<blockquote type=cite><i>", - text, - want_plain ? "" : "</i></blockquote>"); - - g_free (text); - g_free (colour); - g_free (credits); - - return ret_text; - } - - return NULL; -} - - -/** - * mail_tool_forward_message: - * @message: mime message to forward - * @quoted: whether to forwarded it quoted (%TRUE) or inline (%FALSE) - * - * Returns an allocated buffer containing the forwarded message. - */ -gchar * -mail_tool_forward_message (CamelMimeMessage *message, gboolean quoted) -{ - GConfClient *gconf; - char *text; - - gconf = mail_config_get_gconf_client (); - - text = mail_get_message_body (CAMEL_DATA_WRAPPER (message), FALSE, FALSE); - - if (text != NULL) { - char *sig, *p, *ret_text; - - /* FIXME: this code should be merged with the quote_message() code above somehow... */ - - /* look for the signature and strip it off */ - sig = text; - while ((p = strstr (sig, "\n-- \n"))) - sig = p + 1; - - if (sig != text) - *sig = '\0'; - - if (quoted) { - char *colour; - - colour = gconf_client_get_string (gconf, "/apps/evolution/mail/display/citation_colour", NULL); - - ret_text = g_strdup_printf ("-----%s-----<br>" - "<!--+GtkHTML:<DATA class=\"ClueFlow\" key=\"orig\" value=\"1\">-->" - "<font color=\"%s\">\n%s%s%s</font>" - "<!--+GtkHTML:<DATA class=\"ClueFlow\" clear=\"orig\">-->", - _("Forwarded Message"), - colour ? colour : "#737373", - "<blockquote type=cite><i>", text, - "</i></blockquote>"); - - g_free (colour); - } else { - ret_text = g_strdup_printf ("-----%s-----<br>%s", _("Forwarded Message"), text ? text : ""); - } - - g_free (text); - - return ret_text; - } - - return NULL; -} - /** * mail_tools_x_evolution_message_parse: diff --git a/mail/mail-tools.h b/mail/mail-tools.h index eaca223a2d..2f4b61123c 100644 --- a/mail/mail-tools.h +++ b/mail/mail-tools.h @@ -65,10 +65,6 @@ CamelFolder *mail_tool_uri_to_folder (const char *uri, guint32 flags, CamelExcep GHashTable *mail_lookup_url_table (CamelMimeMessage *mime_message); -gchar *mail_tool_quote_message (CamelMimeMessage *message, const char *fmt, ...); - -gchar *mail_tool_forward_message (CamelMimeMessage *message, gboolean quoted); - CamelFolder *mail_tools_x_evolution_message_parse (char *in, unsigned int inlen, GPtrArray **uids); char *mail_tools_folder_to_url (CamelFolder *folder); diff --git a/mail/mail-vfolder.c b/mail/mail-vfolder.c index 05eaa174c6..001a0ae57e 100644 --- a/mail/mail-vfolder.c +++ b/mail/mail-vfolder.c @@ -32,7 +32,6 @@ #include "evolution-storage.h" #include "evolution-shell-component.h" -#include "folder-browser.h" #include "mail-vfolder.h" #include "mail-tools.h" #include "mail-autofilter.h" diff --git a/mail/mail.h b/mail/mail.h index 1c40b52107..81832cd2a5 100644 --- a/mail/mail.h +++ b/mail/mail.h @@ -26,10 +26,8 @@ #include <shell/evolution-storage.h> #include "mail-accounts.h" #include "mail-account-editor.h" -#include "mail-callbacks.h" #include "mail-config.h" #include "mail-config-druid.h" -#include "mail-display-stream.h" #include "mail-session.h" #include "mail-types.h" diff --git a/mail/message-browser.c b/mail/message-browser.c deleted file mode 100644 index 9949f0ff0c..0000000000 --- a/mail/message-browser.c +++ /dev/null @@ -1,402 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright 2001 Ximian, Inc. (www.ximian.com) - * - * 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. - * - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <gconf/gconf.h> -#include <gconf/gconf-client.h> - -#include <gal/util/e-util.h> - -#include <bonobo/bonobo-exception.h> -#include <bonobo/bonobo-ui-component.h> -#include <bonobo/bonobo-ui-container.h> -#include <bonobo/bonobo-ui-util.h> - -#include "message-browser.h" - -#include "mail.h" -#include "mail-callbacks.h" -#include "mail-tools.h" -#include "message-list.h" -#include "mail-ops.h" -#include "mail-vfolder.h" -#include "mail-autofilter.h" -#include "mail-mt.h" - -#include "mail-local.h" -#include "mail-config.h" - -#include "folder-browser-ui.h" - -#define d(x) - -#define MINIMUM_WIDTH 600 -#define MINIMUM_HEIGHT 400 - -#define PARENT_TYPE BONOBO_TYPE_WINDOW - -/* Size of the window last time it was changed. */ -static GtkAllocation last_allocation = { 0, 0 }; - -static BonoboWindowClass *message_browser_parent_class; - -static void -message_browser_destroy (GtkObject *object) -{ - MessageBrowser *message_browser; - - message_browser = MESSAGE_BROWSER (object); - - if (message_browser->ml_built_id) { - g_signal_handler_disconnect (message_browser->fb->message_list, message_browser->ml_built_id); - message_browser->ml_built_id = 0; - } - - if (message_browser->loaded_id) { - g_signal_handler_disconnect (message_browser->fb, message_browser->loaded_id); - message_browser->loaded_id = 0; - } - - if (message_browser->fb) { - g_object_unref (message_browser->fb); - message_browser->fb = NULL; - } - - if (GTK_OBJECT_CLASS (message_browser_parent_class)->destroy) - (GTK_OBJECT_CLASS (message_browser_parent_class)->destroy) (object); -} - -static void -message_browser_class_init (GObjectClass *object_class) -{ - ((GtkObjectClass *)object_class)->destroy = message_browser_destroy; - - message_browser_parent_class = g_type_class_ref (PARENT_TYPE); -} - -static void -message_browser_init (GtkObject *object) -{ - -} - -static void -transfer_msg_done (gboolean ok, void *data) -{ - MessageBrowser *mb = data; - - gtk_widget_destroy ((GtkWidget *) mb); - - g_object_unref (mb); -} - -static void -transfer_msg (MessageBrowser *mb, int del) -{ - const char *allowed_types[] = { "mail/*", "vtrash", NULL }; - extern EvolutionShellClient *global_shell_client; - GNOME_Evolution_Folder *folder; - static char *last_uri = NULL; - GPtrArray *uids; - char *desc; - -/* if (GTK_OBJECT_DESTROYED(mb)) - return;*/ - - if (last_uri == NULL) - last_uri = g_strdup (""); - - if (del) - desc = _("Move message(s) to"); - else - desc = _("Copy message(s) to"); - - evolution_shell_client_user_select_folder (global_shell_client, - GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (mb))), - desc, last_uri, allowed_types, &folder); - if (!folder) - return; - - if (strcmp (last_uri, folder->evolutionUri) != 0) { - g_free (last_uri); - last_uri = g_strdup (folder->evolutionUri); - } - - uids = g_ptr_array_new (); - message_list_foreach (mb->fb->message_list, enumerate_msg, uids); - - if (del) { - g_object_ref (mb); - mail_transfer_messages (mb->fb->folder, uids, del, - folder->physicalUri, 0, transfer_msg_done, mb); - } else { - mail_transfer_messages (mb->fb->folder, uids, del, - folder->physicalUri, 0, NULL, NULL); - } - - CORBA_free (folder); -} - - -/* UI callbacks */ - -static void -message_browser_close (BonoboUIComponent *uih, void *user_data, const char *path) -{ - gtk_widget_destroy (GTK_WIDGET (user_data)); -} - -static void -message_browser_move (BonoboUIComponent *uih, void *user_data, const char *path) -{ - transfer_msg (user_data, TRUE); -} - -static void -message_browser_copy (BonoboUIComponent *uih, void *user_data, const char *path) -{ - transfer_msg (user_data, FALSE); -} - -static void -message_browser_delete (BonoboUIComponent *uih, void *user_data, const char *path) -{ - MessageBrowser *mb = user_data; - GPtrArray *uids; - int i; - - uids = g_ptr_array_new (); - message_list_foreach (mb->fb->message_list, enumerate_msg, uids); - camel_folder_freeze (mb->fb->folder); - for (i = 0; i < uids->len; i++) { - camel_folder_set_message_flags (mb->fb->folder, uids->pdata[i], - CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, - CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN); - g_free (uids->pdata[i]); - } - - camel_folder_thaw (mb->fb->folder); - - g_ptr_array_free (uids, TRUE); - - gtk_widget_destroy ((GtkWidget *) mb); -} - -static BonoboUIVerb -browser_verbs [] = { - BONOBO_UI_UNSAFE_VERB ("MessageBrowserClose", message_browser_close), - BONOBO_UI_UNSAFE_VERB ("MessageMove", message_browser_move), - BONOBO_UI_UNSAFE_VERB ("MessageCopy", message_browser_copy), - BONOBO_UI_UNSAFE_VERB ("MessageDelete", message_browser_delete), - BONOBO_UI_VERB_END -}; - -/* FB message loading hookups */ - -static void -message_browser_message_loaded (FolderBrowser *fb, const char *uid, MessageBrowser *mb) -{ - CamelMimeMessage *message; - char *subject = NULL; - char *title; - - folder_browser_ui_message_loaded(fb); - - message = fb->mail_display->current_message; - - if (message) - subject = (char *) camel_mime_message_get_subject (message); - - if (subject == NULL) - subject = _("(No subject)"); - - title = g_strdup_printf (_("%s - Message"), subject); - - gtk_window_set_title (GTK_WINDOW (mb), title); - - g_free (title); -} - -static void -message_browser_message_list_built (MessageList *ml, MessageBrowser *mb) -{ - const char *uid = g_object_get_data (G_OBJECT (mb), "uid"); - - g_signal_handler_disconnect (ml, mb->ml_built_id); - mb->ml_built_id = 0; - - message_list_select_uid (ml, uid); -} - -static void -message_browser_folder_loaded (FolderBrowser *fb, const char *uri, MessageBrowser *mb) -{ - g_signal_handler_disconnect (fb, mb->loaded_id); - mb->loaded_id = 0; - - mb->ml_built_id = g_signal_connect (fb->message_list, "message_list_built", - G_CALLBACK (message_browser_message_list_built), mb); -} - -static void -message_browser_size_allocate_cb (GtkWidget *widget, - GtkAllocation *allocation) -{ - last_allocation = *allocation; -} - -/* Construction */ - -static void -set_default_size (GtkWidget *widget) -{ - int width, height; - - width = MAX (MINIMUM_WIDTH, last_allocation.width); - height = MAX (MINIMUM_HEIGHT, last_allocation.height); - - gtk_window_set_default_size (GTK_WINDOW (widget), width, height); -} - -static void -set_bonobo_ui (GtkWidget *widget, FolderBrowser *fb) -{ - BonoboUIContainer *uicont; - BonoboUIComponent *uic; - CORBA_Environment ev; - - uicont = bonobo_window_get_ui_container (BONOBO_WINDOW (widget)); - - uic = bonobo_ui_component_new_default (); - bonobo_ui_component_set_container (uic, BONOBO_OBJREF (uicont), NULL); - folder_browser_set_ui_component (fb, uic); - - /* Load our UI */ - - /*bonobo_ui_component_freeze (uic, NULL);*/ - bonobo_ui_util_set_ui (uic, PREFIX, - EVOLUTION_UIDIR "/evolution-mail-messagedisplay.xml", - "evolution-mail", NULL); - - /* Load the appropriate UI stuff from the folder browser */ - - folder_browser_ui_add_message (fb); - - /* We just opened the message! We don't need to open it again. */ - - CORBA_exception_init (&ev); - /* remove the broken menus and toolbar items */ - bonobo_ui_component_rm (uic, "/menu/File/FileOps/MessageOpen", &ev); - bonobo_ui_component_rm (uic, "/menu/Actions/ComponentActionsPlaceholder/MailMessageActions/GoTo", &ev); - bonobo_ui_component_rm (uic, "/menu/Tools", &ev); - bonobo_ui_component_rm (uic, "/Toolbar/MailNextButtons", &ev); - CORBA_exception_free (&ev); - - /* Hack around the move/copy/delete commands api's */ - bonobo_ui_component_remove_listener (uic, "MessageCopy"); - bonobo_ui_component_remove_listener (uic, "MessageMove"); - bonobo_ui_component_remove_listener (uic, "MessageDelete"); - - /* Add the Close & Move/Copy/Delete items */ - - bonobo_ui_component_add_verb_list_with_data (uic, browser_verbs, widget); - - /* Done */ - - /*bonobo_ui_component_thaw (uic, NULL);*/ -} - -static int -on_key_press (GtkWidget *widget, GdkEventKey *key, gpointer data) -{ - MessageBrowser *mb = data; - - if (key->state & GDK_CONTROL_MASK) - return FALSE; - - switch (key->keyval) { - case GDK_Delete: - case GDK_KP_Delete: - message_browser_delete (NULL, mb, NULL); - return TRUE; - case GDK_Escape: - message_browser_close (NULL, mb, NULL); - return TRUE; - default: - } - - return FALSE; -} - -GtkWidget * -message_browser_new (const GNOME_Evolution_Shell shell, const char *uri, const char *uid) -{ - GtkWidget *vbox; - MessageBrowser *new; - FolderBrowser *fb; - - new = g_object_new (MESSAGE_BROWSER_TYPE, "title", "Ximian Evolution", NULL); - - g_object_set_data_full (G_OBJECT (new), "uid", g_strdup (uid), g_free); - - fb = FOLDER_BROWSER (folder_browser_new (shell, uri)); - g_object_ref (fb); - gtk_object_sink ((GtkObject *) fb); - - new->fb = fb; - - set_bonobo_ui (GTK_WIDGET (new), fb); - - /* some evil hackery action... */ - vbox = gtk_vbox_new (TRUE, 0); - gtk_widget_ref (GTK_WIDGET (fb->mail_display)); - gtk_widget_reparent (GTK_WIDGET (fb->mail_display), vbox); - /* Note: normally we'd unref the fb->mail_display now, except - that if we do then our refcounts will not be in - harmony... both the fb *and* the message-browser need to - own a ref on the mail_display. */ - gtk_widget_show (GTK_WIDGET (fb->mail_display)); - gtk_widget_show (vbox); - - g_signal_connect (new, "size-allocate", G_CALLBACK (message_browser_size_allocate_cb), NULL); - - bonobo_window_set_contents (BONOBO_WINDOW (new), vbox); - gtk_widget_grab_focus (GTK_WIDGET (MAIL_DISPLAY (fb->mail_display)->html)); - - set_default_size (GTK_WIDGET (new)); - - /* more evil hackery... */ - new->loaded_id = g_signal_connect (fb, "folder_loaded", G_CALLBACK (message_browser_folder_loaded), new); - g_signal_connect (fb, "message_loaded", G_CALLBACK (message_browser_message_loaded), new); - - g_signal_connect (new, "key_press_event", G_CALLBACK (on_key_press), new); - - return GTK_WIDGET (new); -} - -/* Fin */ - -E_MAKE_TYPE (message_browser, "MessageBrowser", MessageBrowser, message_browser_class_init, - message_browser_init, PARENT_TYPE); diff --git a/mail/message-browser.h b/mail/message-browser.h deleted file mode 100644 index 297c90c895..0000000000 --- a/mail/message-browser.h +++ /dev/null @@ -1,63 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright 2001 Ximian, Inc. (www.ximian.com) - * - * 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. - * - */ - -#ifndef _MESSAGE_BROWSER_H_ -#define _MESSAGE_BROWSER_H_ - -#include <gnome.h> -#include <bonobo/bonobo-window.h> - -#include <camel/camel-folder.h> -#include "folder-browser.h" -#include "mail-display.h" -#include "mail-types.h" - -#define MESSAGE_BROWSER_TYPE (message_browser_get_type ()) -#define MESSAGE_BROWSER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MESSAGE_BROWSER_TYPE, MessageBrowser)) -#define MESSAGE_BROWSER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), MESSAGE_BROWSER_TYPE, MessageBrowserClass)) -#define IS_MESSAGE_BROWSER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MESSAGE_BROWSER_TYPE)) -#define IS_MESSAGE_BROWSER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MESSAGE_BROWSER_TYPE)) - -struct _MessageBrowser { - BonoboWindow parent; - - /* - * The current URI being displayed by the MessageBrowser - */ - FolderBrowser *fb; - gulong ml_built_id; - gulong loaded_id; -}; - - -typedef struct { - BonoboWindowClass parent_class; - -} MessageBrowserClass; - -GtkType message_browser_get_type (void); - -GtkWidget *message_browser_new (const GNOME_Evolution_Shell shell, - const char *uri, const char *uid); - -#endif /* _MESSAGE_BROWSER_H_ */ - diff --git a/mail/message-list.c b/mail/message-list.c index 77e03b656e..80bedfb406 100644 --- a/mail/message-list.c +++ b/mail/message-list.c @@ -61,6 +61,8 @@ #include "mail-tools.h" #include "mail-ops.h" +#include "em-utils.h" + #include "art/mail-new.xpm" #include "art/mail-read.xpm" #include "art/mail-replied.xpm" @@ -86,6 +88,13 @@ #define d(x) #define t(x) +struct _MessageListPrivate { + GtkWidget *invisible; /* 4 selection */ + + GPtrArray *primary_uids; /* uids in primary selection */ + GPtrArray *clipboard_uids; /* uids in clipboard selection */ +}; + /* * Default sizes for the ETable display * @@ -508,19 +517,20 @@ void message_list_select_uid (MessageList *message_list, const char *uid) { ETreePath node; + + if (message_list->regen) { + g_free(message_list->pending_select_uid); + message_list->pending_select_uid = g_strdup(uid); + } node = g_hash_table_lookup (message_list->uid_nodemap, uid); if (node) { CamelMessageInfo *info; - + info = get_message_info (message_list, node); + + /* This will emit a changed signal that we'll pick up */ e_tree_set_cursor (message_list->tree, node); - - g_free (message_list->cursor_uid); - message_list->cursor_uid = g_strdup (camel_message_info_uid (info)); - - g_signal_emit (GTK_OBJECT (message_list), message_list_signals[MESSAGE_SELECTED], 0, - camel_message_info_uid (info)); } else { g_free (message_list->cursor_uid); message_list->cursor_uid = NULL; @@ -562,6 +572,155 @@ message_list_select_next_thread (MessageList *message_list) } +/** + * message_list_select_all: + * @message_list: Message List widget + * + * Selects all messages in the message list. + **/ +void +message_list_select_all (MessageList *message_list) +{ + ESelectionModel *etsm; + + etsm = e_tree_get_selection_model (message_list->tree); + + e_selection_model_select_all (etsm); +} + + +typedef struct thread_select_info { + MessageList *ml; + GPtrArray *paths; +} thread_select_info_t; + +static gboolean +select_node (ETreeModel *model, ETreePath path, gpointer user_data) +{ + thread_select_info_t *tsi = (thread_select_info_t *) user_data; + + g_ptr_array_add (tsi->paths, path); + return FALSE; /*not done yet*/ +} + +static void +thread_select_foreach (ETreePath path, gpointer user_data) +{ + thread_select_info_t *tsi = (thread_select_info_t *) user_data; + ETreeModel *model = tsi->ml->model; + ETreePath node; + + /* @path part of the initial selection. If it has children, + * we select them as well. If it doesn't, we select its siblings and + * their children (ie, the current node must be inside the thread + * that the user wants to mark. + */ + + if (e_tree_model_node_get_first_child (model, path)) { + node = path; + } else { + node = e_tree_model_node_get_parent (model, path); + + /* Let's make an exception: if no parent, then we're about + * to mark the whole tree. No. */ + if (e_tree_model_node_is_root (model, node)) + node = path; + } + + e_tree_model_node_traverse (model, node, select_node, tsi); +} + +/** + * message_list_select_thread: + * @message_list: Message List widget + * + * Selects all messages in the current thread (based on cursor). + **/ +void +message_list_select_thread (MessageList *message_list) +{ + ETreeSelectionModel *etsm; + thread_select_info_t tsi; + int i; + + tsi.ml = message_list; + tsi.paths = g_ptr_array_new (); + + etsm = (ETreeSelectionModel *) e_tree_get_selection_model (message_list->tree); + + e_tree_selected_path_foreach (message_list->tree, thread_select_foreach, &tsi); + + for (i = 0; i < tsi.paths->len; i++) + e_tree_selection_model_add_to_selection (etsm, tsi.paths->pdata[i]); + + g_ptr_array_free (tsi.paths, TRUE); +} + + +/** + * message_list_invert_selection: + * @message_list: Message List widget + * + * Invert the current selection in the message-list. + **/ +void +message_list_invert_selection (MessageList *message_list) +{ + ESelectionModel *etsm; + + etsm = e_tree_get_selection_model (message_list->tree); + + e_selection_model_invert_selection (etsm); +} + +void +message_list_copy(MessageList *ml, gboolean cut) +{ + struct _MessageListPrivate *p = ml->priv; + GPtrArray *uids; + + if (p->clipboard_uids) { + message_list_free_uids(ml, p->clipboard_uids); + p->clipboard_uids = NULL; + } + + uids = message_list_get_selected(ml); + + if (uids->len > 0) { + if (cut) { + int i; + + camel_folder_freeze(ml->folder); + for (i=0;i<uids->len;i++) + camel_folder_set_message_flags(ml->folder, uids->pdata[i], + CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED, + CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED); + + camel_folder_thaw(ml->folder); + } + + p->clipboard_uids = uids; + gtk_selection_owner_set(p->invisible, GDK_SELECTION_CLIPBOARD, gtk_get_current_event_time()); + } else { + message_list_free_uids(ml, uids); + gtk_selection_owner_set(NULL, GDK_SELECTION_CLIPBOARD, gtk_get_current_event_time()); + } +} + +gboolean +message_list_has_primary_selection(MessageList *ml) +{ + return ml->priv->primary_uids != NULL; +} + +void +message_list_paste(MessageList *ml) +{ + gtk_selection_convert(ml->priv->invisible, GDK_SELECTION_CLIPBOARD, + gdk_atom_intern("x-evolution-message", FALSE), + GDK_CURRENT_TIME); +} + /* * SimpleTableModel::col_count */ @@ -1252,6 +1411,141 @@ message_list_setup_etree (MessageList *message_list, gboolean outgoing) } } +static void +ml_selection_get(GtkWidget *widget, GtkSelectionData *data, guint info, guint time_stamp, MessageList *ml) +{ + GPtrArray *uids; + + if (info & 1) + uids = ml->priv->primary_uids; + else + uids = ml->priv->clipboard_uids; + + if (uids == NULL) + return; + + if (info & 2) { + /* text_plain */ + printf("setting text/plain selection for uids\n"); + em_utils_selection_set_mailbox(data, ml->folder, uids); + } else { + /* x-evolution-message */ + printf("setting x-evolution-message selection for uids\n"); + em_utils_selection_set_uidlist(data, ml->folder_uri, uids); + } +} + +static void +ml_selection_clear_event(GtkWidget *widget, GdkEventSelection *event, MessageList *ml) +{ + struct _MessageListPrivate *p = ml->priv; + + if (event->selection == GDK_SELECTION_PRIMARY) { + if (p->primary_uids) { + message_list_free_uids(ml, p->primary_uids); + p->primary_uids = NULL; + } + } else if (event->selection == GDK_SELECTION_CLIPBOARD) { + if (p->clipboard_uids) { + message_list_free_uids(ml, p->clipboard_uids); + p->clipboard_uids = NULL; + } + } +} + +static void +ml_selection_received_uidlist(MessageList *ml, GtkSelectionData *data) +{ + CamelFolder *folder; + GPtrArray *uids; + char *uri; + + if (em_utils_selection_get_uidlist(data, &uri, &uids) == 0) + return; + + folder = mail_tool_uri_to_folder(uri, 0, NULL); + if (folder) { + mail_transfer_messages(folder, uids, FALSE, ml->folder_uri, 0, NULL, NULL); + camel_object_unref(folder); + } else { + /* FIXME: error box? */ + g_warning("could not open paste source uri '%s'", uri); + em_utils_uids_free(uids); + } + + g_free(uri); +} + +static void +ml_selection_received(GtkWidget *widget, GtkSelectionData *data, guint time, MessageList *ml) +{ + if (data->target != gdk_atom_intern("x-evolution-message", FALSE)) { + printf("Unknown selection received by message-list\n"); + + return; + } + + ml_selection_received_uidlist(ml, data); +} + +static GtkTargetEntry ml_drag_types[] = { + { "x-evolution-message", 0, 0 }, + { "message/rfc822", 0, 1 }, + /* not included in dest types */ + { "text/uri-list", 0, 2 }, +}; + +static void +ml_tree_drag_data_get (ETree *tree, int row, ETreePath path, int col, + GdkDragContext *context, GtkSelectionData *data, + guint info, guint time, MessageList *ml) +{ + GPtrArray *uids; + + uids = message_list_get_selected(ml); + + if (uids->len > 0) { + switch (info) { + case 0 /*DND_TARGET_TYPE_X_EVOLUTION_MESSAGE*/: + em_utils_selection_set_uidlist(data, ml->folder_uri, uids); + break; + case 1 /*DND_TARGET_TYPE_MESSAGE_RFC822*/: + em_utils_selection_set_mailbox(data, ml->folder, uids); + break; + case 2 /*DND_TARGET_TYPE_TEXT_URI_LIST*/: + em_utils_selection_set_urilist(data, ml->folder, uids); + break; + } + } + + message_list_free_uids(ml, uids); +} + +static void +ml_tree_drag_data_received (ETree *tree, int row, ETreePath path, int col, + GdkDragContext *context, gint x, gint y, + GtkSelectionData *data, guint info, + guint time, MessageList *ml) +{ + /* this means we are receiving no data */ + if (data->data == NULL || data->length == -1) + return; + + /* Note: we don't receive text/uri-list, since we have no + guarantee on what the content would be */ + + switch (info) { + case 1 /*DND_TARGET_TYPE_MESSAGE_RFC822*/: + em_utils_selection_get_mailbox(data, ml->folder); + break; + case 0 /*DND_TARGET_TYPE_X_EVOLUTION_MESSAGE*/: + ml_selection_received_uidlist(ml, data); + break; + } + + gtk_drag_finish(context, TRUE, TRUE, time); +} + /* * GtkObject::init */ @@ -1259,6 +1553,8 @@ static void message_list_init (GtkObject *object) { MessageList *message_list = MESSAGE_LIST (object); + struct _MessageListPrivate *p; + GdkAtom matom; gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (message_list), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); @@ -1275,6 +1571,22 @@ message_list_init (GtkObject *object) message_list->uid_nodemap = g_hash_table_new (g_str_hash, g_str_equal); message_list->async_event = mail_async_event_new(); + + /* TODO: Should this only get the selection if we're realised? */ + p = message_list->priv = g_malloc0(sizeof(*message_list->priv)); + p->invisible = gtk_invisible_new(); + g_object_ref(p->invisible); + gtk_object_sink((GtkObject *)p->invisible); + + matom = gdk_atom_intern("x-evolution-message", FALSE); + gtk_selection_add_target(p->invisible, GDK_SELECTION_CLIPBOARD, matom, 0); + gtk_selection_add_target(p->invisible, GDK_SELECTION_PRIMARY, matom, 1); + gtk_selection_add_target(p->invisible, GDK_SELECTION_CLIPBOARD, GDK_SELECTION_TYPE_STRING, 2); + gtk_selection_add_target(p->invisible, GDK_SELECTION_PRIMARY, GDK_SELECTION_TYPE_STRING, 3); + + g_signal_connect(p->invisible, "selection_get", G_CALLBACK(ml_selection_get), message_list); + g_signal_connect(p->invisible, "selection_clear_event", G_CALLBACK(ml_selection_clear_event), message_list); + g_signal_connect(p->invisible, "selection_received", G_CALLBACK(ml_selection_received), message_list); } static void @@ -1287,7 +1599,8 @@ static void message_list_destroy(GtkObject *object) { MessageList *message_list = MESSAGE_LIST (object); - + struct _MessageListPrivate *p = message_list->priv; + if (message_list->async_event) { mail_async_event_destroy(message_list->async_event); message_list->async_event = NULL; @@ -1307,7 +1620,12 @@ message_list_destroy(GtkObject *object) camel_object_unref (message_list->folder); message_list->folder = NULL; } - + + if (p->invisible) { + g_object_unref(p->invisible); + p->invisible = NULL; + } + if (message_list->extras) { g_object_unref (message_list->extras); message_list->extras = NULL; @@ -1337,6 +1655,7 @@ static void message_list_finalise (GObject *object) { MessageList *message_list = MESSAGE_LIST (object); + struct _MessageListPrivate *p = message_list->priv; g_hash_table_foreach (message_list->normalised_hash, normalised_free, NULL); g_hash_table_destroy (message_list->normalised_hash); @@ -1355,6 +1674,16 @@ message_list_finalise (GObject *object) g_mutex_free(message_list->hide_lock); + g_free(message_list->folder_uri); + message_list->folder_uri = NULL; + + if (p->primary_uids) + message_list_free_uids(message_list, p->primary_uids); + if (p->clipboard_uids) + message_list_free_uids(message_list, p->clipboard_uids); + + g_free(p); + G_OBJECT_CLASS (message_list_parent_class)->finalize (object); } @@ -1446,8 +1775,30 @@ message_list_construct (MessageList *message_list) g_signal_connect((message_list->tree), "selection_change", G_CALLBACK (on_selection_changed_cmd), message_list); + + e_tree_drag_source_set(message_list->tree, GDK_BUTTON1_MASK, + ml_drag_types, sizeof(ml_drag_types)/sizeof(ml_drag_types[0]), + GDK_ACTION_MOVE|GDK_ACTION_COPY); + + g_signal_connect(message_list->tree, "tree_drag_data_get", + G_CALLBACK(ml_tree_drag_data_get), message_list); + + /* note, we only include 2 types, we don't include text/uri-list as a receiver */ + e_tree_drag_dest_set(message_list->tree, GTK_DEST_DEFAULT_ALL, + ml_drag_types, 2, + GDK_ACTION_MOVE|GDK_ACTION_COPY); + + g_signal_connect(message_list->tree, "tree_drag_data_received", + G_CALLBACK(ml_tree_drag_data_received), message_list); } +/** + * message_list_new: + * + * Creates a new message-list widget. + * + * Returns a new message-list widget. + **/ GtkWidget * message_list_new (void) { @@ -2128,17 +2479,28 @@ message_changed (CamelObject *o, gpointer event_data, gpointer user_data) mail_async_event_emit(ml->async_event, MAIL_ASYNC_GUI, (MailAsyncFunc)main_folder_changed, o, changes, user_data); } + +/** + * message_list_set_folder: + * @message_list: Message List widget + * @folder: folder backend to be set + * @uri: uri of @folder. + * @outgoing: whether this is an outgoing folder + * + * Sets @folder to be the backend folder for @message_list. If + * @outgoing is %TRUE, then the message-list UI changes to default to + * the "Outgoing folder" column view. + **/ void -message_list_set_folder (MessageList *message_list, CamelFolder *camel_folder, gboolean outgoing) +message_list_set_folder (MessageList *message_list, CamelFolder *folder, const char *uri, gboolean outgoing) { gboolean hide_deleted; GConfClient *gconf; CamelException ex; - g_return_if_fail (message_list != NULL); g_return_if_fail (IS_MESSAGE_LIST (message_list)); - if (message_list->folder == camel_folder) + if (message_list->folder == folder) return; camel_exception_init (&ex); @@ -2171,8 +2533,12 @@ message_list_set_folder (MessageList *message_list, CamelFolder *camel_folder, g camel_folder_thread_messages_unref(message_list->thread_tree); message_list->thread_tree = NULL; } - - message_list->folder = camel_folder; + + if (message_list->folder_uri != uri) { + g_free(message_list->folder_uri); + message_list->folder_uri = g_strdup(uri); + message_list->folder = folder; + } if (message_list->cursor_uid) { g_free(message_list->cursor_uid); @@ -2180,9 +2546,9 @@ message_list_set_folder (MessageList *message_list, CamelFolder *camel_folder, g g_signal_emit(message_list, message_list_signals[MESSAGE_SELECTED], 0, NULL); } - if (camel_folder) { + if (folder) { /* Setup the strikeout effect for non-trash folders */ - if (!(camel_folder->folder_flags & CAMEL_FOLDER_IS_TRASH)) { + if (!(folder->folder_flags & CAMEL_FOLDER_IS_TRASH)) { ECell *cell; cell = e_table_extras_get_cell (message_list->extras, "render_date"); @@ -2204,16 +2570,16 @@ message_list_set_folder (MessageList *message_list, CamelFolder *camel_folder, g /* Build the etree suitable for this folder */ message_list_setup_etree (message_list, outgoing); - camel_object_hook_event (camel_folder, "folder_changed", + camel_object_hook_event (folder, "folder_changed", folder_changed, message_list); - camel_object_hook_event (camel_folder, "message_changed", + camel_object_hook_event (folder, "message_changed", message_changed, message_list); - camel_object_ref (camel_folder); + camel_object_ref (folder); gconf = mail_config_get_gconf_client (); hide_deleted = !gconf_client_get_bool (gconf, "/apps/evolution/mail/display/show_deleted", NULL); - message_list->hidedeleted = hide_deleted && !(camel_folder->folder_flags & CAMEL_FOLDER_IS_TRASH); + message_list->hidedeleted = hide_deleted && !(folder->folder_flags & CAMEL_FOLDER_IS_TRASH); hide_load_state (message_list); mail_regen_list (message_list, message_list->search, NULL, NULL); @@ -2266,26 +2632,33 @@ on_cursor_activated_cmd (ETree *tree, int row, ETreePath path, gpointer user_dat } static void -get_selected_cb(ETreePath path, MessageList *ml) -{ - g_free(ml->cursor_uid); - ml->cursor_uid = g_strdup(get_message_uid(ml, path)); -} - -static void on_selection_changed_cmd(ETree *tree, MessageList *ml) { - ESelectionModel *esm = e_tree_get_selection_model (ml->tree); - int selected = e_selection_model_selected_count (esm); + GPtrArray *uids; + uids = message_list_get_selected(ml); g_free(ml->cursor_uid); - ml->cursor_uid = NULL; - - if (selected == 1) - e_tree_selected_path_foreach(ml->tree, (ETreeForeachFunc)get_selected_cb, ml); + if (uids->len == 1) + ml->cursor_uid = g_strdup(uids->pdata[0]); + else + ml->cursor_uid = NULL; - if ((selected == 1 || selected == 0) && !ml->idle_id) + if (uids->len <= 1 && !ml->idle_id) ml->idle_id = g_idle_add_full (G_PRIORITY_LOW, on_cursor_activated_idle, ml, NULL); + + if (ml->priv->primary_uids) { + message_list_free_uids(ml, ml->priv->primary_uids); + ml->priv->primary_uids = NULL; + } + + if (uids->len > 0) { + ml->priv->primary_uids = uids; + gtk_selection_owner_set(ml->priv->invisible, GDK_SELECTION_PRIMARY, gtk_get_current_event_time()); + } else { + message_list_free_uids(ml, uids); + gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, gtk_get_current_event_time()); + } + } static gint @@ -2368,6 +2741,47 @@ message_list_foreach (MessageList *message_list, mlfe_callback, &mlfe_data); } +struct _ml_selected_data { + MessageList *ml; + GPtrArray *uids; +}; + +static void +ml_getselected_cb(ETreePath path, void *user_data) +{ + struct _ml_selected_data *data = user_data; + const char *uid; + + if (e_tree_model_node_is_root (data->ml->model, path)) + return; + + uid = get_message_uid(data->ml, path); + g_assert(uid != NULL); + g_ptr_array_add(data->uids, g_strdup(uid)); +} + +GPtrArray * +message_list_get_selected(MessageList *ml) +{ + struct _ml_selected_data data = { + ml, + g_ptr_array_new() + }; + + e_tree_selected_path_foreach(ml->tree, ml_getselected_cb, &data); + + return data.uids; +} + +void message_list_free_uids(MessageList *ml, GPtrArray *uids) +{ + int i; + + for (i=0;i<uids->len;i++) + g_free(uids->pdata[i]); + g_ptr_array_free(uids, TRUE); +} + /* set whether we are in threaded view or flat view */ void message_list_set_threaded (MessageList *ml, gboolean threaded) @@ -2845,6 +3259,14 @@ regen_list_free (struct _mail_msg *mm) However, since we have a received function, this will always be called in gui thread */ m->ml->regen = g_list_remove(m->ml->regen, m); + if (m->ml->regen == NULL && m->ml->pending_select_uid) { + char *uid = m->ml->pending_select_uid; + + m->ml->pending_select_uid = NULL; + message_list_select_uid(m->ml, uid); + g_free(uid); + } + g_object_unref(m->ml); } diff --git a/mail/message-list.h b/mail/message-list.h index de2ddc199f..a5c88cf308 100644 --- a/mail/message-list.h +++ b/mail/message-list.h @@ -1,4 +1,26 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast <fejj@ximian.com> + * + * Copyright 2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + #ifndef _MESSAGE_LIST_H_ #define _MESSAGE_LIST_H_ @@ -9,6 +31,11 @@ #include <gal/e-table/e-tree-scrolled.h> #include "mail-types.h" +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + #define MESSAGE_LIST_TYPE (message_list_get_type ()) #define MESSAGE_LIST(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MESSAGE_LIST_TYPE, MessageList)) #define MESSAGE_LIST_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), MESSAGE_LIST_TYPE, MessageListClass)) @@ -55,14 +82,17 @@ enum { struct _MessageList { ETreeScrolled parent; + struct _MessageListPrivate *priv; + /* The table */ ETreeModel *model; ETree *tree; ETreePath tree_root; ETableExtras *extras; - /* The folder */ + /* The folder & matching uri */ CamelFolder *folder; + char *folder_uri; GHashTable *uid_nodemap; /* uid (from info) -> tree node mapping */ @@ -99,6 +129,7 @@ struct _MessageList { /* list of outstanding regeneration requests */ GList *regen; + char *pending_select_uid; /* set if we were busy regnerating while we had a select come in */ /* the current camel folder thread tree, if any */ struct _CamelFolderThread *thread_tree; @@ -126,14 +157,16 @@ typedef enum { GtkType message_list_get_type (void); GtkWidget *message_list_new (void); -void message_list_set_folder (MessageList *message_list, - CamelFolder *camel_folder, - gboolean outgoing); +void message_list_set_folder (MessageList *message_list, CamelFolder *camel_folder, const char *uri, gboolean outgoing); void message_list_foreach (MessageList *message_list, MessageListForeachFunc callback, gpointer user_data); +GPtrArray *message_list_get_selected(MessageList *ml); +void message_list_free_uids(MessageList *ml, GPtrArray *uids); + +/* select next/prev message helpers */ gboolean message_list_select (MessageList *message_list, MessageListSelectDirection direction, guint32 flags, @@ -143,24 +176,38 @@ gboolean message_list_select (MessageList *message_list, void message_list_select_uid (MessageList *message_list, const char *uid); -void message_list_select_next_thread (MessageList *messageList); +void message_list_select_next_thread (MessageList *ml); + +/* selection manipulation */ +void message_list_select_all (MessageList *ml); +void message_list_select_thread (MessageList *ml); +void message_list_invert_selection (MessageList *ml); + +/* clipboard stuff */ +void message_list_copy(MessageList *ml, gboolean cut); +gboolean message_list_has_primary_selection(MessageList *ml); +void message_list_paste (MessageList *ml); /* info */ -unsigned int message_list_length(MessageList *ml); -unsigned int message_list_hidden(MessageList *ml); +unsigned int message_list_length (MessageList *ml); +unsigned int message_list_hidden (MessageList *ml); /* hide specific messages */ -void message_list_hide_add(MessageList *ml, const char *expr, unsigned int lower, unsigned int upper); -void message_list_hide_uids(MessageList *ml, GPtrArray *uids); -void message_list_hide_clear(MessageList *ml); +void message_list_hide_add (MessageList *ml, const char *expr, unsigned int lower, unsigned int upper); +void message_list_hide_uids (MessageList *ml, GPtrArray *uids); +void message_list_hide_clear (MessageList *ml); -void message_list_set_threaded(MessageList *ml, gboolean threaded); -void message_list_set_hidedeleted(MessageList *ml, gboolean hidedeleted); -void message_list_set_search(MessageList *ml, const char *search); +void message_list_set_threaded (MessageList *ml, gboolean threaded); +void message_list_set_hidedeleted (MessageList *ml, gboolean hidedeleted); +void message_list_set_search (MessageList *ml, const char *search); void message_list_save_state (MessageList *ml); #define MESSAGE_LIST_LOCK(m, l) g_mutex_lock(((MessageList *)m)->l) #define MESSAGE_LIST_UNLOCK(m, l) g_mutex_unlock(((MessageList *)m)->l) +#ifdef __cplusplus +} +#endif /* __cplusplus */ + #endif /* _MESSAGE_LIST_H_ */ diff --git a/mail/subscribe-dialog.c b/mail/subscribe-dialog.c deleted file mode 100644 index cf9768aff0..0000000000 --- a/mail/subscribe-dialog.c +++ /dev/null @@ -1,1663 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* subscribe-dialog.c: Subscribe dialog */ -/* - * Authors: Chris Toshok <toshok@ximian.com> - * Peter Williams <peterw@ximian.com> - * - * Copyright 2000 Ximian, Inc. (www.ximian.com) - * - * 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. - * - */ - -/* This doens't do what it's supposed to do ... - I think etree changed so it just fills out the whole tree always anyway */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <string.h> - -#include <gal/util/e-util.h> - -#include <gal/e-table/e-cell-toggle.h> -#include <gal/e-table/e-cell-text.h> -#include <gal/e-table/e-cell-tree.h> - -#include <gal/e-table/e-tree-scrolled.h> -#include <gal/e-table/e-tree-memory-callbacks.h> -#include <gal/e-table/e-tree.h> - -#include <pthread.h> - -#include "evolution-shell-component-utils.h" -#include "mail.h" -#include "mail-tools.h" -#include "mail-ops.h" -#include "mail-mt.h" -#include "mail-folder-cache.h" -#include "camel/camel-exception.h" -#include "camel/camel-store.h" -#include "camel/camel-session.h" -#include "subscribe-dialog.h" - -#include "art/empty.xpm" -#include "art/mark.xpm" - -#define d(x) - -/* Things to test. - * - Feature - * + How to check that it works. - * - * - Proper stores displayed - * + Skip stores that don't support subscriptions - * + Skip disabled stores - * - Changing subscription status - * + Select single folder, double-click row -> toggled - * + Select multiple folders, press subscribe -> all selected folders end up subscribed - * - (un)Subscribing from/to already (un)subscribed folder - * + Check that no IMAP command is sent - * - Switching views between stores - * + Proper tree shown - * - No crashes when buttons are pressed with "No store" screen - * + obvious - * - Restoring filter settings when view switched - * + Enter search, change view, change back -> filter checked and search entry set - * + Clear search, change view, change back -> "all" checked - * - Cancelling in middle of get_store - * + Enter invalid hostname, open dialog, click Close - * - Cancelling in middle if listing - * + Open large directory, click Close - * - Cancelling in middle of subscription op - * + How to test? - * - Test with both IMAP and NNTP - * + obvious - * - Verify that refresh view works - * + obvious - * - No unnecessary tree rebuilds - * + Show All folders, change filter with empty search -> no tree rebuild - * + Converse - * - No out of date tree - * + Show All Folders, change to filter with a search -> tree rebuild - * - Tree construction logic (mostly IMAP-specific terminology) - * + Tree is created progressively - * + No wasted LIST responses - * + No extraneous LIST commands - * + Specifying "folder names begin with" works - * + Always show folders below IMAP namespace (no escaping the namespace) - * + Don't allow subscription to NoSelect folders - * + IMAP accounts always show INBOX - * - Shell interactions - * + Folders are properly created / delete from folder tree when subscribed / unsubscribed - * + Folders with spaces in names / 8bit chars - * + Toplevel as well as subfolders - * + Mail Folder Cache doesn't complain - * - No ETable wackiness - * + Verify columns cannot be DnD'd - * + Alphabetical order always - * - UI cleanliness - * + Keybindings work - * + Some widget has focus by default - * + Escape / enter work - * + Close button works - */ - -/* FIXME: we should disable/enable the subscribe/unsubscribe buttons as - * appropriate when only a single folder is selected. We need a - * mechanism to learn when the selected folder's subscription status - * changes, so when the user double-clicks it (eg) the buttons can - * (de)sensitize appropriately. See Ximian bug #7673. - */ - -/*#define NEED_TOGGLE_SELECTION*/ - -typedef struct _FolderETree FolderETree; -typedef struct _FolderETreeClass FolderETreeClass; - -typedef void (*FolderETreeActivityCallback) (int level, gpointer user_data); - -struct _FolderETree { - ETreeMemory parent; - ETreePath root; - - GHashTable *scan_ops; - GHashTable *subscribe_ops; - - GHashTable *node_full_name; - - CamelStore *store; - EvolutionStorage *e_storage; - char *service_name; - - FolderETreeActivityCallback activity_cb; - gpointer activity_data; - int activity_level; -}; - -struct _FolderETreeClass { - ETreeMemoryClass parent; -}; - -static GtkObjectClass *folder_etree_parent_class = NULL; - -typedef struct _FolderETreeExtras FolderETreeExtras; -typedef struct _FolderETreeExtrasClass FolderETreeExtrasClass; - -enum { - FOLDER_COL_SUBSCRIBED, - FOLDER_COL_NAME, - FOLDER_COL_LAST -}; - -struct _FolderETreeExtras { - ETableExtras parent; - GdkPixbuf *toggles[2]; -}; - -struct _FolderETreeExtrasClass { - ETableExtrasClass parent; -}; - -static GtkObjectClass *ftree_extras_parent_class = NULL; - -/* util */ - -static void -recursive_add_folder (EvolutionStorage *storage, const char *path, const char *name, const char *url) -{ - char *parent, *pname, *p; - - p = strrchr (path, '/'); - if (p && p != path) { - parent = g_strndup (path, p - path); - if (!evolution_storage_folder_exists (storage, parent)) { - p = strrchr (parent, '/'); - if (p) - pname = g_strdup (p + 1); - else - pname = g_strdup (""); - recursive_add_folder (storage, parent, pname, ""); - g_free (pname); - } - g_free (parent); - } - - evolution_storage_new_folder (storage, path, name, "mail", url, name, NULL, FALSE, TRUE, 0); -} - -/* ** Get one level of folderinfo ****************************************** */ - -typedef void (*SubscribeShortFolderinfoFunc) (CamelStore *store, char *prefix, CamelFolderInfo *info, gpointer data); - -int subscribe_get_short_folderinfo (FolderETree *ftree, const char *prefix, - SubscribeShortFolderinfoFunc func, gpointer user_data); - -struct _get_short_folderinfo_msg { - struct _mail_msg msg; - - char *prefix; - - FolderETree *ftree; - CamelFolderInfo *info; - - SubscribeShortFolderinfoFunc func; - gpointer user_data; -}; - -static char * -get_short_folderinfo_desc (struct _mail_msg *mm, int done) -{ - struct _get_short_folderinfo_msg *m = (struct _get_short_folderinfo_msg *) mm; - char *ret, *name; - - name = camel_service_get_name (CAMEL_SERVICE (m->ftree->store), TRUE); - - if (m->prefix) - ret = g_strdup_printf (_("Scanning folders under %s on \"%s\""), m->prefix, name); - else - ret = g_strdup_printf (_("Scanning root-level folders on \"%s\""), name); - - g_free (name); - return ret; -} - -static void -get_short_folderinfo_get (struct _mail_msg *mm) -{ - struct _get_short_folderinfo_msg *m = (struct _get_short_folderinfo_msg *) mm; - - m->info = camel_store_get_folder_info (m->ftree->store, m->prefix, CAMEL_STORE_FOLDER_INFO_FAST, &mm->ex); - - d(printf("%d: getted folderinfo '%s'\n", mm->seq, m->prefix)); -} - -static void -get_short_folderinfo_got (struct _mail_msg *mm) -{ - struct _get_short_folderinfo_msg *m = (struct _get_short_folderinfo_msg *) mm; - - d(printf("%d: got folderinfo '%s'\n", mm->seq, m->prefix)); - - if (camel_exception_is_set (&mm->ex) && camel_exception_get_id(&mm->ex) != CAMEL_EXCEPTION_USER_CANCEL) { - g_warning ("Error getting folder info from store at %s: %s", - camel_service_get_url (CAMEL_SERVICE (m->ftree->store)), - camel_exception_get_description (&mm->ex)); - } - - if (m->func) - m->func (m->ftree->store, m->prefix, m->info, m->user_data); -} - -static void -get_short_folderinfo_free (struct _mail_msg *mm) -{ - struct _get_short_folderinfo_msg *m = (struct _get_short_folderinfo_msg *) mm; - - camel_store_free_folder_info (m->ftree->store, m->info); - g_object_unref((m->ftree)); - - g_free (m->prefix); /* may be NULL but that's ok */ -} - -static struct _mail_msg_op get_short_folderinfo_op = { - get_short_folderinfo_desc, - get_short_folderinfo_get, - get_short_folderinfo_got, - get_short_folderinfo_free, -}; - -int -subscribe_get_short_folderinfo (FolderETree *ftree, - const char *prefix, - SubscribeShortFolderinfoFunc func, - gpointer user_data) -{ - struct _get_short_folderinfo_msg *m; - int id; - - m = mail_msg_new (&get_short_folderinfo_op, NULL, sizeof(*m)); - - m->ftree = ftree; - g_object_ref((ftree)); - m->prefix = g_strdup (prefix); - m->func = func; - m->user_data = user_data; - - d(printf("%d: get folderinfo '%s'\n", m->msg.seq, m->prefix)); - - id = m->msg.seq; - e_thread_put (mail_thread_queued, (EMsg *)m); - return id; -} - -/* ** Subscribe folder operation **************************************** */ - -typedef void (*SubscribeFolderCallback) (const char *, const char *, gboolean, gboolean, gpointer); - -struct _subscribe_msg { - struct _mail_msg msg; - - CamelStore *store; - gboolean subscribe; - char *full_name; - char *name; - - SubscribeFolderCallback cb; - gpointer cb_data; -}; - -static char * -subscribe_folder_desc (struct _mail_msg *mm, int done) -{ - struct _subscribe_msg *m = (struct _subscribe_msg *) mm; - - if (m->subscribe) - return g_strdup_printf (_("Subscribing to folder \"%s\""), m->name); - else - return g_strdup_printf (_("Unsubscribing to folder \"%s\""), m->name); -} - -static void -subscribe_folder_subscribe (struct _mail_msg *mm) -{ - struct _subscribe_msg *m = (struct _subscribe_msg *) mm; - - if (m->subscribe) - camel_store_subscribe_folder (m->store, m->full_name, &mm->ex); - else - camel_store_unsubscribe_folder (m->store, m->full_name, &mm->ex); -} - -static void -subscribe_folder_subscribed (struct _mail_msg *mm) -{ - struct _subscribe_msg *m = (struct _subscribe_msg *) mm; - - if (m->cb) - (m->cb) (m->full_name, m->name, m->subscribe, - !camel_exception_is_set (&mm->ex), m->cb_data); -} - -static void -subscribe_folder_free (struct _mail_msg *mm) -{ - struct _subscribe_msg *m = (struct _subscribe_msg *) mm; - - g_free (m->name); - g_free (m->full_name); - - camel_object_unref (m->store); -} - -static struct _mail_msg_op subscribe_folder_op = { - subscribe_folder_desc, - subscribe_folder_subscribe, - subscribe_folder_subscribed, - subscribe_folder_free, -}; - -static int -subscribe_do_subscribe_folder (CamelStore *store, const char *full_name, const char *name, - gboolean subscribe, SubscribeFolderCallback cb, gpointer cb_data) -{ - struct _subscribe_msg *m; - int id; - - g_return_val_if_fail (CAMEL_IS_STORE (store), 0); - g_return_val_if_fail (full_name, 0); - - m = mail_msg_new (&subscribe_folder_op, NULL, sizeof(*m)); - m->store = store; - m->subscribe = subscribe; - m->name = g_strdup (name); - m->full_name = g_strdup (full_name); - m->cb = cb; - m->cb_data = cb_data; - - camel_object_ref(store); - - id = m->msg.seq; - e_thread_put (mail_thread_queued, (EMsg *)m); - return id; -} - -/* ** FolderETree Extras *************************************************** */ - -static void -fete_finalise (GObject *object) -{ - FolderETreeExtras *extras = (FolderETreeExtras *) object; - - g_object_unref (extras->toggles[0]); - g_object_unref (extras->toggles[1]); - - ((GObjectClass *)ftree_extras_parent_class)->finalize (object); -} - -static void -fete_class_init (GObjectClass *object_class) -{ - object_class->finalize = fete_finalise; - - ftree_extras_parent_class = g_type_class_ref (E_TABLE_EXTRAS_TYPE); -} - -static void -fete_init (GtkObject *object) -{ - FolderETreeExtras *extras = (FolderETreeExtras *) object; - ECell *cell; - ECell *text_cell; - - /* text column */ - - cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT); - text_cell = cell; - g_object_set (G_OBJECT (cell), - "bold_column", FOLDER_COL_SUBSCRIBED, - NULL); - e_table_extras_add_cell (E_TABLE_EXTRAS (extras), "cell_text", cell); - - /* toggle column */ - - extras->toggles[0] = gdk_pixbuf_new_from_xpm_data ((const char **)empty_xpm); - extras->toggles[1] = gdk_pixbuf_new_from_xpm_data ((const char **)mark_xpm); - cell = e_cell_toggle_new (0, 2, extras->toggles); - e_table_extras_add_cell (E_TABLE_EXTRAS (extras), "cell_toggle", cell); - - /* tree cell */ - - cell = e_cell_tree_new (NULL, NULL, TRUE, text_cell); - e_table_extras_add_cell (E_TABLE_EXTRAS (extras), "cell_tree", cell); - - /* misc */ - - e_table_extras_add_pixbuf (E_TABLE_EXTRAS (extras), "subscribed-image", extras->toggles[1]); -} - -/* naughty! */ -static -E_MAKE_TYPE (fete, "FolderETreeExtras", FolderETreeExtras, fete_class_init, fete_init, E_TABLE_EXTRAS_TYPE); - -/* ** Global Extras ******************************************************** */ - -static FolderETreeExtras *global_extras = NULL; - -static void -global_extras_destroyed (void *user_data, GObject *obj) -{ - global_extras = NULL; -} - -static ETableExtras * -subscribe_get_global_extras (void) -{ - if (global_extras == NULL) { - global_extras = g_object_new (fete_get_type(), NULL); - /*g_object_ref(global_extras); - gtk_object_sink((GtkObject *)global_extras);*/ - g_object_weak_ref(G_OBJECT(global_extras), global_extras_destroyed, NULL); - } else { - g_object_ref(global_extras); - } - - return E_TABLE_EXTRAS (global_extras); -} - -/* ** Folder Tree Node ***************************************************** */ - -typedef struct _ftree_node ftree_node; - -struct _ftree_node { - guint8 flags; - char *cache; - int uri_offset; - int full_name_offset; - - /* format: {name}{\0}{uri}{\0}{full_name}{\0} - * (No braces). */ - char data[1]; -}; - -#define FTREE_NODE_GOT_CHILDREN (1 << 0) -#define FTREE_NODE_SUBSCRIBABLE (1 << 1) -#define FTREE_NODE_SUBSCRIBED (1 << 2) -#define FTREE_NODE_ROOT (1 << 3) - -static ftree_node * -ftree_node_new_root (void) -{ - ftree_node *node; - - node = g_malloc (sizeof (ftree_node)); - node->flags = FTREE_NODE_ROOT; - node->uri_offset = 0; - node->full_name_offset = 0; - node->data[0] = '\0'; - - return node; -} - -static ftree_node * -ftree_node_new (CamelStore *store, CamelFolderInfo *fi) -{ - ftree_node *node; - int uri_offset, full_name_offset; - size_t size; - - uri_offset = strlen (fi->name) + 1; - full_name_offset = uri_offset + strlen (fi->url) + 1; - size = full_name_offset + strlen (fi->full_name); - - /* - 1 for sizeof(node.data) but +1 for terminating \0 */ - node = g_malloc (sizeof (*node) + size); - - node->cache = NULL; - - node->flags = FTREE_NODE_SUBSCRIBABLE; - - /* subscribed? */ - - if (camel_store_folder_subscribed (store, fi->full_name)) - node->flags |= FTREE_NODE_SUBSCRIBED; - - /* Copy strings */ - - node->uri_offset = uri_offset; - node->full_name_offset = full_name_offset; - - strcpy (node->data, fi->name); - strcpy (node->data + uri_offset, fi->url); - strcpy (node->data + full_name_offset, fi->full_name); - - /* Done */ - - return node; -} - -#define ftree_node_subscribable(node) ( ((ftree_node *) (node))->flags & FTREE_NODE_SUBSCRIBABLE ) -#define ftree_node_subscribed(node) ( ((ftree_node *) (node))->flags & FTREE_NODE_SUBSCRIBED ) -#define ftree_node_get_name(node) ( ((ftree_node *) (node))->data ) -#define ftree_node_get_full_name(node) ( ((ftree_node *) (node))->data + ((ftree_node *) (node))->full_name_offset ) -#define ftree_node_get_uri(node) ( ((ftree_node *) (node))->data + ((ftree_node *) (node))->uri_offset ) - -/* ** Folder Tree Model **************************************************** */ - -/* A subscribe or scan operation */ - -typedef struct _ftree_op_data ftree_op_data; - -struct _ftree_op_data { - FolderETree *ftree; - ETreePath path; - ftree_node *data; - int handle; -}; - - -/* ETreeModel functions */ - -static int -fe_column_count (ETreeModel *etm) -{ - return FOLDER_COL_LAST; -} - -static void * -fe_duplicate_value (ETreeModel *etm, int col, const void *val) -{ - return g_strdup (val); -} - -static void -fe_free_value (ETreeModel *etm, int col, void *val) -{ - g_free (val); -} - -static void* -fe_init_value (ETreeModel *etm, int col) -{ - return g_strdup (""); -} - -static gboolean -fe_value_is_empty (ETreeModel *etm, int col, const void *val) -{ - return !(val && *(char *)val); -} - -static char * -fe_value_to_string (ETreeModel *etm, int col, const void *val) -{ - return g_strdup (val); -} - -static GdkPixbuf * -fe_icon_at (ETreeModel *etree, ETreePath path) -{ - return NULL; /* XXX no icons for now */ -} - -static gpointer -fe_root_value_at (FolderETree *ftree, int col) -{ - switch (col) { - case FOLDER_COL_NAME: - return ftree->service_name; - case FOLDER_COL_SUBSCRIBED: - return GINT_TO_POINTER (0); - default: - printf ("Oh no, unimplemented column %d in subscribe dialog\n", col); - } - - return NULL; -} - -static gpointer -fe_real_value_at (FolderETree *ftree, int col, gpointer data) -{ - switch (col) { - case FOLDER_COL_NAME: - return ftree_node_get_name (data); - case FOLDER_COL_SUBSCRIBED: - if (ftree_node_subscribed (data)) - return GINT_TO_POINTER (1); - return GINT_TO_POINTER (0); - default: - printf ("Oh no, unimplemented column %d in subscribe dialog\n", col); - } - - return NULL; -} - -static void * -fe_value_at (ETreeModel *etree, ETreePath path, int col) -{ - FolderETree *ftree = (FolderETree *) etree; - gpointer node_data; - - if (path == ftree->root) - return fe_root_value_at (ftree, col); - - node_data = e_tree_memory_node_get_data (E_TREE_MEMORY (etree), path); - return fe_real_value_at (ftree, col, node_data); -} - -static void -fe_set_value_at (ETreeModel *etree, ETreePath path, int col, const void *val) -{ - /* nothing */ -} - -static gboolean -fe_return_false (void) -{ - return FALSE; -} - -static gint -fe_sort_folder (ETreeMemory *etmm, ETreePath left, ETreePath right, gpointer user_data) -{ - ftree_node *n_left, *n_right; - - n_left = e_tree_memory_node_get_data (etmm, left); - n_right = e_tree_memory_node_get_data (etmm, right); - - /* if in utf8 locale ? */ - return strcasecmp (ftree_node_get_name (n_left), ftree_node_get_name (n_right)); -} - - -static void fe_check_for_children (FolderETree *ftree, ETreePath path); - -/* scanning */ -static void -fe_got_children (CamelStore *store, char *prefix, CamelFolderInfo *info, gpointer data) -{ - ftree_op_data *closure = (ftree_op_data *) data; - - if (!info) /* cancelled */ - goto done; - - /* also cancelled, but camel returned data, might leak */ - if (closure->handle == -1) - goto done; - - if (!prefix) - prefix = ""; - - for ( ; info; info = info->sibling) { - ETreePath child_path; - ftree_node *node; - - if (g_hash_table_lookup(closure->ftree->node_full_name, info->full_name)) - continue; - - node = ftree_node_new (store, info); - child_path = e_tree_memory_node_insert (E_TREE_MEMORY (closure->ftree), - closure->path, - 0, - node); - g_hash_table_insert(closure->ftree->node_full_name, ftree_node_get_full_name(node), child_path); - - if (!(info->flags & CAMEL_FOLDER_NOINFERIORS)) - fe_check_for_children (closure->ftree, child_path); - } - - /* FIXME: this needs to be added back to sort the tree */ - e_tree_memory_sort_node (E_TREE_MEMORY (closure->ftree), - closure->path, - fe_sort_folder, - NULL); - - if (closure->data) - closure->data->flags |= FTREE_NODE_GOT_CHILDREN; - - g_hash_table_remove (closure->ftree->scan_ops, closure->path); - -done: - /* finish off the activity of this task */ - /* hack, we know activity_data is an object */ - closure->ftree->activity_level--; - (closure->ftree->activity_cb) (closure->ftree->activity_level, closure->ftree->activity_data); - g_object_unref(closure->ftree->activity_data); - - g_free (closure); -} - -static void -fe_check_for_children (FolderETree *ftree, ETreePath path) -{ - ftree_op_data *closure; - ftree_node *node; - char *prefix; - - node = e_tree_memory_node_get_data (E_TREE_MEMORY (ftree), path); - - /* have we already gotten these children? */ - if (node->flags & FTREE_NODE_GOT_CHILDREN) - return; - - /* or we're loading them right now? */ - if (g_hash_table_lookup (ftree->scan_ops, path)) - return; - - /* figure out our search prefix */ - if (path == ftree->root) - prefix = ""; - else - prefix = ftree_node_get_full_name (node); - - closure = g_new (ftree_op_data, 1); - closure->ftree = ftree; - closure->path = path; - closure->data = node; - closure->handle = -1; - - g_hash_table_insert (ftree->scan_ops, path, closure); - - /* hack, we know this is an object ... infact the subscribe dialog */ - g_object_ref(ftree->activity_data); - ftree->activity_level++; - (ftree->activity_cb) (ftree->activity_level, ftree->activity_data); - - /* FIXME. Tiny race possiblity I guess. */ - closure->handle = subscribe_get_short_folderinfo (ftree, prefix, fe_got_children, closure); -} - -static void -fe_create_root_node (FolderETree *ftree) -{ - ftree_node *node; - - node = ftree_node_new_root (); - ftree->root = e_tree_memory_node_insert (E_TREE_MEMORY(ftree), NULL, 0, node); - fe_check_for_children (ftree, ftree->root); -} - -static ETreePath -fe_get_first_child (ETreeModel *model, ETreePath path) -{ - ETreePath child_path; - - child_path = E_TREE_MODEL_CLASS (folder_etree_parent_class)->get_first_child (model, path); - if (child_path) - fe_check_for_children ((FolderETree *) model, child_path); - else - fe_check_for_children ((FolderETree *) model, path); - return child_path; -} - -/* subscribing */ -static void -fe_done_subscribing (const char *full_name, const char *name, gboolean subscribe, gboolean success, gpointer user_data) -{ - ftree_op_data *closure = (ftree_op_data *) user_data; - - if (success && closure->handle != -1) { - char *path; - - path = g_strdup_printf ("/%s", full_name); - - if (subscribe) { - closure->data->flags |= FTREE_NODE_SUBSCRIBED; - recursive_add_folder (closure->ftree->e_storage, - path, name, - ftree_node_get_uri (closure->data)); - } else { - closure->data->flags &= ~FTREE_NODE_SUBSCRIBED; - - /* FIXME: recursively remove folder as well? Possible? */ - } - - g_free (path); - e_tree_model_node_data_changed (E_TREE_MODEL (closure->ftree), closure->path); - } - - if (closure->handle != -1) - g_hash_table_remove (closure->ftree->subscribe_ops, closure->path); - - g_free (closure); -} - -/* cleanup */ - -static gboolean -fe_cancel_op_foreach (gpointer key, gpointer value, gpointer user_data) -{ - /*FolderETree *ftree = (FolderETree *) user_data;*/ - ftree_op_data *closure = (ftree_op_data *) value; - - if (closure->handle != -1) { - d(printf("%d: cancel get messageinfo\n", closure->handle)); - mail_msg_cancel (closure->handle); - } - - closure->handle = -1; - - return TRUE; -} - -static void -fe_kill_current_tree (FolderETree *ftree) -{ - g_hash_table_foreach_remove (ftree->scan_ops, fe_cancel_op_foreach, ftree); - g_assert (g_hash_table_size (ftree->scan_ops) == 0); -} - -static void -fe_finalise (GObject *obj) -{ - FolderETree *ftree = (FolderETree *) (obj); - - d(printf("fe finalise!?\n")); - - fe_kill_current_tree (ftree); - - g_hash_table_foreach_remove (ftree->subscribe_ops, fe_cancel_op_foreach, ftree); - - g_hash_table_destroy (ftree->scan_ops); - g_hash_table_destroy (ftree->subscribe_ops); - g_hash_table_destroy(ftree->node_full_name); - - camel_object_unref (ftree->store); - bonobo_object_unref (BONOBO_OBJECT (ftree->e_storage)); - - g_free (ftree->service_name); - - ((GObjectClass *)folder_etree_parent_class)->finalize(obj); -} - -typedef gboolean (*bool_func_1) (ETreeModel *, ETreePath, int); -typedef gboolean (*bool_func_2) (ETreeModel *); - -static void -folder_etree_class_init (GObjectClass *klass) -{ - ETreeModelClass *etree_model_class = E_TREE_MODEL_CLASS (klass); - - folder_etree_parent_class = g_type_class_ref (E_TREE_MEMORY_TYPE); - - klass->finalize = fe_finalise; - - etree_model_class->value_at = fe_value_at; - etree_model_class->set_value_at = fe_set_value_at; - etree_model_class->column_count = fe_column_count; - etree_model_class->duplicate_value = fe_duplicate_value; - etree_model_class->free_value = fe_free_value; - etree_model_class->initialize_value = fe_init_value; - etree_model_class->value_is_empty = fe_value_is_empty; - etree_model_class->value_to_string = fe_value_to_string; - etree_model_class->icon_at = fe_icon_at; - etree_model_class->is_editable = (bool_func_1) fe_return_false; - etree_model_class->has_save_id = (bool_func_2) fe_return_false; - etree_model_class->has_get_node_by_id = (bool_func_2) fe_return_false; - etree_model_class->get_first_child = fe_get_first_child; -} - -static void -folder_etree_init (GtkObject *object) -{ - FolderETree *ftree = (FolderETree *) object; - - e_tree_memory_set_node_destroy_func (E_TREE_MEMORY (ftree), (GFunc) g_free, ftree); - - ftree->scan_ops = g_hash_table_new (g_direct_hash, g_direct_equal); - ftree->subscribe_ops = g_hash_table_new (g_direct_hash, g_direct_equal); - - ftree->activity_level = 0; - ftree->node_full_name = g_hash_table_new(g_str_hash, g_str_equal); -} - -static FolderETree * -folder_etree_construct (FolderETree *ftree, - CamelStore *store, - FolderETreeActivityCallback activity_cb, - gpointer activity_data) -{ - e_tree_memory_construct (E_TREE_MEMORY (ftree)); - - ftree->store = store; - camel_object_ref (store); - - ftree->service_name = camel_service_get_name (CAMEL_SERVICE (store), FALSE); - - ftree->e_storage = mail_lookup_storage (store); /* this gives us a ref */ - - ftree->activity_cb = activity_cb; - ftree->activity_data = activity_data; - - fe_create_root_node (ftree); - - return ftree; -} - -static -E_MAKE_TYPE (folder_etree, "FolderETree", FolderETree, folder_etree_class_init, folder_etree_init, E_TREE_MEMORY_TYPE); - -/* public */ - -static FolderETree * -folder_etree_new (CamelStore *store, - FolderETreeActivityCallback activity_cb, - gpointer activity_data) -{ - FolderETree *ftree; - - ftree = g_object_new (folder_etree_get_type(), NULL); - ftree = folder_etree_construct (ftree, store, activity_cb, activity_data); - return ftree; -} - -static void -folder_etree_clear_tree (FolderETree *ftree) -{ - e_tree_memory_freeze (E_TREE_MEMORY (ftree)); - e_tree_memory_node_remove (E_TREE_MEMORY (ftree), ftree->root); - fe_create_root_node (ftree); - g_hash_table_destroy(ftree->node_full_name); - ftree->node_full_name = g_hash_table_new(g_str_hash, g_str_equal); - e_tree_memory_thaw (E_TREE_MEMORY (ftree)); -} - -static int -folder_etree_path_set_subscription (FolderETree *ftree, ETreePath path, gboolean subscribe) -{ - ftree_op_data *closure; - ftree_node *node; - - /* already in progress? */ - - if (g_hash_table_lookup (ftree->subscribe_ops, path)) - return 0; - - /* noselect? */ - - node = e_tree_memory_node_get_data (E_TREE_MEMORY (ftree), path); - - if (!ftree_node_subscribable (node)) - return -1; - - /* noop? */ - - /* uh, this should be a not XOR or something */ - if ((ftree_node_subscribed (node) && subscribe) || - (!ftree_node_subscribed (node) && !subscribe)) - return 0; - - closure = g_new (ftree_op_data, 1); - closure->ftree = ftree; - closure->path = path; - closure->data = node; - closure->handle = -1; - - g_hash_table_insert (ftree->subscribe_ops, path, closure); - - closure->handle = subscribe_do_subscribe_folder (ftree->store, - ftree_node_get_full_name (node), - ftree_node_get_name (node), - subscribe, - fe_done_subscribing, - closure); - return 0; -} - -static int -folder_etree_path_toggle_subscription (FolderETree *ftree, ETreePath path) -{ - ftree_node *node = e_tree_memory_node_get_data (E_TREE_MEMORY (ftree), path); - - if (ftree_node_subscribed (node)) - return folder_etree_path_set_subscription (ftree, path, FALSE); - else - return folder_etree_path_set_subscription (ftree, path, TRUE); -} - -static void -folder_etree_cancel_all(FolderETree *ftree) -{ - g_hash_table_foreach_remove (ftree->scan_ops, fe_cancel_op_foreach, ftree); - g_hash_table_foreach_remove (ftree->subscribe_ops, fe_cancel_op_foreach, ftree); -} - -/* ** StoreData ************************************************************ */ - -typedef struct _StoreData StoreData; - -typedef void (*StoreDataStoreFunc) (StoreData *, CamelStore *, gpointer); - -struct _StoreData { - int refcount; - char *uri; - - FolderETree *ftree; - CamelStore *store; - - int request_id; - - GtkWidget *widget; - StoreDataStoreFunc store_func; - gpointer store_data; -}; - -static StoreData * -store_data_new (const char *uri) -{ - StoreData *sd; - - sd = g_new0 (StoreData, 1); - sd->refcount = 1; - sd->uri = g_strdup (uri); - - return sd; -} - -static void -store_data_free (StoreData *sd) -{ - d(printf("store data free?\n")); - - if (sd->request_id) - mail_msg_cancel (sd->request_id); - - if (sd->ftree) { - folder_etree_cancel_all(sd->ftree); - g_object_unref(sd->ftree); - } - - if (sd->store) - camel_object_unref (sd->store); - - g_free (sd->uri); - g_free (sd); -} - -static void -store_data_ref (StoreData *sd) -{ - sd->refcount++; -} - -static void -store_data_unref (StoreData *sd) -{ - if (sd->refcount <= 1) { - store_data_free (sd); - } else { - sd->refcount--; - } -} - -static void -sd_got_store (char *uri, CamelStore *store, gpointer user_data) -{ - StoreData *sd = (StoreData *) user_data; - - sd->store = store; - - if (store) /* we can have exceptions getting the store... server is down, eg */ - camel_object_ref (sd->store); - - /* uh, so we might have a problem if this operation is cancelled. Unsure. */ - sd->request_id = 0; - - if (sd->store_func) - (sd->store_func) (sd, sd->store, sd->store_data); - - store_data_unref (sd); -} - -static void -store_data_async_get_store (StoreData *sd, StoreDataStoreFunc func, gpointer user_data) -{ - if (sd->request_id) { - d(printf ("Already loading store, nooping\n")); - return; - } - - if (sd->store) { - /* um, is this the best behavior? */ - func (sd, sd->store, user_data); - return; - } - - sd->store_func = func; - sd->store_data = user_data; - store_data_ref (sd); - sd->request_id = mail_get_store (sd->uri, NULL, sd_got_store, sd); -} - -static void -store_data_cancel_get_store (StoreData *sd) -{ - g_return_if_fail (sd->request_id); - - mail_msg_cancel (sd->request_id); - sd->request_id = 0; -} - -static void -sd_toggle_cb (ETree *tree, int row, ETreePath path, int col, GdkEvent *event, gpointer user_data) -{ - StoreData *sd = (StoreData *) user_data; - - folder_etree_path_toggle_subscription (sd->ftree, path); -} - -static GtkWidget * -store_data_get_widget (StoreData *sd, - FolderETreeActivityCallback activity_cb, - gpointer activity_data) -{ - GtkWidget *tree; - - if (!sd->store) { - d(printf ("store data can't get widget before getting store.\n")); - return NULL; - } - - if (sd->widget) - return sd->widget; - - sd->ftree = folder_etree_new (sd->store, activity_cb, activity_data); - - /* You annoy me, etree! */ - tree = gtk_widget_new (E_TREE_SCROLLED_TYPE, - "hadjustment", NULL, - "vadjustment", NULL, - NULL); - - tree = (GtkWidget *) e_tree_scrolled_construct_from_spec_file (E_TREE_SCROLLED (tree), - E_TREE_MODEL (sd->ftree), - subscribe_get_global_extras (), - EVOLUTION_ETSPECDIR "/subscribe-dialog.etspec", - NULL); - e_tree_root_node_set_visible (e_tree_scrolled_get_tree(E_TREE_SCROLLED(tree)), TRUE); - g_signal_connect(e_tree_scrolled_get_tree(E_TREE_SCROLLED (tree)), - "double_click", G_CALLBACK (sd_toggle_cb), sd); - - g_object_unref(global_extras); - - sd->widget = tree; - - return sd->widget; -} - -typedef struct _selection_closure { - StoreData *sd; - enum { SET, CLEAR, TOGGLE } mode; -} selection_closure; - -static void -sd_subscribe_folder_foreach (int model_row, gpointer closure) -{ - selection_closure *sc = (selection_closure *) closure; - StoreData *sd = sc->sd; - ETree *tree = e_tree_scrolled_get_tree(E_TREE_SCROLLED(sd->widget)); - ETreePath path = e_tree_node_at_row (tree, model_row); - - /* ignore results */ - switch (sc->mode) { - case SET: - folder_etree_path_set_subscription (sd->ftree, path, TRUE); - break; - case CLEAR: - folder_etree_path_set_subscription (sd->ftree, path, FALSE); - break; - case TOGGLE: - folder_etree_path_toggle_subscription (sd->ftree, path); - break; - } -} - -static void -store_data_selection_set_subscription (StoreData *sd, gboolean subscribe) -{ - selection_closure sc; - ETree *tree; - - sc.sd = sd; - if (subscribe) - sc.mode = SET; - else - sc.mode = CLEAR; - - tree = e_tree_scrolled_get_tree (E_TREE_SCROLLED (sd->widget)); - e_tree_selected_row_foreach (tree, sd_subscribe_folder_foreach, &sc); -} - -#ifdef NEED_TOGGLE_SELECTION -static void -store_data_selection_toggle_subscription (StoreData *sd) -{ - selection_closure sc; - ETree *tree; - - sc.sd = sd; - sc.mode = TOGGLE; - - tree = e_tree_scrolled_get_tree (E_TREE_SCROLLED (sd->widget)); - e_tree_selected_row_foreach (tree, sd_subscribe_folder_foreach, &sc); -} -#endif - -static gboolean -store_data_mid_request (StoreData *sd) -{ - return (gboolean) sd->request_id; -} - -/* ** yaay, SubscribeDialog ******************************************************* */ - -#define PARENT_TYPE (gtk_object_get_type ()) - -#ifdef JUST_FOR_TRANSLATORS -static char *str = N_("Folder"); -#endif - -#define STORE_DATA_KEY "store-data" - -struct _SubscribeDialogPrivate { - GladeXML *xml; - GList *store_list; - - StoreData *current_store; - GtkWidget *current_widget; - - GtkWidget *default_widget; - GtkWidget *none_item; - GtkWidget *search_entry; - GtkWidget *hbox; - GtkWidget *filter_radio, *all_radio; - GtkWidget *sub_button, *unsub_button, *refresh_button, *close_button; - GtkWidget *progress; - - int cancel; /* have we been cancelled? */ - guint activity_timeout_id; -}; - -static GtkObjectClass *subscribe_dialog_parent_class; - -static void -sc_refresh_pressed (GtkWidget *widget, gpointer user_data) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); - - if (sc->priv->current_store) - folder_etree_clear_tree (sc->priv->current_store->ftree); -} - -static void -sc_close_pressed (GtkWidget *widget, gpointer user_data) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); - - /* order important here */ - gtk_object_destroy (GTK_OBJECT (sc)); - gtk_widget_destroy (GTK_WIDGET (sc->app)); -} - -static void -sc_subscribe_pressed (GtkWidget *widget, gpointer user_data) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); - StoreData *store = sc->priv->current_store; - - if (!store) - return; - - store_data_selection_set_subscription (store, TRUE); -} - -static void -sc_unsubscribe_pressed (GtkWidget *widget, gpointer user_data) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); - StoreData *store = sc->priv->current_store; - - if (!store) - return; - - store_data_selection_set_subscription (store, FALSE); -} - -static void -kill_default_view (SubscribeDialog *sc) -{ - gtk_widget_hide (sc->priv->none_item); - - gtk_widget_set_sensitive (sc->priv->sub_button, TRUE); - gtk_widget_set_sensitive (sc->priv->unsub_button, TRUE); - gtk_widget_set_sensitive (sc->priv->refresh_button, TRUE); -} - -static void -sc_selection_changed (GtkObject *obj, gpointer user_data) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); - gboolean sensitive; - - if (e_selection_model_selected_count (E_SELECTION_MODEL (obj))) - sensitive = TRUE; - else - sensitive = FALSE; - - gtk_widget_set_sensitive (sc->priv->sub_button, sensitive); - gtk_widget_set_sensitive (sc->priv->unsub_button, sensitive); -} - -static gboolean -sc_activity_timeout (SubscribeDialog *sc) -{ - gtk_progress_bar_pulse(GTK_PROGRESS_BAR(sc->priv->progress)); - - return TRUE; -} - -static void -sc_activity_cb (int level, SubscribeDialog *sc) -{ - g_assert (pthread_self() == mail_gui_thread); - - if (sc->priv->cancel) - return; - - if (level) { - if (sc->priv->activity_timeout_id) - return; - - sc->priv->activity_timeout_id = g_timeout_add(50, (GSourceFunc)sc_activity_timeout, sc); - gtk_widget_show(sc->priv->progress); - } else { - if (sc->priv->activity_timeout_id) { - g_source_remove (sc->priv->activity_timeout_id); - sc->priv->activity_timeout_id = 0; - } - - gtk_widget_hide(sc->priv->progress); - } -} - -static void -menu_item_selected (GtkMenuItem *item, gpointer user_data) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); - StoreData *sd = g_object_get_data (G_OBJECT (item), STORE_DATA_KEY); - - g_return_if_fail (sd); - - if (sd->widget == NULL) { - GtkWidget *widget; - ESelectionModel *esm; - ETree *tree; - - widget = store_data_get_widget (sd, (FolderETreeActivityCallback) sc_activity_cb, sc); - gtk_box_pack_start (GTK_BOX (sc->priv->hbox), widget, TRUE, TRUE, 0); - - tree = e_tree_scrolled_get_tree (E_TREE_SCROLLED (widget)); - esm = e_tree_get_selection_model (tree); - g_signal_connect(esm, "selection_changed", G_CALLBACK(sc_selection_changed), sc); - sc_selection_changed ((GtkObject *)esm, sc); - } - - if (sc->priv->current_widget == sc->priv->default_widget) - kill_default_view (sc); - - gtk_widget_hide (sc->priv->current_widget); - gtk_widget_show (sd->widget); - sc->priv->current_widget = sd->widget; - sc->priv->current_store = sd; -} - -static void -dummy_item_selected (GtkMenuItem *item, gpointer user_data) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); - - gtk_widget_hide (sc->priv->current_widget); - gtk_widget_show (sc->priv->default_widget); - sc->priv->current_widget = sc->priv->default_widget; - sc->priv->current_store = NULL; - - gtk_entry_set_text (GTK_ENTRY (sc->priv->search_entry), ""); -} - -/* wonderful */ - -static void -got_sd_store (StoreData *sd, CamelStore *store, gpointer data) -{ - if (store && camel_store_supports_subscriptions (store)) - gtk_widget_show (GTK_WIDGET (data)); -} - -/* FIXME: if there aren't any stores that are subscribable, the option - * menu will only have the "No server selected" item and the user will - * be confused. */ - -static void -populate_store_list (SubscribeDialog *sc) -{ - EAccountList *accounts; - EAccount *account; - EIterator *iter; - GtkWidget *menu; - GtkWidget *omenu; - GList *l; - - accounts = mail_config_get_accounts (); - iter = e_list_get_iterator ((EList *) accounts); - while (e_iterator_is_valid (iter)) { - StoreData *sd; - - account = (EAccount *) e_iterator_get (iter); - - if (account->enabled && account->source->url) { - sd = store_data_new (account->source->url); - sc->priv->store_list = g_list_prepend (sc->priv->store_list, sd); - } - - e_iterator_next (iter); - } - - g_object_unref (iter); - - menu = gtk_menu_new (); - - for (l = sc->priv->store_list; l; l = l->next) { - GtkWidget *item; - CamelURL *url; - char *string; - - url = camel_url_new (((StoreData *) l->data)->uri, NULL); - string = camel_url_to_string (url, CAMEL_URL_HIDE_ALL); - camel_url_free (url); - item = gtk_menu_item_new_with_label (string); - store_data_async_get_store (l->data, got_sd_store, item); - g_object_set_data (G_OBJECT (item), STORE_DATA_KEY, l->data); - g_signal_connect (item, "activate", G_CALLBACK (menu_item_selected), sc); - g_free (string); - - gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item); - } - - sc->priv->none_item = gtk_menu_item_new_with_label (_("No server has been selected")); - g_signal_connect (sc->priv->none_item, "activate", G_CALLBACK (dummy_item_selected), sc); - gtk_widget_show (sc->priv->none_item); - gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), sc->priv->none_item); - - gtk_widget_show (menu); - - omenu = glade_xml_get_widget (sc->priv->xml, "store_menu"); - gtk_option_menu_set_menu (GTK_OPTION_MENU (omenu), menu); -} - -static void -subscribe_dialog_finalise (GObject *object) -{ - SubscribeDialog *sc; - GList *iter; - - sc = SUBSCRIBE_DIALOG (object); - - if (sc->priv->store_list) { - for (iter = sc->priv->store_list; iter; iter = iter->next) { - StoreData *data = iter->data; - store_data_unref (data); - } - - g_list_free (sc->priv->store_list); - sc->priv->store_list = NULL; - } - - g_free (sc->priv); - sc->priv = NULL; - - ((GObjectClass *)subscribe_dialog_parent_class)->finalize (object); -} - -static void -subscribe_dialog_destroy (GtkObject *object) -{ - SubscribeDialog *sc; - GList *iter; - - sc = SUBSCRIBE_DIALOG (object); - - d(printf("subscribe_dialog_destroy\n")); - - if (!sc->priv->cancel) { - sc->priv->cancel = 1; - - if (sc->priv->activity_timeout_id) { - g_source_remove (sc->priv->activity_timeout_id); - sc->priv->activity_timeout_id = 0; - } - - if (sc->priv->store_list) { - for (iter = sc->priv->store_list; iter; iter = iter->next) { - StoreData *data = iter->data; - - if (store_data_mid_request (data)) - store_data_cancel_get_store (data); - - if (data->ftree) - folder_etree_cancel_all(data->ftree); - - data->store_func = NULL; - } - } - - if (sc->priv->xml) { - g_object_unref(sc->priv->xml); - sc->priv->xml = NULL; - } - } - - subscribe_dialog_parent_class->destroy (object); -} - -static void -subscribe_dialog_class_init (GtkObjectClass *object_class) -{ - object_class->destroy = subscribe_dialog_destroy; - ((GObjectClass *)object_class)->finalize = subscribe_dialog_finalise; - - subscribe_dialog_parent_class = g_type_class_ref (PARENT_TYPE); -} - -static void -subscribe_dialog_init (GtkObject *object) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (object); - - sc->priv = g_new0 (SubscribeDialogPrivate, 1); -} - -static GtkWidget * -sc_create_default_widget (void) -{ - GtkWidget *label; - GtkWidget *viewport; - - label = gtk_label_new (_("Please select a server.")); - gtk_widget_show (label); - - viewport = gtk_viewport_new (NULL, NULL); - gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_IN); - gtk_container_add (GTK_CONTAINER (viewport), label); - - return viewport; -} - -static void -subscribe_dialog_construct (GtkObject *object) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (object); - - /* Load the XML */ - /* "app2" */ - sc->priv->xml = glade_xml_new (EVOLUTION_GLADEDIR "/subscribe-dialog.glade", "subscribe_dialog", NULL); - - sc->app = glade_xml_get_widget (sc->priv->xml, "subscribe_dialog"); - sc->priv->hbox = glade_xml_get_widget (sc->priv->xml, "tree_box"); - sc->priv->close_button = glade_xml_get_widget (sc->priv->xml, "close_button"); - sc->priv->sub_button = glade_xml_get_widget (sc->priv->xml, "subscribe_button"); - sc->priv->unsub_button = glade_xml_get_widget (sc->priv->xml, "unsubscribe_button"); - sc->priv->refresh_button = glade_xml_get_widget (sc->priv->xml, "refresh_button"); - sc->priv->progress = glade_xml_get_widget(sc->priv->xml, "progress_bar"); - - /* create default view */ - sc->priv->default_widget = sc_create_default_widget(); - sc->priv->current_widget = sc->priv->default_widget; - gtk_box_pack_start (GTK_BOX (sc->priv->hbox), sc->priv->default_widget, TRUE, TRUE, 0); - gtk_widget_show (sc->priv->default_widget); - - gtk_widget_set_sensitive (sc->priv->sub_button, FALSE); - gtk_widget_set_sensitive (sc->priv->unsub_button, FALSE); - gtk_widget_set_sensitive (sc->priv->refresh_button, FALSE); - - /* hook up some signals */ - g_signal_connect(sc->priv->close_button, "clicked", G_CALLBACK(sc_close_pressed), sc); - g_signal_connect(sc->priv->sub_button, "clicked", G_CALLBACK(sc_subscribe_pressed), sc); - g_signal_connect(sc->priv->unsub_button, "clicked", G_CALLBACK(sc_unsubscribe_pressed), sc); - g_signal_connect(sc->priv->refresh_button, "clicked", G_CALLBACK(sc_refresh_pressed), sc); - - /* progress */ - gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(sc->priv->progress), 0.1); - gtk_widget_hide(sc->priv->progress); - - /* reasonable starting point */ - gtk_window_set_default_size((GtkWindow *)sc->app, 350, 400); - - /* Get the list of stores */ - populate_store_list (sc); -} - -GtkObject * -subscribe_dialog_new (void) -{ - SubscribeDialog *subscribe_dialog; - - subscribe_dialog = g_object_new (SUBSCRIBE_DIALOG_TYPE, NULL); - subscribe_dialog_construct (GTK_OBJECT (subscribe_dialog)); - - return GTK_OBJECT (subscribe_dialog); -} - -E_MAKE_TYPE (subscribe_dialog, "SubscribeDialog", SubscribeDialog, subscribe_dialog_class_init, subscribe_dialog_init, PARENT_TYPE); diff --git a/mail/subscribe-dialog.glade b/mail/subscribe-dialog.glade index 276ac3f947..c38505df4d 100644 --- a/mail/subscribe-dialog.glade +++ b/mail/subscribe-dialog.glade @@ -82,7 +82,7 @@ <property name="orientation">GTK_PROGRESS_LEFT_TO_RIGHT</property> <property name="fraction">0</property> <property name="pulse_step">0.1</property> - <property name="text" translatable="yes">Scanning folders ...</property> + <property name="text" translatable="yes"></property> </widget> <packing> <property name="padding">0</property> diff --git a/mail/subscribe-dialog.h b/mail/subscribe-dialog.h deleted file mode 100644 index c2e6b3d46f..0000000000 --- a/mail/subscribe-dialog.h +++ /dev/null @@ -1,62 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Authors: Chris Toshok <toshok@ximian.com> - * Peter Williams <peterw@ximian.com> - * - * Copyright 2000 Ximian, Inc. (www.ximian.com) - * - * 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. - * - */ - - -#ifndef _SUBSCRIBE_DIALOG_H_ -#define _SUBSCRIBE_DIALOG_H_ - -#include <gtk/gtktable.h> -#include <bonobo/bonobo-control.h> -#include <bonobo/bonobo-property-bag.h> -#include <gal/e-table/e-tree-model.h> -#include <gal/e-table/e-table-model.h> -#include "shell/evolution-storage.h" -#include "mail-types.h" -#include "camel/camel-store.h" - -#define SUBSCRIBE_DIALOG_TYPE (subscribe_dialog_get_type ()) -#define SUBSCRIBE_DIALOG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SUBSCRIBE_DIALOG_TYPE, SubscribeDialog)) -#define SUBSCRIBE_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), SUBSCRIBE_DIALOG_TYPE, SubscribeDialogClass)) -#define IS_SUBSCRIBE_DIALOG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SUBSCRIBE_DIALOG_TYPE)) -#define IS_SUBSCRIBE_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), SUBSCRIBE_DIALOG_TYPE)) - -typedef struct _SubscribeDialogPrivate SubscribeDialogPrivate; - -struct _SubscribeDialog { - GtkObject parent; - - GtkWidget *app; - SubscribeDialogPrivate *priv; -}; - -typedef struct { - GtkObjectClass parent_class; -} SubscribeDialogClass; - -GtkType subscribe_dialog_get_type (void); -GtkObject *subscribe_dialog_new (void); - -/* helper macro */ -#define subscribe_dialog_show(dialog) gtk_widget_show (SUBSCRIBE_DIALOG (dialog)->app) - -#endif /* _SUBSCRIBE_DIALOG_H_ */ |